JBoss Community Archive (Read Only)

Latest WildFly Documentation

Migrate Legacy Security to Elytron Security

Authentication Configuration

Properties Based Authentication / Authorization

PicketBox Based Configuration

This migration example assumes a deployed web application is configured to require authentication using FORM based authentication and is referencing a PicketBox based security domain using the UsersRolesLoginModule to load user information from a pair or properties files.

Original Configuration

A security domain can be defined in the legacy security subsystem using the following management operations: -

./subsystem=security/security-domain=application-security:add
./subsystem=security/security-domain=application-security/authentication=classic:add(login-modules=[{code=UsersRoles, flag=Required, module-options={usersProperties=file://${jboss.server.config.dir}/example-users.properties, rolesProperties=file://${jboss.server.config.dir}/example-roles.properties}}])

This would result in a security domain definition: -

  <security-domain name="application-security">
    <authentication>
      <login-module code="UsersRoles" flag="required">
        <module-option name="usersProperties" value="file://${jboss.server.config.dir}/example-users.properties"/>
        <module-option name="rolesProperties" value="file://${jboss.server.config.dir}/example-roles.properties"/>
      </login-module>
    </authentication>
  </security-domain>
Intermediate Configuration

It is possible to take a previously defined PicketBox security domain and expose it as an Elytron security realm so it can be wired into a complete Elytron based configuration, if only properties based authentication was to be migrated it would be recommended to jump to the fully migration configuration and avoid the unnecessary dependency on the legacy security subsystem but for situations where that is not immediately possible these commands illustrate an intermediate solution.

These steps assume the original configuration is already in place.

The first step is to add a mapping to an Elytron security realm within the legacy security subsystem.

./subsystem=security/elytron-realm=application-security:add(legacy-jaas-config=application-security)

This results in the following configuration.

  <subsystem xmlns="urn:jboss:domain:security:2.0">
    ...
    <elytron-integration>
      <security-realms>
        <elytron-realm name="application-security" legacy-jaas-config="application-security"/>
      </security-realms>
    </elytron-integration>
    ...
  </subsystem>

Within the Elytron subsystem a security domain can be defined which references the exported security realm and also a http authentication factory which supports FORM based authentication.

./subsystem=elytron/security-domain=application-security:add(realms=[{realm=application-security}], default-realm=application-security, permission-mapper=default-permission-mapper)
./subsystem=elytron/http-authentication-factory=application-security-http:add(http-server-mechanism-factory=global, security-domain=application-security, mechanism-configurations=[{mechanism-name=FORM}])

And the resulting configuration: -

  <subsystem xmlns="urn:wildfly:elytron:1.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <security-domains>
      ...
      <security-domain name="application-security" default-realm="application-security" permission-mapper="default-permission-mapper">
        <realm name="application-security"/>
      </security-domain>
    </security-domains>
    ...
    <http>
      ...
      <http-authentication-factory name="application-security-http" http-server-mechanism-factory="global" security-domain="application-security">
        <mechanism-configuration>
          <mechanism mechanism-name="FORM"/>
        </mechanism-configuration>
      </http-authentication-factory>
      ...
    </http>
    ...
  </subsystem>

Finally configuration needs to be added to the Undertow subsystem to map the security domain referenced by the deployment to the newly defined http authentication factory.

./subsystem=undertow/application-security-domain=application-security:add(http-authentication-factory=application-security-http)

Which results in: -

  <subsystem xmlns="urn:jboss:domain:undertow:4.0">
    ...
    <application-security-domains>
      <application-security-domain name="application-security" http-authentication-factory="application-security-http"/>
    </application-security-domains>
    ...
  </subsystem>

Note: If the deployment was already deployed at this point the application server should be reloaded or the deployment redeployed for the application security domain mapping to take effect.

The following command can then be used to verify the mapping was applied to the deployment.

[standalone@localhost:9990 /] ./subsystem=undertow/application-security-domain=application-security:read-resource(include-runtime=true)
{
    "outcome" => "success",
    "result" => {
        "enable-jacc" => false,
        "http-authentication-factory" => "application-security-http",
        "override-deployment-config" => false,
        "referencing-deployments" => ["HelloWorld.war"],
        "setting" => undefined
    }
}

The deployment being tested here is 'HelloWorld.war' and the output from the previous command shows this deployment is referencing the mapping.

At this stage the previously defined security domain is used for it's LoginModule configuration but this is wrapped by Elytron components which take over authentication.

Fully Migrated Configuration

Alternatively the configuration can be completely defined within the Elytron subsystem, in this case it is assumed none of the previous commands have been executed and this is started from a clean configuration - however if the security domain definition does exist in the legacy security subsystem that will remain completely independent.

First a new security realm can be defined within the Elytron subsystem referencing the files referenced previously: -

./subsystem=elytron/properties-realm=application-properties:add(users-properties={path=example-users.properties, relative-to=jboss.server.config.dir, plain-text=true, digest-realm-name="Application Security"}, groups-properties={path=example-roles.properties, relative-to=jboss.server.config.dir}, groups-attribute=Roles)

As before a security domain and http authentication factory can be defined.

./subsystem=elytron/security-domain=application-security:add(realms=[{realm=application-properties}], default-realm=application-properties, permission-mapper=default-permission-mapper)
./subsystem=elytron/http-authentication-factory=application-security-http:add(http-server-mechanism-factory=global, security-domain=application-security, mechanism-configurations=[{mechanism-name=FORM}])

This results in the following overall configuration.

  <subsystem xmlns="urn:wildfly:elytron:1.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <security-domains>
      ...
      <security-domain name="application-security" default-realm="application-properties" permission-mapper="default-permission-mapper">
        <realm name="application-properties"/>
      </security-domain>
    </security-domains>
    <security-realms>
      ...
      <properties-realm name="application-properties" groups-attribute="Roles">
        <users-properties path="example-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="Application Security" plain-text="true"/>
        <groups-properties path="example-roles.properties" relative-to="jboss.server.config.dir"/>
      </properties-realm>
    </security-realms>
    ...
    <http>
      ...
      <http-authentication-factory name="application-security-http" http-server-mechanism-factory="global" security-domain="application-security">
        <mechanism-configuration>
          <mechanism mechanism-name="FORM"/>
        </mechanism-configuration>
      </http-authentication-factory>
      ...
    </http>
    ...
  </subsystem>

As before the application-security-domain mapping should be added to the Undertow subsystem and the server reloaded or the deployment redeployed as required.

./subsystem=undertow/application-security-domain=application-security:add(http-authentication-factory=application-security-http)

Which results in: -

  <subsystem xmlns="urn:jboss:domain:undertow:4.0">
    ...
    <application-security-domains>
      <application-security-domain name="application-security" http-authentication-factory="application-security-http"/>
    </application-security-domains>
    ...
  </subsystem>

At this stage the authentication is the equivalent of the original configuration however now Elytron components are used exclusively.

Legacy Security Realm

Original Configuration

A legacy security realm can be defined using the following commands to load users passwords and group information from properties files.

./core-service=management/security-realm=ApplicationSecurity:add
./core-service=management/security-realm=ApplicationSecurity/authentication=properties:add(relative-to=jboss.server.config.dir, path=example-users.properties, plain-text=true)
./core-service=management/security-realm=ApplicationSecurity/authorization=properties:add(relative-to=jboss.server.config.dir, path=example-roles.properties)

This results in the following realm definition.

  <security-realm name="ApplicationSecurity">
    <authentication>
      <properties path="example-users.properties" relative-to="jboss.server.config.dir" plain-text="true"/>
    </authentication>
    <authorization>
      <properties path="example-roles.properties" relative-to="jboss.server.config.dir"/>
    </authorization>
  </security-realm>

A legacy security realm would typically be used to secure either the management interfaces or remoting connectors.

Migrated Configuration

One of the motivations for adding the Elytron based security to the application server is to allow a consistent security solution to be used across the server, to replace the security realm the same steps as described in the previous 'Fully Migrated' section can be followed again up until the http-authentication-factory is defined.

A legacy security realm can also be used for SASL based authentication so a sasl-authentication-factory should also be defined.

./subsystem=elytron/sasl-authentication-factory=application-security-sasl:add(sasl-server-factory=elytron, security-domain=application-security, mechanism-configurations=[{mechanism-name=PLAIN}])
  <subsystem xmlns="urn:wildfly:elytron:1.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <sasl>
      ...
      <sasl-authentication-factory name="application-security-sasl" sasl-server-factory="elytron" security-domain="application-security">
        <mechanism-configuration>
          <mechanism mechanism-name="PLAIN"/>
        </mechanism-configuration>
      </sasl-authentication-factory>
      ...
    </sasl>
  </subsystem>

This can be associated with a Remoting connector to use for authentication and the existing security realm reference cleared.

./subsystem=remoting/http-connector=http-remoting-connector:write-attribute(name=sasl-authentication-factory, value=application-security-sasl)
./subsystem=remoting/http-connector=http-remoting-connector:undefine-attribute(name=security-realm)
  <subsystem xmlns="urn:jboss:domain:remoting:4.0">
    ...
    <http-connector name="http-remoting-connector" connector-ref="default" sasl-authentication-factory="application-security-sasl"/>
  </subsystem>

If this new configuration was to be used to secure the management interfaces more suitable names should be chosen but the following commands illustrate how to set the two authentication factories and clear the existing security realm reference.

./core-service=management/management-interface=http-interface:write-attribute(name=http-authentication-factory, value=application-security-http)
./core-service=management/management-interface=http-interface:write-attribute(name=http-upgrade.sasl-authentication-factory, value=application-security-sasl)
./core-service=management/management-interface=http-interface:undefine-attribute(name=security-realm)
  <management-interfaces>
    <http-interface http-authentication-factory="application-security-http">
      <http-upgrade enabled="true" sasl-authentication-factory="application-security-sasl"/>
      <socket-binding http="management-http"/>
    </http-interface>
  </management-interfaces>

LDAP Authentication Migration

The section describing how to migrate from properties based authentication using either PicketBox or legacy security realms to Elytron also contained a lot of additional information regarding defining security domains, authentication factories, and how these are mapped to be used for authentication. This section will illustrate some equivalent LDAP configuration using legacy security realms and PicketBox security domains and show the equivalent configuration using Elytron but will not repeat the steps to wire it all together covered in the previous section.

These configuration examples are developed against a test LDAP sever with user entries like: -

dn: uid=TestUserOne,ou=users,dc=group-to-principal,dc=wildfly,dc=org
objectClass: top
objectClass: inetOrgPerson
objectClass: uidObject
objectClass: person
objectClass: organizationalPerson
cn: Test User One
sn: Test User One
uid: TestUserOne
userPassword: {SSHA}UG8ov2rnrnBKakcARVvraZHqTa7mFWJZlWt2HA==

The group entries then look like: -

dn: uid=GroupOne,ou=groups,dc=group-to-principal,dc=wildfly,dc=org
objectClass: top
objectClass: groupOfUniqueNames
objectClass: uidObject
cn: Group One
uid: GroupOne
uniqueMember: uid=TestUserOne,ou=users,dc=group-to-principal,dc=wildfly,dc=org

For authentication purposes the username will be matched against the 'uid' attribute, also the resulting group name will be taken from the 'uid' attribute of the group entry.

Legacy Security Realm

A connection to the LDAP server and related security realm can be created with the following commands: -

batch
./core-service=management/ldap-connection=MyLdapConnection:add(url="ldap://localhost:10389", search-dn="uid=admin,ou=system", search-credential="secret")

./core-service=management/security-realm=LDAPRealm:add
./core-service=management/security-realm=LDAPRealm/authentication=ldap:add(connection="MyLdapConnection", username-attribute=uid, base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org")


./core-service=management/security-realm=LDAPRealm/authorization=ldap:add(connection=MyLdapConnection)
./core-service=management/security-realm=LDAPRealm/authorization=ldap/username-to-dn=username-filter:add(attribute=uid, base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org")
./core-service=management/security-realm=LDAPRealm/authorization=ldap/group-search=group-to-principal:add(base-dn="ou=groups,dc=group-to-principal,dc=wildfly,dc=org", iterative=true, prefer-original-connection=true, principal-attribute=uniqueMember, search-by=DISTINGUISHED_NAME, group-name=SIMPLE, group-name-attribute=uid)
run-batch

This results in the following configuration.

  <management>
    <security-realms>
      ...
      <security-realm name="LDAPRealm">
        <authentication>
          <ldap connection="MyLdapConnection" base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org">
            <username-filter attribute="uid"/>
          </ldap>
        </authentication>
        <authorization>
          <ldap connection="MyLdapConnection">
            <username-to-dn>
              <username-filter base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org" attribute="uid"/>
            </username-to-dn>
            <group-search group-name="SIMPLE" iterative="true" group-name-attribute="uid">
              <group-to-principal search-by="DISTINGUISHED_NAME" base-dn="ou=groups,dc=group-to-principal,dc=wildfly,dc=org" prefer-original-connection="true">
                <membership-filter principal-attribute="uniqueMember"/>
              </group-to-principal>
            </group-search>
          </ldap>
        </authorization>
      </security-realm>
    </security-realms>
    <outbound-connections>
      <ldap name="MyLdapConnection" url="ldap://localhost:10389" search-dn="uid=admin,ou=system" search-credential="secret"/>
    </outbound-connections>
    ...
  </management>

PicketBox LdapExtLoginModule

The following commands can create a PicketBox security domain configured to use the LdapExtLoginModule to verify a username and password.

./subsystem=security/security-domain=application-security:add
./subsystem=security/security-domain=application-security/authentication=classic:add(login-modules=[{code=LdapExtended, flag=Required, module-options={ \
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, \
java.naming.provider.url=ldap://localhost:10389, \
java.naming.security.authentication=simple, \
bindDN="uid=admin,ou=system", \
bindCredential=secret, \
baseCtxDN="ou=users,dc=group-to-principal,dc=wildfly,dc=org", \
baseFilter="(uid={0})", \
rolesCtxDN="ou=groups,dc=group-to-principal,dc=wildfly,dc=org",\
roleFilter="(uniqueMember={1})", \
roleAttributeID="uid" \
}}])

This results in the following configuration.

  <subsystem xmlns="urn:jboss:domain:security:2.0">
    ...
    <security-domains>
      ...
      <security-domain name="application-security">
        <authentication>
          <login-module code="LdapExtended" flag="required">
            <module-option name="java.naming.factory.initial" value="com.sun.jndi.ldap.LdapCtxFactory"/>
            <module-option name="java.naming.provider.url" value="ldap://localhost:10389"/>
            <module-option name="java.naming.security.authentication" value="simple"/>
            <module-option name="bindDN" value="uid=admin,ou=system"/>
            <module-option name="bindCredential" value="secret"/>
            <module-option name="baseCtxDN" value="ou=users,dc=group-to-principal,dc=wildfly,dc=org"/>
            <module-option name="baseFilter" value="(uid={0})"/>
            <module-option name="rolesCtxDN" value="ou=groups,dc=group-to-principal,dc=wildfly,dc=org"/>
            <module-option name="roleFilter" value="(uniqueMember={1})"/>
            <module-option name="roleAttributeID" value="uid"/>
          </login-module>
        </authentication>
      </security-domain>
    </security-domains>
  </subsystem>

Migrated

Within the Elytron subsystem a directory context can be defined for the connection to LDAP: -

./subsystem=elytron/dir-context=ldap-connection:add(url=ldap://localhost:10389, principal="uid=admin,ou=system", credential-reference={clear-text=secret})

Then a security realm can be created to search LDAP and verify the supplied password: -

./subsystem=elytron/ldap-realm=ldap-realm:add(dir-context=ldap-connection, \
direct-verification=true, \
identity-mapping={search-base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org", \
rdn-identifier="uid", \
attribute-mapping=[{filter-base-dn="ou=groups,dc=group-to-principal,dc=wildfly,dc=org",filter="(uniqueMember={1})",from="uid",to="Roles"}]})

In the prior two examples information is loaded from LDAP to use directly as groups or roles, in the Elytron case information can be loaded from LDAP to associate with the identity as attributes - these can subsequently be mapped to roles but attributes can be loaded for other purposes as well.

By default, if no role-decoder is defined for given security-domain, identity attribute "Roles" is mapped to the identity roles.

This leads to the following configuration.

  <subsystem xmlns="urn:wildfly:elytron:1.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <security-realms>
      ...
      <ldap-realm name="ldap-realm" dir-context="ldap-connection" direct-verification="true">
        <identity-mapping rdn-identifier="uid" search-base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org">
          <attribute-mapping>
            <attribute from="uid" to="Roles" filter="(uniqueMember={1})" filter-base-dn="ou=groups,dc=group-to-principal,dc=wildfly,dc=org"/>
          </attribute-mapping>
        </identity-mapping>
      </ldap-realm>
    </security-realms>
    ...
    <dir-contexts>
      <dir-context name="ldap-connection" url="ldap://localhost:10389" principal="uid=admin,ou=system">
        <credential-reference clear-text="secret"/>
      </dir-context>
    </dir-contexts>
  </subsystem>

Composite Stores Migration

When using either PicketBox or the legacy security realms it is possible to define a configuration where authentication is performed against one identity store whilst the information used for authorization is loaded from a different store, when using WildFly Elytron this can be achieved by using an aggregate security realm.

The example here makes use of a properties file for authentication and then searches LDAP to load group / role information. Both of these are based on the previous examples within this document so the environmental information is not repeated here.

PicketBox Based Configuration

A PicketBox based security domain can be created by using the following CLI commands: -

./subsystem=security/security-domain=application-security:add
./subsystem=security/security-domain=application-security/authentication=classic:add(login-modules=[ \
{code=UsersRoles, flag=Required, module-options={ \
password-stacking=useFirstPass, \
usersProperties=file://${jboss.server.config.dir}/example-users.properties, \
rolesProperties=file://${jboss.server.config.dir}/example-roles.properties}} \
{code=LdapExtended, flag=Required, module-options={ \
password-stacking=useFirstPass, \
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, \
java.naming.provider.url=ldap://localhost:10389, \
java.naming.security.authentication=simple, \
bindDN="uid=admin,ou=system", \
bindCredential=secret, \
baseCtxDN="ou=users,dc=group-to-principal,dc=wildfly,dc=org", \
baseFilter="(uid={0})", \
rolesCtxDN="ou=groups,dc=group-to-principal,dc=wildfly,dc=org",\
roleFilter="(uniqueMember={1})", \
roleAttributeID="uid" \
}}])

This results in the following domain definition: -

  <security-domain name="application-security">
    <authentication>
      <login-module code="UsersRoles" flag="required">
        <module-option name="password-stacking" value="useFirstPass"/>
        <module-option name="usersProperties" value="file://${jboss.server.config.dir}/example-users.properties"/>
        <module-option name="rolesProperties" value="file://${jboss.server.config.dir}/example-roles.properties"/>
      </login-module>
      <login-module code="LdapExtended" flag="required">
        <module-option name="password-stacking" value="useFirstPass"/>
        <module-option name="java.naming.factory.initial" value="com.sun.jndi.ldap.LdapCtxFactory"/>
        <module-option name="java.naming.provider.url" value="ldap://localhost:10389"/>
        <module-option name="java.naming.security.authentication" value="simple"/>
        <module-option name="bindDN" value="uid=admin,ou=system"/>
        <module-option name="bindCredential" value="secret"/>
        <module-option name="baseCtxDN" value="ou=users,dc=group-to-principal,dc=wildfly,dc=org"/>
        <module-option name="baseFilter" value="(uid={0})"/>
        <module-option name="rolesCtxDN" value="ou=groups,dc=group-to-principal,dc=wildfly,dc=org"/>
        <module-option name="roleFilter" value="(uniqueMember={1})"/>
        <module-option name="roleAttributeID" value="uid"/>
      </login-module>
    </authentication>
  </security-domain>

During an authentication attempt the 'UsersRoles' login module will first be called to perform authentication based on the supplied credential, then the 'LdapExtLoginModule' will be called which will proceed to query LDAP to load the roles for the identity.

Legacy Security Realm Configuration

An equivalent configuration can also be created using the legacy security realms with the following commands: -

./core-service=management/ldap-connection=MyLdapConnection:add(url="ldap://localhost:10389", search-dn="uid=admin,ou=system", search-credential="secret")

./core-service=management/security-realm=ApplicationSecurity:add
./core-service=management/security-realm=ApplicationSecurity/authentication=properties:add(path=example-users.properties, relative-to=jboss.server.config.dir, plain-text=true)

batch
./core-service=management/security-realm=ApplicationSecurity/authorization=ldap:add(connection=MyLdapConnection)
./core-service=management/security-realm=ApplicationSecurity/authorization=ldap/username-to-dn=username-filter:add(attribute=uid, base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org")
./core-service=management/security-realm=ApplicationSecurity/authorization=ldap/group-search=group-to-principal:add(base-dn="ou=groups,dc=group-to-principal,dc=wildfly,dc=org", iterative=true, prefer-original-connection=true, principal-attribute=uniqueMember, search-by=DISTINGUISHED_NAME, group-name=SIMPLE, group-name-attribute=uid)
run-batch

This results in the following realm definition: -

  <security-realm name="ApplicationSecurity">
    <authentication>
      <properties path="example-users.properties" relative-to="jboss.server.config.dir" plain-text="true"/>
    </authentication>
    <authorization>
      <ldap connection="MyLdapConnection">
        <username-to-dn>
          <username-filter base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org" attribute="uid"/>
        </username-to-dn>
        <group-search group-name="SIMPLE" iterative="true" group-name-attribute="uid">
          <group-to-principal search-by="DISTINGUISHED_NAME" base-dn="ou=groups,dc=group-to-principal,dc=wildfly,dc=org" prefer-original-connection="true">
            <membership-filter principal-attribute="uniqueMember"/>
          </group-to-principal>
        </group-search>
      </ldap>
    </authorization>
  </security-realm>

  <outbound-connections>
    <ldap name="MyLdapConnection" url="ldap://localhost:10389" search-dn="uid=admin,ou=system" search-credential="secret"/>
  </outbound-connections>

As with the PicketBox example, authentication is first performed using the properties file - then group searching is performed against LDAP.

Migrated WildFly Elytron Configuration

The equivalent WildFly Elytron configuration can be defined with the following commands: -

./subsystem=elytron/dir-context=ldap-connection:add(url=ldap://localhost:10389, principal="uid=admin,ou=system", credential-reference={clear-text=secret})

./subsystem=elytron/ldap-realm=ldap-realm:add(dir-context=ldap-connection, \
direct-verification=true, \
identity-mapping={search-base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org", \
rdn-identifier="uid", \
attribute-mapping=[{filter-base-dn="ou=groups,dc=group-to-principal,dc=wildfly,dc=org",filter="(uniqueMember={1})",from="uid",to="Roles"}]})

./subsystem=elytron/properties-realm=application-properties:add(users-properties={path=example-users.properties, relative-to=jboss.server.config.dir, plain-text=true, digest-realm-name="Application Security"}, groups-properties={path=example-roles.properties, relative-to=jboss.server.config.dir}, groups-attribute=Roles)

./subsystem=elytron/aggregate-realm=combined-realm:add(authentication-realm=application-properties, authorization-realm=ldap-realm)

./subsystem=elytron/security-domain=application-security:add(realms=[{realm=combined-realm}], default-realm=combined-realm, permission-mapper=default-permission-mapper)
./subsystem=elytron/http-authentication-factory=application-security-http:add(http-server-mechanism-factory=global, security-domain=application-security, mechanism-configurations=[{mechanism-name=BASIC}])

This results in the following definitions: -

  <subsystem xmlns="urn:wildfly:elytron:1.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <security-domains>
      ...
      <security-domain name="application-security" default-realm="combined-realm" permission-mapper="default-permission-mapper">
        <realm name="combined-realm"/>
      </security-domain>
    </security-domains>
    <security-realms>
      <aggregate-realm name="combined-realm" authentication-realm="application-properties" authorization-realm="ldap-realm"/>
        ...
        <properties-realm name="application-properties" groups-attribute="Roles">
          <users-properties path="example-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="Application Security" plain-text="true"/>
          <groups-properties path="example-roles.properties" relative-to="jboss.server.config.dir"/>
        </properties-realm>
        <ldap-realm name="ldap-realm" dir-context="ldap-connection" direct-verification="true">
          <identity-mapping rdn-identifier="uid" search-base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org">
            <attribute-mapping>
              <attribute from="uid" to="Roles" filter="(uniqueMember={1})" filter-base-dn="ou=groups,dc=group-to-principal,dc=wildfly,dc=org"/>
            </attribute-mapping>
          </identity-mapping>
        </ldap-realm>
    </security-realms>
    ...
    <http>
      ...
      <http-authentication-factory name="application-security-http" http-server-mechanism-factory="global" security-domain="application-security">
        <mechanism-configuration>
          <mechanism mechanism-name="BASIC"/>
        </mechanism-configuration>
      </http-authentication-factory>
      ...
    </http>
    ...
    <dir-contexts>
      <dir-context name="ldap-connection" url="ldap://localhost:10389" principal="uid=admin,ou=system">
        <credential-reference clear-text="secret"/>
      </dir-context>
    </dir-contexts>
  </subsystem>

Within the WildFly Elytron example a new security realm 'aggregate-realm' has been defined, this definition specifies which of the defined security realms should be used for the authentication step and which of the security realms should be used for the loading of the identity used for subsequent authorization decisions.

Database Authentication

The section describing how to migrate from database accessible via JDBC datasource based authentication using PicketBox to Elytron. This section will illustrate some equivalent configuration using PicketBox security domains and show the equivalent configuration using Elytron but will not repeat the steps to wire it all together covered in the previous sections.

These configuration examples are developed against a test database with users table like:

CREATE TABLE User (
    id BIGINT NOT NULL,
    username VARCHAR(255),
    password VARCHAR(255),
    role ENUM('admin', 'manager', 'user'),
    PRIMARY KEY (id),
    UNIQUE (username)
)

For authentication purposes the username will be matched against the 'username' column, password will be expected in hex-encoded MD5 hash in 'password' column. User role for authorization purposes will be taken from 'role' column.

PicketBox Database LoginModule

The following commands can create a PicketBox security domain configured to use database accessible via JDBC datasource to verify a username and password and to assign roles.

./subsystem=security/security-domain=application-security/:add
./subsystem=security/security-domain=application-security/authentication=classic:add(login-modules=[{code=Database, flag=Required, module-options={ \
    dsJndiName="java:jboss/datasources/ExampleDS", \
    principalsQuery="SELECT password FROM User WHERE username = ?", \
    rolesQuery="SELECT role, 'Roles' FROM User WHERE username = ?", \
    hashAlgorithm=MD5, \
    hashEncoding=base64 \
}}])

This results in the following configuration.

        <subsystem xmlns="urn:jboss:domain:security:2.0">
            <security-domains>
                ...
                <security-domain name="application-security">
                    <authentication>
                        <login-module code="Database" flag="required">
                            <module-option name="dsJndiName" value="java:jboss/datasources/ExampleDS"/>
                            <module-option name="principalsQuery" value="SELECT password FROM User WHERE username = ?"/>
                            <module-option name="rolesQuery" value="SELECT role, 'Roles' FROM User WHERE username = ?"/>
                            <module-option name="hashAlgorithm" value="MD5"/>
                            <module-option name="hashEncoding" value="base64"/>
                        </login-module>
                    </authentication>
                </security-domain>
            </security-domains>
        </subsystem>

Migrated

Within the Elytron subsystem to use database accesible via JDBC you need to define jdbc-realm:

./subsystem=elytron/jdbc-realm=jdbc-realm:add(principal-query=[{ \
    data-source=ExampleDS, \
    sql="SELECT role, password FROM User WHERE username = ?", \
    attribute-mapping=[{index=1, to=Roles}] \
    simple-digest-mapper={algorithm=simple-digest-md5, password-index=2}, \
}])

This results in the following overall configuration:

        <subsystem xmlns="urn:wildfly:elytron:1.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
            ...
            <security-realms>
                ...
                <jdbc-realm name="jdbc-realm">
                    <principal-query sql="SELECT role, password FROM User WHERE username = ?" data-source="ExampleDS">
                        <attribute-mapping>
                            <attribute to="Roles" index="1"/>
                        </attribute-mapping>
                        <simple-digest-mapper password-index="2"/>
                    </principal-query>
                </jdbc-realm>
                ...
            </security-realms>
            ...
        </subsystem>

In comparison with PicketBox solution, Elytron jdbc-realm use one SQL query to obtain all user attributes and credentials. Their extraction from SQL result specifies mappers.

N-M relation beetween user and roles

When using a n:m-relation beetween user and roles (which means: the user has multiple roles), the previous configuration does not work.

The database:

CREATE TABLE User (
    id BIGINT NOT NULL,
    username VARCHAR(255),
    password VARCHAR(255),
    PRIMARY KEY (id),
    UNIQUE (username)
)

CREATE TABLE Role(
    id BIGINT NOT NULL,
    rolename VARCHAR(255),
    PRIMARY KEY (id),
    UNIQUE (rolename)
)

CREATE TABLE Userrole(
    userid BIGINT not null,
    roleid BIGINT not null,
    PRIMARY KEY (userid, roleid),
    FOREIGN KEY (userid) references User(id,
    FOREIGN KEY (roleid) references Role(id)
)

Here you need two configure two principal queries:

	<jdbc-realm name="jdbc-realm">
		<principal-query sql="SELECT PASSWORD FROM USER WHERE USERNAME = ?" data-source="ExampleDS">
			<clear-password-mapper password-index="1"/>
		</principal-query>
		<principal-query sql="SELECT R.ROLENAME from ROLE AS R, USERROLE AS UR, USER AS U WHERE U.USERNAME=? AND UR.ROLEID = R.ID AND UR.USERID = U.ID" data-source="ExampleDS">
			<attribute-mapping>
				<attribute to="roles" index="1"/>
			</attribute-mapping>
		</principal-query>
	</jdbc-realm>

The second query needs an attribute mapping to decode the selected rolename column (index 1):

	<mappers>
		...
		<simple-role-decoder name="from-roles-attribute" attribute="roles"/>
		...
	</mappers>

The role decoder is referenced by the security domain:

	<security-domain name="MyDomain" default-realm="jdbc-realm" permission-mapper="default-permission-mapper">
		<realm name="MyDbRealm" role-decoder="from-roles-attribute"/>
	</security-domain>

Kerberos Authentication Migration

When working with Kerberos configuration it is possible for the application server to rely on configuration from the environment or the key configuration can be specified using system properties, for the purpose of these examples I define system properties - these properties are applicable to both the legacy configuration and the migrated Elytron configuration.

./system-property=sun.security.krb5.debug:add(value=true)
./system-property=java.security.krb5.realm:add(value=ELYTRON.ORG)
./system-property=java.security.krb5.kdc:add(value=kdc.elytron.org)

The first line makes debugging easier but the last two lines specify the Kerberos realm in use and the address of the KDC.

  <system-properties>
    <property name="sun.security.krb5.debug" value="true"/>
    <property name="java.security.krb5.realm" value="ELYTRON.ORG"/>
    <property name="java.security.krb5.kdc" value="kdc.elytron.org"/>
  </system-properties>

HTTP Authentication

Legacy Security Realm

A legacy security realm can be define so that SPNEGO authentication can be enabled for the HTTP management interface.

./core-service=management/security-realm=Kerberos:add
./core-service=management/security-realm=Kerberos/server-identity=kerberos:add
./core-service=management/security-realm=Kerberos/server-identity=kerberos/keytab=HTTP\/test-server.elytron.org@ELYTRON.ORG:add(path=/home/darranl/src/kerberos/test-server.keytab, debug=true)
./core-service=management/security-realm=Kerberos/authentication=kerberos:add(remove-realm=true)

This results in the following configuration: -

  <security-realms>
    ...
    <security-realm name="Kerberos">
      <server-identities>
        <kerberos>
          <keytab principal="HTTP/test-server.elytron.org@ELYTRON.ORG" path="/home/darranl/src/kerberos/test-server.keytab" debug="true"/>
        </kerberos>
      </server-identities>
      <authentication>
        <kerberos remove-realm="true"/>
      </authentication>
    </security-realm>
  </security-realms>
Application SPNEGO

Alternatively deployed applications would make use of a pair of security domains.

./subsystem=security/security-domain=host:add
./subsystem=security/security-domain=host/authentication=classic:add
./subsystem=security/security-domain=host/authentication=classic/login-module=1:add(code=Kerberos, flag=Required, module-options={storeKey=true, useKeyTab=true, principal=HTTP/test-server.elytron.org@ELYTRON.ORG, keyTab=/home/darranl/src/kerberos/test-server.keytab, debug=true}
./subsystem=security/security-domain=SPNEGO:add
./subsystem=security/security-domain=SPNEGO/authentication=classic:add
./subsystem=security/security-domain=SPNEGO/authentication=classic/login-module=1:add(code=SPNEGO, flag=requisite,  module-options={password-stacking=useFirstPass, serverSecurityDomain=host})
./subsystem=security/security-domain=SPNEGO/authentication=classic/login-module=1:write-attribute(name=module, value=org.jboss.security.negotiation)
./subsystem=security/security-domain=SPNEGO/authentication=classic/login-module=2:add(code=UsersRoles, flag=required, module-options={password-stacking=useFirstPass, usersProperties=file:///home/darranl/src/kerberos/spnego-users.properties, rolesProperties=file:///home/darranl/src/kerberos/spnego-roles.properties, defaultUsersProperties=file:///home/darranl/src/kerberos/spnego-users.properties, defaultRolesProperties=file:///home/darranl/src/kerberos/spnego-roles.properties})

This results in: -

  <subsystem xmlns="urn:jboss:domain:security:2.0">
    <security-domains>
      ...
      <security-domain name="host">
        <authentication>
          <login-module name="1" code="Kerberos" flag="required">
            <module-option name="storeKey" value="true"/>
            <module-option name="useKeyTab" value="true"/>
            <module-option name="principal" value="HTTP/test-server.elytron.org@ELYTRON.ORG"/>
            <module-option name="keyTab" value="/home/darranl/src/kerberos/test-server.keytab"/>
            <module-option name="debug" value="true"/>
          </login-module>
        </authentication>
      </security-domain>
      <security-domain name="SPNEGO">
        <authentication>
          <login-module name="1" code="SPNEGO" flag="requisite" module="org.jboss.security.negotiation">
            <module-option name="password-stacking" value="useFirstPass"/>
            <module-option name="serverSecurityDomain" value="host"/>
          </login-module>
          <login-module name="2" code="UsersRoles" flag="required">
            <module-option name="password-stacking" value="useFirstPass"/>
            <module-option name="usersProperties" value="file:///home/darranl/src/kerberos/spnego-users.properties"/>
            <module-option name="rolesProperties" value="file:///home/darranl/src/kerberos/spnego-roles.properties"/>
            <module-option name="defaultUsersProperties" value="file:///home/darranl/src/kerberos/spnego-users.properties"/>
            <module-option name="defaultRolesProperties" value="file:///home/darranl/src/kerberos/spnego-roles.properties"/>
          </login-module>
        </authentication>
      </security-domain>
    </security-domains>
  </subsystem>

An application can now be deployed referencing the SPNEGO security domain and secured with SPNEGO mechanism.

Migrated SPNEGO

The equivalent configuration can be achieved with WildFly Elytron by first defining a security realm which will be used to load identity information.

./subsystem=elytron/properties-realm=spnego-properties:add(users-properties={path=/home/darranl/src/kerberos/spnego-users.properties, plain-text=true, digest-realm-name=ELYTRON.ORG}, groups-properties={path=/home/darranl/src/kerberos/spnego-roles.properties})

Next a Kerberos security factory is defined which allows the server to load it's own Kerberos identity.

./subsystem=elytron/kerberos-security-factory=test-server:add(path=/home/darranl/src/kerberos/test-server.keytab, principal=HTTP/test-server.elytron.org@ELYTRON.ORG, debug=true)

As with the previous examples we define a security realm to pull together the policy as well as a HTTP authentication factory for the authentication policy.

./subsystem=elytron/security-domain=SPNEGODomain:add(default-realm=spnego-properties, realms=[{realm=spnego-properties, role-decoder=groups-to-roles}], permission-mapper=default-permission-mapper)
./subsystem=elytron/http-authentication-factory=spnego-http-authentication:add(security-domain=SPNEGODomain, http-server-mechanism-factory=global,mechanism-configurations=[{mechanism-name=SPNEGO, credential-security-factory=test-server}])

Overall this results in the following configuration: -

  <subsystem xmlns="urn:wildfly:elytron:1.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <security-domains>
    ...
      <security-domain name="SPNEGODomain" default-realm="spnego-properties" permission-mapper="default-permission-mapper">
        <realm name="spnego-properties" role-decoder="groups-to-roles"/>
      </security-domain>
    </security-domains>
    <security-realms>
      ...
      <properties-realm name="spnego-properties">
        <users-properties path="/home/darranl/src/kerberos/spnego-users.properties" digest-realm-name="ELYTRON.ORG" plain-text="true"/>
        <groups-properties path="/home/darranl/src/kerberos/spnego-roles.properties"/>
      </properties-realm>
    </security-realms>
    <credential-security-factories>
      <kerberos-security-factory name="test-server" principal="HTTP/test-server.elytron.org@ELYTRON.ORG" path="/home/darranl/src/kerberos/test-server.keytab" debug="true"/>
    </credential-security-factories>
    ...
    <http>
      ...
      <http-authentication-factory name="spnego-http-authentication" http-server-mechanism-factory="global" security-domain="SPNEGODomain">
        <mechanism-configuration>
          <mechanism mechanism-name="SPNEGO" credential-security-factory="test-server"/>
        </mechanism-configuration>
      </http-authentication-factory>
      ...
    </http>
    ...
  </subsystem>

Now, to enable SPNEGO authentication for the HTTP management interface, update this interface to reference the http-authentication-factory defined above, as described in the properties authentication section.

Alternatively, to secure an application using SPNEGO authentication, an application security domain can be defined in the Undertow subsystem to map security domains to the http-authentication-factory defined above, as described in the properties authentication section.

Remoting / SASL Authentication

Legacy Security Realm

It is also possible to define a legacy security realm for Kerberos / GSSAPI SASL authenticatio for Remoting authentication such as the native management interface.

./core-service=management/security-realm=Kerberos:add
./core-service=management/security-realm=Kerberos/server-identity=kerberos:add
./core-service=management/security-realm=Kerberos/server-identity=kerberos/keytab=remote\/test-server.elytron.org@ELYTRON.ORG:add(path=/home/darranl/src/kerberos/remote-test-server.keytab, debug=true)
./core-service=management/security-realm=Kerberos/authentication=kerberos:add(remove-realm=true)
  <management>
    <security-realms>
      ...
      <security-realm name="Kerberos">
        <server-identities>
          <kerberos>
            <keytab principal="remote/test-server.elytron.org@ELYTRON.ORG" path="/home/darranl/src/kerberos/remote-test-server.keytab" debug="true"/>
          </kerberos>
        </server-identities>
        <authentication>
          <kerberos remove-realm="true"/>
        </authentication>
      </security-realm>
    </security-realms>
    ...
  </management>
Migrated GSSAPI

The steps to define the equivalent Elytron configuration are very similar to the HTTP example.

First define the security realm to load the identity from: -

./path=kerberos:add(relative-to=user.home, path=src/kerberos)
./subsystem=elytron/properties-realm=kerberos-properties:add(users-properties={path=kerberos-users.properties, relative-to=kerberos, digest-realm-name=ELYTRON.ORG}, groups-properties={path=kerberos-groups.properties, relative-to=kerberos})

Then define the Kerberos security factory for the server's identity.

./subsystem=elytron/kerberos-security-factory=test-server:add(relative-to=kerberos, path=remote-test-server.keytab, principal=remote/test-server.elytron.org@ELYTRON.ORG)

Finally define the security domain and this time a SASL authentication factory.

./subsystem=elytron/security-domain=KerberosDomain:add(default-realm=kerberos-properties, realms=[{realm=kerberos-properties, role-decoder=groups-to-roles}], permission-mapper=default-permission-mapper)
./subsystem=elytron/sasl-authentication-factory=gssapi-authentication-factory:add(security-domain=KerberosDomain, sasl-server-factory=elytron, mechanism-configurations=[{mechanism-name=GSSAPI, credential-security-factory=test-server}])

This results in the following subsystem configuration: -

  <subsystem xmlns="urn:wildfly:elytron:1.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <security-domains>
      ...
      <security-domain name="KerberosDomain" default-realm="kerberos-properties" permission-mapper="default-permission-mapper">
        <realm name="kerberos-properties" role-decoder="groups-to-roles"/>
      </security-domain>
    </security-domains>
    <security-realms>
     ...
       <properties-realm name="kerberos-properties">
         <users-properties path="kerberos-users.properties" relative-to="kerberos" digest-realm-name="ELYTRON.ORG"/>
         <groups-properties path="kerberos-groups.properties" relative-to="kerberos"/>
       </properties-realm>
     </security-realms>
     <credential-security-factories>
       <kerberos-security-factory name="test-server" principal="remote/test-server.elytron.org@ELYTRON.ORG" path="remote-test-server.keytab" relative-to="kerberos"/>
     </credential-security-factories>
     ...
     <sasl>
       ...
       <sasl-authentication-factory name="gssapi-authentication-factory" sasl-server-factory="elytron" security-domain="KerberosDomain">
         <mechanism-configuration>
           <mechanism mechanism-name="GSSAPI" credential-security-factory="test-server"/>
         </mechanism-configuration>
       </sasl-authentication-factory>
       ...
     </sasl>
   </subsystem>

The management interface or Remoting connectors can now be updated to reference the SASL authentication factory.

The two Elytron examples defined here could also be combined into one to use a shared security domain and security realm and just use protocol specific authentication factories each referencing their own Kerberos security factory.

Caching Migration

Where a PicketBox based security domain is defined it is possible to enable caching for that security domain, this enables subsequent hits to the identity store to be avoided as an in memory cache can be used instead, this example demonstrates how caching can be used with a WildFly Elytron based configuration.

The purpose of this chapter is to highlight the migration of a configuration with caching enabled, this example is based in the previous LDAP example but with caching enabled.

PicketBox Example

A PicketBox based security domain can be defined with the following commands.

./subsystem=security/security-domain=application-security:add(cache-type=default)
./subsystem=security/security-domain=application-security/authentication=classic:add(login-modules=[{code=LdapExtended, flag=Required, module-options={ \
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, \
java.naming.provider.url=ldap://localhost:10389, \
java.naming.security.authentication=simple, \
bindDN="uid=admin,ou=system", \
bindCredential=secret, \
baseCtxDN="ou=users,dc=group-to-principal,dc=wildfly,dc=org", \
baseFilter="(uid={0})", \
rolesCtxDN="ou=groups,dc=group-to-principal,dc=wildfly,dc=org",\
roleFilter="(uniqueMember={1})", \
roleAttributeID="uid" \
}}])

Resulting in the following security domain definition: -

  <subsystem xmlns="urn:jboss:domain:security:2.0">
    <security-domains>
      ...
      <security-domain name="application-security" cache-type="default">
        <authentication>
          <login-module code="LdapExtended" flag="required">
            <module-option name="java.naming.factory.initial" value="com.sun.jndi.ldap.LdapCtxFactory"/>
            <module-option name="java.naming.provider.url" value="ldap://localhost:10389"/>
            <module-option name="java.naming.security.authentication" value="simple"/>
            <module-option name="bindDN" value="uid=admin,ou=system"/>
            <module-option name="bindCredential" value="secret"/>
            <module-option name="baseCtxDN" value="ou=users,dc=group-to-principal,dc=wildfly,dc=org"/>
            <module-option name="baseFilter" value="(uid={0})"/>
            <module-option name="rolesCtxDN" value="ou=groups,dc=group-to-principal,dc=wildfly,dc=org"/>
            <module-option name="roleFilter" value="(uniqueMember={1})"/>
            <module-option name="roleAttributeID" value="uid"/>
          </login-module>
        </authentication>
      </security-domain>
    </security-domains>
  </subsystem>

The key difference to the raw LDAP example is that a cache-type of 'default' has been specified on the security domain. The default cache-type is an in memory cache, when using PicketBox it is also possible to specify a cache-type of 'infinispan' although this is not supported with WildFly Elytron as various aspects of a SecurityIdentity are not suitable for replication.

Migrated Example

When using WildFly Elytron where caching is required the individual security realm is wrapped using a cache, a migrated configuration can be defined with the following commands: -

./subsystem=elytron/dir-context=ldap-connection:add(url=ldap://localhost:10389, principal="uid=admin,ou=system", credential-reference={clear-text=secret})
./subsystem=elytron/ldap-realm=ldap-realm:add(dir-context=ldap-connection, \
direct-verification=true, \
identity-mapping={search-base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org", \
rdn-identifier="uid", \
attribute-mapping=[{filter-base-dn="ou=groups,dc=group-to-principal,dc=wildfly,dc=org",filter="(uniqueMember={1})",from="uid",to="Roles"}]})
./subsystem=elytron/caching-realm=cached-ldap:add(realm=ldap-realm)

These can then be used in a security domain and subsequently an authentication factory.

./subsystem=elytron/security-domain=application-security:add(realms=[{realm=cached-ldap}], default-realm=cached-ldap, permission-mapper=default-permission-mapper)
./subsystem=elytron/http-authentication-factory=application-security-http:add(http-server-mechanism-factory=global, security-domain=application-security, mechanism-configurations=[{mechanism-name=BASIC}])

In this final step it is very important that the caching-realm is referenced rather than the original realm otherwise caching will be bypassed.

This results in the following definitions: -

  <subsystem xmlns="urn:wildfly:elytron:1.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <security-domains>
      ...
      <security-domain name="application-security" default-realm="cached-ldap" permission-mapper="default-permission-mapper">
        <realm name="cached-ldap"/>
      </security-domain>
    </security-domains>
    <security-realms>
      ...
      <ldap-realm name="ldap-realm" dir-context="ldap-connection" direct-verification="true">
        <identity-mapping rdn-identifier="uid" search-base-dn="ou=users,dc=group-to-principal,dc=wildfly,dc=org">
          <attribute-mapping>
            <attribute from="uid" to="Roles" filter="(uniqueMember={1})" filter-base-dn="ou=groups,dc=group-to-principal,dc=wildfly,dc=org"/>
          </attribute-mapping>
        </identity-mapping>
      </ldap-realm>
      <caching-realm name="cached-ldap" realm="ldap-realm"/>
    </security-realms>
    ...
    <http>
      ...
      <http-authentication-factory name="application-security-http" http-server-mechanism-factory="global" security-domain="application-security">
        <mechanism-configuration>
          <mechanism mechanism-name="BASIC"/>
        </mechanism-configuration>
      </http-authentication-factory>
      ...
    </http>
    ...
    <dir-contexts>
      <dir-context name="ldap-connection" url="ldap://localhost:10389" principal="uid=admin,ou=system">
        <credential-reference clear-text="secret"/>
      </dir-context>
    </dir-contexts>
  </subsystem>

Clients

Application Client Migration

Naming Client

This migration example assumes a client application performs a remote JNDI lookup using an InitialContext backed by the org.jboss.naming.remote.client.InitialContextFactory class.

Original Configuration

An InitialContext backed by the org.jboss.naming.remote.client.InitialContextFactory class can be created by specifying properties that contain the URL of the naming provider to connect to along with appropriate user credentials:

Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
properties.put(Context.PROVIDER_URL, "http-remoting://127.0.0.1:8080");
properties.put(Context.SECURITY_PRINCIPAL, "bob");
properties.put(Context.SECURITY_CREDENTIALS, "secret");
InitialContext context = new InitialContext(properties);
Bar bar = (Bar) context.lookup("foo/bar");
...
Migrated Configuration

An InitialContext backed by the org.wildfly.naming.client.WildFlyInitialContextFactory class can be created by specifying a property that contains the URL of the naming provider to connect to. The user credentials can be specified using a WildFly client configuration file or programmatically.

Configuration File Approach

A wildfly-config.xml file that contains the user credentials to use when establishing a connection to the naming provider can be added to the client application's META-INF directory:

wildfly-config.xml

<configuration>
    <authentication-client xmlns="urn:elytron:1.0">
        <authentication-rules>
            <rule use-configuration="namingConfig">
                <match-host name="127.0.0.1"/>
            </rule>
        </authentication-rules>
        <authentication-configurations>
            <configuration name="namingConfig">
                <set-user-name name="bob"/>
                <credentials>
                    <clear-password password="secret"/>
                </credentials>
            </configuration>
        </authentication-configurations>
    </authentication-client>
</configuration>

An InitialContext can then be created as follows:

Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
properties.put(Context.PROVIDER_URL, "remote+http://127.0.0.1:8080");
InitialContext context = new InitialContext(properties);
Bar bar = (Bar) context.lookup("foo/bar");
...
Programmatic Approach

The user credentials to use when establishing a connection to the naming provider can be specified directly in the client application’s code:

// create your authentication configuration
AuthenticationConfiguration namingConfig = AuthenticationConfiguration.empty().useName("bob").usePassword("secret");

// create your authentication context
AuthenticationContext context = AuthenticationContext.empty().with(MatchRule.ALL.matchHost("127.0.0.1"), namingConfig);

// create a callable that creates and uses an InitialContext
Callable<Void> callable = () -> {
    Properties properties = new Properties();
    properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
    properties.put(Context.PROVIDER_URL, "remote+http://127.0.0.1:8080");
    InitialContext context = new InitialContext(properties);
    Bar bar = (Bar) context.lookup("foo/bar");
    ...
    return null;
};

// use your authentication context to run your callable
context.runCallable(callable);

EJB Client

This migration example assumes a client application is configured to invoke an EJB deployed on a remote server using a jboss-ejb-client.properties file.

Original Configuration

A jboss-ejb-client.properties file that contains the information needed to connect to the remote server can be specified in a client application’s META-INF directory:

jboss-ejb-client.properties

remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
remote.connections=default
remote.connection.default.host=127.0.0.1
remote.connection.default.port = 8080
remote.connection.default.username=bob
remote.connection.default.password=secret

An EJB can then be looked up and a method can be invoked on it as follows:

// create an InitialContext
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
properties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
InitialContext context = new InitialContext(properties);

// look up an EJB and invoke one of its methods
RemoteCalculator statelessRemoteCalculator = (RemoteCalculator) context.lookup(
    "ejb:/ejb-remote-server-side//CalculatorBean!" + RemoteCalculator.class.getName());
int sum = statelessRemoteCalculator.add(101, 202);
Migrated Configuration

The information needed to connect to the remote server can be specified using a WildFly client configuration file or programmatically.

Configuration File Approach

A wildfly-config.xml file that contains the information needed to connect to the remote server can be added to the client application's META-INF directory:

wildfly-config.xml

<configuration>
    <authentication-client xmlns="urn:elytron:1.0">
        <authentication-rules>
            <rule use-configuration="ejbConfig">
                <match-host name="127.0.0.1"/>
            </rule>
        </authentication-rules>
        <authentication-configurations>
            <configuration name="ejbConfig">
                <set-user-name name="bob"/>
                <credentials>
                    <clear-password password="secret"/>
                </credentials>
            </configuration>
        </authentication-configurations>
    </authentication-client>
    <jboss-ejb-client xmlns="urn:jboss:wildfly-client-ejb:3.0">
        <connections>
            <connection uri="remote+http://127.0.0.1:8080" />
        </connections>
    </jboss-ejb-client>
</configuration>

An EJB can then be looked up and a method can be invoked on it as follows:

// create an InitialContext
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
InitialContext context = new InitialContext(properties);

// look up an EJB and invoke one of its methods (same as before)
RemoteCalculator statelessRemoteCalculator = (RemoteCalculator) context.lookup(
    "ejb:/ejb-remote-server-side//CalculatorBean!" + RemoteCalculator.class.getName());
int sum = statelessRemoteCalculator.add(101, 202);
Programmatic Approach

The information needed to connect to the remote server can be specified directly in the client application’s code:

// create your authentication configuration
AuthenticationConfiguration ejbConfig = AuthenticationConfiguration.empty().useName("bob").usePassword("secret");

// create your authentication context
AuthenticationContext context = AuthenticationContext.empty().with(MatchRule.ALL.matchHost("127.0.0.1"), ejbConfig);

// create a callable that invokes an EJB
Callable<Void> callable = () -> {

    // create an InitialContext
    Properties properties = new Properties();
    properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
    properties.put(Context.PROVIDER_URL, "remote+http://127.0.0.1:8080");
    InitialContext context = new InitialContext(properties);

    // look up an EJB and invoke one of its methods (same as before)
    RemoteCalculator statelessRemoteCalculator = (RemoteCalculator) context.lookup(
        "ejb:/ejb-remote-server-side//CalculatorBean!" + RemoteCalculator.class.getName());
    int sum = statelessRemoteCalculator.add(101, 202);
    ...
    return null;
};

// use your authentication context to run your callable
context.runCallable(callable);

General Utilities

Security Vault Migration

Security Vault is primarily used in legacy configurations, a vault is used to store sensitive strings outside of the configuration files. WildFly server may only contain a single security vault.

Credential Store introduced in WildFly 11 is meant to expand Security Vault in terms of storing different credential types and introduce easy to implement SPI which allows to deploy custom implementations of CredentialStore SPI. Credentials are stored safely encrypted in storage file outside WildFly configuration files. Each WildFly server may contain multiple credential stores.

To easily migrate vault content into credential store we have added "vault" command into WildFly Elytron Tool. The tool could be found at $JBOSS_HOME/bin directory. It has several scripts named "elytron-tool.*" dependent on your platform of choice. One can use also simple form "java -jar $JBOSS_HOME/bin/wildfly-elytron-tool.jar <command> <arguments>" if it better suites ones needs.

Single Security Vault Conversion

To convert single security vault credential store use following example:

- to get sample vault use testing resources of Elytron Tool project from GitHub [1]

Command to run actual conversion:

./bin/elytron-tool.sh vault --enc-dir vault_data/ --keystore vault-jceks.keystore --keystore-password MASK-2hKo56F1a3jYGnJwhPmiF5 --iteration 34 --salt 12345678 --alias test --location cs-v1.store --summary

Output:

Vault (enc-dir="vault_data/";keystore="vault-jceks.keystore") converted to credential store "cs-v1.store"
Vault Conversion summary:
--------------------------------------
Vault Conversion Successful
CLI command to add new credential store:
/subsystem=elytron/credential-store=test:add(relative-to=jboss.server.data.dir,create=true,modifiable=true,location="cs-v1.store",implementation-properties={"keyStoreType"=>"JCEKS"},credential-reference={clear-text="MASK-2hKo56F1a3jYGnJwhPmiF5;12345678;34"})

Use elytron-tool.sh vault --help to get description of all parameters.

Notes:

- Elytron Tool cannot handle very first version of Security Vault data file.
- --keystore-password can come in two forms (1) masked as shown in the example or (2) clear text. Parameter --salt and --iteration are there to supply information to decrypt the masked password or to generate masked password in output. In case --salt and --iteration are omitted default values are used.
- When --summary parameter is specified, one can see nice output with CLI command to be used in WildFly console to add converted credential store to the configuration.

Bulk Security Vault Conversion

There is possibility to convert multiple vaults to credential store using --bulk-convert parameter with description file.
Example of description file from our tests [2]:

# Bulk conversion descriptor
keystore:target/test-classes/vault-v1/vault-jceks.keystore
keystore-password:MASK-2hKo56F1a3jYGnJwhPmiF5
enc-dir:target/test-classes/vault-v1/vault_data/
salt:12345678
iteration:34
location:target/v1-cs-1.store
alias:test

keystore:target/test-classes/vault-v1/vault-jceks.keystore
keystore-password:secretsecret
enc-dir:target/test-classes/vault-v1/vault_data/
location:target/v1-cs-2.store
alias:test

# different vault vault-v1-more
keystore:target/test-classes/vault-v1-more/vault-jceks.keystore
keystore-password:MASK-2hKo56F1a3jYGnJwhPmiF5
enc-dir:target/test-classes/vault-v1-more/vault_data/
salt:12345678
iteration:34
location:target/v1-cs-more.store
alias:test

After each "keystore:" option new conversion starts. All options are mandatory except "salt:", "iteration:" and "properties:"

Execute following command:

./bin/elytron-tool.sh vault --bulk-convert bulk-vault-conversion-desc --summary

Output:

Vault (enc-dir="vault-v1/vault_data/";keystore="vault-v1/vault-jceks.keystore") converted to credential store "v1-cs-1.store"
Vault Conversion summary:
--------------------------------------
Vault Conversion Successful
CLI command to add new credential store:
/subsystem=elytron/credential-store=test:add(relative-to=jboss.server.data.dir,create=true,modifiable=true,location="v1-cs-1.store",implementation-properties={"keyStoreType"=>"JCEKS"},credential-reference={clear-text="MASK-2hKo56F1a3jYGnJwhPmiF5;12345678;34"})
--------------------------------------

Vault (enc-dir="vault-v1/vault_data/";keystore="vault-v1/vault-jceks.keystore") converted to credential store "v1-cs-2.store"
Vault Conversion summary:
--------------------------------------
Vault Conversion Successful
CLI command to add new credential store:
/subsystem=elytron/credential-store=test:add(relative-to=jboss.server.data.dir,create=true,modifiable=true,location="v1-cs-2.store",implementation-properties={"keyStoreType"=>"JCEKS"},credential-reference={clear-text="secretsecret"})
--------------------------------------

Vault (enc-dir="vault-v1-more/vault_data/";keystore="vault-v1-more/vault-jceks.keystore") converted to credential store "v1-cs-more.store"
Vault Conversion summary:
--------------------------------------
Vault Conversion Successful
CLI command to add new credential store:
/subsystem=elytron/credential-store=test:add(relative-to=jboss.server.data.dir,create=true,modifiable=true,location="v1-cs-more.store",implementation-properties={"keyStoreType"=>"JCEKS"},credential-reference={clear-text="MASK-2hKo56F1a3jYGnJwhPmiF5;12345678;34"})
--------------------------------------

The result is conversion of all vaults with proper CLI commands. 

Security Properties

Lets suppose security properties "a" and "c" defined in legacy security:

        <subsystem xmlns="urn:jboss:domain:security:2.0">
            ...
            <security-properties>
                <property name="a" value="b" />
                <property name="c" value="d" />
            </security-properties>
        </subsystem>

To define security properties in Elytron subsystem you need to set attribute security-properties of the subsystem:

./subsystem=elytron:write-attribute(name=security-properties, value={ \
    a = "b", \
    c = "d" \
})

You can also add or change one another property without modification of others using map operations. Following command will set property "e":

./subsystem=elytron:map-put(name=security-properties, key=e, value=f)

By the same way you can also remove one of properties - in example newly created property "e":

./subsystem=elytron:map-remove(name=security-properties, key=e)

Output XML configuration will be:

        <subsystem xmlns="urn:wildfly:elytron:1.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
            <security-properties>
                <security-property name="a" value="b"/>
                <security-property name="c" value="d"/>
            </security-properties>
            ...
        </subsystem>

SSL Migration

Simple SSL Migration

This section describe securing HTTP connections to the server using SSL using Elytron.
It suppose you have already configured SSL using legacy security-realm, for example by Admin Guide#Enable SSL, and your configuration looks like:

<security-realm name="ApplicationRealm">
  <server-identities>
    <ssl>
      <keystore path="server.keystore" relative-to="jboss.server.config.dir" keystore-password="keystore_password" alias="server" key-password="key_password" />
    </ssl>
  </server-identities>
</security-realm>

To switch to Elytron you need to:

  1. Create Elytron key-store - specifying where is the keystore file stored and password by which it is encrypted. Default type of keystore generated using keytool is JKS:

    /subsystem=elytron/key-store=LocalhostKeyStore:add(path=server.keystore,relative-to=jboss.server.config.dir,credential-reference={clear-text="keystore_password"},type=JKS)
  2. Create Elytron key-manager - specifying keystore, alias (using alias-filter) and password of key:

    /subsystem=elytron/key-manager=LocalhostKeyManager:add(key-store=LocalhostKeyStore,alias-filter=server,credential-reference={clear-text="key_password"})
  3. Create Elytron server-ssl-context - specifying only reference to key-manager defined above:

    /subsystem=elytron/server-ssl-context=LocalhostSslContext:add(key-manager=LocalhostKeyManager)
  4. Switch https-listener from legacy security-realm to newly created Elytron ssl-context:

    /subsystem=undertow/server=default-server/https-listener=https:undefine-attribute(name=security-realm)
    /subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=ssl-context,value=LocalhostSslContext)
  5. And reload the server:

    reload

Output XML configuration of Elytron subsystem should look like:

        <subsystem xmlns="urn:wildfly:elytron:1.0" ...>
            ...
            <tls>
                <key-stores>
                    <key-store name="LocalhostKeyStore">
                        <credential-reference clear-text="keystore_password"/>
                        <implementation type="JKS"/>
                        <file path="server.keystore" relative-to="jboss.server.config.dir"/>
                    </key-store>
                </key-stores>
                <key-managers>
                    <key-manager name="LocalhostKeyManager" key-store="LocalhostKeyStore">
                        <credential-reference clear-text="key_password"/>
                    </key-manager>
                </key-managers>
                <server-ssl-contexts>
                    <server-ssl-context name="LocalhostSslContext" key-manager="LocalhostKeyManager"/>
                </server-ssl-contexts>
            </tls>
        </subsystem>

Output https-listener in Undertow subsystem should be:

<https-listener name="https" socket-binding="https" ssl-context="LocalhostSslContext" enable-http2="true"/>

Client-Cert SSL Authentication Migration

This suppose you have already configured Client-Cert SSL authentication using truststore in legacy security-realm, for example by Admin Guide#Add Client-Cert to SSL, and your configuration looks like:

<security-realm name="ApplicationRealm">
  <server-identities>
    <ssl>
      <keystore path="server.keystore" relative-to="jboss.server.config.dir" keystore-password="keystore_password" alias="server" key-password="key_password" />
    </ssl>
  </server-identities>
  <authentication>
    <truststore path="server.truststore" relative-to="jboss.server.config.dir" keystore-password="truststore_password" />
    <local default-user="$local"/>
    <properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
  </authentication>
</security-realm>

Following configuration is sufficient to prevent users without valid certificate and private key to access the server, but it does not provide user identity to the application. That require to define CLIENT_CERT HTTP mechanism / EXTERNAL SASL mechanism, which will be described later.)

At first use steps above to migrate basic part of the configuration. Then continue by following:

  1. Create key-store of truststore - like for keystore above:

    /subsystem=elytron/key-store=TrustStore:add(path=server.truststore,relative-to=jboss.server.config.dir,credential-reference={clear-text="truststore_password"},type=JKS)
  2. Create trust-manager - specifying key-store of trustore, created above:

    /subsystem=elytron/trust-manager=TrustManager:add(key-store=TrustStore)
  3. Modify server-ssl-context to use newly created trustmanager:

    /subsystem=elytron/server-ssl-context=LocalhostSslContext:write-attribute(name=trust-manager,value=TrustManager)
  4. Enable client authentication for server-ssl-context:

    /subsystem=elytron/server-ssl-context=LocalhostSslContext:write-attribute(name=need-client-auth,value=true)
  5. And reload the server:

    reload

Output XML configuration of Elytron subsystem should look like:

        <subsystem xmlns="urn:wildfly:elytron:1.0" ...>
            ...
            <tls>
                <key-stores>
                    <key-store name="LocalhostKeyStore">
                        <credential-reference clear-text="keystore_password"/>
                        <implementation type="JKS"/>
                        <file path="server.keystore" relative-to="jboss.server.config.dir"/>
                    </key-store>
                    <key-store name="TrustStore">
                        <credential-reference clear-text="truststore_password"/>
                        <implementation type="JKS"/>
                        <file path="server.truststore" relative-to="jboss.server.config.dir"/>
                    </key-store>
                </key-stores>
                <key-managers>
                    <key-manager name="LocalhostKeyManager" key-store="LocalhostKeyStore" alias-filter="server">
                        <credential-reference clear-text="key_password"/>
                    </key-manager>
                </key-managers>
                <trust-managers>
                    <trust-manager name="TrustManager" key-store="TrustStore"/>
                </trust-managers>
                <server-ssl-contexts>
                    <server-ssl-context name="LocalhostSslContext" need-client-auth="true" key-manager="LocalhostKeyManager" trust-manager="TrustManager"/>
                </server-ssl-contexts>
            </tls>
        </subsystem>

SSL with Client Cert Migration

As this documentation is primarily intended for users migrating to WildFly Elytron I am going to jump straight into the configuration required with WildFly Elytron.

This section will cover how to create the various resources required to achieve CLIENT_CERT authentication with fallback to username / password authentication for both HTTP and SASL (i.e. Remoting) - both are being covered at the same time as predominantly they require the same core configuration, it is not until the definition of the authentication factories that the configuration becomes really specific.

The WildFly Elytron project already contains a dummy certificate authority set up that we use for testing, the KeyStores within this documentation are from the dummy certificate authority although for anything other than testing these should be replaced with your own.

As a first step we define some paths that point to the wildfly-elytron project so we can use these in the subsequent configuration.

./path=elytron.project:add(path=/home/darranl/src/wildfly10/wildfly-elytron)
./path=elytron.project.jks:add(path=src/test/resources/ca/jks, relative-to=elytron.project)
./path=elytron.project.properties:add(path=src/test/resources/org/wildfly/security/auth/realm, relative-to=elytron.project)

This results in the following configuration.

  <paths>
    <path name="elytron.project" path="/home/darranl/src/wildfly10/wildfly-elytron"/>
    <path name="elytron.project.jks" path="src/test/resources/ca/jks" relative-to="elytron.project"/>
    <path name="elytron.project.properties" path="src/test/resources/org/wildfly/security/auth/realm" relative-to="elytron.project"/>
  </paths>

KeyStores, KeyManagers, and TrustManagers.

The next step is to define the KeyStore resources, for this example three different keystores are used: -

  1. localhost.keystore - Contains the servers key and certificate for 'localhost'.

  2. beetles.keystore - Contains the individual client certificates.

  3. ca.keystore - Contains the certificate of the certificate authority.

When we define the overall configuration we will use the localhost keystore along with the ca keystore for the incoming connections so initially all client certificates signed by the certificate authority will be accepted and subsequently a security realm will check it against the actual certificates within beetles keystore.

./subsystem=elytron/key-store=localhost:add(type=jks, relative-to=elytron.project.jks, path=localhost.keystore, credential-reference={clear-text=Elytron})
./subsystem=elytron/key-store=beetles:add(type=jks, relative-to=elytron.project.jks, path=beetles.keystore, credential-reference={clear-text=Elytron})
./subsystem=elytron/key-store=ca:add(type=jks, relative-to=elytron.project.jks, path=ca.truststore, credential-reference={clear-text=Elytron})

This results in the following definitions: -

  <subsystem xmlns="urn:wildfly:elytron:1.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <tls>
      <key-stores>
        <key-store name="localhost">
          <credential-reference clear-text="Elytron"/>
          <implementation type="jks"/>
          <file path="localhost.keystore" relative-to="elytron.project.jks"/>
        </key-store>
        <key-store name="beetles">
          <credential-reference clear-text="Elytron"/>
          <implementation type="jks"/>
          <file path="beetles.keystore" relative-to="elytron.project.jks"/>
        </key-store>
        <key-store name="ca">
          <credential-reference clear-text="Elytron"/>
          <implementation type="jks"/>
          <file path="ca.truststore" relative-to="elytron.project.jks"/>
        </key-store>
      </key-stores>
    </tls>
  </subsystem>

Next the key and trust manager resources will be defined using these keystores.

./subsystem=elytron/key-manager=localhost-manager:add(algorithm=SunX509, key-store=localhost, credential-reference={clear-text=Elytron})
./subsystem=elytron/trust-manager=ca-manager:add(algorithm=SunX509, key-store=ca)

Resulting in: -

  <subsystem xmlns="urn:wildfly:elytron:1.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <tls>
     ...
       <key-managers>
         <key-manager name="localhost-manager" algorithm="SunX509" key-store="localhost">
           <credential-reference clear-text="Elytron"/>
         </key-manager>
       </key-managers>
       <trust-managers>
         <trust-manager name="ca-manager" algorithm="SunX509" key-store="ca"/>
       </trust-managers>
    </tls>
  </subsystem>

Realms and Domains

Two security realms are now defined, one of these uses properties files from within the WildFly Elytron project to support username/password authentication and the other using the clients certificates for verification.

./subsystem=elytron/properties-realm=test-users:add(users-properties={relative-to=elytron.project.properties, path=clear.properties, plain-text=true, digest-realm-name=ManagementRealm}, groups-properties={relative-to=elytron.project.properties, path=groups.properties}) 
./subsystem=elytron/key-store-realm=key-store-realm:add(key-store=beetles)

Resulting in: -

  <subsystem xmlns="urn:wildfly:elytron:1.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <security-realms>
      ...
      <key-store-realm name="key-store-realm" key-store="beetles"/>
      ...
      <properties-realm name="test-users">
        <users-properties path="clear.properties" relative-to="elytron.project.properties" digest-realm-name="ManagementRealm" plain-text="true"/>
        <groups-properties path="groups.properties" relative-to="elytron.project.properties"/>
      </properties-realm>
    </security-realms>
    ...
  </subsystem>

These security realms can now be referenced from a security domain: -

./subsystem=elytron/security-domain=client-cert-domain:add(realms=[{realm=test-users},{realm=key-store-realm}], \
  default-realm=test-users, \
  permission-mapper=default-permission-mapper)

Resulting in: -

  <subsystem xmlns="urn:wildfly:elytron:1.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <security-domains>
      ...
      <security-domain name="client-cert-domain" default-realm="test-users" permission-mapper="default-permission-mapper">
        <realm name="test-users"/>
        <realm name="key-store-realm"/>
      </security-domain>
    </security-domains>
    ...
  </subsystem>

Before moving onto the individual authentication factories a couple of additional utility resources are also required: -

./subsystem=elytron/constant-realm-mapper=key-store-realm:add(realm-name=key-store-realm)
./subsystem=elytron/x500-attribute-principal-decoder=x500-decoder:add(attribute-name=CN, maximum-segments=1)

Resulting in: -

  <subsystem xmlns="urn:wildfly:elytron:1.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <mappers>
      ...
      <x500-attribute-principal-decoder name="x500-decoder" attribute-name="CN" maximum-segments="1"/>
      ...
      <constant-realm-mapper name="key-store-realm" realm-name="key-store-realm"/>
      ...
    </mappers>
    ...
  </subsystem>

HTTP Authentication Factory

For the HTTP connections we now define a HTTP authentication factory using the previously defined resources and it is configured to support CLIENT_CERT and DIGEST authentication.

./subsystem=elytron/http-authentication-factory=client-cert-digest:add(http-server-mechanism-factory=global, \
  security-domain=client-cert-domain, \
 mechanism-configurations=[{ \
  mechanism-name=CLIENT_CERT, \
  realm-mapper=key-store-realm, \
  pre-realm-principal-transformer=x500-decoder}, \
 {mechanism-name=DIGEST, mechanism-realm-configurations=[{realm-name=ManagementRealm}]}])

Resulting in: -

  <subsystem xmlns="urn:wildfly:elytron:1.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <http>
      ...
      <http-authentication-factory name="client-cert-digest" http-server-mechanism-factory="global" security-domain="client-cert-domain">
        <mechanism-configuration>
          <mechanism mechanism-name="CLIENT_CERT" pre-realm-principal-transformer="x500-decoder" realm-mapper="key-store-realm"/>
          <mechanism mechanism-name="DIGEST">
            <mechanism-realm realm-name="ManagementRealm"/>
          </mechanism>
        </mechanism-configuration>
      </http-authentication-factory>
      ...
    </http>
    ... 
  </subsystem>

Where DIGEST authentication is used we rely on the default configuration within the security domain to select the 'test-users' realm, however where CLIENT_CERT authentication is in use an alternative realm-mapper is referenced to ensure the 'key-store-realm' is used.

Additionally for CLIENT_CERT authentication a principal-transformer is referenced to extract the CN attribute from the distinguished name of the client certificate and use this when accessing the identity from the security realm.

SASL Authentication Factory

The architecture of the two authentication factories if very similar so a SASL authentication factory can be defined in the same way as the HTTP equivalent.

./subsystem=elytron/sasl-authentication-factory=client-cert-digest:add(sasl-server-factory=elytron, \
  security-domain=client-cert-domain, \
  mechanism-configurations=[{mechanism-name=EXTERNAL, \
  realm-mapper=key-store-realm, \
  pre-realm-principal-transformer=x500-decoder}, \
  {mechanism-name=DIGEST-MD5, mechanism-realm-configurations=[{realm-name=ManagementRealm}]}])

This results in: -

  <subsystem xmlns="urn:wildfly:elytron:1.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <sasl>
      ...
      <sasl-authentication-factory name="client-cert-digest" sasl-server-factory="elytron" security-domain="client-cert-domain">
        <mechanism-configuration>
          <mechanism mechanism-name="EXTERNAL" pre-realm-principal-transformer="x500-decoder" realm-mapper="key-store-realm"/>
          <mechanism mechanism-name="DIGEST-MD5">
            <mechanism-realm realm-name="ManagementRealm"/>
          </mechanism>
        </mechanism-configuration>
      </sasl-authentication-factory>
      ...
    </sasl>
    ...
  </subsystem>

Realm mappers and principal transformers are defined in the same way as were defined for HTTP.

SSL Context

An SSL context is also defined for use by the server.

./subsystem=elytron/server-ssl-context=localhost:add(key-manager=localhost-manager, trust-manager=ca-manager, \
  security-domain=client-cert-domain, \
  authentication-optional=true, \
  want-client-auth=true, \
  need-client-auth=false)

Resulting in: -

  <subsystem xmlns="urn:wildfly:elytron:1.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
    ...
    <tls>
      ...
      <server-ssl-contexts>
        <server-ssl-context name="localhost" security-domain="client-cert-domain" want-client-auth="true" need-client-auth="false" authentication-optional="true" key-manager="localhost-manager" trust-manager="ca-manager"/>
      </server-ssl-contexts>
    </tls>
  </subsystem>

As we will be supporting fallback to username/password authentication need-client-auth is set to false as well as authentication-optional being set to false, this allows connections to be established but an alternative form of authentication will be required.

Using for Management

At this point the management interfaces can be updated to use the newly defined resources, we need to add references to the two new authentication factories and the SSL context, we can also remove the existing reference to the legacy security realm. As this is modifying existing interfaces a server reload will also be required.

./core-service=management/management-interface=http-interface:write-attribute(name=ssl-context, value=localhost)
./core-service=management/management-interface=http-interface:write-attribute(name=secure-socket-binding, value=management-https)
./core-service=management/management-interface=http-interface:write-attribute(name=http-authentication-factory, value=client-cert-digest)
./core-service=management/management-interface=http-interface:write-attribute(name=http-upgrade.sasl-authentication-factory, value=client-cert-digest)
./core-service=management/management-interface=http-interface:undefine-attribute(name=security-realm)
:reload

The management interface configuration then becomes: -

  <management>
    ...
    <management-interfaces>
      <http-interface http-authentication-factory="client-cert-digest" ssl-context="localhost">
        <http-upgrade enabled="true" sasl-authentication-factory="client-cert-digest"/>
        <socket-binding http="management-http" https="management-https"/>
      </http-interface>
    </management-interfaces>
    ...
  </management>
Admin Clients

At this stage assuming the same files have been used as in this example it should be possible to connect to the management interface of the server either using a web browser or the JBoss CLI with the username elytron and password passwd12#$

For certificate based authentication the keys and certificates from the WildFly Elytron tests can be used, these are found in JKS keystores under 'src/test/resources/ca/jks', these keystores have a password of Elytron.

Web Browser Configuration

A PKCS#12 file can be created from the test keystores,this can then be imported into the web browser to use when connecting to the server.

keytool -importkeystore -srckeystore ladybird.keystore \
  -destkeystore ladybird.pkcs12 \
  -srcstoretype jks \
  -deststoretype pkcs12 \
  -deststorepass Elytron \
  -srcalias ladybird \
  -destalias ladybird
CLI Configuration

Since the integration of WildFly Elytron it is possible with the CLI to use a configuration file wildfly-config.xml to define the security settings including the settings for the client side SSL context.

For the purpose of this example copy the ladybird,keystore and ca.truststore from the Wildfly Elytron testsuite to the location the JBoss CLI is being started from, the following wildfly-config.xml can be created in this location as well: -

<?xml version="1.0" encoding="UTF-8"?>

<configuration>
    <authentication-client xmlns="urn:elytron:1.0">
        <key-stores>
            <key-store name="ladybird" type="jks" >
                <file name="ladybird.keystore"/>
                <key-store-clear-password password="Elytron" />
            </key-store>
            <key-store name="ca" type="jks">
                <file name="ca.truststore"/>
                <key-store-clear-password password="Elytron" />
            </key-store>
        </key-stores>
        <ssl-context-rules>
            <rule use-ssl-context="default" />
        </ssl-context-rules>
        <ssl-contexts>
            <ssl-context name="default">
                <key-store-ssl-certificate key-store-name="ladybird" alias="ladybird">
                    <key-store-clear-password password="Elytron" />
                </key-store-ssl-certificate>
                <trust-store key-store-name="ca" />
            </ssl-context>
        </ssl-contexts>
    </authentication-client>
</configuration>

The CLI can now be started using the following command: -

./jboss-cli.sh -c -Dwildfly.config.url=wildfly-config.xml

The :whoami command can be used within the CLI to double check the current identity.

[standalone@localhost:9993 /] :whoami(verbose=true)
{
    "outcome" => "success",
    "result" => {
        "identity" => {"username" => "Ladybird"},
        "mapped-roles" => ["SuperUser"]
    }
}

Documentation Still Needed

  • How to migrate application which uses different identity store for authentication and authorization (migration to Elytron aggregate-realm).

  • How migrate to using cache (migration to caching-realm)

  • Limitations for migration from PicketBox/legacy security to Elytron, for example, Infinispan cache cannot be used, any others?

JBoss.org Content Archive (Read Only), exported from JBoss Community Documentation Editor at 2020-03-13 13:36:45 UTC, last content change 2017-09-29 15:24:18 UTC.