The current JASPIC implementation within WildFly is based on the integration with the legacy PicketBox security SPIs, this task is to provide a clean JASPIC integration with WildFly Elytron. Additionally JSR-375 will be build on JASPIC, the JSR-375 implementation will be included as it's own task but the clean JASPIC implementation should take into account what is coming.
The JASPIC specification can be found under JSR-196
This initial feature request / analysis is covering the core JASPIC implementation as required for the servlet profile including backwards compatible activation of the existing PicketBox implementation.
This initial implementation does not cover the LoginModule profile which will be covered under WFLY-9737, additionally the initial Callbacks supported will be limited to those mandated by the JASPI specification, support for Elytron specific callbacks will be covered under ELY-1504.
Once complete it should be possible to make use of JASPIC with no reference to or configuration in the legacy security subsystem although use of LoginModules will be dependent on WFLY-9737 being implemented.
So for this first stage the core requirements being handled are: -
- Configuration of JASPI ServerAuthConfig
- Configuration associating the ServerAuthConfig with layer and appId to be returned by an AuthConfigProvider. (This may or may not be combined with #1)
- Support dynamic registration of custom AuthConfigProvider instances with the factory.
- Deployments using PicketBox defined JASPIC should deploy as before with no modification required.
- This should remain operational if the WildFly Elytron extension is not present in the application server.
- Real time updates to the AuthConfigFactory should apply immediately.
It is not a requirement of the feature request but it is anticipated that the Elytron / JASPI implementation will be usable with Undertow stand alone, this should allow for some isolated unit tests to be developed to detect issues without relying on a complete application server.
The application-security-domain resource was added to the Undertow subsystem as a mechanism to allow us to deploy using PicketBox by default but deploy using Elytron authentication where a mapping was found, at some point in the future the PicketBox subsystem could be removed entirely so we should ensure we have some approaches that do not depend on the use of the application-security-domain resource.
Within the legacy security subsystem JASPIC related configuration is associated with the security domain.
This implementation was only usable with the servlet profile as well as the login module profile.
As a web application is deployed a reference to the SecurityDomain is obtained, if that security domain contains JASPIC configuration then JASPIC will be enabled for the web application - if no configuration is present then JASPIC will not be enabled at all for the web application. This is contrary to the JASPIC specification where the decision to use JASPIC or not should be decided based on a call to https://docs.oracle.com/javaee/7/api/javax/security/auth/message/config/AuthConfigFactory.html#getConfigProvider-java.lang.String-java.lang.String-javax.security.auth.message.config.RegistrationListener-
Applications that wish to use JASPIC but not backed by a security domain configuration are forced to use a dummy domain like the example configuration for it to be enabled.
The default AuthConfigFactory if no other is registered is org.jboss.security.auth.message.config.JBossAuthConfigFactory, mappings can be added to this but by default at the time of authentication if there is no new match then an instance of org.jboss.security.auth.message.config.JBossAuthConfigProvider is returned. The JBossAuthConfigProvider works by obtaining the current SecurityContext and from this obtaining the SecurityDomain and from there accessing the JASPIC configuration on the domain.
Overall although a JASPIC configuration can be enabled it does not match the behaviour as defined in the specification.
- Specification says getConfigProvider() can be called and cached at deployment time but current impl does call it for each request IF JASPIC was at least discovered at the time of deployment.
Prior implementations handled JASPIC as a specific authentication mechanism, this had a side effect that changes specifically made for JASPIC would impact the other mechanism handling and subsequently updates for the other mechanisms would impact JASPIC - integrating WildFly Elytron changes were made to Undertow to make it easier to take full control of the security handlers, my intention is to keep the JASPIC integration as independent as possible to minimise requirement conflicts.
Comments captured against prior implementation JASPIC implementation issues · GitHub
Modes of Operation
Once implemented there will be three modes of operation.
Fully Integrated (Elytron)
In fully integrated mode a SecurityDomain will either be associated with the managed AuthConfig definition or a SecurityDomain will be associated with the deployment and the AuthConfig will either be managed or dynamically registered.
In this mode the identity established by the ServerAuthModule must be present in the SecurityDomain, the identity also must have been granted the LoginPermission.
Partial Integrated (Elytron)
In this mode a SecurityDomain will be associated with the deployment, however instead of using the SecurityDomain to load a real identity instead it will just be used to create an adHoc identity. This will give us identity propagation to different containers and possibly some inflow / outflow especially for credentials but will not require the Elytron domains to be configured and populated with identities.
Pure JASPI (Elytron)
The integration will operate in pure mode where no SecurityDomain was associated with the AuthConfig or the deployment, in this case the ServerAuthModule will be trusted to establish any identity it chooses and associate any groups it chooses - however as this does not map to a SecurityDomain SecurityIdentity this identity will only be usable within the servlet deployment and will not propagate in any outbound calls.
In both of the Elytron modes the selection of a JASPI mode will be made at the time of the request, this will allow runtime updates to the factory either via configuration or dynamic updates will be able to happen immediately. Initially updates to existing resources or the removal of resources may put the server into a reload required state but it should be possible to handle additions without requiring a reload. If an AuthConfig is available at runtime then the request will be executed using JASPI based authentication, if no AuthConfig can be identified then the default authentication for that deployment will be used which may be via an elytron HttpAuthenticationFactory or may be no authentication at all.
PicketBox Integration (Legacy)
At present if a deployment references a security domain and that security domain is not mapped using an application-security-domain resource then legacy security using Undertow mechanisms backed by a PicketBox domain will be used. This behaviour will remain.
In this situation PicketBox based JASPI will be used only if the associated PicketBox domain contains a JASPI configuraton. The PicketBox integration will retain any existing dynamic AuthConfig discovery it already supports, however this will not switch to an Elytron mode and will remain in a PicketBox mode.
Security Domain Selection
Where an AuthConfig definition is managed within the application server we will make it possible to optionally associate a SecurityDomain with the AuthConfig, where this AuthConfig is used this will always be the preferred SecurityDomain. The disadvantage of this association is the SecurityDomani will not be associated with the deployment class loader so calls to SecurityDomain.getCurrent() will not work, flexible association due to the invocation of HTTP Servlet Request methods such as login() may not be possible.
If there is no SecurityDomain associated with the AuthConfig (either managed or dynamically registered) then the SecurityDomain associated with the deployment will be used.
Finally if there is not SecurityDomain associated with the AuthConfig and no SecurityDomain associated with the deployment then the integration will operate in 'Pure JASPI' mode, any identity established will be visible using the servlet APIs and role based access will be possible but without a SecurityDomain there will be no identity to propagate for any calls to other secured resources so unless an alternative strategy is used to establish an identity the outbound call will be anonymous.
Authentication Module Handling
The JSR-196 specification is fairly vague in describing how multiple authentication modules can be combined, section 126.96.36.199 of the specification contains the following text: -
"Contexts which encapsulate alternative sufficient modules must ensure that the same message values are passed to each invoked alternative of the context. If a context invokes multiple authentication modules, the context must combine the AuthStatus values returned by the invoked authentication modules to establish the AuthStatus value returned by the context to the messaging runtime. The context implementation must define the logic for combining the returned AuthStatus values."
JAAS also includes the notion of ordered modules with the flags Required, Requisite, Sufficient, Optional - we can look to associate these with ServerAuthenticationModules. Additionally the invocation of an authentication module can lead to various outcomes AuthStatus.SUCCESS, AuthStatus.SEND_SUCCESS, AuthStatus.FAILURE, AuthStatus.SEND_FAILURE, AuthStatus.SEND_CONTINUE, AuthException.
At this point it is worth highlighting that the WildFly Elytron HTTP authentication framework uses a very different SPI for authentication mechanisms to coordinate multiple mechanisms working together with different challenge requirements, the API defined by JSR-196 does not offer the same level of control so the interpretation of the flags combined with the results of calling the authentication modules is not intended to be as comprehensive.
Although there are a lot of permutations at the time validateRequest is being called effective they decision being made is "Should this request be allowed to proceed to the authorization stage and potentially the secured resource, or should it be turned around?".
Where a ServerAuthenticationModule returns an AuthStatus of AuthStatus.SUCCESS we will take that to mean the module approves the request continuing to authorization.
Where any ServerAuthenticationModule throws an AuthException we will interpret that as a serious failure and return to the client immediately. The remaining AuthStatus values that can be returned will indicate that the request must return to the client and not proceed to the authorization stage however the flag setting for the module will also affect the decision.
|Flag||AuthStatus.Success||AuthStatus.SEND_SUCCESS, AuthStatus.SEND_FAILURE, AuthStatus.SEND_CONTINUE||AuthException|
|Required||Validation will continue to the remaining modules, provided the requirements of the remaining modules are satisfied the request will be allowed to proceed to authorization.||Validation will continue to the remaining modules, however regardless of their outcome the validation is not successful so control will return to the client.||An error will be immediately reported to the client without further login modules being called.|
|Requisite||Validation will continue to the remaining modules, provided the requirements of the remaining modules are satisfied the request will be allowed to proceed to authorization.||The request will return immediately to the client.||An error will be immediately reported to the client without further login modules being called.|
|Sufficient||Validation is deemed successful and complete, provided no previous Required or Requisite module has returned an AuthStatus other than AuthStatus.SUCCESS the request will proceed to authorization of the secured resource.||Validation will continue down the list of remaining modules, this status will only affect the decision if there are no REQUIRED or REQUISITE modules.||An error will be immediately reported to the client without further login modules being called.|
|Optional||Validation will continue to the remaining modules, provided no 'Required' or 'Requisite' modules have not returned SUCCESS this will be sufficient for validation to be deemed successful and for the request to proceed to the authorization stage and the secured resource.||Validation will continue down the list of remaining modules, this status will only affect the decision if there are no REQUIRED or REQUISITE modules.||An error will be immediately reported to the client without further login modules being called.|
Note: A ServerAuthModule is not allowed to return 'FAILURE' from the validateRequest method.
Where different SAMs return a different AuthStatus the different values will be ranked in the following order: -
SUCCESS, SEND_SUCCESS, SEND_CONTINUE, SEND_FAILURE
In the case of REQUIRED modules, values to the right of this list will override values to left of the list, as an example is there are two SAMs configured as being REQUIRED and one returns SEND_CONTINUE whilst the other returns SEND_FAILURE the overall status will be SEND_FAILURE.
In the case of OPTIONAL or SUFFICIENT modules, values to the left of this list will override values to the right of the list, as an example is there are two SAMs configured as being OPTIONAL and one returns SEND_CONTINUE whilst the other returns SEND_FAILURE the overall status will be SEND_CONTINUE.
Other than the case where a SUFFICIENT module can immediately return a SUCCESS status the status values of OPTIONAL and SUFFICIENT modules will only affect the outcome if there are no REQUIRED or REQUISITE modules.
For the purpose of this work we are only supporting the servlet profile so are only supporting server side authentication modules, if we add support for client side modules at a later point we would need to re-review the meaning of the flags on the client side.
AuthStatus.SEND_SUCCESS and javax.servlet.http.registerSession
The responses of SEND_SUCCESS combined with registerSession being set in the MessageInfo Map will also need some special handling.
Under normal circumstances if registerSession is set to true and the request is being allowed to proceed to authorization when the resulting identity will be associated with the underlying session and restored automatically for future request. If all of the Required and Requisite modules have returned SUCCESS with at least one SEND_SUCCESS or if a SUFFICIENT module has returned SEND_SUCCESS without any of the Required or Requisite modules failing then the identity will be associated with the session and the current request returning to the client. Subsequent requests will then use the identity associated with the session.
Callback Handler Implementation
Authentication modules called during JASPI authentication require a CallbackHandler implementation to handle the set of Callbacks as defined in the JSR-196 specification, there are a few design options here: -
1. Existing ServerAuthenticationContext CallbackHandler
We already have a CallbackHandler implementation in ServerAuthenticationContext, using this directly however has problems of it's own.
Firstly our APIs don't allow access to this unless within a mechanism factory, we could always break this apart if justified but IMO this would still have problems. In prior application server versions where we have tied JASPI to the container authentication we have ended up with yo-yo bugs where a fix to one mode breaks another - keeping the JASPI Callback handling separate has it's benefits. Additionally if we want to support pure JASPI without a SecurityDomain we have tied the implementation to the SecurityDomain architecture.
2. A new *AuthenticationContext
If we don't use the existing ServerAuthenticationContext we could write a clean new JaspiAuthenticationContext, this could map the behaviour of ServerAuthenticationContext but simplified to only cover the scenarios required for the JASPI callbacks.
This would lead to a reasonable level of duplication but would be a very clean AuthenticationContext we can maintain for JASPI, but the drawback is the ServerAuthenticationContext requires direct access to a lot of the SecurityDomain API so an independent implementation would not have this.
3. Wrapped ServerAuthenticationContext
A third option is to wrap the ServerAuthenticationContext but provide our own CallbackHandler implementation, the ServerAuthenticationContext does actually provide an API that allows it to be used directly so although we can not obtain the CallbackHandler we can still implement a CallbackHandler that delegates the call into the ServerAuthenticationContext.
The set of Callbacks we need to support is a small finite set so it should not be difficult to map these onto the ServerAuthenticationContext API.
Option 3 is the option that is going to be followed with Callbacks mapped as follows: -
If this Callback is received without any prior password validation it is assumed the authentication module is handling it's own validation and the identity is to be loaded from the security domain and authorized, if called after the PasswordValidationCallback this is an authorization check which depending on if the Principal matches the identity established by the PasswordValidationCallback could also be a runAs check.
If there has been no PasswordValidationCallback then depending on if the CallerPrincipalCallBack contains a Principal or a name one of the following methods will be used to establish the authentication identity setAuthenticationName(Principal prinicpal), setAuthenticationName(String name), the authorize() method will then be used to authorize as the identity established for authentication. If however neither was specified the handling will jump immediately to authorizing as anonymous with the authorizeAnonymous() method.
If there has been a prior PasswordValidationCallback then one of the following authorize methods will be called using the Principal or name supplied with the CallerPrincipalCallback - authorize(Principal principal), authorize(String name). If no Principal or name are provided it will be assumed we are authorizing as the identity used for authentication and the authorize() method will be used.
Whilst handling the Callback there is no mechanism to indicate if this callback was successfully handled, however if it was successfully handled the supplied caller Principal will be associated with the supplied Subject, if no caller Principal was supplied then the resulting identity will be queries to obtain the Principal to associate with the Subject.
The ServerAuthenticationContext does not currently expose a method to add a RoleMapper, but the general idea is a named RoleMapper will be associated with the SecurityIdentity and the set of groups passed in via this Callback will become a static set of Roles - we will probably want the name to be configurable.
Similar to the ServerAuthenticationContext the JaspiAuthenticationContext method will also have a "SecurityIdentity getAuthorizedIdentity() throws IllegalStateException;" method, this means that the JaspiAuthenticationContext can hold on to the groups until late in the process and associate a RoleMapper at the time this method is called.
The username from the callback will be passed to the ServerAuthenticationContext via the setAuthenticationName(String name) method.
The result returned via the PasswordValidationCallback will indicate that authentication was successful, it however does not indicate that authorization will succeed.
If authentication was successful the password will be wrapped by a PrivateCredential and associated with the private credentials of the security identity and Subject from the callback.
Two new attributes will be added to the application-security-domain resource: -
- enable-jaspi (Default true) - If an administrator wants to guarantee that JASPI will not be enabled even if a matching AuthConfig is found this should be set to true.
- security-domain (Mutually exclusive to http-authentication-factory) - Reference to a SecurityDomain that should be associated with the deployments class loader. This is only a resource association and will not guarantee JASPI authentication occurs as that will still be dependent on a matching AuthConfig.
Where the http-authentication-factory is registered the SecurityDomain used by that factory is associated with the deployments class loader, that behaviour will remain even if JASPI is in use - this way the http-authentication-factory can always offer 'backup' authentication mechanisms.
This will require some additional thought, in general we need to cover AuthConfig definitions which in turn will reference ServerAuthModule classes, somehow these will need to mapped using 'layer' and 'appId'. At some point within these configurations we will want an optional reference to a SecurityDomain.
I expect during the deployment process listeners will be registered with the AuthFactory to detect subsequent registrations, if possible it may be good to reference this at runtime making it easier for an administrator to check which mappings may be required.
Main Tracking Issue - WFLY-9067
Move Undertow Integration from subsystem to Elytron Web
WFLY-9790 - Remove the SPI implementations from the Undertow subsystem.
WFCORE-3591 - Add the undertow-servlet module from Elytron Web.
ELYWEB-1 - Add the SPI implementation to the Elytron Web project.
Darran Lofthouse - email@example.com