1. About

WildFly Elytron brings to WildFly a single unified security framework across the whole of the application server. As a single framework it will be usable both for configuring management access to the server and for applications deployed to the server. It will also be usable across all process types so there will be no need to learn a different security framework for host controllers in a domain compared to configuring a standalone server.

The project covers these main areas:

  • Authentication

  • Authorization

  • SSL / TLS

  • Secure Credential Storage

1.1. Authentication

One of the fundamental objectives of the project was to ensure that we can use stronger authentication mechanisms for both HTTP and SASL based authentication, in both cases the new framework also makes it possible to bring in new implementations opening up various integration opportunities with external solutions.

1.2. Authorization

The architecture of the project makes a very clear distinction between the raw representation of the identity as returned by a SecurityRealm from the repository of identities and the final representation as a SecurityIdentity after roles have been decoded and mapped and permissions have been mapped.

Custom implementations of the components to perform role decoding and mapping, and permission mapping can be provided allowing for further flexibility beyond the default set of components provided by the project.

1.3. SSL / TLS

The project becomes the centralised point within the application server for configuring SSL related resources meaning they can be configured in a central location and referenced by resources across the application server. The centralised configuration also covers advanced options such as configuration of enabled cipher suites and protocols without this information needing to be distributed across the management model.

The SSL / TLS implementation also includes an optimisation where it can be closely tied to authentication allowing for permissions checks to be performed on establishment of a connection before the first request is received and the eager construction of a SecurityIdentity eliminating the need for it to be constructed on a per-request basis.

1.4. Secure Credential Storage

The previous vault used for plain text String encryption is replaced with a newly designed credential store. in addition to the protection it offers for the credentials stored within it, the store currently supports storage of clear text credentials.

2. General Elytron Architecture

The overall architecture for WildFly Elytron is building up a full security policy from assembling smaller components together, by default we include various implementations of the components - in addition to this, custom implementations of many components can be provided in order to provide more specialised implementations.

Within WildFly the different Elytron components are handled as capabilities meaning that different implementations can be mixed and matched, however the different implementations are modelled using distinct resources. This section contains a number of diagrams to show the general relationships between different components to provide a high level view, however the different resource definitions may use different dependencies depending on their purpose.

2.1. Security Domains

Within WildFly Elytron a SecurityDomain can be considered as a security policy backed by one or more SecurityRealm instances. Resources that make authorization decisions will be associated with a SecurityDomain, from the SecurityDomain a SecurityIdentity can be obtained which is a representation of the current identity, from this the identity’s roles and permissions can be checked to make the authorization decision for the resource.

The SecurityDomain is the general wrapper around the policy describing a resulting SecurityIdentity and makes use of the following components to define this policy.

  • NameRewriter

NameRewriters are used in multiple places within the Elytron configuration, as their name implies, their purpose is to take a name and map it to another representation of the name or perform some normalisation or clean up of the name.

  • RealmMapper

As a SecurityDomain is able to reference multiple SecurityRealms the RealmMapper is responsible for identifying which SecurityRealm to use based on the supplied name for authentication.

  • SecurityRealm

One or more named SecurityRealms are associated with a SecurityDomain. A SecurityRealm provides access to the underlying repository of identities. It is used for obtaining credentials to allow authentication mechanisms to perform verification, for validation of Evidence, and for obtaining the raw AuthorizationIdentity performing the authentication.

Some SecurityRealm implementations are also modifiable and therefore expose an API that allows for updates to be made to the repository containing the identities.

  • RoleDecoder

Along with the SecurityRealm association is also a reference to a RoleDecoder, the RoleDecoder takes the raw AuthorizationIdentity returned from the SecurityRealm and converts its attributes into roles.

  • RoleMapper

After the roles have been decoded for an identity, further mapping can be applied. This could be as simple as normalising the format of role names, or perhaps adding or removing specific role names. If a RoleMapper is referenced by a SecurityRealm association, that RoleMapper is applied first before applying the RoleMapper associated with the SecurityDomain.

  • PrincipalDecoder

A PrincipalDecoder converts from a Principal to a String representation of a name. One example for this is the X500PrincipalDecoder, which is able to extract an attribute from a distinguished name.

  • PermissionMapper

In addition to having roles, a SecurityIdentity can also have a set of permissions. The PermissionMapper assigns those permissions to the identity.

Different secured resources can be associated with different SecurityDomains for their authorization decisions. Within WildFly Elytron we have the ability to configure inflow between different SecurityDomains. The inflow process means that a SecurityIdentity inflowed into a second SecurityDomain has the mappings of the new SecurityDomain applied to it. So although a common identity may be calling different resources, each of those resources could have a very different view of the roles and permissions associated with the identity.

2.2. SASL Authentication

The SaslAuthenticationFactory is an authentication policy for authentication using SASL authentication mechanisms. In addition to being a policy it is also a factory for configured authentication mechanisms backed by a SecurityDomain.

The SaslAuthenticationFactory references the following: -

  • SecurityDomain

This is the security domain that any mechanism authentication will be performed against.

  • SaslServerFactory

This is the general factory for server side SASL authentication mechanisms.

  • MechanismConfigurationSelector

Additional configuration can be supplied for the authentication mechanisms. The configuration will be described in more detail later but the purpose of the MechanismConfigurationSelector is to obtain configuration specific to the mechanism selected. This can include information about realm names a mechanism should present to a remote client plus additional NameRewriters and RealmMappers to use during the authentication process.

The reason some components referenced by the SecurityDomain are duplicated is so that mechanism-specific mappings can be applied.

2.3. HTTP Authentication

The HttpAuthenticationFactory is an authentication policy for authentication using HTTP authentication mechanisms, including the BASIC, DIGEST, EXTERNAL, FORM, SPNEGO, and CLIENT_CERT mechanisms. In addition to being a policy, it is also a factory for configured authentication mechanisms backed by a SecurityDomain.

The HttpAuthenticationFactory references the following:

  • SecurityDomain

This is the security domain that any mechanism authentication will be performed against.

  • HttpServerAuthenticationMechanismFactory

This is the general factory for server side HTTP authentication mechanisms.

  • MechanismConfigurationSelector

Additional configuration can be supplied for the authentication mechanisms. The configuration will be described in more detail later but the purpose of the MechanismConfigurationSelector is to obtain configuration specific to the mechanism selected. This can include information about realm names a mechanism should present to a remote client plus additional NameRewriters and RealmMappers to use during the authentication process.

The reason some components referenced by the SecurityDomain are duplicated is so that mechanism-specific mappings can be applied.

2.4. SSL / TLS

The SSLContext defined within Elytron is a javax.net.ssl.SSLContext meaning it can be used by anything that uses an SSLContext directly.

In addition to the usual configuration for an SSLContext it is possible to configure additional items such as cipher suites and protocols and the SSLContext returned will wrap any engines created to set these values.

The SSLContext within Elytron can also reference the following:

  • KeyManagers

An array of KeyManager instances to be used by the SSLContext, this in turn can reference a KeyStore to load the keys.

  • TrustManagers

An array of TrustManager instances to be used by the SSLContext, this in turn can also reference a KeyStore to load the certificates.

  • SecurityDomain

If an SSLContext is (optionally) configured to reference a SecurityDomain then verification of a client’s certificate can be performed as an authentication ensuring the appropriate permissions to Logon are assigned before even allowing the connection to be fully opened. Additionally, the SecurityIdentity can be established at the time the connection is opened and used for any invocations over the connection.

2.5. Passwords

One of the core features of WildFly Elytron is the ability to work with many different formats for representing passwords, WildFly Elytron also contains APIs that can be used to convert from clear text passwords to these representations that can be stored in the underlying identity stores.

This section will document how these APIs can be used to work with the different password types.

2.5.1. PasswordFactory

Working with passwords will require interaction with the org.wildfly.security.password.PasswordFactory API, this obtains access to implementations from java.security.Provider instances, there are two different ways these can be identified: -

  1. By querying the globally installed Providers

  2. By passing in the Provider to the getInstance method of the PasswordFactory API.

In both cases the WildFly Elytron implementations are provided by the org.wildfly.security.WildFlyElytronProvider provider.

When relying on the Provider being globally registered the PasswordFactory for a specific algorithm can be obtained as: -

PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm);

However an alternative approach could be to manually instantiate the Provider and pass it in when obtaining an instance of the PasswordFactory: -

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
...
PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm, ELYTRON_PROVIDER);

2.5.2. Clear Password

The simplest type of Password to obtain from the PasswordFactory is a clear text password, the following code illustrates how this can be obtained.

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
static final String TEST_PASSWORD = "test_password";

public static void main(String[] args) throws Exception {
    PasswordFactory passwordFactory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, ELYTRON_PROVIDER);

    ClearPasswordSpec passwordSpec = new ClearPasswordSpec(TEST_PASSWORD.toCharArray());
    Password password = passwordFactory.generatePassword(passwordSpec);

    System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(password, TEST_PASSWORD.toCharArray())));
}

A second approach is to obtain a raw representation of the ClearPassword, however this will need to be translated into the PasswordFactory if it will be used for evidence validation but if the Password is not being used for verification this can be a suitable alternative provided the parameters have been pre-verified.

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
static final String TEST_PASSWORD = "test_password";

public static void main(String[] args) throws Exception {
    PasswordFactory passwordFactory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, ELYTRON_PROVIDER);

    Password rawPassword = ClearPassword.createRaw(ClearPassword.ALGORITHM_CLEAR, TEST_PASSWORD.toCharArray());

    Password password = passwordFactory.translate(rawPassword);

    System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(password, TEST_PASSWORD.toCharArray())));
}

The raw password can be used for other areas of the Elytron APIs however if it is used for validation an error similar to the following will be thrown.

Exception in thread "main" java.security.InvalidKeyException
    at org.wildfly.security.password.impl.PasswordFactorySpiImpl.engineVerify(PasswordFactorySpiImpl.java:762)
    at org.wildfly.security.password.PasswordFactory.verify(PasswordFactory.java:210)
[source,java]

2.5.3. Simple Digest

The next type of password is the simple digest, for this password type the clear text password id digested using the specified algorithm however no salt is used.

The following algorithms are applicable to this type of encoding.

  • simple-digest-md2

  • simple-digest-md5

  • simple-digest-sha-1

  • simple-digest-sha-256

  • simple-digest-sha-384

  • simple-digest-sha-512

The following example illustrates how the Password instance can be created starting from the clear text password,from this the digested representation of the password can be obtained which could be used to store the password. The digested representation is then used to create a further Password instance.

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
static final String TEST_PASSWORD = "test_password";

public static void main(String[] args) throws Exception {
    PasswordFactory passwordFactory = PasswordFactory.getInstance(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_512, ELYTRON_PROVIDER);

    ClearPasswordSpec clearSpec = new ClearPasswordSpec(TEST_PASSWORD.toCharArray());
    SimpleDigestPassword original = (SimpleDigestPassword) passwordFactory.generatePassword(clearSpec);

    byte[] digest = original.getDigest();
    HashPasswordSpec hashSpec = new HashPasswordSpec(digest);

    SimpleDigestPassword restored = (SimpleDigestPassword) passwordFactory.generatePassword(hashSpec);

    System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));
}

Starting from the digest the raw APIs can be used as.

SimpleDigestPassword rawPassword = SimpleDigestPassword.createRaw(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_512, digest);

SimpleDigestPassword restored = (SimpleDigestPassword) passwordFactory.translate(rawPassword);

System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));

2.5.4. Salted Digest

Two variations of salted digests are supported, these can either be digested with the salt first or the password first. The following algorithms are supported: -

  • password-salt-digest-md5

  • password-salt-digest-sha-1

  • password-salt-digest-sha-256

  • password-salt-digest-sha-384

  • password-salt-digest-sha-512

  • salt-password-digest-md5

  • salt-password-digest-sha-1

  • salt-password-digest-sha-256

  • salt-password-digest-sha-384

  • salt-password-digest-sha-512

The following example shows how using a generated salt the password can be created from a clear text password and then subsequently how the password can be recreated from the salt and digest.

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
static final String TEST_PASSWORD = "test_password";

public static void main(String[] args) throws Exception {
    PasswordFactory passwordFactory = PasswordFactory.getInstance(SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512, ELYTRON_PROVIDER);

    byte[] salt = new byte[32];
    SecureRandom random = new SecureRandom();
    random.nextBytes(salt);

    SaltedPasswordAlgorithmSpec saltedSpec = new SaltedPasswordAlgorithmSpec(salt);
    EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(TEST_PASSWORD.toCharArray(), saltedSpec);

    SaltedSimpleDigestPassword original = (SaltedSimpleDigestPassword) passwordFactory.generatePassword(encryptableSpec);

    byte[] digest = original.getDigest();

    SaltedHashPasswordSpec saltedHashSpec = new SaltedHashPasswordSpec(digest, salt);

    SaltedSimpleDigestPassword restored = (SaltedSimpleDigestPassword) passwordFactory.generatePassword(saltedHashSpec);

    System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));
}

Alternatively the salt could be generated automatically by using a ClearPasswordSpec.

ClearPasswordSpec clearSpec = new ClearPasswordSpec(TEST_PASSWORD.toCharArray());
SaltedSimpleDigestPassword original = (SaltedSimpleDigestPassword) passwordFactory.generatePassword(clearSpec);

byte[] salt = original.getSalt();
byte[] digest = original.getDigest();

Starting with the digest and salt the raw APIs can also be used.

SaltedSimpleDigestPassword rawPassword = SaltedSimpleDigestPassword.createRaw(SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512, digest, salt);

SaltedSimpleDigestPassword restored = (SaltedSimpleDigestPassword) passwordFactory.translate(rawPassword);

System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));

2.5.5. Digest

The Digest passwords are an alternative form of digest where the username, realm and password are digested together delimited with a ':', these are usable with clear text authentication mechanisms but also usable with the digest authentication mechanisms also eliminating the transmission of clear text passwords during authentication. The following algorithms are supported: -

  • digest-md5

  • digest-sha

  • digest-sha-256

  • digest-sha-384

  • digest-sha-512

  • digest-sha-512-256

The following example illustrates how a password can be created from the username, realm, and password and then how it can be recreated from the digest, realm, and password.

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
static final String TEST_USERNAME = "test_username";
static final String TEST_REALM = "Test Realm";
static final String TEST_PASSWORD = "test_password";

public static void main(String[] args) throws Exception {
    PasswordFactory passwordFactory = PasswordFactory.getInstance(DigestPassword.ALGORITHM_DIGEST_MD5, ELYTRON_PROVIDER);

    DigestPasswordAlgorithmSpec digestAlgorithmSpec = new DigestPasswordAlgorithmSpec(TEST_USERNAME, TEST_REALM);
    EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(TEST_PASSWORD.toCharArray(), digestAlgorithmSpec);

    DigestPassword original = (DigestPassword) passwordFactory.generatePassword(encryptableSpec);

    byte[] digest = original.getDigest();

    DigestPasswordSpec digestPasswordSpec = new DigestPasswordSpec(TEST_USERNAME, TEST_REALM, digest);

    DigestPassword restored = (DigestPassword) passwordFactory.generatePassword(digestPasswordSpec);

    System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));
}

For this password type is not possible to create the password from the ClearPasswordSpec as additional information always needs to be specified and can not be dynamically or randomly generated, however the raw APIs can still be used.

DigestPassword rawPassword = DigestPassword.createRaw(DigestPassword.ALGORITHM_DIGEST_MD5, TEST_USERNAME, TEST_REALM, digest);

DigestPassword restored = (DigestPassword) passwordFactory.translate(rawPassword);

System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));

2.5.6. SCRAM

Another set of passwords more tightly tied to a specific authentication mechanism are the SCRAM password types, the following algorithms are supported: -

  • scram-sha-1

  • scram-sha-256

  • scram-sha-384

  • scram-sha-512

The following demonstrates how a clear password can be converted to a scram password using a specified salt and iteration count and how this can be recreated from the digested value.

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();

static final String TEST_PASSWORD = "test_password";

public static void main(String[] args) throws Exception {
    PasswordFactory passwordFactory = PasswordFactory.getInstance(ScramDigestPassword.ALGORITHM_SCRAM_SHA_512, ELYTRON_PROVIDER);

    byte[] salt = new byte[12];
    SecureRandom random = new SecureRandom();
    random.nextBytes(salt);

    IteratedSaltedPasswordAlgorithmSpec iteratedAlgorithmSpec = new IteratedSaltedPasswordAlgorithmSpec(2000, salt);
    EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(TEST_PASSWORD.toCharArray(), iteratedAlgorithmSpec);

    ScramDigestPassword original = (ScramDigestPassword) passwordFactory.generatePassword(encryptableSpec);

    byte[] digest = original.getDigest();

    IteratedSaltedHashPasswordSpec scramPasswordSpec = new IteratedSaltedHashPasswordSpec(digest, salt, 2000);

    ScramDigestPassword restored = (ScramDigestPassword) passwordFactory.generatePassword(scramPasswordSpec);

    System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));
}

Alternatively instead of using the IteratedSaltedPasswordAlgorithmSpec is it also possible to use a SaltedPasswordAlgorithmSpec when converting from the clear text password and a default iteration count will be used instead, this is similar to how the conversion happens for salted digest passwords.

It is also possible to omit the salt and iteration count and these will be generated.

ClearPasswordSpec clearSpec = new ClearPasswordSpec(TEST_PASSWORD.toCharArray());

ScramDigestPassword original = (ScramDigestPassword) passwordFactory.generatePassword(clearSpec);

byte[] salt = original.getSalt();
byte[] digest = original.getDigest();
int iterationCount = original.getIterationCount();

Starting with a digest, salt, and iteration count the raw APIs can also be used.

ScramDigestPassword rawPassword = ScramDigestPassword.createRaw(ScramDigestPassword.ALGORITHM_SCRAM_SHA_256, digest, salt, 2000);

ScramDigestPassword restored = (ScramDigestPassword) passwordFactory.translate(rawPassword);

System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));

2.5.7. OTP

One more type of mechanism specific password is the one time password type. The following algorithms are supported: -

  • otp-md5

  • otp-sha1

  • otp-sha256

  • otp-sha384

  • otp-sha512

The following demonstrates how a clear password can be converted to a one time password using a specified seed and iteration count and how this can be recreated from the hashed value.

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
static final String TEST_PASSWORD = "test_password";

public static void main(String[] args) throws Exception {
    PasswordFactory passwordFactory = PasswordFactory.getInstance(OneTimePassword.ALGORITHM_OTP_SHA_512, ELYTRON_PROVIDER);

    String seed = "ke1234";
    int sequenceNumber = 500;

    OneTimePasswordAlgorithmSpec oneTimeAlgorithmSpec = new OneTimePasswordAlgorithmSpec(OneTimePassword.ALGORITHM_OTP_SHA_512, seed, sequenceNumber);
    EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(TEST_PASSWORD.toCharArray(), oneTimeAlgorithmSpec);

    OneTimePassword original = (OneTimePassword) passwordFactory.generatePassword(encryptableSpec);

    byte[] hash = original.getHash();

    OneTimePasswordSpec oneTimeSpec = new OneTimePasswordSpec(hash, seed, sequenceNumber);

    OneTimePassword restored = (OneTimePassword) passwordFactory.generatePassword(oneTimeSpec);
}

This example does not include verification as that is handled by the SASL mechanism which also increments the sequence and replaces the hash, this does mean this password type needs to be used with a security realm which also supports updates.

Starting with the hash and sequence number the raw APIs can also be used.

OneTimePassword rawPassword = OneTimePassword.createRaw(OneTimePassword.ALGORITHM_OTP_SHA_512, original.getHash(), original.getSeed(), original.getSequenceNumber());

OneTimePassword restored = (OneTimePassword) passwordFactory.translate(rawPassword);

2.5.8. Other Iterated Salted Types

The following algorithms are also supported for alternative iterated salted password types: -

  • bcrypt

  • sun-crypt-md5

  • sun-crypt-md5-bare-salt

  • crypt-sha-256

  • crypt-sha-512

  • bsd-crypt-des

The general pattern for working with these password types is the same as was used for Scram password types if an interaction count is specified or the same as salted digest password types if a default iteration count is to be used instead.

2.5.9. Other Salted Types

The following algorithms are also supported for salted password types: -

  • crypt-md5

  • crypt-des

The general pattern for working with these password types is the same as was used for salted digest password types as no iteration count is required.

2.5.10. Masked Password Types

Finally a set of masked password types are also supported to add support for legacy password types which were previously supported within PicketBox, the following algorithms are supported.

  • masked-MD5-DES

  • masked-MD5-DES-CBC-PKCS5

  • masked-MD5-3DES

  • masked-MD5-3DES-CBC-PKCS5

  • masked-SHA1-DES-EDE

  • masked-SHA1-DES-EDE-CBC-PKCS5

  • masked-SHA1-RC2-40

  • masked-SHA1-RC2-40-CBC-PKCS5

  • masked-SHA1-RC2-128

  • masked-SHA1-RC2-128-CBC-PKCS5

  • masked-SHA1-RC4-40

  • masked-SHA1-RC4-40-ECB

  • masked-SHA1-RC4-128

  • masked-SHA1-RC4-128-ECB

  • masked-HMAC-SHA1-AES-128

  • masked-HMAC-SHA224-AES-128

  • masked-HMAC-SHA256-AES-128

  • masked-HMAC-SHA384-AES-128

  • masked-HMAC-SHA512-AES-128

  • masked-HMAC-SHA1-AES-256

  • masked-HMAC-SHA224-AES-256

  • masked-HMAC-SHA256-AES-256

  • masked-HMAC-SHA384-AES-256

  • masked-HMAC-SHA512-AES-256

  • masked-PBKDF-HMAC-SHA1

  • masked-PBKDF-HMAC-SHA224

  • masked-PBKDF-HMAC-SHA256

  • masked-PBKDF-HMAC-SHA384

  • masked-PBKDF-HMAC-SHA512

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();

static final String TEST_PASSWORD = "test_password";

public static void main(String[] args) throws Exception {
    PasswordFactory passwordFactory = PasswordFactory.getInstance(MaskedPassword.ALGORITHM_MASKED_MD5_DES, ELYTRON_PROVIDER);

    char[] key = "my_secret_key".toCharArray();

    byte[] salt = new byte[8];
    SecureRandom random = new SecureRandom();
    random.nextBytes(salt);

    int iterationCount = 100;

    MaskedPasswordAlgorithmSpec maskedAlgorithmSpec = new MaskedPasswordAlgorithmSpec(key, iterationCount, salt);
    EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(TEST_PASSWORD.toCharArray(), maskedAlgorithmSpec);

    MaskedPassword original = (MaskedPassword) passwordFactory.generatePassword(encryptableSpec);

    byte[] masked = original.getMaskedPasswordBytes();

    MaskedPasswordSpec maskedPasswordSpec = new MaskedPasswordSpec(key, iterationCount, salt, masked);

    MaskedPassword restored = (MaskedPassword) passwordFactory.generatePassword(maskedPasswordSpec);

    System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));
}

As with the other password types the raw password APIs can also be used to recreate the password.

MaskedPassword rawPassword = MaskedPassword.createRaw(MaskedPassword.ALGORITHM_MASKED_MD5_DES, key, iterationCount, salt, masked);

MaskedPassword restored = (MaskedPassword) passwordFactory.translate(rawPassword);

System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));

2.5.11. Modular Crypt Encoding

A number of password types can be encoded using modular crypt allowing information such as the password type, the hash or digest, the salt, and the iteration count to be encoded in a single String, this can make storage and retrieval of passwords easier as multiple pieces of related data can be handled as one.

Within the WildFly Elytron project the utility org.wildfly.security.password.util.ModularCrypt can be used to handle the encoding and decoding.

The following password types can be encoded and decoded: -

  • BCryptPassword

  • BSDUnixDESCryptPassword

  • UnixDESCryptPassword

  • UnixMD5CryptPassword

  • SunUnixMD5CryptPassword

  • UnixSHACryptPassword

The following code demonstrates this for a BSDUnixDESCryptPassword: -

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();

static final String TEST_PASSWORD = "myPassword";

public static void main(String[] args) throws Exception {
    PasswordFactory passwordFactory = PasswordFactory.getInstance(BSDUnixDESCryptPassword.ALGORITHM_BSD_CRYPT_DES, ELYTRON_PROVIDER);

    int iterationCount = BSDUnixDESCryptPassword.DEFAULT_ITERATION_COUNT;

    byte[] salt = new byte[BSDUnixDESCryptPassword.BSD_CRYPT_DES_SALT_SIZE];
    SecureRandom random = new SecureRandom();
    random.nextBytes(salt);

    IteratedSaltedPasswordAlgorithmSpec iteratedAlgorithmSpec = new IteratedSaltedPasswordAlgorithmSpec(iterationCount, salt);
    EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(TEST_PASSWORD.toCharArray(), iteratedAlgorithmSpec);

    BSDUnixDESCryptPassword original = (BSDUnixDESCryptPassword) passwordFactory.generatePassword(encryptableSpec);

    String modularCryptString = ModularCrypt.encodeAsString(original);

    Password rawPassword = ModularCrypt.decode(modularCryptString);

    BSDUnixDESCryptPassword restored = (BSDUnixDESCryptPassword) passwordFactory.translate(rawPassword);

    System.out.println(String.format("Password Verified '%b'", passwordFactory.verify(restored, TEST_PASSWORD.toCharArray())));
}
The Password returned from the call to ModularCrypt.decode(…​) is a raw password so needs translating by the PasswordFactory.

3. Elytron Subsystem

WildFly Elytron is a security framework used to unify security across the entire application server. The elytron subsystem enables a single point of configuration for securing both applications and the management interfaces. WildFly Elytron also provides a set of APIs and SPIs for providing custom implementations of functionality and integrating with the elytron subsystem.

In addition, there are several other important features of the WildFly Elytron:

  • Stronger authentication mechanisms for HTTP and SASL authentication.

  • Improved architecture that allows for SecurityIdentities to be propagated across security domains and transparently transformed ready to be used for authorization. This transformation takes place using configurable role decoders, role mappers, and permission mappers.

  • Centralized point for SSL/TLS configuration including cipher suites and protocols.

  • SSL/TLS optimizations such as eager SecureIdentity construction and closely tying authorization to establishing an SSL/TLS connection. Eager SecureIdentity construction eliminates the need for a SecureIdentity to be constructed on a per-request basis. Closely tying authentication to establishing an SSL/TLS connection enables permission checks to happen BEFORE the first request is received.

  • A secure credential store that replaces the previous vault implementation to store clear text credentials.

The new elytron subsystem exists in parallel to the legacy security subsystem and legacy core management authentication. Both the legacy and Elytron methods may be used for securing the management interfaces as well as providing security for applications.

3.1. Get Started using the Elytron Subsystem

To get started using Elytron, refer to these topics:

3.2. Provided components

WildFly Elytron provides a default set of implementations in the elytron subsystem.

3.2.1. Factories

Component Description

aggregate-http-server-mechanism-factory

An HTTP server factory definition where the HTTP server factory is an aggregation of other HTTP server factories.

aggregate-sasl-server-factory

A SASL server factory definition where the SASL server factory is an aggregation of other SASL server factories.

configurable-http-server-mechanism-factory

A SASL server factory definition where the SASL server factory is an aggregation of other SASL server factories.

configurable-sasl-server-factory

A SASL server factory definition where the SASL server factory is an aggregation of other SASL server factories.

custom-credential-security-factory

A custom credential SecurityFactory definition.

http-authentication-factory

Resource containing the association of a security domain with a HttpServerAuthenticationMechanismFactory.

kerberos-security-factory

A security factory for obtaining a GSSCredential for use during authentication.

mechanism-provider-filtering-sasl-server-factory

A SASL server factory definition that enables filtering by provider where the factory was loaded using a provider.

provider-http-server-mechanism-factory

An HTTP server factory definition where the HTTP server factory is an aggregation of factories from the provider list.

provider-sasl-server-factory

A SASL server factory definition where the SASL server factory is an aggregation of factories from the provider list.

sasl-authentication-factory

Resource containing the association of a security domain with a SaslServerFactory.

service-loader-http-server-mechanism-factory

An HTTP server factory definition where the HTTP server factory is an aggregation of factories identified using a ServiceLoader

service-loader-sasl-server-factory

A SASL server factory definition where the SASL server factory is an aggregation of factories identified using a ServiceLoader

3.2.2. Principal Transformers

Component Description

aggregate-principal-transformer

A principal transformer definition where the principal transformer is an aggregation of other principal transformers.

case-principal-transformer

A principal transformer definition where the principal is adjusted to upper or lower case.

chained-principal-transformer

A principal transformer definition where the principal transformer is a chaining of other principal transformers.

constant-principal-transformer

A principal transformer definition where the principal transformer always returns the same constant.

custom-principal-transformer

A custom principal transformer definition.

regex-principal-transformer

A regular expression based principal transformer

regex-validating-principal-transformer

A regular expression based principal transformer which uses the regular expression to validate the name.

3.2.3. Principal Decoders

Component Description

aggregate-principal-decoder

A principal decoder definition where the principal decoder is an aggregation of other principal decoders.

concatenating-principal-decoder

A principal decoder definition where the principal decoder is a concatenation of other principal decoders.

constant-principal-decoder

Definition of a principal decoder that always returns the same constant.

custom-principal-decoder

Definition of a custom principal decoder.

x500-attribute-principal-decoder

Definition of a X500 attribute based principal decoder.

3.2.4. Evidence Decoders

Component Description

x509-subject-alt-name-evidence-decoder

An evidence decoder that derives the principal associated with a certificate chain from an X.509 subject alternative name from the first certificate in the given chain.

x500-subject-evidence-decoder

An evidence decoder that derives the principal associated with a certificate chain from the subject from the first certificate in the given chain.

custom-evidence-decoder

Definition of a custom evidence decoder.

aggregate-evidence-decoder

An evidence decoder that is an aggregation of other evidence decoders. Given evidence, these evidence decoders will be attempted in order until one returns a non-null principal or until there are no more evidence decoders left to try.

3.2.5. Realm Mappers

Component Description

constant-realm-mapper

Definition of a constant realm mapper that always returns the same value.

custom-realm-mapper

Definition of a custom realm mapper

mapped-regex-realm-mapper

Definition of a realm mapper implementation that first uses a regular expression to extract the realm name, this is then converted using the configured mapping of realm names.

simple-regex-realm-mapper

Definition of a simple realm mapper that attempts to extract the realm name using the capture group from the regular expression, if that does not provide a match then the delegate realm mapper is used instead.

3.2.6. Realms

Component Description

aggregate-realm

A realm definition that is an aggregation of two or more realms, one for the authentication steps and one or more for loading the identity for the authorization steps and aggregating the resulting attributes.

caching-realm

A realm definition that enables caching to another security realm. Caching strategy is Least Recently Used where least accessed entries are discarded when maximum number of entries is reached. When credentials are updated externally, the user’s obsolete credential will be removed when the user authenticates with the updated credential.

custom-modifiable-realm

Custom realm configured as being modifiable will be expected to implement the ModifiableSecurityRealm interface. By configuring a realm as being modifiable management operations will be made available to manipulate the realm.

custom-realm

A custom realm definitions can implement either the s SecurityRealm interface or the ModifiableSecurityRealm interface. Regardless of which interface is implemented management operations will not be exposed to manage the realm. However other services that depend on the realm will still be able to perform a type check and cast to gain access to the modification API.

distributed-realm

A security realm definition for authentication and authorization identities distributed between multiple security realms.

failover-realm

A realm definition that is an aggregation of two realms, one for default behaviour and second for cases when first realm is unavailable.

filesystem-realm

A simple security realm definition backed by the filesystem.

identity-realm

A security realm definition where identities are represented in the management model.

jaas-realm

A security realm definition that uses JAAS configuration file to initialize LoginContext that is used to obtain identities.

jdbc-realm

A security realm definition backed by database using JDBC.

key-store-realm

A security realm definition backed by a keystore.

ldap-realm

A security realm definition backed by LDAP.

properties-realm

A security realm definition backed by properties files.

token-realm

A security realm definition capable of validating and extracting identities from security tokens.

3.2.7. Permission Mappers

Component Description

custom-permission-mapper

Definition of a custom permission mapper.

logical-permission-mapper

Definition of a logical permission mapper.

simple-permission-mapper

Definition of a simple configured permission mapper.

constant-permission-mapper

Definition of a permission mapper that always returns the same constant.

3.2.8. Role Decoders

Component Description

custom-role-decoder

Definition of a custom RoleDecoder

simple-role-decoder

Definition of a simple RoleDecoder that takes a single attribute and maps it directly to roles.

source-address-role-decoder

Definition of a RoleDecoder that maps roles based on the IP address of the remote client.

aggregate-role-decoder

A role decoder that is an aggregation of other role decoders. An aggregate role decoder combines the roles obtained using each role decoder.

3.2.9. Role Mappers

Component Description

add-prefix-role-mapper

A role mapper definition for a role mapper that adds a prefix to each provided.

add-suffix-role-mapper

A role mapper definition for a role mapper that adds a suffix to each provided.

constant-role-mapper

A role mapper definition where a constant set of roles is always returned.

aggregate-role-mapper

A role mapper definition where the role mapper is an aggregation of other role mappers.

logical-role-mapper

A role mapper definition for a role mapper that performs a logical operation using two referenced role mappers.

custom-role-mapper

Definition of a custom role mapper

mapped-role-mapper

A role mapper definition for a role mapper that uses configured mapping of role names to map role names.

regex-role-mapper

A role mapper definition for a role mapper that performs a regex matching and maps matching roles with provided pattern. Regex can capture groups that replacement pattern can make use of.

3.2.10. SSL Components

Component Description

client-ssl-context

An SSLContext for use on the client side of a connection.

dynamic-client-ssl-context

An SSL context for use on the client side of a connection. It delegates to alternate instances based on peer information and rules provided in its authentication-context attribute.

filtering-key-store

A filtering keystore definition, which provides a keystore by filtering a key-store.

key-manager

A key manager definition for creating the key manager list as used to create an SSL context.

key-store

A keystore definition.

ldap-key-store

An LDAP keystore definition, which loads a keystore from an LDAP server.

server-ssl-context

An SSL context for use on the server side of a connection.

trust-manager

A trust manager definition for creating the TrustManager list as used to create an SSL context.

certificate-authority-account

A certificate authority account which can be used to obtain and revoke signed certificates.

3.2.11. Other

Component Description

aggregate-providers

An aggregation of two or more Provider[] resources.

authentication-configuration

An individual authentication configuration definition, which is used by clients deployed to WildFly and other resources for authenticating when making a remote connection.

authentication-context

An individual authentication context definition, which is used to supply an ssl-context and authentication-configuration when clients deployed to WildFly and other resources make a remoting connection.

credential-store

Credential store to keep alias for sensitive information such as passwords for external services.

dir-context

The configuration to connect to a directory (LDAP) server.

provider-loader

A definition for a provider loader.

security-domain

A security domain definition.

security-property

A definition of a security property to be set.

3.3. Out of the Box Configuration

WildFly provides a set of components configured by default. While these components are ready to use, the legacy security subsystem and legacy core management authentication is still used by default. To configure WildFly to use the these configured components as well as create new ones, see the Using the Elytron Subsystem section.

Default Component Description

ApplicationDomain

The ApplicationDomain security domain uses ApplicationRealm and groups-to-roles for authentication. It also uses default-permission-mapper to assign the login permission.

ManagementDomain

The ManagementDomain security domain uses two security realms for authentication: ManagementRealm with groups-to-roles and local with super-user-mapper. It also uses default-permission-mapper to assign the login permission.

local (security realm)

The local security realm does no authentication and sets the identity of principals to $local

ApplicationRealm

The ApplicationRealm security realm is a properties realm that authenticates principals using application-users.properties and assigns roles using application-roles.properties. These files are located under jboss.server.config.dir, which by default, maps to EAP_HOME/standalone/configuration. They are also the same files used by the legacy security default configuration.

ManagementRealm

The ManagementRealm security realm is a properties realm that authenticates principals using mgmt-users.properties and assigns roles using mgmt-groups.properties. These files are located under jboss.server.config.dir, which by default, maps to EAP_HOME/standalone/configuration. They are also the same files used by the legacy security default configuration.

default-permission-mapper

The default-permission-mapper mapper is a simple permission mapper that uses the default-permissions permission set to assign the full set of permissions that an identity would require to access any services on the server. For example, the default-permission-mapper mapper uses org.wildfly.extension.batch.jberet.deployment.BatchPermission specified by the default-permissions permission set to assign permission for batch jobs. The batch permissions are start, stop, restart, abandon, and read which aligns with jakarta.batch.operations.JobOperator. The default-permission-mapper mapper also uses org.wildfly.security.auth.permission.LoginPermission specified by the the login-permission permission set to assign the login permission.

local (mapper)

The local mapper is a constant role mapper that maps to the local security realm. This is used to map authentication to the local security realm.

groups-to-roles

The groups-to-roles mapper is a simple-role-decoder that will decode the groups information of a principal and use it for the role information.

super-user-mapper

The super-user-mapper mapper is a constant role mapper that maps the SuperUser role to a principal.

management-http-authentication

The management-http-authentication http-authentication-factory can be used for doing authentication over http. It uses the global provider-http-server-mechanism-factory to filter authentication mechanism and uses ManagementDomain for authenticating principals. It accepts the DIGEST authentication mechanisms and exposes it as ManagementRealm to applications.

global (provider-http-server-mechanism-factory)

This is the HTTP server factory mechanism definition used to list the provided authentication mechanisms when creating an http authentication factory.

management-sasl-authentication

The management-sasl-authentication sasl-authentication-factory can be used for authentication using SASL. It uses the configured sasl-server-factory to filter authentication mechanisms, which also uses the global provider-sasl-server-factory to filter by provider names. management-sasl-authentication uses the ManagementDomain security domain for authentication of principals. It also maps authentication using JBOSS-LOCAL-USER mechanisms using the local realm mapper and authentication using DIGEST-MD5 to ManagementRealm.

application-sasl-authentication

The application-sasl-authentication sasl-authentication-factory can be used for authentication using SASL. It uses the configured sasl-server-factory to filter authentication mechanisms, which also uses the global provider-sasl-server-factory to filter by provider names. application-sasl-authentication uses the ApplicationDomain security domain for authentication of principals.

global (provider-sasl-server-factory)

This is the SASL server factory definition used to create SASL authentication factories.

elytron (mechanism-provider-filtering-sasl-server-factor)

This is used to filter which sasl-authentication-factory is used based on the provider. In this case, elytron will match on the WildFlyElytron provider name.

configured (configurable-sasl-server-factory)

This is used to filter sasl-authentication-factory is used based on the mechanism name. In this case, configured will match on JBOSS-LOCAL-USER and DIGEST-MD5. It also sets the wildfly.sasl.local-user.default-user to $local.

applicationSSC

The applicationSSC server SSL context can be used to automatically generate a self-signed certificate the first time the HTTPS interface is accessed. This server SSL context should only be used for testing purposes. It should never be used in a production environment.

combined-providers

Is an aggregate provider that aggregates the elytron and openssl provider loaders.

elytron

A provider loader

openssl

A provider loader

Default WildFly Configuration

/subsystem=elytron:read-resource(recursive=true)
{
    "outcome" => "success",
    "result" => {
        "default-authentication-context" => undefined,
        "disallowed-providers" => ["OracleUcrypto"],
        "final-providers" => "combined-providers",
        "initial-providers" => undefined,
        "security-properties" => undefined,
        "add-prefix-role-mapper" => undefined,
        "add-suffix-role-mapper" => undefined,
        "aggregate-evidence-decoder" => undefined,
        "aggregate-http-server-mechanism-factory" => undefined,
        "aggregate-principal-decoder" => undefined,
        "aggregate-principal-transformer" => undefined,
        "aggregate-providers" => {"combined-providers" => {"providers" => [
            "elytron",
            "openssl"
        ]}},
        "aggregate-realm" => undefined,
        "aggregate-role-decoder" => undefined,
        "aggregate-role-mapper" => undefined,
        "aggregate-sasl-server-factory" => undefined,
        "aggregate-security-event-listener" => undefined,
        "authentication-configuration" => undefined,
        "authentication-context" => undefined,
        "caching-realm" => undefined,
        "case-principal-transformer" => undefined,
        "certificate-authority-account" => undefined,
        "chained-principal-transformer" => undefined,
        "client-ssl-context" => undefined,
        "concatenating-principal-decoder" => undefined,
        "configurable-http-server-mechanism-factory" => undefined,
        "configurable-sasl-server-factory" => {"configured" => {
            "filters" => undefined,
            "properties" => {"wildfly.sasl.local-user.default-user" => "$local"},
            "protocol" => undefined,
            "sasl-server-factory" => "elytron",
            "server-name" => undefined
        }},
        "constant-permission-mapper" => undefined,
        "constant-principal-decoder" => undefined,
        "constant-principal-transformer" => undefined,
        "constant-realm-mapper" => {"local" => {"realm-name" => "local"}},
        "constant-role-mapper" => {"super-user-mapper" => {"roles" => ["SuperUser"]}},
        "credential-store" => undefined,
        "custom-credential-security-factory" => undefined,
        "custom-evidence-decoder" => undefined,
        "custom-modifiable-realm" => undefined,
        "custom-permission-mapper" => undefined,
        "custom-principal-decoder" => undefined,
        "custom-principal-transformer" => undefined,
        "custom-realm" => undefined,
        "custom-realm-mapper" => undefined,
        "custom-role-decoder" => undefined,
        "custom-role-mapper" => undefined,
        "custom-security-event-listener" => undefined,
        "dir-context" => undefined,
        "file-audit-log" => {"local-audit" => {
            "format" => "JSON",
            "path" => "audit.log",
            "relative-to" => "jboss.server.log.dir",
            "synchronized" => true
        }},
        "filesystem-realm" => undefined,
        "filtering-key-store" => undefined,
        "http-authentication-factory" => {
            "management-http-authentication" => {
                "http-server-mechanism-factory" => "global",
                "mechanism-configurations" => [{
                    "mechanism-name" => "DIGEST",
                    "mechanism-realm-configurations" => [{"realm-name" => "ManagementRealm"}]
                }],
                "security-domain" => "ManagementDomain"
            }
        },
        "identity-realm" => {"local" => {
            "attribute-name" => undefined,
            "attribute-values" => undefined,
            "identity" => "$local"
        }},
        "jdbc-realm" => undefined,
        "kerberos-security-factory" => undefined,
        "key-manager" => {
            "applicationKM" => {
                "algorithm" => undefined,
                "alias-filter" => undefined,
                "credential-reference" => {"clear-text" => "password"},
                "generate-self-signed-certificate-host" => "localhost",
                "key-store" => "applicationKS",
                "provider-name" => undefined,
                "providers" => undefined
            }
        }
        "key-store" => {
            "applicationKS" => {
                "alias-filter" => undefined,
                "credential-reference" => {"clear-text" => "password"},
                "path" => "application.keystore",
                "relative-to" => "jboss.server.config.dir",
                "required" => false,
                "provider-name" => undefined,
                "providers" => undefined,
                "type" => "JKS"
            }
        },
        "key-store-realm" => undefined,
        "ldap-key-store" => undefined,
        "ldap-realm" => undefined,
        "logical-permission-mapper" => undefined,
        "logical-role-mapper" => undefined,
        "mapped-regex-realm-mapper" => undefined,
        "mapped-role-mapper" => undefined,
        "mechanism-provider-filtering-sasl-server-factory" => {"elytron" => {
            "enabling" => true,
            "filters" => [{"provider-name" => "WildFlyElytron"}],
            "sasl-server-factory" => "global"
        }},
        "periodic-rotating-file-audit-log" => undefined,
        "permission-set" => {
            "login-permission" => {"permissions" => [{"class-name" => "org.wildfly.security.auth.permission.LoginPermission"}]},
            "default-permissions" => {"permissions" => [
                {
                    "class-name" => "org.wildfly.extension.batch.jberet.deployment.BatchPermission",
                    "module" => "org.wildfly.extension.batch.jberet",
                    "target-name" => "*"
                },
                {
                    "class-name" => "org.wildfly.transaction.client.RemoteTransactionPermission",
                    "module" => "org.wildfly.transaction.client"
                },
                {
                    "class-name" => "org.jboss.ejb.client.RemoteEJBPermission",
                    "module" => "org.jboss.ejb-client"
                }
            ]}
        },
        "policy" => undefined,
        "properties-realm" => {
            "ApplicationRealm" => {
                "groups-attribute" => "groups",
                "groups-properties" => {
                    "path" => "application-roles.properties",
                    "relative-to" => "jboss.server.config.dir"
                },
                "users-properties" => {
                    "path" => "application-users.properties",
                    "relative-to" => "jboss.server.config.dir",
                    "digest-realm-name" => "ApplicationRealm"
                }
            },
            "ManagementRealm" => {
                "groups-attribute" => "groups",
                "groups-properties" => {
                    "path" => "mgmt-groups.properties",
                    "relative-to" => "jboss.server.config.dir"
                },
                "users-properties" => {
                    "path" => "mgmt-users.properties",
                    "relative-to" => "jboss.server.config.dir",
                    "digest-realm-name" => "ManagementRealm"
                }
            }
        },
        "provider-http-server-mechanism-factory" => {"global" => {"providers" => undefined}},
        "provider-loader" => {
            "elytron" => {
                "argument" => undefined,
                "class-names" => undefined,
                "configuration" => undefined,
                "module" => "org.wildfly.security.elytron",
                "path" => undefined,
                "relative-to" => undefined
            },
            "openssl" => {
                "argument" => undefined,
                "class-names" => undefined,
                "configuration" => undefined,
                "module" => "org.wildfly.openssl",
                "path" => undefined,
                "relative-to" => undefined
            }
        },
        "provider-sasl-server-factory" => {"global" => {"providers" => undefined}},
        "regex-role-mapper" => undefined,
        "regex-principal-transformer" => undefined,
        "regex-validating-principal-transformer" => undefined,
        "sasl-authentication-factory" => {
            "application-sasl-authentication" => {
                "mechanism-configurations" => [
                    {
                        "mechanism-name" => "JBOSS-LOCAL-USER",
                        "realm-mapper" => "local"
                    },
                    {
                        "mechanism-name" => "DIGEST-MD5",
                        "mechanism-realm-configurations" => [{"realm-name" => "ApplicationRealm"}]
                    }
                ],
                "sasl-server-factory" => "configured",
                "security-domain" => "ApplicationDomain"
            },
            "management-sasl-authentication" => {
                "mechanism-configurations" => [
                    {
                        "mechanism-name" => "JBOSS-LOCAL-USER",
                        "realm-mapper" => "local"
                    },
                    {
                        "mechanism-name" => "DIGEST-MD5",
                        "mechanism-realm-configurations" => [{"realm-name" => "ManagementRealm"}]
                    }
                ],
                "sasl-server-factory" => "configured",
                "security-domain" => "ManagementDomain"
            }
        },
        "security-domain" => {
            "ApplicationDomain" => {
                "default-realm" => "ApplicationRealm",
                "outflow-anonymous" => false,
                "outflow-security-domains" => undefined,
                "permission-mapper" => "default-permission-mapper",
                "post-realm-principal-transformer" => undefined,
                "pre-realm-principal-transformer" => undefined,
                "principal-decoder" => undefined,
                "realm-mapper" => undefined,
                "realms" => [
                    {
                        "realm" => "ApplicationRealm",
                        "role-decoder" => "groups-to-roles"
                    },
                    {"realm" => "local"}
                ],
                "role-mapper" => undefined,
                "security-event-listener" => undefined,
                "trusted-security-domains" => undefined
            },
            "ManagementDomain" => {
                "default-realm" => "ManagementRealm",
                "outflow-anonymous" => false,
                "outflow-security-domains" => undefined,
                "permission-mapper" => "default-permission-mapper",
                "post-realm-principal-transformer" => undefined,
                "pre-realm-principal-transformer" => undefined,
                "principal-decoder" => undefined,
                "realm-mapper" => undefined,
                "realms" => [
                    {
                        "realm" => "ManagementRealm",
                        "role-decoder" => "groups-to-roles"
                    },
                    {
                        "realm" => "local",
                        "role-mapper" => "super-user-mapper"
                    }
                ],
                "role-mapper" => undefined,
                "security-event-listener" => undefined,
                "trusted-security-domains" => undefined
            }
        },
        "server-ssl-context" => {
            "applicationSSC" => {
                "authentication-optional" => false,
                "cipher-suite-filter" => "DEFAULT",
                "cipher-suite-names" => undefined,
                "final-principal-transformer" => undefined,
                "key-manager" => "applicationKM",
                "maximum-session-cache-size" => -1,
                "need-client-auth" => false,
                "post-realm-principal-transformer" => undefined,
                "pre-realm-principal-transformer" => undefined,
                "protocols" => undefined,
                "provider-name" => undefined,
                "providers" => undefined,
                "realm-mapper" => undefined,
                "security-domain" => undefined,
                "session-timeout" => -1,
                "trust-manager" => undefined,
                "use-cipher-suites-order" => true,
                "want-client-auth" => false,
                "wrap" => false,
                "ssl-session" => undefined
            }
        },
        "service-loader-http-server-mechanism-factory" => undefined,
        "service-loader-sasl-server-factory" => undefined,
        "simple-permission-mapper" => {"default-permission-mapper" => {
            "mapping-mode" => "first",
            "permission-mappings" => [
                {
                    "principals" => ["anonymous"],
                    "permission-sets" => [{"permission-set" => "default-permissions"}]
                },
                {
                    "match-all" => true,
                    "permission-sets" => [
                        {"permission-set" => "login-permission"},
                        {"permission-set" => "default-permissions"}
                    ]
                }
            ]
        }},
        "simple-regex-realm-mapper" => undefined,
        "simple-role-decoder" => {"groups-to-roles" => {"attribute" => "groups"}},
        "size-rotating-file-audit-log" => undefined,
        "source-address-role-decoder" => undefined,
        "syslog-audit-log" => undefined,
        "token-realm" => undefined,
        "trust-manager" => undefined,
        "x500-attribute-principal-decoder" => undefined,
        "x500-subject-evidence-decoder" => undefined,
        "x509-subject-alt-name-evidence-decoder" => undefined
    }
}

3.4. Default Application Authentication Configuration

By default, applications are secured using legacy security domains. Applications must specify a security domain in their web.xml as well as the authentication method. If no security domain is specified by the application, WildFly will use the provided other legacy security domain.

3.4.1. Update WildFly to Use the Default Elytron Components for Application

Authentication

/subsystem=undertow/application-security-domain=exampleApplicationDomain:add(http-authentication-factory=example-http-auth)

For more information on configuring an http-authentication-factory, see configure an http-authentication-factory

SSL/TLS

Undertow can be configured to make use of the applicationSSC server SSL context for testing purposes, as shown below:

batch
/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=applicationSSC)
run-batch
reload

The applicationSSC server SSL context references the applicationKM key manager:

/subsystem=elytron/key-manager=applicationKM:read-resource()
{
    "outcome" => "success",
    "result" => {
        "algorithm" => undefined,
        "alias-filter" => undefined,
        "credential-reference" => {"clear-text" => "password"},
        "generate-self-signed-certificate-host" => "localhost",
        "key-store" => "applicationKS",
        "provider-name" => undefined,
        "providers" => undefined
    }
}

When the applicationSSC server SSL context is used by Undertow, a self-signed certificate will automatically be generated the first time the HTTPS interface is accessed if the file that backs the applicationKS key store doesn’t exist. This self-signed certificate will then be persisted to the file that backs the applicationKS key store. The generate-self-signed-certificate-host value, localhost, will be used as the Common Name (CN) value in the generated self-signed certificate. The following messages will appear in the server log file:

13:21:39,197 WARN  [org.wildfly.extension.elytron] (MSC service thread 1-6) WFLYELY01083: KeyStore /wildfly/standalone/configuration/application.keystore not found, it will be auto generated on first use with a self-signed certificate for host localhost
...
13:39:57,152 WARN  [org.wildfly.extension.elytron] (default task-1) WFLYELY01084: Generated self-signed certificate at /wildfly/dist/target/wildfly-21.0.0.Beta1-SNAPSHOT/standalone/configuration/application.keystore. Please note that self-signed certificates are not secure and should only be used for testing purposes. Do not use this self-signed certificate in production.
SHA-1 fingerprint of the generated key is fc:16:cf:bf:de:3a:6d:d6:fe:ec:f9:cd:9d:22:c9:3d:43:d7:e3:57
SHA-256 fingerprint of the generated key is 38:69:00:4e:39:e2:40:e2:ef:b6:95:58:c6:ba:d0:0f:56:c5:7c:5d:fc:d5:c3:b9:b0:94:80:9c:f5:45:9d:40

NOTE To disable the automatic self-signed certificate generation, undefine the generate-self-signed-certificate-host attribute on the applicationKM key manager.

WARNING This self-signed certificate is only intended to be used for testing purposes. This self-signed certificate should never be used in a production environment. For more information on configuring an ssl-context, see Configuring a server SSLContext. For more information on how to easily obtain a signed certificate using the WildFly CLI, see Obtain a signed certificate from Let’s Encrypt.

3.4.2. Default Elytron ApplicationDomain Configuration

The http-authentication-factory can be configured to use the ApplicationDomain security domain.

/subsystem=elytron/security-domain=ApplicationDomain:read-resource()
{
    "outcome" => "success",
    "result" => {
        "default-realm" => "ApplicationRealm",
        "permission-mapper" => "default-permission-mapper",
        "post-realm-principal-transformer" => undefined,
        "pre-realm-principal-transformer" => undefined,
        "principal-decoder" => undefined,
        "realm-mapper" => undefined,
        "realms" => [{
            "realm" => "ApplicationRealm",
            "role-decoder" => "groups-to-roles"
        }],
        "role-mapper" => undefined,
        "trusted-security-domains" => undefined
    }
}

The ApplicationDomain security domain is backed by the ApplicationRealm Elytron security realm, which is a properties-based realm.

/subsystem=elytron/properties-realm=ApplicationRealm:read-resource()
{
    "outcome" => "success",
    "result" => {
        "groups-attribute" => "groups",
        "groups-properties" => {
            "path" => "application-roles.properties",
            "relative-to" => "jboss.server.config.dir"
        },
        "users-properties" => {
            "path" => "application-users.properties",
            "relative-to" => "jboss.server.config.dir",
            "digest-realm-name" => "ApplicationRealm"
        }
    }
}

3.5. Default Management Authentication Configuration

By default, the WildFly management interfaces are secured using WildFly Elytron capabilities.

3.5.1. Default Elytron Management HTTP Authentication Configuration

When you access the management interface over HTTP, for example when using the web-based management console, WildFly will use the management-http-authentication http-authentication-factory.

/subsystem=elytron/http-authentication-factory=management-http-authentication:read-resource()
{
    "outcome" => "success",
    "result" => {
        "http-server-mechanism-factory" => "global",
        "mechanism-configurations" => [{
            "mechanism-name" => "DIGEST",
            "mechanism-realm-configurations" => [{"realm-name" => "ManagementRealm"}]
        }],
        "security-domain" => "ManagementDomain"
    }
}

The management-http-authentication http-authentication-factory, is configured to use the ManagementDomain security domain.

/subsystem=elytron/security-domain=ManagementDomain:read-resource()
{
    "outcome" => "success",
    "result" => {
        "default-realm" => "ManagementRealm",
        "outflow-anonymous" => false,
        "outflow-security-domains" => undefined,
        "permission-mapper" => "default-permission-mapper",
        "post-realm-principal-transformer" => undefined,
        "pre-realm-principal-transformer" => undefined,
        "principal-decoder" => undefined,
        "realm-mapper" => undefined,
        "realms" => [
            {
                "realm" => "ManagementRealm",
                "role-decoder" => "groups-to-roles"
            },
            {
                "realm" => "local",
                "role-mapper" => "super-user-mapper"
            }
        ],
        "role-mapper" => undefined,
        "security-event-listener" => undefined,
        "trusted-security-domains" => undefined
    }
}

The ManagementDomain security domain is backed by the ManagementRealm Elytron security realm, which is a properties-based realm.

/subsystem=elytron/properties-realm=ManagementRealm:read-resource()
{
    "outcome" => "success",
    "result" => {
        "groups-attribute" => "groups",
        "groups-properties" => {
            "path" => "mgmt-groups.properties",
            "relative-to" => "jboss.server.config.dir"
        },
        "plain-text" => false,
        "users-properties" => {
            "path" => "mgmt-users.properties",
            "relative-to" => "jboss.server.config.dir",
            "digest-realm-name" => "ManagementRealm"
        }
    }
}

3.5.2. Default Elytron Management CLI Authentication

By default, the management CLI ( jboss-cli.sh) is configured to connect over remote+http.

Default jboss-cli.xml

<jboss-cli xmlns="urn:jboss:cli:3.3">
 
    <default-protocol use-legacy-override="true">remote+http</default-protocol>
 
    <!-- The default controller to connect to when 'connect' command is executed w/o arguments -->
    <default-controller>
        <protocol>remote+http</protocol>
        <host>localhost</host>
        <port>9990</port>
    </default-controller>

This will establish a connection over HTTP and use HTTP upgrade to change the communication protocol to native. The HTTP upgrade connection is secured in the http-upgrade section of the http-interface using a sasl-authentication-factory.

Example Configuration with Default Components

/core-service=management/management-interface=http-interface:read-resource()
{
    "outcome" => "success",
    "result" => {
        "allowed-origins" => undefined,
        "console-enabled" => true,
        "http-authentication-factory" => "management-http-authentication",
        "http-upgrade" => {
            "enabled" => true,
            "sasl-authentication-factory" => "management-sasl-authentication"
        },
        "http-upgrade-enabled" => true,
        "sasl-protocol" => "remote",
        "secure-socket-binding" => undefined,
        "security-realm" => undefined,
        "server-name" => undefined,
        "socket-binding" => "management-http",
        "ssl-context" => undefined
    }
}

The default sasl-authentication-factory is management-sasl-authentication.

/subsystem=elytron/sasl-authentication-factory=management-sasl-authentication:read-resource()
{
    "outcome" => "success",
    "result" => {
        "mechanism-configurations" => [
            {
                "mechanism-name" => "JBOSS-LOCAL-USER",
                "realm-mapper" => "local"
            },
            {
                "mechanism-name" => "DIGEST-MD5",
                "mechanism-realm-configurations" => [{"realm-name" => "ManagementRealm"}]
            }
        ],
        "sasl-server-factory" => "configured",
        "security-domain" => "ManagementDomain"
    }
}

The management-sasl-authentication sasl-authentication-factory specifies JBOSS-LOCAL-USER and DIGEST-MD5 mechanisms.

JBOSS-LOCAL-USER Realm

/subsystem=elytron/identity-realm=local:read-resource()
{
    "outcome" => "success",
    "result" => {
        "attribute-name" => undefined,
        "attribute-values" => undefined,
        "identity" => "$local"
    }
}

The local Elytron security realm is for handling silent authentication for local users.

The ManagementRealm Elytron security realm is the same realm used in the management-http-authentication http-authentication-factory.

3.6. Comparing Legacy Approaches to Elytron Approaches

Legacy Approach Elytron Approach

UsersRoles Login Module

Configure Authentication with a Properties File-Based Identity Store

Database Login Module

Configure Authentication with a Database Identity Store

Ldap, LdapExtended, AdvancedLdap, AdvancedADLdap Login Modules

Configure Authentication with an LDAP-Based Identity Store

Certificate, Certificate Roles Login Module

Configure Authentication with Certificates

Kerberos, SPNEGO Login Modules

Configure Authentication with a Kerberos-Based Identity Store

Kerberos, SPNEGO Login Modules with Fallback

Configure Authentication with a Form as a Fallback for Kerberos

RoleMapping Login Module

Configure Authentication with a Mapped Role Mapper

Vault

Create and Use a Credential Store

Legacy Security Realms

Secure the Management Interfaces with a New Identity Store, Silent Authentication

RBAC

Using RBAC with Elytron

Legacy Security Realms for One-way and Two-way SSL/TLS for Applications

Enable One-way SSL/TLS for Applications, Enable Two-way SSL/TLS in WildFly for Applications

Legacy Security Realms for One-way and Two-way SSL/TLS for Management Interfaces

Enable One-way for the Management Interfaces Using the Elytron Subsystem, Enable Two-way SSL/TLS for the Management Interfaces using the Elytron Subsystem

4. Using the Elytron Subsystem

4.1. Set Up and Configure Authentication for Applications

4.1.1. Configure Authentication with a Properties File-Based Identity Store

Create properties files:

You need to create two properties files: one that maps user to passwords and another that maps users to roles. Usually these files are located in the jboss.server.config.dir directory and follow the naming convention *-users.properties and *-roles.properties, but other locations and names may be used. The *-users.properties file must also contain a reference to the properties-realm, which you will create in the next step: #$REALM_NAME=YOUR_PROPERTIES_REALM_NAME$

Example user to password file: example-users.properties

#$REALM_NAME=examplePropRealm$
user1=password123
user2=password123

Example user to roles file: example-roles.properties

user1=Admin
user2=Guest

The Properties Realm also supports storing hashed passwords in the properties files, namely DIGEST passwords. Learn how to specify the encoding and character set for these passwords in the next section.

Configure a properties-realm in WildFly:
/subsystem=elytron/properties-realm=examplePropRealm:add(groups-attribute=groups,groups-properties={path=example-roles.properties,relative-to=jboss.server.config.dir},users-properties={path=example-users.properties,relative-to=jboss.server.config.dir,plain-text=true})

The name of the properties-realm is examplePropRealm, which is used in the previous step in the example-users.properties file. Also, if your properties files are located outside of jboss.server.config.dir, then you need to change the path and relative-to values appropriately.

Additionally, if storing hashed passwords in the properties file, you can specify the following attributes:

  • hash-encoding: This attribute specifies the string format for the password if it is not stored in plain text. It is set to hex encoding by default, but base64 is also supported.

  • hash-charset: This attribute specifies the character set to use when converting the password string to a byte array. It is set to UTF-8 by default.

For example, the following properties realm is configured to use base64 encoding and the character set GB2312.

/subsystem=elytron/properties-realm=examplePropRealm:add(groups-attribute=groups,groups-properties={path=example-roles.properties,relative-to=jboss.server.config.dir},users-properties={path=example-users.properties,relative-to=jboss.server.config.dir},hash-encoding=base64,hash-charset=GB2312)
Configure a security-domain:
/subsystem=elytron/security-domain=exampleSD:add(realms=[{realm=examplePropRealm,role-decoder=groups-to-roles}],default-realm=examplePropRealm,permission-mapper=default-permission-mapper)
Configure an http-authentication-factory:
/subsystem=elytron/http-authentication-factory=example-http-auth:add(http-server-mechanism-factory=global,security-domain=exampleSD,mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=exampleApplicationDomain}]}])

This example shows creating an http-authentication-factory using BASIC authentication, but it could be updated to other mechanisms such as FORM.

Configure an application-security-domain in the Undertow subsystem:
/subsystem=undertow/application-security-domain=exampleApplicationDomain:add(http-authentication-factory=example-http-auth)
Configure your application’s web.xml and jboss-web.xml.

Your application’s web.xml and jboss-web.xml must be updated to use the application-security-domain you configured in WildFly. An example of this is available in the Configure Applications to Use Elytron for Authentication section.

4.1.2. Configure Authentication with a Filesystem-Based Identity Store

Chose a directory for users:

You need a directory where your users will be stored. In this example, we are using a directory called fs-realm-users located in jboss.server.config.dir.

Configure a filesystem-realm in WildFly:
/subsystem=elytron/filesystem-realm=exampleFsRealm:add(path=fs-realm-users,relative-to=jboss.server.config.dir)

If your directory is located outside of jboss.server.config.dir, then you need to change the path and relative-to values appropriately.

Encryption

To enable encryption for a filesystem-realm, there are a couple of attributes that can be configured.

  • credential-store: This attribute specifies the credential-store where the secret key used for encrypting and decrypting the realm is stored.

  • secret-key: This attribute specifies the alias within the credential-store that contains the secret key to be used to encrypt and decrypt the realm.

As an example, to create a credential-store that is automatically populated with a SecretKey under the alias key, the following command can be used:

/subsystem=elytron/secret-key-credential-store=mycredstore:add(path=propcredstore.cs, relative-to=jboss.server.config.dir, create=true, populate=true)

An encrypted filesystem-realm that makes use of the credential-store could be then created as follows:

/subsystem=elytron/filesystem-realm=exampleFsRealm:add(path=fs-realm-users,relative-to=jboss.server.config.dir, credential-store=mycredstore, secret-key=key)
Integrity

It is also possible to enable support for integrity checking on the filesystem realm by configuring the key-store and key-store-alias attributes for the realm.

When enabling integrity checking on a filesystem realm, you will have to specify the following 2 attributes

  • key-store: This attribute specifies the key-store that contains the key pair (private key and public key) that’s used for signing each identity file and for integrity checking.

  • key-store-alias: This attribute specifies the alias within the key-store that identifies the PrivateKeyEntry to use to sign identity files and perform filesystem integrity checks.

To create a key-store and a key-pair for the attributes above, you can create a key-store resource using the following commands. This will create a key-store called keystore with the alias localhost, and the password secret.

/subsystem=elytron/key-store=keystore:add(path=keystore, relative-to=jboss.server.config.dir, type=PKCS12, credential-reference={clear-text=secret})
/subsystem=elytron/key-store=keystore:generate-key-pair(alias=localhost,algorithm=RSA,key-size=1024,validity=365,distinguished-name="CN=localhost")
/subsystem=elytron/key-store=keystore:store()

To create an integrity enabled filesystem realm with a key-store called keystore and the key-store-alias localhost, the following command would be used

/subsystem=elytron/filesystem-realm=exampleFsRealm:add(path=fs-realm-users,relative-to=jboss.server.config.dir, key-store=keystore, key-store-alias=localhost)

Integrity and Encryption can be used in conjuction together. To create an encrypted filesystem realm with integrity enabled, the following command would be used

/subsystem=elytron/filesystem-realm=exampleFsRealm:add(path=fs-realm-users,relative-to=jboss.server.config.dir, credential-store=mycredstore, secret-key=key, key-store=keystore, key-store-alias=localhost)
Add a user:

When using the filesystem-realm, you can add users using the management CLI.

/subsystem=elytron/filesystem-realm=exampleFsRealm:add-identity(identity=user1)
/subsystem=elytron/filesystem-realm=exampleFsRealm:set-password(clear={password="password123"}, identity=user1)
/subsystem=elytron/filesystem-realm=exampleFsRealm:add-identity-attribute(identity=user1, name=Roles, value=["Admin","Guest"])
Add a simple-role-decoder:
/subsystem=elytron/simple-role-decoder=from-roles-attribute:add(attribute=Roles)

This simple-role-decoder decodes a principal’s roles from the Roles attribute. You can change this value if your roles are in a different attribute.

Configure a security-domain:
/subsystem=elytron/security-domain=exampleFsSD:add(realms=[{realm=exampleFsRealm,role-decoder=from-roles-attribute}],default-realm=exampleFsRealm,permission-mapper=default-permission-mapper)
Configure an http-authentication-factory:
/subsystem=elytron/http-authentication-factory=example-fs-http-auth:add(http-server-mechanism-factory=global,security-domain=exampleFsSD,mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=exampleApplicationDomain}]}])

This example shows creating an http-authentication-factory using BASIC authentication, but it could be updated to other mechanisms such as FORM.

Configure an application-security-domain in the Undertow subsystem:
/subsystem=undertow/application-security-domain=exampleApplicationDomain:add(http-authentication-factory=example-fs-http-auth)
Configure your application’s web.xml and jboss-web.xml.

Your application’s web.xml and jboss-web.xml must be updated to use the application-security-domain you configured in WildFly. An example of this is available in the Configure Applications to Use Elytron for Authentication section.

Your application is now using a filesystem-based identity store for authentication.

4.1.3. Configure Authentication with a Database Identity Store

Determine your database format for usernames, passwords, and roles:

To set up authentication using a database for an identity store, you need to determine how your usernames, passwords, and roles are stored in that database. In this example, we are using a single table with the following sample data:

username password roles

user1

password123

Admin

user2

password123

Guest

Configure a datasource:

To connect to a database from WildFly, you must have the appropriate database driver deployed as well as a datasource configured. This example shows deploying the driver for postgres and configuring a datasource in WildFly:

deploy /path/to/postgresql-9.4.1210.jar
 
data-source add --name=examplePostgresDS --jndi-name=java:jboss/examplePostgresDS --driver-name=postgresql-9.4.1210.jar  --connection-url=jdbc:postgresql://localhost:5432/postgresdb --user-name=postgresAdmin --password=mysecretpassword
Configure a jdbc-realm in WildFly:
/subsystem=elytron/jdbc-realm=exampleDbRealm:add(principal-query=[{sql="SELECT password,roles FROM wildfly_users WHERE username=?",data-source=examplePostgresDS,clear-password-mapper={password-index=1},attribute-mapping=[{index=2,to=groups}]}])

NOTE: The above example shows how to obtain passwords and roles from a single principal-query. You can also create additional principal-query with attribute-mapping attributes if you require multiple queries to obtain roles or additional authentication or authorization information.

Configure a security-domain:
/subsystem=elytron/security-domain=exampleDbSD:add(realms=[{realm=exampleDbRealm,role-decoder=groups-to-roles}],default-realm=exampleDbRealm,permission-mapper=default-permission-mapper)
Configure an http-authentication-factory:
/subsystem=elytron/http-authentication-factory=example-db-http-auth:add(http-server-mechanism-factory=global,security-domain=exampleDbSD,mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=exampleDbSD}]}])

This example shows creating an http-authentication-factory using BASIC authentication, but it could be updated to other mechanisms such as FORM.

Configure an application-security-domain in the Undertow subsystem:
/subsystem=undertow/application-security-domain=exampleApplicationDomain:add(http-authentication-factory=example-db-http-auth)
Configure your application’s web.xml and jboss-web.xml.

Your application’s web.xml and jboss-web.xml must be updated to use the application-security-domain you configured in WildFly. An example of this is available in the Configure Applications to Use Elytron for Authentication section.

4.1.4. Configure Authentication with an LDAP-Based Identity Store

Determine your LDAP format for usernames, passwords, and roles:

To set up authentication using an LDAP server for an identity store, you need to determine how your usernames, passwords, and roles are stored. In this example, we are using the following structure:

dn: dc=wildfly,dc=org
dc: wildfly
objectClass: top
objectClass: domain
 
dn: ou=Users,dc=wildfly,dc=org
objectClass: organizationalUnit
objectClass: top
ou: Users
 
dn: uid=jsmith,ou=Users,dc=wildfly,dc=org
objectClass: top
objectClass: person
objectClass: inetOrgPerson
cn: John Smith
sn: smith
uid: jsmith
userPassword: password123
 
dn: ou=Roles,dc=wildfly,dc=org
objectclass: top
objectclass: organizationalUnit
ou: Roles
 
dn: cn=Admin,ou=Roles,dc=wildfly,dc=org
objectClass: top
objectClass: groupOfNames
cn: Admin
member: uid=jsmith,ou=Users,dc=wildfly,dc=org
Configure a dir-context:

To connect to the LDAP server from WildFly, you need to configure a dir-context that provides the URL as well as the principal used to connect to the server.

/subsystem=elytron/dir-context=exampleDC:add(url="ldap://127.0.0.1:10389",principal="uid=admin,ou=system",credential-reference={clear-text="secret"})
Configure an ldap-realm in WildFly:
/subsystem=elytron/ldap-realm=exampleLR:add(dir-context=exampleDC,identity-mapping={search-base-dn="ou=Users,dc=wildfly,dc=org",rdn-identifier="uid",user-password-mapper={from="userPassword"},attribute-mapping=[{filter-base-dn="ou=Roles,dc=wildfly,dc=org",filter="(&(objectClass=groupOfNames)(member={1}))",from="cn",to="Roles"}]})

Additionally, if storing hashed passwords in the LDIF file, you can specify the following attributes:

  • hash-encoding: This attribute specifies the string format for the password if it is not stored in plain text. It is set to base64 encoding by default, but hex is also supported.

  • hash-charset: This attribute specifies the character set to use when converting the password string to a byte array. It is set to UTF-8 by default.

For example, the following LDAP realm is storing hexadecimal encoded strings hashed using the GB2312 character set:

/subsystem=elytron/ldap-realm=exampleLR:add(dir-context=exampleDC,identity-mapping={...},hash-encoding=hex,hash-charset=GB2312)
Add a simple-role-decoder:
/subsystem=elytron/simple-role-decoder=from-roles-attribute:add(attribute=Roles)
Configure a security-domain:
/subsystem=elytron/security-domain=exampleLdapSD:add(realms=[{realm=exampleLR,role-decoder=from-roles-attribute}],default-realm=exampleLR,permission-mapper=default-permission-mapper)
Configure an http-authentication-factory:
/subsystem=elytron/http-authentication-factory=example-ldap-http-auth:add(http-server-mechanism-factory=global,security-domain=exampleLdapSD,mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=exampleApplicationDomain}]}])

This example shows creating an http-authentication-factory using BASIC authentication, but it could be updated to other mechanisms such as FORM.

Configure an application-security-domain in the Undertow subsystem:
/subsystem=undertow/application-security-domain=exampleApplicationDomain:add(http-authentication-factory=example-ldap-http-auth)
Configure your application’s web.xml and jboss-web.xml.

Your application’s web.xml and jboss-web.xml must be updated to use the application-security-domain you configured in WildFly. An example of this is available in the Configure Applications to Use Elytron for Authentication section.

IMPORTANT: In cases where you configure an LDAP server in the elytron subsystem for authentication and that LDAP server then becomes unreachable, WildFly will return a 500, or internal server error, error code when attempting authentication using that unreachable LDAP server. This behavior differs from the legacy security subsystem, which will return a 401, or unauthorized, error code under the same conditions.

4.1.5. Configure Authentication with Certificates

IMPORTANT: Before you can set up certificate-based authentication, you must have two-way SSL configured.

Configure a key-store-realm.
/subsystem=elytron/key-store-realm=ksRealm:add(key-store=twoWayTS)

You must configure this realm with a truststore that contains the client’s certificate. The authentication process uses the same certificate presented by the client during the two-way SSL handshake.

Create a Decoder.

You need to create a x500-attribute-principal-decoder to decode the principal you get from your certificate. The below example will decode the principal based on the first CN value.

/subsystem=elytron/x500-attribute-principal-decoder=CNDecoder:add(oid="2.5.4.3",maximum-segments=1)

For example, if the full DN was CN=client,CN=client-certificate,DC=example,DC=jboss,DC=org, CNDecoder would decode the principal as client. This decoded principal is used as the alias value to lookup a certificate in the truststore configured in ksRealm.

IMPORTANT: The decoded principal * MUST* must be the alias value you set in your server’s truststore for the client’s certificate.

Configure an evidence-decoder

By default, the principal associated with a certificate will be the subject name from the certificate. This subject name will then be rewritten using any configured principal decoders and principal transformers to obtain the name that should be used when locating and loading the identity from the underlying identity store.

To specify that a subject alternative name from a certificate should be used as the principal associated with that certificate, an x509-subject-alt-name-evidence-decoder needs to be configured. This element has two attributes:

  • alt-name-type - The subject alternative name type to decode. This attribute is required. Must be one of the subject alternative name types that can be represented as a String:

    • rfc822Name

    • dNSName

    • uniformResourceIdentifier

    • iPAddress

    • registeredID

    • directoryName

  • segment - The 0-based occurrence of the subject alternative name to map. This attribute is optional and only used when there is more than one subject alternative name of the given alt-name-type. The default value is 0.

For example, consider the following X.509 v3 Subject Alternative Name extension from the first certificate in an X.509 certificate chain:

X509v3 Subject Alternative Name:
DNS:one.example.org, IP Address:127.0.0.1

To associate the certificate chain evidence with the principal "one.example.org", the following x509-subject-alt-name-evidence-decoder could be configured:

/subsystem=elytron/x509-subject-alt-name-evidence-decoder=exampleDnsDecoder:add(alt-name-type=dNSName)

Next, consider the following X.509 v3 Subject Alternative Name extension from the first certificate in an X.509 certificate chain:

X509v3 Subject Alternative Name:
DNS:one.example.org, DNS:two.example.org, IP Address:127.0.0.1

To associate the evidence with the principal "two.example.org", the following x509-subject-alt-name-evidence-decoder could be configured:

/subsystem=elytron/x509-subject-alt-name-evidence-decoder=anotherDnsDecoder:add(alt-name-type=dNSName, segment=1)

It is also possible to configure an x500-subject-evidence-decoder. This evidence decoder will extract the subject from the first certificate in the certificate chain, as an X500Principal. Example configuration:

/subsystem=elytron/x500-subject-evidence-decoder=exampleSubjectDecoder:add()

To make use of a custom org.wildfly.security.auth.server.EvidenceDecoder implementation, a custom-evidence-decoder can be configured. The custom implementation class needs to first be packaged in a JAR. A module can then be added to WildFly that contains this JAR. See Custom Components for more information on how to add a custom Elytron component to WildFly.

Example configuration:

/subsystem=elytron/custom-evidence-decoder=myCustomEvidenceDecoder:add(module=org.wildfly.security.examples, class-name=org.wildfly.security.examples.MyCustomEvidenceDecoder)

Finally, it is also possible to configure an aggregate-evidence-decoder. This consists of two or more evidence-decoder elements where each element is reference to a configured evidence decoder. Given evidence, these evidence decoders will be attempted in order until one returns a non-null principal or until there are no more evidence decoders left to try.

Example configuration:

/subsystem=elytron/x509-subject-alt-name-evidence-decoder=emailDecoder:add(alt-name-type=rfc822Name)
/subsystem=elytron/x509-subject-alt-name-evidence-decoder=dnsDecoder:add(alt-name-type=dNSName)
/subsystem=elytron/x500-subject-evidence-decoder=subjectDecoder:add()
/subsystem=elytron/aggregate-evidence-decoder=aggregateDecoder:add(evidence-decoders=[emailDecoder,subjectDecoder,dnsDecoder])

Once an evidence-decoder has been configured, it can be referenced from a security-domain:

/subsystem=elytron/security-domain=exampleCertSD:add(..., evidence-decoder=aggregateDecoder)

If no evidence-decoder is specified for a security-domain, then the principal associated with the evidence will just be the default principal for the evidence type, i.e., for an X.509 certificate chain, this would continue to default to the subject from the first certificate in the certificate chain.

Add a constant-role-mapper for assigning roles.

This is example uses a constant-role-mapper to assign roles to a principal from ksRealm but other approaches may also be used.

/subsystem=elytron/constant-role-mapper=constantClientCertRole:add(roles=[Admin,Guest])
Configure a security-domain.
/subsystem=elytron/security-domain=exampleCertSD:add(realms=[{realm=ksRealm}],default-realm=ksRealm,permission-mapper=default-permission-mapper,principal-decoder=CNDecoder,role-mapper=constantClientCertRole)
Configure an http-authentication-factory.
/subsystem=elytron/http-authentication-factory=exampleCertHttpAuth:add(http-server-mechanism-factory=global,security-domain=exampleCertSD,mechanism-configurations=[{mechanism-name=CLIENT_CERT}])
Configure an application-security-domain in the Undertow subsystem.
/subsystem=undertow/application-security-domain=exampleApplicationDomain:add(http-authentication-factory=exampleCertHttpAuth)
Update server-ssl-context.
/subsystem=elytron/server-ssl-context=twoWaySSC:write-attribute(name=security-domain,value=exampleCertSD)
/subsystem=elytron/server-ssl-context=twoWaySSC:write-attribute(name=authentication-optional, value=true)
Configure your application’s web.xml and jboss-web.xml.

Your application’s web.xml and jboss-web.xml must be updated to use the application-security-domain you configured in WildFly. An example of this is available in the Configure Applications to Use Elytron for Authentication section.

In addition, you need to update your web.xml to use CLIENT-CERT as its authentication method.

<login-config>
  <auth-method>CLIENT-CERT</auth-method>
  <realm-name>exampleApplicationDomain</realm-name>
</login-config>

4.1.6. Configure Authentication with a Kerberos-Based Identity Store

IMPORTANT: The following steps assume you have a working KDC and Kerberos domain as well as your client browsers configured.

Configure a kerberos-security-factory.
/subsystem=elytron/kerberos-security-factory=krbSF:add(principal="HTTP/host@REALM",path="/path/to/http.keytab",mechanism-oids=[1.2.840.113554.1.2.2,1.3.6.1.5.5.2])
Configure the system properties for Kerberos.

Depending on how your environment is configured, you will need to set some of the system properties below.

System Property Description

java.security.krb5.kdc

The host name of the KDC.

java.security.krb5.realm

The name of the realm.

java.security.krb5.conf

The path to the configuration krb5.conf file.

sun.security.krb5.debug

If true, debugging mode will be enabled.

To configure a system property in WildFly:

/system-property=java.security.krb5.conf:add(value="/path/to/krb5.conf")
Configure an Elytron security realm for assigning roles.

The the client’s Kerberos token will provide the principal, but you need a way to map that principal to a role for your application. There are several ways to accomplish this, but this example creates a filesystem-realm, adds a user to the realm that matches the principal from the Kerberos token, and assigns roles to that user.

/subsystem=elytron/filesystem-realm=exampleFsRealm:add(path=fs-realm-users,relative-to=jboss.server.config.dir)
/subsystem=elytron/filesystem-realm=exampleFsRealm:add-identity(identity=user1@REALM)
/subsystem=elytron/filesystem-realm=exampleFsRealm:add-identity-attribute(identity=user1@REALM,name=Roles,value=["Admin","Guest"])
Add a simple-role-decoder.
/subsystem=elytron/simple-role-decoder=from-roles-attribute:add(attribute=Roles)

This simple-role-decoder decodes a principal’s roles from the Roles attribute. You can change this value if your roles are in a different attribute.

Configure a security-domain.
/subsystem=elytron/security-domain=exampleFsSD:add(realms=[{realm=exampleFsRealm,role-decoder=from-roles-attribute}],default-realm=exampleFsRealm,permission-mapper=default-permission-mapper)
Configure an http-authentication-factory that uses the

kerberos-security-factory.

/subsystem=elytron/http-authentication-factory=example-krb-http-auth:add(http-server-mechanism-factory=global,security-domain=exampleFsSD,mechanism-configurations=[{mechanism-name=SPNEGO,mechanism-realm-configurations=[{realm-name=exampleFsSD}],credential-security-factory=krbSF}])
Configure an application-security-domain in the Undertow subsystem:
/subsystem=undertow/application-security-domain=exampleApplicationDomain:add(http-authentication-factory=example-krb-http-auth)
Configure your application’s web.xml, jboss-web.xml and

jboss-deployment-structure.xml.

Your application’s web.xml and jboss-web.xml must be updated to use the application-security-domain you configured in WildFly. An example of this is available in the Configure Applications to Use Elytron for Authentication section.

In addition, you need to update your web.xml to use SPNEGO as its authentication method.

<login-config>
  <auth-method>SPNEGO</auth-method>
  <realm-name>exampleApplicationDomain</realm-name>
</login-config>

4.1.7. Configure Authentication with a Form as a Fallback for Kerberos

Configure kerberos-based authentication.

Configuring kerberos-based authentication is covered in a previous section.

Add a mechanism for FORM authentication in the

http-authentication-factory.

You can use the existing http-authentication-factory you configured for kerberos-based authentication and and an additional mechanism for FORM authentication.

/subsystem=elytron/http-authentication-factory=example-krb-http-auth:list-add(name=mechanism-configurations, value={mechanism-name=FORM})
Add additional fallback principals.

The existing configuration for kerberos-based authentication should already have a security realm configured for mapping principals from kerberos token to roles for the application. You can add additional users for fallback authentication to that realm. For example if you used a filesystem-realm, you can simply create a new user with the appropriate roles:

/subsystem=elytron/filesystem-realm=exampleFsRealm:add-identity(identity=fallbackUser1)
/subsystem=elytron/filesystem-realm=exampleFsRealm:set-password(identity=fallbackUser1,clear={password="password123"})
/subsystem=elytron/filesystem-realm=exampleFsRealm:add-identity-attribute(identity=fallbackUser1,name=Roles,value=["Admin","Guest"])
Update the web.xml for FORM fallback.

You need to update the web.xml to use the value SPNEGO,FORM for the auth-method, which will use FORM as a fallback authentication method if SPNEGO fails. You also need to specify the location of your login and error pages.

<login-config>
  <auth-method>SPNEGO,FORM</auth-method>
  <realm-name>exampleApplicationDomain</realm-name>
  <form-login-config>
    <form-login-page>/login.jsp</form-login-page>
    <form-error-page>/error.jsp</form-error-page>
  </form-login-config>
</login-config>

4.1.8. Configure Applications to Use Elytron for Authentication

After you have configured the elytron subsystem for authentication, you need to configure your application to use it.

Configure your application’s web.xml.

Your application’s web.xml needs to be configured to use the appropriate authentication method. When using elytron, this is defined in the http-authentication-factory you created.

Example web.xml with BASIC Authentication

<web-app>
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>secure</web-resource-name>
      <url-pattern>/secure/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>Admin</role-name>
    </auth-constraint>
  </security-constraint>
  <security-role>
    <description>The role that is required to log in to /secure/*</description>
    <role-name>Admin</role-name>
  </security-role>
  <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>exampleApplicationDomain</realm-name>
  </login-config>
</web-app>

BASIC Authentication can be configured to be silent

<auth-method>BASIC?silent=true</auth-method>

Basic authentication in silent mode will send challenge to authenticate only if the request contained authorization header, otherwise it is assumed another method will send the challenge.

Configure your application to use a security domain.

You can configure your application’s jboss-web.xml to specify the security domain you want to use for authentication. When using the elytron subsystem, this is defined when you created the application-security-domain.

Example jboss-web.xml

<jboss-web>
  <security-domain>exampleApplicationDomain</security-domain>
</jboss-web>

Using jboss-web.xml allows you to configure the security domain for a single application only. Alternatively, you can specify a default security domain for all applications using the undertow subsystem. This allows you to omit using jboss-web.xml to configure a security domain for an individual application.

/subsystem=undertow:write-attribute(name=default-security-domain, value="exampleApplicationDomain")

IMPORTANT: Setting default-security-domain in the undertow subsystem will apply to ALL applications. If default-security-domain is set and an application specifies a security domain in a jboss-web.xml file, the configuration in jboss-web.xml will override the default-security-domain in the undertow subsystem.

4.1.9. Override an Application’s Authentication Configuration

You can override the authentication configuration of an application with one configured in WildFly. To do this, use the override-deployment-configuration property in the application-security-domain section of the undertow subsystem:

/subsystem=undertow/application-security-domain=exampleApplicationDomain:write-attribute(name=override-deployment-config,value=true)

For example, an application is configured to use FORM authentication with the exampleApplicationDomain in its jboss-web.xml.

Example jboss-web.xml

<login-config>
  <auth-method>FORM</auth-method>
  <realm-name>exampleApplicationDomain</realm-name>
</login-config>

By enabling override-deployment-configuration, you can create a new http-authentication-factory that specifies a different authentication mechanism such as BASIC.

Example http-authentication-factory

/subsystem=elytron/http-authentication-factory=exampleHttpAuth:read-resource()
{
    "outcome" => "success",
    "result" => {
        "http-server-mechanism-factory" => "global",
        "mechanism-configurations" => [{
            "mechanism-name" => "BASIC",
            "mechanism-realm-configurations" => [{"realm-name" => "exampleApplicationDomain"}]
        }],
        "security-domain" => "exampleSD"
    }
}

This will override the authentication mechanism defined in the application’s jboss-web.xml and attempt to authenticate a user using BASIC instead of FORM.

4.1.10. Create and Use a Credential Store

Create credential store.
/subsystem=elytron/credential-store=exampleCS:add(relative-to=jboss.server.data.dir, location=example.jceks,create=true,credential-reference={clear-text=cs-secret})
Add a credential to a credential store.
/subsystem=elytron/credential-store=exampleCS:add-alias(alias=keystorepw,secret-value=secret)
List all credentials in a credential store.
/subsystem=elytron/credential-store=exampleCS:read-aliases()
{
    "outcome" => "success",
    "result" => ["keystorepw"]
}
Remove a credential from a credential store.
/subsystem=elytron/credential-store=exampleCS:remove-alias(alias=keystorepw)
Use a credential store.
/subsystem=elytron/key-store=twoWayKS:write-attribute(name=credential-reference,value={store=exampleCS,alias=keystorepw})

4.2. Set up and Configure Authentication for the Management Interfaces

4.2.1. Secure the Management Interfaces with a New Identity Store

Create a security domain and any supporting security realms,

decoders, or mappers for your identity store.

This process is covered in a previous section. For example, if you wanted to secure the management interfaces using a filesystem-based identity store, you would follow the steps in Configure Authentication with a Filesystem-Based Identity Store.

Create an http-authentication-factory or

sasl-authentication-factory.

Example http-authentication-factory

/subsystem=elytron/http-authentication-factory=example-http-auth:add(http-server-mechanism-factory=global,security-domain=exampleSD,mechanism-configurations=[{mechanism-name=DIGEST,mechanism-realm-configurations=[{realm-name=exampleManagementRealm}]}])

Example sasl-authentication-factory

/subsystem=elytron/sasl-authentication-factory=example-sasl-auth:add(sasl-server-factory=configured,security-domain=exampleSD,mechanism-configurations=[{mechanism-name=DIGEST-MD5,mechanism-realm-configurations=[{realm-name=exampleManagementRealm}]}])
Update the management interfaces to use your

http-authentication-factory or sasl-authentication-factory.

Example update http-authentication-factory

/core-service=management/management-interface=http-interface:write-attribute(name=http-authentication-factory, value=example-http-auth)
{
   "outcome" => "success",
   "response-headers" => {
       "operation-requires-reload" => true,
       "process-state" => "reload-required"
   }
}
 
reload

Example update sasl-authentication-factory

/core-service=management/management-interface=http-interface:write-attribute(name=http-upgrade.sasl-authentication-factory, value=example-sasl-auth)
{
   "outcome" => "success",
   "response-headers" => {
       "operation-requires-reload" => true,
       "process-state" => "reload-required"
   }
}
 
reload

4.2.2. Silent Authentication

By default, WildFly provides an authentication mechanism for local users, also know as silent authentication, through the local security realm.

Silent authentication must be used via a sasl-authentication-factory.

IMPORTANT: When enabling silent authentication, you must ensure the security domain referenced by your sasl-authentication-factory references a security realm that contains the $local user. By default, WildFly provides the local identity realm that provides this user.

Add silent authentication to an existing

sasl-authentication-factory.

/subsystem=elytron/sasl-authentication-factory=example-sasl-auth:list-add(name=mechanism-configurations, value={mechanism-name=JBOSS-LOCAL-USER, realm-mapper=local})
 
reload
Create a new sasl-server-factory with silent authentication.
/subsystem=elytron/sasl-authentication-factory=example-sasl-auth:add(sasl-server-factory=configured,security-domain=exampleSD,mechanism-configurations=[{mechanism-name=DIGEST-MD5,mechanism-realm-configurations=[{realm-name=exampleManagementRealm}]},{mechanism-name=JBOSS-LOCAL-USER, realm-mapper=local}])
 
reload
Remove silent authentication from an existing sasl-server-factory:
/subsystem=elytron/sasl-authentication-factory=managenet-sasl-authentication:read-resource
{
    "outcome" => "success",
    "result" => {
        "mechanism-configurations" => [
            {
                "mechanism-name" => "JBOSS-LOCAL-USER",
                "realm-mapper" => "local"
            },
            {
                "mechanism-name" => "DIGEST-MD5",
                "mechanism-realm-configurations" => [{"realm-name" => "ManagementRealm"}]
            }
        ],
        "sasl-server-factory" => "configured",
        "security-domain" => "ManagementDomain"
    }
}
 
/subsystem=elytron/sasl-authentication-factory=temp-sasl-authentication:list-remove(name=mechanism-configurations,index=0)
 
reload

4.2.3. Using RBAC with Elytron

RBAC can be configured to automatically assign or exclude roles for users that are members of groups. This is configured in the access-control section of the core management. When the management interfaces are secured with the elytron subsystem, and users are assigned groups when they authenticate. You can also configure roles to be assigned to authenticated users in a variety of ways using the elytron subsystem, for example using a role mapper or a role decoder.

4.3. Configure SSL/TLS

4.3.1. Enable One-way SSL/TLS for Applications

There are a couple ways to enable one-way SSL/TLS for deployed applications.

Using a security command:

The security enable-ssl-http-server command can be used to enable one-way SSL/TLS for deployed applications. Example of wizard usage:

security enable-ssl-http-server --interactive --override-ssl-context
Please provide required pieces of information to enable SSL:
Key-store file name (default default-server.keystore): keystore.pkcs12
Password (blank generated): secret
What is your first and last name? [Unknown]: localhost
What is the name of your organizational unit? [Unknown]:
What is the name of your organization? [Unknown]:
What is the name of your City or Locality? [Unknown]:
What is the name of your State or Province? [Unknown]:
What is the two-letter country code for this unit? [Unknown]:
Is CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct y/n [y]?
Validity (in days, blank default): 365
Alias (blank generated): localhost
Enable SSL Mutual Authentication y/n (blank n): n

SSL options:
key store file: keystore.pkcs12
distinguished name: CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
password: secret
validity: 365
alias: localhost
Server keystore file keystore.pkcs12, certificate file keystore.pem and keystore.csr file
will be generated in server configuration directory.
Do you confirm y/n: y

NB: Once the command is executed, the CLI will reload the server.

HTTPS is now enabled for applications.

Using Elytron subsystem commands:

You can also use the Elytron subsystem, along with the Undertow subsystem, to enable HTTPS for deployed applications.

Configure a key-store in WildFly:
/subsystem=elytron/key-store=httpsKS:add(path=/path/to/keystore.pkcs12,credential-reference={clear-text=secret},type=PKCS12)

The previous command uses an absolute path to the keystore. Alternatively you can use the relative-to attribute to specify the base directory variable and path specify a relative path. Also, in case of file-based keystore the type attribute can be omitted and the keystore type will be automatically detected.

/subsystem=elytron/key-store=httpsKS:add(path=keystore.pkcs12,relative-to=jboss.server.config.dir,credential-reference={clear-text=secret},type=PKCS12)

If the keystore file does not exist yet, the following commands can be used to generate an example key pair:

/subsystem=elytron/key-store=httpsKS:generate-key-pair(alias=localhost,algorithm=RSA,key-size=2048,validity=365,credential-reference={clear-text=secret},distinguished-name="CN=localhost")
/subsystem=elytron/key-store=httpsKS:store()
List all aliases (recursively) in a key store.
/subsystem=elytron/key-store=httpsKS:read-aliases(recursive=true,verbose=true)
{
    "outcome" => "success",
    "result" => {"localhost" => {
        "alias" => "localhost",
        "entry-type" => "PrivateKeyEntry",
        "creation-date" => "2022-12-28T15:58:04.818+0530",
        "certificate-chain" => [{
            "type" => "X.509",
            "algorithm" => "RSA",
            "format" => "X.509",
            "public-key" => "30:82:01:22:30:0d:06:09:2a:86:48:86:f7:0d:01:01:01:
05:00:03:82:01:0f:00:30:82:01:0a:02:82:01:01:00:9e:b2:6f:4e:92:e9:f3:69:eb:9c:0e
:63:1e:d6:d4:4b:2b:5d:46:e2:68:fd:96:83:a1:3b:83:a2:70:a8:6e:e1:2e:8d:e9:8d:ed:9
d:d3:f3:56:26:00:29:32:a0:95:61:54:6f:35:95:41:95:76:73:34:46:db:4b:75:3c:f1:64:
91:4a:a6:61:89:80:8c:6b:87:75:8d:d1:d1:56:bb:5e:1b:ed:97:5a:b4:ac:1e:16:06:07:e5
:cb:6c:a8:c5:66:16:e2:92:6a:db:a6:b0:09:d2:7d:dc:fa:68:f3:2c:c5:c2:a6:f2:bd:73:f
5:3b:94:17:16:2f:d0:8a:e6:d0:f3:53:c4:71:c6:31:61:9b:87:9d:5e:63:d8:c3:e3:81:f8:
6f:f6:35:7b:85:9c:81:c9:94:91:d3:5d:bc:89:3b:dc:7d:c2:ae:d2:ca:b4:61:6f:04:5b:4f
:8f:c6:ac:e0:49:de:5f:bf:84:f5:38:b1:07:48:1f:17:fb:72:15:ac:31:7a:13:f8:58:aa:6
7:b0:67:b1:2b:99:c0:7a:a7:f6:42:d3:f9:f5:33:90:2a:9c:7c:78:73:7f:74:54:91:04:c2:
51:ff:59:11:cd:7d:d7:61:e9:5c:c6:c7:d1:a1:f2:f5:7b:41:94:c4:cd:8d:5e:06:f1:6a:f4
:72:24:37:be:27:02:03:01:00:01",
            "sha-1-digest" => "70:46:1d:af:1a:5e:30:34:e9:dd:53:f4:38:b6:3e:89:5
a:1c:f9:90",
            "sha-256-digest" => "61:43:2e:6f:c0:ed:91:b1:3a:20:51:fc:2b:e9:33:d2
:66:d2:a6:f2:32:5c:ca:91:14:67:7b:f2:4e:55:ed:77",
            "encoded" => "30:82:02:cb:30:82:01:b5:a0:03:02:01:02:02:08:0c:e1:64:
83:a0:95:28:72:30:0b:06:09:2a:86:48:86:f7:0d:01:01:0b:30:14:31:12:30:10:06:03:55
:04:03:13:09:6c:6f:63:61:6c:68:6f:73:74:30:22:18:0f:32:30:32:32:31:32:32:38:31:3
0:32:38:30:34:5a:18:0f:32:30:32:33:31:32:32:38:31:30:32:38:30:34:5a:30:14:31:12:
30:10:06:03:55:04:03:13:09:6c:6f:63:61:6c:68:6f:73:74:30:82:01:22:30:0d:06:09:2a
:86:48:86:f7:0d:01:01:01:05:00:03:82:01:0f:00:30:82:01:0a:02:82:01:01:00:9e:b2:6
f:4e:92:e9:f3:69:eb:9c:0e:63:1e:d6:d4:4b:2b:5d:46:e2:68:fd:96:83:a1:3b:83:a2:70:
a8:6e:e1:2e:8d:e9:8d:ed:9d:d3:f3:56:26:00:29:32:a0:95:61:54:6f:35:95:41:95:76:73
:34:46:db:4b:75:3c:f1:64:91:4a:a6:61:89:80:8c:6b:87:75:8d:d1:d1:56:bb:5e:1b:ed:9
7:5a:b4:ac:1e:16:06:07:e5:cb:6c:a8:c5:66:16:e2:92:6a:db:a6:b0:09:d2:7d:dc:fa:68:
f3:2c:c5:c2:a6:f2:bd:73:f5:3b:94:17:16:2f:d0:8a:e6:d0:f3:53:c4:71:c6:31:61:9b:87
:9d:5e:63:d8:c3:e3:81:f8:6f:f6:35:7b:85:9c:81:c9:94:91:d3:5d:bc:89:3b:dc:7d:c2:a
e:d2:ca:b4:61:6f:04:5b:4f:8f:c6:ac:e0:49:de:5f:bf:84:f5:38:b1:07:48:1f:17:fb:72:
15:ac:31:7a:13:f8:58:aa:67:b0:67:b1:2b:99:c0:7a:a7:f6:42:d3:f9:f5:33:90:2a:9c:7c
:78:73:7f:74:54:91:04:c2:51:ff:59:11:cd:7d:d7:61:e9:5c:c6:c7:d1:a1:f2:f5:7b:41:9
4:c4:cd:8d:5e:06:f1:6a:f4:72:24:37:be:27:02:03:01:00:01:a3:21:30:1f:30:1d:06:03:
55:1d:0e:04:16:04:14:ac:99:db:c3:82:18:60:51:92:7d:75:51:ba:b4:a9:65:90:0e:7d:13
:30:0b:06:09:2a:86:48:86:f7:0d:01:01:0b:03:82:01:01:00:97:83:30:22:ed:2e:4e:55:3
3:73:4c:e2:56:d2:37:0a:47:39:7c:01:f9:97:6c:f6:98:f7:33:5b:b2:57:ba:d9:ff:7d:4c:
f1:92:42:64:a5:ee:56:ca:f1:da:ae:64:51:a0:90:5a:d7:a0:eb:e8:7b:e0:ef:6d:a1:8f:d0
:f6:80:c8:05:1b:29:c0:66:14:4d:bb:d9:87:e1:8e:d5:95:3c:c8:0f:fe:49:74:bd:28:a5:4
6:50:9c:e3:d5:6b:0b:48:b4:7f:1b:ad:95:8b:52:ee:5a:e1:03:43:47:6e:6e:ac:c2:5a:da:
63:3c:a4:8a:c5:f8:d1:6e:ae:eb:09:97:8b:0c:cd:37:92:77:0c:05:6b:a1:99:8f:7b:b8:6a
:30:cc:5b:c0:fc:70:f2:2b:50:73:22:d2:aa:80:b6:b0:c2:67:37:25:91:10:80:3a:ed:8c:6
b:04:02:b5:49:0c:7f:a1:a0:ef:3a:66:07:a5:9d:ab:09:be:21:8e:73:f3:91:19:3e:59:75:
cf:0f:85:3a:c8:18:89:22:68:04:95:81:9a:7d:67:19:28:11:bc:6b:e8:cb:c7:7b:9c:b4:64
:59:63:50:88:f4:05:9e:e6:fa:01:17:2b:eb:75:da:8b:34:f2:1f:47:2d:c5:79:8f:76:59:6
9:d9:89:0b:51:99:00:35",
            "subject" => "CN=localhost",
            "issuer" => "CN=localhost",
            "not-before" => "2022-12-28T15:58:04.000+0530",
            "not-after" => "2023-12-28T15:58:04.000+0530",
            "serial-number" => "0c:e1:64:83:a0:95:28:72",
            "signature-algorithm" => "SHA256withRSA",
            "signature" => "97:83:30:22:ed:2e:4e:55:33:73:4c:e2:56:d2:37:0a:47:3
9:7c:01:f9:97:6c:f6:98:f7:33:5b:b2:57:ba:d9:ff:7d:4c:f1:92:42:64:a5:ee:56:ca:f1:
da:ae:64:51:a0:90:5a:d7:a0:eb:e8:7b:e0:ef:6d:a1:8f:d0:f6:80:c8:05:1b:29:c0:66:14
:4d:bb:d9:87:e1:8e:d5:95:3c:c8:0f:fe:49:74:bd:28:a5:46:50:9c:e3:d5:6b:0b:48:b4:7
f:1b:ad:95:8b:52:ee:5a:e1:03:43:47:6e:6e:ac:c2:5a:da:63:3c:a4:8a:c5:f8:d1:6e:ae:
eb:09:97:8b:0c:cd:37:92:77:0c:05:6b:a1:99:8f:7b:b8:6a:30:cc:5b:c0:fc:70:f2:2b:50
:73:22:d2:aa:80:b6:b0:c2:67:37:25:91:10:80:3a:ed:8c:6b:04:02:b5:49:0c:7f:a1:a0:e
f:3a:66:07:a5:9d:ab:09:be:21:8e:73:f3:91:19:3e:59:75:cf:0f:85:3a:c8:18:89:22:68:
04:95:81:9a:7d:67:19:28:11:bc:6b:e8:cb:c7:7b:9c:b4:64:59:63:50:88:f4:05:9e:e6:fa
:01:17:2b:eb:75:da:8b:34:f2:1f:47:2d:c5:79:8f:76:59:69:d9:89:0b:51:99:00:35",
            "version" => "v3"
        }]
    }}
}
List all aliases (recursively, non-verbose) in a key store.
/subsystem=elytron/key-store=httpsKS:read-aliases(recursive=true, verbose=false)
{
    "outcome" => "success",
    "result" => {"localhost" => {
        "alias" => "localhost",
        "entry-type" => "PrivateKeyEntry",
        "creation-date" => "2022-12-28T15:58:04.818+0530",
        "certificate-chain" => [{
            "type" => "X.509",
            "algorithm" => "RSA",
            "format" => "X.509",
            "sha-1-digest" => "70:46:1d:af:1a:5e:30:34:e9:dd:53:f4:38:b6:3e:89:5
a:1c:f9:90",
            "sha-256-digest" => "61:43:2e:6f:c0:ed:91:b1:3a:20:51:fc:2b:e9:33:d2
:66:d2:a6:f2:32:5c:ca:91:14:67:7b:f2:4e:55:ed:77",
            "subject" => "CN=localhost",
            "issuer" => "CN=localhost",
            "not-before" => "2022-12-28T15:58:04.000+0530",
            "not-after" => "2023-12-28T15:58:04.000+0530",
            "serial-number" => "0c:e1:64:83:a0:95:28:72",
            "signature-algorithm" => "SHA256withRSA",
            "signature" => "97:83:30:22:ed:2e:4e:55:33:73:4c:e2:56:d2:37:0a:47:3
9:7c:01:f9:97:6c:f6:98:f7:33:5b:b2:57:ba:d9:ff:7d:4c:f1:92:42:64:a5:ee:56:ca:f1:
da:ae:64:51:a0:90:5a:d7:a0:eb:e8:7b:e0:ef:6d:a1:8f:d0:f6:80:c8:05:1b:29:c0:66:14
:4d:bb:d9:87:e1:8e:d5:95:3c:c8:0f:fe:49:74:bd:28:a5:46:50:9c:e3:d5:6b:0b:48:b4:7
f:1b:ad:95:8b:52:ee:5a:e1:03:43:47:6e:6e:ac:c2:5a:da:63:3c:a4:8a:c5:f8:d1:6e:ae:
eb:09:97:8b:0c:cd:37:92:77:0c:05:6b:a1:99:8f:7b:b8:6a:30:cc:5b:c0:fc:70:f2:2b:50
:73:22:d2:aa:80:b6:b0:c2:67:37:25:91:10:80:3a:ed:8c:6b:04:02:b5:49:0c:7f:a1:a0:e
f:3a:66:07:a5:9d:ab:09:be:21:8e:73:f3:91:19:3e:59:75:cf:0f:85:3a:c8:18:89:22:68:
04:95:81:9a:7d:67:19:28:11:bc:6b:e8:cb:c7:7b:9c:b4:64:59:63:50:88:f4:05:9e:e6:fa
:01:17:2b:eb:75:da:8b:34:f2:1f:47:2d:c5:79:8f:76:59:69:d9:89:0b:51:99:00:35",
            "version" => "v3"
        }]
    }}
}
Read an alias in a key store.
/subsystem=elytron/key-store=httpsKS:read-alias(alias="localhost")
{
    "outcome" => "success",
    "result" => {
        "alias" => "localhost",
        "entry-type" => "PrivateKeyEntry",
        "creation-date" => "2022-12-28T15:58:04.818+0530",
        "certificate-chain" => [{
            "type" => "X.509",
            "algorithm" => "RSA",
            "format" => "X.509",
            "public-key" => "30:82:01:22:30:0d:06:09:2a:86:48:86:f7:0d:01:01:01:
05:00:03:82:01:0f:00:30:82:01:0a:02:82:01:01:00:9e:b2:6f:4e:92:e9:f3:69:eb:9c:0e
:63:1e:d6:d4:4b:2b:5d:46:e2:68:fd:96:83:a1:3b:83:a2:70:a8:6e:e1:2e:8d:e9:8d:ed:9
d:d3:f3:56:26:00:29:32:a0:95:61:54:6f:35:95:41:95:76:73:34:46:db:4b:75:3c:f1:64:
91:4a:a6:61:89:80:8c:6b:87:75:8d:d1:d1:56:bb:5e:1b:ed:97:5a:b4:ac:1e:16:06:07:e5
:cb:6c:a8:c5:66:16:e2:92:6a:db:a6:b0:09:d2:7d:dc:fa:68:f3:2c:c5:c2:a6:f2:bd:73:f
5:3b:94:17:16:2f:d0:8a:e6:d0:f3:53:c4:71:c6:31:61:9b:87:9d:5e:63:d8:c3:e3:81:f8:
6f:f6:35:7b:85:9c:81:c9:94:91:d3:5d:bc:89:3b:dc:7d:c2:ae:d2:ca:b4:61:6f:04:5b:4f
:8f:c6:ac:e0:49:de:5f:bf:84:f5:38:b1:07:48:1f:17:fb:72:15:ac:31:7a:13:f8:58:aa:6
7:b0:67:b1:2b:99:c0:7a:a7:f6:42:d3:f9:f5:33:90:2a:9c:7c:78:73:7f:74:54:91:04:c2:
51:ff:59:11:cd:7d:d7:61:e9:5c:c6:c7:d1:a1:f2:f5:7b:41:94:c4:cd:8d:5e:06:f1:6a:f4
:72:24:37:be:27:02:03:01:00:01",
            "sha-1-digest" => "70:46:1d:af:1a:5e:30:34:e9:dd:53:f4:38:b6:3e:89:5
a:1c:f9:90",
            "sha-256-digest" => "61:43:2e:6f:c0:ed:91:b1:3a:20:51:fc:2b:e9:33:d2
:66:d2:a6:f2:32:5c:ca:91:14:67:7b:f2:4e:55:ed:77",
            "encoded" => "30:82:02:cb:30:82:01:b5:a0:03:02:01:02:02:08:0c:e1:64:
83:a0:95:28:72:30:0b:06:09:2a:86:48:86:f7:0d:01:01:0b:30:14:31:12:30:10:06:03:55
:04:03:13:09:6c:6f:63:61:6c:68:6f:73:74:30:22:18:0f:32:30:32:32:31:32:32:38:31:3
0:32:38:30:34:5a:18:0f:32:30:32:33:31:32:32:38:31:30:32:38:30:34:5a:30:14:31:12:
30:10:06:03:55:04:03:13:09:6c:6f:63:61:6c:68:6f:73:74:30:82:01:22:30:0d:06:09:2a
:86:48:86:f7:0d:01:01:01:05:00:03:82:01:0f:00:30:82:01:0a:02:82:01:01:00:9e:b2:6
f:4e:92:e9:f3:69:eb:9c:0e:63:1e:d6:d4:4b:2b:5d:46:e2:68:fd:96:83:a1:3b:83:a2:70:
a8:6e:e1:2e:8d:e9:8d:ed:9d:d3:f3:56:26:00:29:32:a0:95:61:54:6f:35:95:41:95:76:73
:34:46:db:4b:75:3c:f1:64:91:4a:a6:61:89:80:8c:6b:87:75:8d:d1:d1:56:bb:5e:1b:ed:9
7:5a:b4:ac:1e:16:06:07:e5:cb:6c:a8:c5:66:16:e2:92:6a:db:a6:b0:09:d2:7d:dc:fa:68:
f3:2c:c5:c2:a6:f2:bd:73:f5:3b:94:17:16:2f:d0:8a:e6:d0:f3:53:c4:71:c6:31:61:9b:87
:9d:5e:63:d8:c3:e3:81:f8:6f:f6:35:7b:85:9c:81:c9:94:91:d3:5d:bc:89:3b:dc:7d:c2:a
e:d2:ca:b4:61:6f:04:5b:4f:8f:c6:ac:e0:49:de:5f:bf:84:f5:38:b1:07:48:1f:17:fb:72:
15:ac:31:7a:13:f8:58:aa:67:b0:67:b1:2b:99:c0:7a:a7:f6:42:d3:f9:f5:33:90:2a:9c:7c
:78:73:7f:74:54:91:04:c2:51:ff:59:11:cd:7d:d7:61:e9:5c:c6:c7:d1:a1:f2:f5:7b:41:9
4:c4:cd:8d:5e:06:f1:6a:f4:72:24:37:be:27:02:03:01:00:01:a3:21:30:1f:30:1d:06:03:
55:1d:0e:04:16:04:14:ac:99:db:c3:82:18:60:51:92:7d:75:51:ba:b4:a9:65:90:0e:7d:13
:30:0b:06:09:2a:86:48:86:f7:0d:01:01:0b:03:82:01:01:00:97:83:30:22:ed:2e:4e:55:3
3:73:4c:e2:56:d2:37:0a:47:39:7c:01:f9:97:6c:f6:98:f7:33:5b:b2:57:ba:d9:ff:7d:4c:
f1:92:42:64:a5:ee:56:ca:f1:da:ae:64:51:a0:90:5a:d7:a0:eb:e8:7b:e0:ef:6d:a1:8f:d0
:f6:80:c8:05:1b:29:c0:66:14:4d:bb:d9:87:e1:8e:d5:95:3c:c8:0f:fe:49:74:bd:28:a5:4
6:50:9c:e3:d5:6b:0b:48:b4:7f:1b:ad:95:8b:52:ee:5a:e1:03:43:47:6e:6e:ac:c2:5a:da:
63:3c:a4:8a:c5:f8:d1:6e:ae:eb:09:97:8b:0c:cd:37:92:77:0c:05:6b:a1:99:8f:7b:b8:6a
:30:cc:5b:c0:fc:70:f2:2b:50:73:22:d2:aa:80:b6:b0:c2:67:37:25:91:10:80:3a:ed:8c:6
b:04:02:b5:49:0c:7f:a1:a0:ef:3a:66:07:a5:9d:ab:09:be:21:8e:73:f3:91:19:3e:59:75:
cf:0f:85:3a:c8:18:89:22:68:04:95:81:9a:7d:67:19:28:11:bc:6b:e8:cb:c7:7b:9c:b4:64
:59:63:50:88:f4:05:9e:e6:fa:01:17:2b:eb:75:da:8b:34:f2:1f:47:2d:c5:79:8f:76:59:6
9:d9:89:0b:51:99:00:35",
            "subject" => "CN=localhost",
            "issuer" => "CN=localhost",
            "not-before" => "2022-12-28T15:58:04.000+0530",
            "not-after" => "2023-12-28T15:58:04.000+0530",
            "serial-number" => "0c:e1:64:83:a0:95:28:72",
            "signature-algorithm" => "SHA256withRSA",
            "signature" => "97:83:30:22:ed:2e:4e:55:33:73:4c:e2:56:d2:37:0a:47:3
9:7c:01:f9:97:6c:f6:98:f7:33:5b:b2:57:ba:d9:ff:7d:4c:f1:92:42:64:a5:ee:56:ca:f1:
da:ae:64:51:a0:90:5a:d7:a0:eb:e8:7b:e0:ef:6d:a1:8f:d0:f6:80:c8:05:1b:29:c0:66:14
:4d:bb:d9:87:e1:8e:d5:95:3c:c8:0f:fe:49:74:bd:28:a5:46:50:9c:e3:d5:6b:0b:48:b4:7
f:1b:ad:95:8b:52:ee:5a:e1:03:43:47:6e:6e:ac:c2:5a:da:63:3c:a4:8a:c5:f8:d1:6e:ae:
eb:09:97:8b:0c:cd:37:92:77:0c:05:6b:a1:99:8f:7b:b8:6a:30:cc:5b:c0:fc:70:f2:2b:50
:73:22:d2:aa:80:b6:b0:c2:67:37:25:91:10:80:3a:ed:8c:6b:04:02:b5:49:0c:7f:a1:a0:e
f:3a:66:07:a5:9d:ab:09:be:21:8e:73:f3:91:19:3e:59:75:cf:0f:85:3a:c8:18:89:22:68:
04:95:81:9a:7d:67:19:28:11:bc:6b:e8:cb:c7:7b:9c:b4:64:59:63:50:88:f4:05:9e:e6:fa
:01:17:2b:eb:75:da:8b:34:f2:1f:47:2d:c5:79:8f:76:59:69:d9:89:0b:51:99:00:35",
            "version" => "v3"
        }]
    }
}
Read an alias in a key store (non-verbose).
/subsystem=elytron/key-store=httpsKS:read-alias(alias="localhost",verbose=false)
{
    "outcome" => "success",
    "result" => {
        "alias" => "localhost",
        "entry-type" => "PrivateKeyEntry",
        "creation-date" => "2022-12-28T15:58:04.818+0530",
        "certificate-chain" => [{
            "type" => "X.509",
            "algorithm" => "RSA",
            "format" => "X.509",
            "sha-1-digest" => "70:46:1d:af:1a:5e:30:34:e9:dd:53:f4:38:b6:3e:89:5
a:1c:f9:90",
            "sha-256-digest" => "61:43:2e:6f:c0:ed:91:b1:3a:20:51:fc:2b:e9:33:d2
:66:d2:a6:f2:32:5c:ca:91:14:67:7b:f2:4e:55:ed:77",
            "subject" => "CN=localhost",
            "issuer" => "CN=localhost",
            "not-before" => "2022-12-28T15:58:04.000+0530",
            "not-after" => "2023-12-28T15:58:04.000+0530",
            "serial-number" => "0c:e1:64:83:a0:95:28:72",
            "signature-algorithm" => "SHA256withRSA",
            "signature" => "97:83:30:22:ed:2e:4e:55:33:73:4c:e2:56:d2:37:0a:47:3
9:7c:01:f9:97:6c:f6:98:f7:33:5b:b2:57:ba:d9:ff:7d:4c:f1:92:42:64:a5:ee:56:ca:f1:
da:ae:64:51:a0:90:5a:d7:a0:eb:e8:7b:e0:ef:6d:a1:8f:d0:f6:80:c8:05:1b:29:c0:66:14
:4d:bb:d9:87:e1:8e:d5:95:3c:c8:0f:fe:49:74:bd:28:a5:46:50:9c:e3:d5:6b:0b:48:b4:7
f:1b:ad:95:8b:52:ee:5a:e1:03:43:47:6e:6e:ac:c2:5a:da:63:3c:a4:8a:c5:f8:d1:6e:ae:
eb:09:97:8b:0c:cd:37:92:77:0c:05:6b:a1:99:8f:7b:b8:6a:30:cc:5b:c0:fc:70:f2:2b:50
:73:22:d2:aa:80:b6:b0:c2:67:37:25:91:10:80:3a:ed:8c:6b:04:02:b5:49:0c:7f:a1:a0:e
f:3a:66:07:a5:9d:ab:09:be:21:8e:73:f3:91:19:3e:59:75:cf:0f:85:3a:c8:18:89:22:68:
04:95:81:9a:7d:67:19:28:11:bc:6b:e8:cb:c7:7b:9c:b4:64:59:63:50:88:f4:05:9e:e6:fa
:01:17:2b:eb:75:da:8b:34:f2:1f:47:2d:c5:79:8f:76:59:69:d9:89:0b:51:99:00:35",
            "version" => "v3"
        }]
    }
}
Configure a key-manager that references your key-store:
/subsystem=elytron/key-manager=httpsKM:add(key-store=httpsKS,credential-reference={clear-text=secret})
Configure a server-ssl-context that references your key-manager:
/subsystem=elytron/server-ssl-context=httpsSSC:add(key-manager=httpsKM,protocols=["TLSv1.2"])

IMPORTANT: You need to determine what SSL/TLS protocols you want to support. The example commands above uses TLSv1.2.

Check and see if the https-listener is configured to use a legacy security realm for its SSL configuration:
/subsystem=undertow/server=default-server/https-listener=https:read-attribute(name=security-realm)
{
    "outcome" => "success",
    "result" => "ApplicationRealm"
}

The above command shows that the https-listener is configured to use the ApplicationRealm legacy security realm for its SSL configuration. Undertow cannot reference both a legacy security realm and an ssl-context in Elytron at the same time so you must remove the reference to the legacy security realm. Also there has to be always configured either ssl-context or security-realm. Thus when changing between those, you have to use batch operation:

Remove the reference to the legacy security realm and update the https-listener to use the ssl-context from Elytron :

/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=ssl-context,value=httpsSSC)
Reload the server:
reload

HTTPS is now enabled for applications.

4.3.2. Enable Two-way SSL/TLS in WildFly for Applications

First, obtain or generate your client keystore.

$ keytool -genkeypair -alias client -keyalg RSA -keysize 2048 -validity 365 -keystore client.keystore.pkcs12 -dname "CN=client" -keypass secret -storepass secret

Export the client certificate:

$ keytool -exportcert  -keystore client.keystore.pkcs12 -alias client -keypass secret -storepass secret -file /path/to/client.cer

There are a couple ways to enable two-way SSL/TLS for deployed applications.

Using a security command:

The security enable-ssl-http-server command can be used to enable two-way SSL/TLS for the deployed applications. Example of wizard usage:

In the following example, we enter n when propmted with Validate certificate because the example uses a self-signed certificate. If you use Cretificate Authority (CA) signed certificates, enter y when prompted.

security enable-ssl-http-server --interactive --override-ssl-context
Please provide required pieces of information to enable SSL:
Key-store file name (default default-server.keystore): server.keystore.pkcs12
Password (blank generated): secret
What is your first and last name? [Unknown]: localhost
What is the name of your organizational unit? [Unknown]:
What is the name of your organization? [Unknown]:
What is the name of your City or Locality? [Unknown]:
What is the name of your State or Province? [Unknown]:
What is the two-letter country code for this unit? [Unknown]:
Is CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct y/n [y]?
Validity (in days, blank default): 365
Alias (blank generated): localhost
Enable SSL Mutual Authentication y/n (blank n): y
Client certificate (path to pem file): /path/to/client.cer
Validate certificate y/n (blank y): n
Trust-store file name (management.truststore): server.truststore.pkcs12
Password (blank generated): secret

SSL options:
key store file: server.keystore.pkcs12
distinguished name: CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
password: secret
validity: 365
alias: localhost
client certificate: /path/to/client.cer
trust store file: server.trustore.pkcs12
trust store password: secret
Server keystore file server.keystore.pkcs12, certificate file server.pem and server.csr file will be generated in server configuration directory.
Server truststore file server.trustore.pkcs12 will be generated in server configuration directory.
Do you confirm y/n: y

NB: Once the command is executed, the CLI will reload the server. To complete the two-way SSL/TLS authentication, you need to import the server certificate into the client truststore and configure your client to present the client certificate.

Using Elytron subsystem commands:

You can also use the Elytron subsystem, along with the Undertow subsystem, to enable two-way SSL/TLS for deployed applications.

Obtain or generate your key stores:

Before enabling HTTPS in WildFly, you must obtain or generate the server key store and trust store you plan on using. To generate an example key store and trust store, use the following commands.

Create a server key-store:

/subsystem=elytron/key-store=twoWayKS:add(path=/path/to/server.keystore.pkcs12,credential-reference={clear-text=secret},type=PKCS12)
/subsystem=elytron/key-store=twoWayKS:generate-key-pair(alias=localhost,algorithm=RSA,key-size=2048,validity=365,credential-reference={clear-text=secret},distinguished-name="CN=localhost")
/subsystem=elytron/key-store=twoWayKS:store()

NOTE
The first command above uses an absolute path to the keystore. Alternatively you can use the relative-to attribute to specify the base directory variable and path specify a relative path.

/subsystem=elytron/key-store=twoWayKS:add(path=server.keystore.pkcs12,relative-to=jboss.server.config.dir,credential-reference={clear-text=secret},type=PKCS12)

Export the server certificate:

/subsystem=elytron/key-store=twoWayKS:export-certificate(alias=localhost,path=/path/to/server.cer,pem=true)

Create a key-store for the server truststore and import the client certificate into the server truststore:

In the following example, we enter validate=false in the import-certificate command because the example uses a self-signed certificate. If you use Certificate Authority (CA) signed certificates, omit validate=false.

/subsystem=elytron/key-store=twoWayTS:add(path=/path/to/server.truststore.pkcs12,credential-reference={clear-text=secret},type=PKCS12)
/subsystem=elytron/key-store=twoWayTS:import-certificate(alias=client,path=/path/to/client.cer,credential-reference={clear-text=secret},trust-cacerts=true,validate=false)
/subsystem=elytron/key-store=twoWayTS:store()
Configure a key-manager that references your key store key-store:
/subsystem=elytron/key-manager=twoWayKM:add(key-store=twoWayKS,credential-reference={clear-text=secret})
Configure a trust-manager that references your truststore key-store:
/subsystem=elytron/trust-manager=twoWayTM:add(key-store=twoWayTS)
Configure a server-ssl-context that references your key-manager, trust-manager, and enables client authentication:
/subsystem=elytron/server-ssl-context=twoWaySSC:add(key-manager=twoWayKM,protocols=["TLSv1.2"],trust-manager=twoWayTM,need-client-auth=true)

IMPORTANT
You need to determine what SSL/TLS protocols you want to support. The example commands above uses TLSv1.2.

Check and see if the https-listener is configured to use a legacy security realm for its SSL configuration:
/subsystem=undertow/server=default-server/https-listener=https:read-attribute(name=security-realm)
{
    "outcome" => "success",
    "result" => "ApplicationRealm"
}

The above command shows that the https-listener is configured to use the ApplicationRealm legacy security realm for its SSL configuration. Undertow cannot reference both a legacy security realm and an ssl-context in Elytron at the same time so you must remove the reference to the legacy security realm. Also there has to be always configured either ssl-context or security-realm. Thus when changing between those, you have to use batch operation:

Remove the reference to the legacy security realm and update the https-listener to use the ssl-context from Elytron:
batch
/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=twoWaySSC)
run-batch
Reload the server
reload

To complete the two-way SSL/TLS authentication, you need to import the server certificate into the client truststore and configure your client to present the client certificate.

Import the server certificate into the client truststore
$ keytool -importcert -keystore client.truststore.pkcs12 -storepass secret -alias localhost -trustcacerts -file /path/to/server.cer
Configure your client to use the client certificate

You need to configure your client to present the trusted client certificate to the server to complete the two-way SSL/TLS authentication. For example, if using a browser, you need to import the trusted certificate into the browser’s truststore.

Two-Way HTTPS is now enabled for applications.

4.3.3. Configure certificate revocation in trust-manager

Configure Certificate Revocation List:

You can configure your trust-manager to use certificate-revocation-list (CRL) to check revocation status of obtained certificates.

The supported attributes for a certificate-revocation-list include:

  • path: The path to the configuration file that is used to initialize the certificate revocation list.

  • relative-to: The base path of the certificate revocation list file.

  • maximum-cert-path: The maximum number of non-self-issued intermediate certificates that can exist in a certification path. The default value is 5. (Deprecated. Use maximum-cert-path in trust-manager).

/subsystem=elytron/trust-manager=twoWayTM:write-attribute(name=certificate-revocation-list, value={})

This will use CRLs obtained from distribution points referenced in your certificates.

NOTE: To use a CRL your trust store must contain the certificate chain in order to check validity of both CRL list.

Override CRL location obtained from certificates:
/subsystem=elytron/trust-manager=twoWayTM:write-attribute(name=certificate-revocation-list.path, value=intermediate.crl.pem)
Configure multiple certificate revocation lists

Alternatively, you can configure multiple certificate revocation lists in your trust-manager using the certificate-revocation-lists attribute as follows:

/subsystem=elytron/trust-manager=twoWayTM:write-attribute(name=certificate-revocation-lists, value=[{path="PATH/TO/CRL"}, {path="PATH/TO/OTHER/CRL"}])

The supported attributes for certificate-revocation-lists include a list of certificate-revocation-list objects containing:

  • path: The path to the configuration file that is used to initialize the certificate revocation list.

  • relative-to: The base path of the certificate revocation list file.

Configure OCSP certificate revocation:
/subsystem=elytron/trust-manager=twoWayTM:write-attribute(name=ocsp, value={})

This will enable OCSP certificate revocation by using OCSP responder inside the certificate. In case the responder is known but OCSP revocation status is unknown, the verification will fail.

Override OCSP responder URI extracted from certificate:
/subsystem=elytron/trust-manager=twoWayTM:write-attribute(name=ocsp.responder, value="http://example.com/ocsp-responder")
Configure order of revocation mechanisms:

If both CRL and OCSP are defined, Elytron will use OCSP for obtaining revocation status as first by default. In case you want to prefer CRL:

/subsystem=elytron/trust-manager=twoWayTM:write-attribute(name=ocsp.prefer-crls, value="true")
Other trust-manager configuration:
Configure trust-manager to only check leaf certificates for revocation status
/subsystem=elytron/trust-manager=twoWayTM:write-attribute(name=only-leaf-cert, value="true")
Configure trust-manager to accept certificates with unknown revocation status

In case you want to accept certificates with unknown revocation status, you can enable soft-fail behaviour in your trust-manager.

/subsystem=elytron/trust-manager=twoWayTM:write-attribute(name=soft-fail, value=true)
Set maximum number of intermediate certificates of trust-manager

Sets the value of the maximum number of non-self-issued intermediate certificates that may exist in a certification path with default value of 5.

/subsystem=elytron/trust-manager=twoWayTM:write-attribute(name=maximum-cert-path, value=10)

4.3.4. Enable One-way SSL/TLS for the Management Interfaces

There are a couple ways to enable one-way SSL/TLS for the management interfaces.

Using a security command:

The security enable-ssl-management command can be used to enable one-way SSL/TLS for the management interfaces. Example of wizard usage:

security enable-ssl-management --interactive
Please provide required pieces of information to enable SSL:
Key-store file name (default management.keystore): keystore.pkcs12
Password (blank generated): secret
What is your first and last name? [Unknown]: localhost
What is the name of your organizational unit? [Unknown]:
What is the name of your organization? [Unknown]:
What is the name of your City or Locality? [Unknown]:
What is the name of your State or Province? [Unknown]:
What is the two-letter country code for this unit? [Unknown]:
Is CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct y/n [y]?
Validity (in days, blank default): 365
Alias (blank generated): localhost
Enable SSL Mutual Authentication y/n (blank n): n

SSL options:
key store file: keystore.pkcs12
distinguished name: CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
password: secret
validity: 365
alias: localhost
Server keystore file keystore.pkcs12, certificate file keystore.pem and keystore.csr file
will be generated in server configuration directory.
Do you confirm y/n :y

NB: Once the command is executed, the CLI will reload the server and reconnect to it.

HTTPS is now enabled for the management interfaces.

Using Elytron subsystem commands:

Elytron subsystem commands can also be used to enable one-way SSL/TLS for the management interfaces.

Configure a key-store:
/subsystem=elytron/key-store=httpsKS:add(path=keystore.pkcs12,relative-to=jboss.server.config.dir,credential-reference={clear-text=secret},type=PKCS12)

NOTE: The above command uses relative-to to reference the location of the keystore file. Alternatively, you can specify the full path to the keystore in path and omit relative-to.

If the keystore file does not exist yet, the following commands can be used to generate an example key pair:

/subsystem=elytron/key-store=httpsKS:generate-key-pair(alias=localhost,algorithm=RSA,key-size=2048,validity=365,credential-reference={clear-text=secret},distinguished-name="CN=localhost")
/subsystem=elytron/key-store=httpsKS:store()
Create a key-manager and server-ssl-context.
/subsystem=elytron/key-manager=httpsKM:add(key-store=httpsKS,credential-reference={clear-text=secret})
 
/subsystem=elytron/server-ssl-context=httpsSSC:add(key-manager=httpsKM,protocols=["TLSv1.2"])

IMPORTANT: You need to determine what SSL/TLS protocols you want to support. The example commands above uses TLSv1.2.

Enable HTTPS on the management interface.
/core-service=management/management-interface=http-interface:write-attribute(name=ssl-context, value=httpsSSC)
 
/core-service=management/management-interface=http-interface:write-attribute(name=secure-socket-binding, value=management-https)
Reload the WildFly instance.
reload

HTTPS is now enabled for the management interfaces.

4.3.5. Enable Two-way SSL/TLS for the Management Interfaces

First, obtain or generate your client keystore.

$ keytool -genkeypair -alias client -keyalg RSA -keysize 2048 -validity 365 -keystore client.keystore.pkcs12 -dname "CN=client" -keypass secret -storepass secret

Export your client certificate.

$ keytool -exportcert  -keystore client.keystore.pkcs12 -alias client -keypass secret -storepass secret -file /path/to/client.cer

There are a couple ways to enable two-way SSL/TLS for the management interfaces.

Using a security command:

The security enable-ssl-management command can be used to enable two-way SSL/TLS for the management interfaces. Example of wizard usage:

In the following example, we enter n when propmted with Validate certificate because the example uses a self-signed certificate. If you use Certificate Authority (CA) signed certificates, enter y when prompted.

security enable-ssl-management --interactive
Please provide required pieces of information to enable SSL:
Key-store file name (default management.keystore): server.keystore.pkcs12
Password (blank generated): secret
What is your first and last name? [Unknown]: localhost
What is the name of your organizational unit? [Unknown]:
What is the name of your organization? [Unknown]:
What is the name of your City or Locality? [Unknown]:
What is the name of your State or Province? [Unknown]:
What is the two-letter country code for this unit? [Unknown]:
Is CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct y/n [y]?
Validity (in days, blank default): 365
Alias (blank generated): localhost
Enable SSL Mutual Authentication y/n (blank n): y
Client certificate (path to pem file): /path/to/client.cer
Validate certificate y/n (blank y): n
Trust-store file name (management.truststore): server.truststore.pkcs12
Password (blank generated): secret

SSL options:
key store file: server.keystore.pkcs12
distinguished name: CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
password: secret
validity: 365
alias: localhost
client certificate: /path/to/client.cer
trust store file: server.trustore.pkcs12
trust store password: secret
Server keystore file server.keystore.pkcs12, certificate file server.pem and server.csr file will be generated in server configuration directory.
Server truststore file server.trustore.pkcs12 will be generated in server configuration directory.
Do you confirm y/n: y

NB: Once the command is executed, the CLI will reload the server and attempt to reconnect to it. To complete the two-way SSL/TLS authentication, you need to import the server certificate into the client truststore and configure your client to present the client certificate.

Using Elytron subsystem commands:

Elytron subsystem commands can also be used to enable two-way SSL/TLS for the management interfaces.

Obtain or generate your key stores.

Before enabling HTTPS in WildFly, you must obtain or generate the server key store and trust store you plan on using. To generate an example key store and trust store, use the following commands.

Configure a key-store.

/subsystem=elytron/key-store=twoWayKS:add(path=server.keystore.pkcs12,relative-to=jboss.server.config.dir,credential-reference={clear-text=secret},type=PKCS12)

/subsystem=elytron/key-store=twoWayKS:generate-key-pair(alias=localhost,algorithm=RSA,key-size=2048,validity=365,credential-reference={clear-text=secret},distinguished-name="CN=localhost")

/subsystem=elytron/key-store=twoWayKS:store()

NOTE: The above command uses relative-to to reference the location of the keystore file. Alternatively, you can specify the full path to the keystore in path and omit relative-to.

Export your server certificate.

/subsystem=elytron/key-store=twoWayKS:export-certificate(alias=localhost,path=/path/to/server.cer,pem=true)

Create a key-store for the server trust store and import the client certificate into the server trust store.

In the following example, we enter validate=false in the import-certificate command because the example uses a self-signed certificate. If you use Certificate Authority (CA) signed certificates, omit validate=false.

/subsystem=elytron/key-store=twoWayTS:add(path=server.truststore.pkcs12,relative-to=jboss.server.config.dir,credential-reference={clear-text=secret},type=PKCS12)

/subsystem=elytron/key-store=twoWayTS:import-certificate(alias=client,path=/path/to/client.cer,credential-reference={clear-text=secret},trust-cacerts=true,validate=false)

/subsystem=elytron/key-store=twoWayTS:store()
Configure a key-manager, trust-manager, and server-ssl-context for the server key store and trust store.
/subsystem=elytron/key-manager=twoWayKM:add(key-store=twoWayKS,credential-reference={clear-text=secret})
 
/subsystem=elytron/trust-manager=twoWayTM:add(key-store=twoWayTS)
 
/subsystem=elytron/server-ssl-context=twoWaySSC:add(key-manager=twoWayKM,protocols=["TLSv1.2"],trust-manager=twoWayTM,want-client-auth=true,need-client-auth=true)

IMPORTANT: You need to determine what SSL/TLS protocols you want to support. The example commands above uses TLSv1.2.

Enable HTTPS on the management interface.
/core-service=management/management-interface=http-interface:write-attribute(name=ssl-context, value=twoWaySSC)
 
/core-service=management/management-interface=http-interface:write-attribute(name=secure-socket-binding, value=management-https)
Reload the WildFly instance.
reload

To complete the two-way SSL/TLS authentication, you need to import the server certificate into the client truststore and configure your client to present the client certificate.

Import the server certificate into the client truststore.
$ keytool -importcert -keystore client.truststore.pkcs12 -storepass secret -alias localhost -trustcacerts -file /path/to/server.cer
Configure your client to use the client certificate.

You need to configure your client to present the trusted client certificate to the server to complete the two-way SSL/TLS authentication. For example, if using a browser, you need to import the trusted certificate into the browser’s trust store.

Two-way SSL/TLS is now enabled for the management interfaces.

4.3.6. KeyStore manipulation operations

It is possible to perform various KeyStore manipulation operations on an Elytron key-store resource using the management CLI.

Generate a key pair

The generate-key-pair command generates a key pair and wraps the resulting public key in a self-signed X.509 certificate. The generated private key and self-signed certificate will be added to the KeyStore.

/subsystem=elytron/key-store=httpsKS:generate-key-pair(alias=example,algorithm=RSA,key-size=2048,validity=365,credential-reference={clear-text=secret},distinguished-name="CN=www.example.com")
Generate a certificate signing request

The generate-certificate-signing-request command generates a PKCS #10 certificate signing request using a PrivateKeyEntry from the KeyStore. The generated certificate signing request will be output to a file.

/subsystem=elytron/key-store=httpsKS:generate-certificate-signing-request(alias=example,path=server.csr,relative-to=jboss.server.config.dir,distinguished-name="CN=www.example.com",extensions=[{critical=false,name=KeyUsage,value=digitalSignature}],credential-reference={clear-text=secret})
Import a certificate or certificate chain

The import-certificate command imports a certificate or certificate chain from a file into an entry in the KeyStore.

/subsystem=elytron/key-store=httpsKS:import-certificate(alias=example,path=/path/to/certificate_or_chain/file,relative-to=jboss.server.config.dir,credential-reference={clear-text=secret},trust-cacerts=true)
Export a certificate

The export-certificate command exports a certificate from an entry in the KeyStore to a file.

/subsystem=elytron/key-store=httpsKS:export-certificate(alias=example,path=serverCert.cer,relative-to=jboss.server.config.dir,pem=true)
Change an alias

The change-alias command moves an existing KeyStore entry to a new alias.

/subsystem=elytron/key-store=httpsKS:change-alias(alias=example,new-alias=newExample,credential-reference={clear-text=secret})
Store changes made to key-stores

The store command persists any changes that have been made to the file that backs the KeyStore.

/subsystem=elytron/key-store=httpsKS:store()
Obtain a signed certificate from Let’s Encrypt

Before obtaining a signed certificate from Let’s Encrypt, you must configure a Let’s Encrypt account using the following commands.

Create a key-store to hold your Let’s Encrypt account key.
/subsystem=elytron/key-store=accountsKS:add(path=accounts.keystore.pkcs12,relative-to=jboss.server.config.dir,credential-reference={clear-text=secret},type=PKCS12)
Configure a Let’s Encrypt account
/subsystem=elytron/certificate-authority-account=myLEAccount:add(alias=example,key-store=accountsKS,contact-urls=[mailto:admin@example.org])

Note: Let’s Encrypt is the default certificate authority and therefore the certificate-authority attribute can be omitted when creating a certificate-authority-account. It is also possible to configure an account with different certificate authority than Let’s Encrypt by adding custom certificate-authority resource and passing it to certificate-authority-account.

/subsystem=elytron/certificate-authority=myCA:add(url="https://my.example.url/acme/directory", staging-url="https://my.example.staging.url/acme/directory")
/subsystem=elytron/certificate-authority-account=myCAAccount:add(certificate-authority=myCA,alias=example,key-store=accountsKS,contact-urls=[mailto:admin@example.org])
Obtain a signed certificate from Let’s Encrypt

The obtain-certificate command creates an account with Let’s Encrypt, if such an account does not already exist, obtains a signed certificate from Let’s Encrypt, and stores it in the KeyStore.

/subsystem=elytron/key-store=httpsKS:obtain-certificate(alias=server,domain-names=[www.example.org],certificate-authority-account=myLEAccount,agree-to-terms-of-service=true,algorithm=RSA,key-size=2048,credential-reference={clear-text=secret})
Revoke a signed certificate

The revoke-certificate command revokes a certificate that was issued by Let’s Encrypt.

/subsystem=elytron/key-store=httpsKS:revoke-certificate(alias=server,reason=keyCompromise,certificate-authority-account=myLEAccount)
Check if a certificate is due for renewal

The should-renew-certificate command checks if a certificate is due for renewal. In particular, it will return true if the certificate expires in less than the given number of days and false otherwise.

/subsystem=elytron/key-store=httpsKS:should-renew-certificate(alias=server,expiration=7)

4.3.7. Certificate authority account operations

It is possible to perform various operations on an Elytron certificate-authority-account resource using the management CLI.

Create an account with the certificate authority

The create-account command creates an account with the certificate authority if one does not already exist.

/subsystem=elytron/certificate-authority-account=myLEAccount:create-account(agree-to-terms-of-service=true)
Update an account with the certificate authority

The update-account command updates an account with the certificate authority.

/subsystem=elytron/certificate-authority-account=myLEAccount:update-account(agree-to-terms-of-service=true)
Change the account key

The change-account-key command changes the key associated with the certificate authority account.

/subsystem=elytron/certificate-authority-account=myLEAccount:change-account-key()
Deactivate the account

The deactivate-account command deactivates the certificate authority account.

/subsystem=elytron/certificate-authority-account=myLEAccount:deactivate-account()
Get metadata

The get-metadata command retrieves the metadata (e.g., terms of service URL, website URL, CAA identities, and whether or not an external account is required), if any, associated with the certificate authority.

/subsystem=elytron/certificate-authority-account=myLEAccount:get-metadata()

4.3.8. Using an ldap-key-store

An ldap-key-store allows you to use a keystore stored in an LDAP server. You can use an ldap-key-store in same way you can use a key-store.

To create and use an ldap-key-store:

Configure a dir-context.

To connect to the LDAP server from WildFly, you need to configure a dir-context that provides the URL as well as the principal used to connect to the server.

Example dir-context

/subsystem=elytron/dir-context=exampleDC:add( \
  url="ldap://127.0.0.1:10389", \
  principal="uid=admin,ou=system", \
  credential-reference={clear-text=secret} \
)
Configure an ldap-key-store.

When configure an ldap-key-store, you need to specify both the dir-context used to connect to the LDAP server as well as how to locate the keystore stored in the LDAP server. At a minimum, this requires you specify a search-path.

Example ldap-key-store

/subsystem=elytron/ldap-key-store=ldapKS:add( \
  dir-context=exampleDC, \
  search-path="ou=Keystores,dc=wildfly,dc=org" \
)
Use the ldap-key-store.

Once you have defined your ldap-key-store, you can use it in the same places where a key-store could be used. For example, you could use an ldap-key-store when configuring HTTPS and Two-Way HTTPS for applications.

4.3.9. Using a filtering-key-store

A filtering-key-store allows you to expose a subset of aliases from an existing key-store, and use it in the same places you could use a key-store. For example, if a keystore contained alias1, alias2, and alias3, but you only wanted to expose alias1 and alias3, a filtering-key-store provides you several ways to do that.

To create a filtering-key-store:

Configure a key-store.
/subsystem=elytron/key-store=myKS:add( \
  path=keystore.pkcs12, \
  relative-to=jboss.server.config.dir, \
  credential-reference={ \
    clear-text=secret \
  }, \
  type=PKCS12 \
)
Configure a filtering-key-store.

When you configure a filtering-key-store, you specify which key-store you want to filter and the alias-filter for filtering aliases from the key-store. The filter can be specified in one of the following formats:

  • alias1,alias3, which is a comma-delimited list of aliases to expose.

  • ALL:-alias2, which exposes all aliases in the keystore except the ones listed.

  • NONE:+alias1:+alias3, which exposes no aliases in the keystore except the ones listed.

This example uses a comma-delimted list to expose alias1 and alias3.

/subsystem=elytron/filtering-key-store=filterKS:add( \
  key-store=myKS, \
  alias-filter="alias1,alias3" \
)
Use the filtering-key-store.

Once you have defined your filtering-key-store, you can use it in the same places where a key-store could be used. For example, you could use a filtering-key-store when configuring HTTPS and Two-Way HTTPS for applications.

4.3.10. Reload a Keystore

You can reload a keystore configured in WildFly from the management CLI. This is useful in cases where you have made changes to certificates referenced by a keystore.

To reload a keystore.

/subsystem=elytron/key-store=httpsKS:load

4.3.11. Reinitialize a Key Manager

You can reinitialize a key-manager configured in WildFly from the management CLI. This is useful in cases where you have made changes in certificates provided by keystore resource and you want to apply this change to new SSL connections without restarting the server.

If the key-store is file based then it must be loaded first.

/subsystem=elytron/key-store=httpsKS:load()

To reinitialize a key-manager.

/subsystem=elytron/key-manager=httpsKM:init()

4.3.12. Reinitialize a Trust Manager

You can reinitialize a trust-manager configured in WildFly from the management CLI. This is useful in cases where you have made changes to certificates provided by keystore resource and you want to apply this change to new SSL connections without restarting the server.

If the key-store is file based then it must be loaded first.

/subsystem=elytron/key-store=httpsKS:load()

To reinitialize a trust-manager.

/subsystem=elytron/trust-manager=httpsTM:init()

4.3.13. Check the Content of a Keystore by Alias

If you add a keystore to the elytron subsystem using the key-store component, you can check the keystore’s contents using the alias child element and reading its attributes.

For example:

/subsystem=elytron/key-store=httpsKS/alias=localhost:read-attribute(name=certificate-chain)
{
    "outcome" => "success",
    "result" => [{
        "type" => "X.509",
        "algorithm" => "RSA",
        "format" => "X.509",
        "public-key" => "30:81:9f:30:0d:06:09:2a:8......

The following attributes can be read:

Attribute Description

certificate

The certificate associated with the alias. If the alias has a certificate chain this will always be undefined.

certificate-chain

The certificate chain associated with the alias.

creation-date

The creation date of the entry represented by this alias.

entry-type

The type of the entry for this alias. Available types: PasswordEntry, PrivateKeyEntry, SecretKeyEntry, TrustedCertificateEntry, and Other. Unrecognized types will be reported as Other.

4.3.14. Custom Components

When configuring SSL/TLS in the elytron subsystem, you can provide and use custom implementations of the following components:

  • key-store

  • key-manager

  • trust-manager

  • client-ssl-context

  • server-ssl-context

  • certificate-authority-account

When creating custom implementations of Elytron components, they must present the appropriate capabilities and requirements.

4.3.15. Configuring a server SSLContext

Using the Elytron subsystem, it is possible to configure an SSLContext for use on the server side of a connection.

Adding a server SSLContext takes the general form:

/subsystem=elytron/server-ssl-context=test-server-ssl-context:add(...)

The following attributes can be specified when creating a server-ssl-context:

security-domain

(Optional) A reference to the security-domain to use for authentication during SSL session establishment.

key-manager

(Optional) A reference to the KeyManager to be used by this SSLContext.

trust-manager

(Optional) A reference to the TrustManager to be used by this SSLContext.

cipher-suite-filter

(Optional) The filter to be applied to the cipher suites for TLSv1.2 and below made available by this SSLContext. The format of this attribute is described in detail in org.wildfly.security.ssl.CipherSuiteSelector.fromString(selector). The default value is DEFAULT, which corresponds to all known cipher suites that do not have NULL encryption and excludes any cipher suites that have no authentication.

cipher-suite-names

(Optional) The enabled cipher suites for TLSv1.3. The format of this attribute is a simple colon (":") separated list of TLSv1.3 cipher suite names (e.g., TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256). This attribute must be specified in order for TLSv1.3 to be enabled.

protocols

(Optional) A space separated list of the protocols to be supported by this SSLContext. The default value is TLSv1 TLSv1.1 TLSv1.2 TLSv1.3. Note that the TLSv1.3 protocol will only be usable when running against JDK 11 or higher.

want-client-auth

(Optional) To request (but not to require) a client certificate on SSL handshake. If a security-domain is configured and supports X509 evidence, this will be set to true automatically. Ignored when need-client-auth is set. The default value is false.

need-client-auth

(Optional) If true, a client certificate is required on SSL handshake. A connection without a trusted client certificate will be rejected. The default value is false.

authentication-optional

(Optional) Rejection of the client certificate by the configured security-domain will not prevent the connection. This allows a fall through to use other authentication mechanisms (like form login) when the client certificate is rejected by the security-domain. This has an effect only when the security-domain is configured. The default value is false.

use-cipher-suites-order

(Optional) If true, the cipher suites order defined on the server will be used. If false, the cipher suites order presented by the client will be used. The default value is true.

provider-name

(Optional) The name of the provider to use. If not specified, all providers from providers will be passed to the SSLContext.

providers

(Optional) The name of the providers to obtain the Provider[] to use to load the SSLContext.

maximum-session-cache-size

(Optional) The maximum number of SSL sessions to be cached. The default value -1 indicates that the JVM default value should be used. A value of 0 means there is no limit.

session-timeout

(Optional) The timeout for SSL sessions. The default value -1 indicates that the JVM default value should be used. A value of 0 means there is no limit.

wrap

(Optional) If true, the returned SSLEngine, SSLSocket, and SSLServerSocket instances will be wrapped to protect against further modification. The default value is false.

pre-realm-principal-transformer

(Optional) A principal transformer to apply before the realm is selected.

post-realm-principal-transformer

(Optional) A principal transformer to apply after the realm is selected.

final-principal-transformer

(Optional) A final principal transformer to apply for this mechanism realm.

realm-mapper

(Optional) The realm mapper to be used for SSL authentication.

4.3.16. Configuring a client SSLContext

Using the Elytron subsystem, it is possible to configure an SSLContext for use on the client side of a connection.

Adding a client SSLContext takes the general form:

/subsystem=elytron/client-ssl-context=test-client-ssl-context:add(...)

The following attributes can be specified when creating a client-ssl-context:

key-manager

(Optional) A reference to the KeyManager to be used by this SSLContext.

trust-manager

(Optional) A reference to the TrustManager to be used by this SSLContext.

cipher-suite-filter

(Optional) The filter to be applied to the cipher suites for TLSv1.2 and below made available by this SSLContext. The format of this attribute is described in detail in org.wildfly.security.ssl.CipherSuiteSelector.fromString(selector). The default value is DEFAULT, which corresponds to all known cipher suites that do not have NULL encryption and excludes any cipher suites that have no authentication.

cipher-suite-names

(Optional) The enabled cipher suites for TLSv1.3. The format of this attribute is a simple colon (":") separated list of TLSv1.3 cipher suite names (e.g., TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256). This attribute must be specified in order for TLSv1.3 to be enabled.

protocols

(Optional) A space separated list of the protocols to be supported by this SSLContext. The default value is TLSv1 TLSv1.1 TLSv1.2 TLSv1.3. Note that the TLSv1.3 protocol will only be usable when running against JDK 11 or higher.

provider-name

(Optional) The name of the provider to use. If not specified, all providers from providers will be passed to the SSLContext.

providers

(Optional) The name of the providers to obtain the Provider[] to use to load the SSLContext.

WARNING It is possible to use TLSv1.3 with WildFly when running against JDK 11 or higher. However, if JDK 11 is in use and if there is a very large number of TLSv1.3 requests being made, it is possible that a drop in performance (throughput and response time) will occur compared to TLSv1.2. Upgrading to newer JDK versions should improve performance. For this reason, the use of TLSv1.3 is currently disabled by default. TLSv1.3 can be enabled by configuring the new cipher-suite-names attribute in the SSL Context resource definition in the Elytron subsystem as described in the previous two sections. It is recommended to test for performance degradation prior to enabling TLSv1.3 in a production environment. See WFWIP-160 for more details.

Note: When using TLSv1.3, it is important to keep in mind that session IDs have become essentially obsolete. This means that the session ID can no longer reliably be used to test if a session was resumed. Instead, creation time can be used to test if a session was resumed. Currently, clients need to read from the server after the first handshake in order to receive the NewSessionTicket that can be used for resumption. However, this may change once JDK-8209953 is resolved.

Dynamic client SSLContext

The dynamic-client-ssl-context resource is only available in the community stability level. For more information on how to configure stability levels, please refer to the Admin Guide, particularly the Feature stability levels section.

Using the Elytron subsystem, it is possible to configure an SSLContext that will delegate to alternate instances based on host and port you are connecting to.

You can add such SSLContext as follows:

/subsystem=elytron/dynamic-client-ssl-context=example-dynamic-ssl-context:add(authentication-context=example-authentication-context)

The attribute authentication-context refers to the resource authentication-context configured in the elytron subsystem. This attribute will be used to obtain rules based on which the dynamic client SSL context chooses the specific SSL context to use when making a connection.

Example of adding authentication-context:

/subsystem=elytron/authentication-context=ac:add(match-rules=[{match-port=9443,ssl-context=client1-ssl-context},{match-host="www.example.com",ssl-context=client2-ssl-context}])

4.3.17. Configuring SSLv2Hello

Older JDK versions use SSLv2Hello during the initial SSL handshake message where the SSL/TLS version that will be used for the rest of the communication is negotiated.

Using SSLv2Hello is discouraged, therefore newer JDK versions disable this protocol on the client by default. However, they do provide the ability to re-enable it if necessary.

SSLv2Hello can be configured as a supported protocol for the server SSL context as follows:

/subsystem=elytron/server-ssl-context=server-context:add(protocols=[SSLv2Hello,TLSv1], ...)

Similarly, it can be configured as a supported protocol for the client SSL context as follows:

/subsystem=elytron/client-ssl-context=client-context:add(protocols=[SSLv2Hello,TLSv1], ...)
WARNING:
  • The use of SSLv2Hello is strongly discouraged.

  • SSLv2Hello cannot be configured by itself, as its purpose is to determine which encryption protocols are supported by the server it connects to. It always needs to be configured along side another encryption protocol.

  • Additionally, IBM JDK does not support specifying SSLv2Hello in its client, although a server side connection always accepts this protocol.

4.3.18. Default SSLContext

Many libraries that can be used within deployments may require SSL configuration for any connections they establish, these libraries tend to be configurable by the caller or if no configuration is provided fall back to using the default SSLContext for the process available from: -

javax.net.ssl.SSLContext.getDefault();

By default this SSLContext is configured using system properties, however within the WildFly Elytron subsystem it is possible to specify that one of the configured contexts should be associated and used as the default.

To make use of this feature configure your SSLContext as normal, the following command can then be used to specify which SSLContext should be used as the default.

/subsystem=elytron:write-attribute(name=default-ssl-context, value=client-context)

As existing services and deployments could have cached the default SSLContext prior to this being set a reload is required to ensure the default gets set before the deployments are activated.

:reload

Note: If the default-ssl-context attribute is subsequently 'undefined' the standard APIs do not provide us with a mechanism to revert the default so in this situation the Java process would need be restarted.

/subsystem=elytron:undefine-attribute(name=default-ssl-context)
{
    "outcome" => "success",
    "response-headers" => {
        "operation-requires-restart" => true,
        "process-state" => "restart-required"
    }
}

4.3.19. Configuring SNI

Using the WildFly Elytron subsystem it is possible to configure an SSL context which supports SNI. By supporting SNI if an SNI host name is available whilst the SSLSession is being negotiated a host specific SSLContext will be selected. If no host specific SSLContext is identified either because no host name was received or because there is no match a default SSLContext will be used instead. By identifying a host specific SSLContext it means that a certificate appropriate for that host can be used.

The following command demonstrates how an SNI aware SSLContext can be added: -

[standalone@localhost:9990 /] ./subsystem=elytron/server-ssl-sni-context=test-sni:add(default-ssl-context=jboss,host-context-map={localhost=localhost, wildfly.org=wildfly})
{"outcome" => "success"}

This example assumes that three SSLContexts have been previously defined following the steps available previously in this document, those contexts are jboss, localhost, and wildfly.

During negotiation of the SSLSession if the SNI host name received is localhost then the localhost SSLContext will be used, if the SNI host name is wildfly.org then the wildfly SSLContext will be used. If no SNI host name is received or if we receive a name that does not match this will fallback and use the jboss SSLContext.

The resulting resource looks like: -

[standalone@localhost:9990 /] ./subsystem=elytron/server-ssl-sni-context=test-sni:read-resource
{
    "outcome" => "success",
    "result" => {
        "default-ssl-context" => "jboss",
        "host-context-map" => {
            "localhost" => "localhost",
            "wildfly.org" => "wildfly"
        }
    }
}

Within the host-context-map it is also possible to define wildcard mappings such as * and *.wildfly.org.

4.3.20. Use the Elytron Subsystem

For authentication in applications, you can use the application-security-domain property in the undertow subsystem to configure a security domain in the elytron subsystem.

/subsystem=undertow/application-security-domain=exampleApplicationDomain:add(http-authentication-factory=example-http-auth)

NOTE: This must match the security-domain configured in the jboss-web.xml of your application.

For enabling HTTPS using elytron you need to set the ssl-context attribute.

/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=ssl-context,value=httpsSSC)

4.4. Creating Elytron Subsystem Components

4.4.1. Create an Elytron Security Realm

Security realms in the Elytron subsystem, when used in conjunction with security domains, are use for both core management authentication as well as for authentication with applications. Security realms are also specifically typed based on their identity store, for example jdbc-realm, filesystem-realm, properties-realm, etc.

Adding a security realm takes the general form:

/subsystem=elytron/type-of-realm=realmName:add(....)

Examples of adding specific realms, such as jdbc-realm, filesystem-realm, and properties-realm can be found in previous sections.

4.4.2. Create an Elytron Role Decoder

A role decoder converts attributes from the identity provided by the security realm into roles. Role decoders are also specifically typed based on their functionality, for example empty-role-decoder, simple-role-decoder, custom-role-decoder, and aggregate-role-decoder.

Adding a role decoder takes the general form:

/subsystem=elytron/ROLE-DECODER-TYPE=roleDeoderName:add(....)
Create an Elytron Role Decoder that makes use of the IP Address of the Remote Client

A role decoder that is configured on a security realm assigns roles to an identity based on attributes provided by the security realm. It is also possible to configure a role decoder that makes use of the IP address of the remote client when determining the roles associated with an identity. As an example, we might want to make use of the IP address of the remote client in order to assign a user a particular role when establishing a connection to the server from a corporate network and a different role when establishing a connection to the server from a different network.

Adding a role decoder that makes use of the remote client’s IP address takes the general form:

/subsystem=elytron/source-address-role-decoder=roleDecoderName:add(source-address="...", pattern="...", roles=[...])

In particular, a source-address-role-decoder has the following attributes:

  • source-address - The IP address of the remote client.

  • pattern - A regular expression that specifies the IP address to match.

  • roles - The list of roles to assign if the source IP address matches the given address.

Only one of source-address and pattern should be specified.

For example, the following source-address-role-decoder could be configured to specify that a user should be assigned the "Administrator" role when establishing a connection to the server from the 10.10.10.10 IP address:

/subsystem=elytron/source-address-role-decoder=decoder1:add(source-address="10.10.10.10", roles=["Administrator"])

Once a source-address-role-decoder has been configured, it can be referenced from a security-domain:

/subsystem=elytron/security-domain=exampleSD:add(..., role-decoder=decoder1)

It is also possible to assign permissions to an identity based on its roles using a simple permission mapper.

Example configuration:

/subsystem=elytron/source-address-role-decoder=decoder1:add(source-address="10.10.10.10", roles=["Administrator"])
/subsystem=elytron/simple-permission-mapper=ipPermissionMapper:add(permission-mappings=[{roles=["Administrator"], permission-sets=[{permission-set=login-permission}]}])
/subsystem=elytron/security-domain=exampleSD:add(..., role-decoder=decoder1, permission-mapper=ipPermissionMapper)

The above example configures a security domain with a source address role decoder that will assign the "Administrator" role when the IP address of the remote client matches the configured address and a simple permission mapper that only assigns the "LoginPermission" if the identity has the "Administrator" role. Thus, authentication will succeed if the IP address of the remote client matches the configured address.

It is also possible to configure an aggregate-role-decoder. This consists of two or more role-decoder elements where each element is a reference to a configured role decoder. Each role decoder will be applied and the returned value will be a union of the roles returned by each decoder.

Example configuration:

/subsystem=elytron/source-address-role-decoder=decoder1:add(source-address="10.10.10.10", roles=["Administrator"])
/subsystem=elytron/source-address-role-decoder=decoder2:add(source-address="12.12.12.12", roles=["Users"])
/subsystem=elytron/aggregate-role-decoder=aggregateDecoder:add(role-decoders=[decoder1, decoder2])

4.4.3. Create an Elytron Permission Set

Permission sets can be used to assign permissions to an identity.

Adding a permission set takes the general form:

/subsystem=elytron/permission-set=PermissionSetName:add(permissions=[{class-name="...", module="...", target-name="...", action="..."}...])

where permissions consists of a set of permissions, where each permission has the following attributes:

  • class-name is the fully qualified class name of the permission. This is the only permission attribute that is required.

  • module is the optional module to use to load the permission.

  • target-name is the optional target name to pass to the permission as it is constructed.

  • action is the optional action to pass to the permission as it is constructed.

4.4.4. Create an Elytron Permission Mapper

In addition to roles being assigned to a identity, permissions may also be assigned. A permission mapper assigns permissions to an identity. Permission mappers are also specifically typed based on their functionality, for example logical-permission-mapper, simple-permission-mapper, and custom-permission-mapper.

Adding a permission mapper takes the general form:

/subsystem=elytron/simple-permission-mapper=PermissionMapperName:add(...)

4.4.5. Create an Elytron Role Mapper

A role mapper maps roles after they have been decoded to other roles. Examples include normalizing role names or adding and removing specific roles from principals after they have been decoded. Role mappers are also specifically typed based on their functionality, for example add-prefix-role-mapper, add-suffix-role-mapper, and constant-role-mapper.

Adding a role mapper takes the general form:

/subsystem=elytron/ROLEM-MAPPER-TYPE=roleMapperName:add(...)

4.4.6. Create an Elytron Security Domain

Security domains in the Elytron subsystem, when used in conjunction with security realms, are use for both core management authentication as well as for authentication with applications.

Adding a security domain takes the general form:

/subsystem=elytron/security-domain=domainName:add(realms=[{realm=realmName,role-decoder=roleDecoderName}],default-realm=realmName,permission-mapper=permissionMapperName,role-mapper=roleMapperName,...)

4.4.7. Create an Elytron Authentication Factory

An authentication factory is an authentication policy used for specific authentication mechanisms. Authentication factories are specifically based on the authentication mechanism, for example http-authentication-factory and
sasl-authentication-factory and kerberos-security-factory.

Adding an authentication factory takes the general form:

/subsystem=elytron/AUTH-FACTORY-TYPE=authFactoryName:add(....)

4.4.8. Create an Elytron Policy Provider

Elytron subsystem provides a specific resource definition that can be used to configure a default ruby Policy provider. The subsystem allows you to define multiple policy providers but select a single one as the default:

/subsystem=elytron/policy=policy-provider-a:add(custom-policy=\[{name=policy-provider-a, class-name=MyPolicyProviderA, module=x.y.z}\])

4.4.9. Create an Elytron Case Principal Transformer

Principal transformers can take a name and map it to another representation of the name or perform some normalisation. A case-principal-transformer converts a principal to upper or lower case. As an example, we might want to convert our principal to upper case if the identities in our realm are stored in upper case.

Adding a case-principal-transformer that converts a principal to upper/lower case takes the general form:

/subsystem=elytron/case-principal-transformer=transformerName:add(upper-case="...")

In particular, a case-principal-transformer has the following attribute:

  • upper-case - A boolean value to indicate whether the principal should be converted to upper case. Indicating false for this attribute converts the principal to lower case.

For example, the following case-principal-transformer could be configured to specify that a principal should be transformed to lower case:

/subsystem=elytron/case-principal-transformer=transformer1:add(upper-case="false")

On the other hand, a case-principal-transformer could be configured by omitting the upper-case attribute, which is then set to true by default:

/subsystem=elytron/case-principal-transformer=transformer2:add()

4.5. Component Documentation

4.5.1. Security Realms

The primary responsibility of a security realm is the ability to load identities with associated attributes, these identities are then used within the authentication process for credential validation and subsequently wrapped to represent the SecurityIdentity instances using within applications for authorization.

Generally a security realm operates in one of three modes, in the first mode on loading an identity from its store the security realm also loads one or more credential representations from the store and holds these within the identity. During authentication the credentials within the identity can be used to verify the connection attempt. The reason multiple credentials are loaded is because specific types may be applicable to specific mechanisms so an appropriate credential representation can be selected at the time of authentication.

In the second mode the identity is loaded as in the first mode however no credentials are loaded, in this mode evidence can be passed in to the identity for verification. This mode is sometimes the only mode available where it is not possible to load the representation of a credential from its store.

In the third mode evidence is passed to the security realm and used to construct the resulting identity, this mode is predominantly used with certificate and token based authentication mechanisms where the verified certificate or token can be used to construct the resulting identity.

Once an identity has been loaded for authorization decisions the identity will have a set of associated attributes, these will subsequent be mapped using role decoders, role mappers, and permission mappers as required for identity specific authorization decisions.

4.5.2. Aggregate Security Realm

The aggregate security realm allows for two or more security realms to be aggregated into a single security realm allowing one to be used during the authentication steps and one or more to be used to assemble the identity used for authorization decisions and aggregating any associated attributes.

The aggregate-realm resource contains the following attributes: -

  • authentication-realm - This realm use used to load the credentials of the identity or perform evidence verification.

  • authorization-realm - The realm to use to load the identities attributes used for authorization.

  • authorization-realms - A list of one or more realms to use to load the identities attributes and aggregate into a single set of attributes.

  • principal-transformer - A principal transformer that can be used to transform the principal after loading the credentials for authentication but before loading the identities attributes for authorization. This attribute is optional.

authorization-realm and authorization-realms are mutually exclusive so exactly one of these must be specified.
Defining a Simple Aggregation

Assuming two realms properties-realm and jdbc-realm already exist an aggregate-realm combining these two can be created with the following command.

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

Where this realm is used the properties-realm will be used to load an identity’s credentials and the jdbc-realm will be used to load the attributes for the identity.

The following command is exactly the same except the authorization-realms attribute is used instead.

/subsystem=elytron/aggregate-realm=combined:add(
    authentication-realm=properties-realm,
    authorization-realms=[jdbc-realm])

The following command will reference a principal transformer defined in the mappers configuration to be used to transform the principal after authentication.

/subsystem=elytron/aggregate-realm=combined:add(
    authentication-realm=properties-realm,
    authorization-realms=[jdbc-realm],
    principal-transformer=custom-transformer)
Aggregating Attributes

If a third realm ldap-realm also exists that shoud also be used for the loading of attributes an aggregate-realm can be defined using the following command: -

/subsystem=elytron/aggregate-realm=combined:add(
    authentication-realm=properties-realm,
    authorization-realms=[jdbc-realm, ldap-realm])

As before the properties-realm will be used to load the identity’s credentials or perform evidence verification but the attributes for the identity will be loaded both from the jdbc-realm and ldap-realm then combined together.

The approach taken to combine attributes makes use of the first instance of each attribute, if the same attribute is loaded by multiple realms only the first occurrence will be used. Later occurrences of an existing attribtue will be ignored.

As an example if the following attributes were loaded from the jdbc-realm: -

Table 1. JDBC Attributes
ATTRIBUTE VALUES

e-mail

example@wildfly.org

groups

Supervisor, User

And the following attributes were loaded from the ldap-realm: -

Table 2. LDAP Attributes
ATTRIBUTE VALUES

e-mail

administrator@wildfly.org

phone

0000 0000 0000

The resulting attributes would be: -

Table 3. Combined Attributes
ATTRIBUTE VALUES

e-mail

example@wildfly.org

groups

Supervisor, User

phone

0000 0000 0000

4.5.3. Distributed Security Realm

The distributed realm allows combining identities located across multiple security realms of any type into a single security realm.

The distributed-realm resource contains the following attributes: -

  • realms - List of realms in the order they should be queried

  • ignore-unavailable-realms - Whether subsequent realms should be checked after an unavailable realm is reached. The default value is false.

  • emit-events - Whether a SecurityEvent signifying realm unavailability should be emitted. The default value is true.

Defining a Distributed Realm

As an example, assuming realms named properties-realm, ldap-realm and jdbc-realm have already been configured, then a distributed-realm that references these three realms can be created as follows:

/subsystem=elytron/distributed-realm=distributedRealmExample:add(realms=[properties-realm, ldap-realm, jdbc-realm])

When configuring a distributed-realm, it is possible to ignore any unavailable realms by setting ignore-unavailable-realms to true. When retrieving an identity from a distributed-realm, each of the configured realms is attempted sequentially until a realm containing the identity is found. If a security realm becomes unavailable for some reason, if ignore-unavailable-realms is true, any subsequent realms will still be checked. If ignore-unavailable-realms is true and searched identity happened to be stored in the unavailable realm, authentication fails, and 401 response wil be received. If ignore-unavailable-realms is set to false or not set, and the realm happens to be unavailable then an org.wildfly.security.auth.server.RealmUnavailableException will be thrown and no further realms will be attempted.

/subsystem=elytron/distributed-realm=distributedRealmExample:add(realms=[properties-realm, ldap-realm, jdbc-realm], ignore-unavailable-realms=true)

While ignore-unavailable-realms is set to true, emit-events is also set to true by default, so the SecurityEvent will be emitted in case any of the attempted realms is unavailable. This can be turned off by setting emit-events to false.

/subsystem=elytron/distributed-realm=distributedRealmExample:add(realms=[properties-realm, ldap-realm, jdbc-realm], ignore-unavailable-realms=true, emit-events=false)

4.5.4. Failover Security Realm

The failover security realm allows for a realm to fail-over to another realm in case the first is unavailable.

The failover-realm resource contains the following attributes: -

  • delegate-realm - The realm to use as a default.

  • failover-realm - The realm to use in case delegate-realm is not available.

  • emit-events - Whenever to emit SecurityEvent in case the delegate-realm is unavailable.

Defining a Failover

Assuming two realms properties-realm and jdbc-realm already exist an failover-realm combining these two can be created with the following command.

/subsystem=elytron/failover-realm=theRealm:add(delegate-realm=jdbc-realm, failover-realm=properties-realm)
The failover-realm keeps modifiability of the delegate-realm unless failover happens. In that case, the failover-realm will function as unmodifiable.

4.5.5. Filesystem Security Realm

The filesystem security realm is a security realm developed to support storing of identities in a filesystem with the option of associating multiple credentials and multiple attributes with each identity. Both credentials and attributes can contain multiple values.

Create and Populate Filesystem Security Realm

Every identity is stored in an XML file where the name of the file is the name of the identity. It is sufficient to just specify the path to the directory where identity files will be stored. There are other optional attributes.

  • path The actual filesystem path. Treated as an absolute path, unless the 'relative-to' attribute is specified, in which case the value is treated as relative to that path.

  • relative-to If 'relative-to' is provided, the value of the 'path' attribute is treated as relative to the path specified by this attribute.

  • levels The number of levels of directory hashing to apply. When this attribute is set to positive value, filesystem realm will store identities in directory structure where the name of subdirectories will be derived from first characters of identity name. For example, location of identity named "alex" could be a/l/alex.xml. This is useful not only because some filesystems can limit the amount of files that can be stored in a single directory, but also for performance reasons, because that might be influenced by the number of entries in a single directory as well. Default value is 2.

  • encoded When encoding is set to true, the identity names will be BASE32 encoded before they are used as filenames. This is beneficial, because some filesystems are case-insensitive or might restrict set of characters allowed in a filename. Default value is true.

  • hash-encoding specifies the string format for the password if it is not stored in plain text. Set to BASE64 bt default, but HEX is also supported.

  • hash-charset specifies the character set to use when converting the password string to a byte array. Set to UTF-8 by default.

  • credential-store specifies the credential store where the secret key is stored if the filesystem realm is to be encrypted. This attribute is optional.

  • secret-key specifies the alias of the secret key that is stored in the previously specified credential store. This attribute is optional.

  • key-store specifies the key store where the key pair is stored if the integrity is to be enabled on the filesystem realm. This attribute is optional.

  • key-store-alias specifies the alias of the key pair that is stored in the previously specified key store. This attribute is optional.

Filesystem security realm can be added with WildFly management CLI or with Elytron API.

WildFly management CLI

The following command creates realm with name fsRealm that resides in directory fs-realm-users inside the base configuration directory defined by jboss.server.config.dir property. The levels number is 2 and encoded value is true. The charset is UTF-8 and the string encoding is BASE64.

The credential-store being referenced is called mycredstore and the alias of the secret-key in that credential-store is key. The secret key will be used to encrypt and decrypt the filesystem realm.

The key-store being referenced is called keystore and the alias of the key pair in that key-store is localhost. The localhost key pair will be used to sign identity files and verify the pre-existing signatures to ensure that the filesystem realm has not been tampered with.

/subsystem=elytron/filesystem-realm=fsRealm:add(path=fs-realm-users, relative-to=jboss.server.config.dir, credential-store=mycredstore, secret-key=key, key-store=keystore, key-store-alias=localhost)
Although identities stored within the same realm can be hashed using different algorithms, they are all hashed using the same character set and stored using the same string encoding across the whole realm.

To add identity with the name "alex" to this filesystem realm:

/subsystem=elytron/filesystem-realm=fsRealm:add-identity(identity=alex)

To delete existing identity from filesystem realm:

/subsystem=elytron/filesystem-realm=fsRealm:remove-identity(identity=alex)

It’s possible to update the key-store or key-store-alias to use for integrity checking using the update-key-pair command as shown below. These commands will also update the signature in any existing identity files.

batch
/subsystem=elytron/filesystem-realm=fsRealm:write-attribute(name=key-store, value=newKeystore)
/subsystem=elytron/filesystem-realm=fsRealm:write-attribute(name=key-store-alias, value=newKeystoreAlias)
run-batch
reload
/subsystem=elytron/filesystem-realm=fsRealm:update-key-pair()
An update of attributes requires a reload or restart to take effect. Otherwise, the update-key-pair operation can result in an inconsistent state.
Programmatic approach

When creating filesystem realm with Elytron API, you can specify name rewriter in the constructor. The name rewriter will be applied to identity names to transform them from one form to another. It can be used to normalize the case, trim extra whitespaces, map one naming scheme to another, remove realm component from identity name (e.g. "user@realm" to "user") or to perform validation step on the syntax of the name.

To enable encryption for a filesystem realm programmatically, you can specify a SecretKey when instantiating the realm.

To enable integrity for the filesystem realm programmatically, you can specify a PublicKey and PrivateKey when instantiating the realm.

A org.wildfly.security.auth.realm.FileSystemSecurityRealm can be instantiated using a builder() method as shown in the example below.

FileSystemSecurityRealm fsRealm = FileSystemSecurityRealm.builder()
    .setRoot(fsRealmPath)
    .setNameRewriter(IDENTITY_REWRITER)
    .setLevels(2)
    .setEncoded(true)
    .setProviders(ELYTRON_PASSWORD_PROVIDERS)
    .setSecretKey(secretKey)
    .setPublicKey(publicKey)
    .setPrivateKey(privateKey)
    .build();

org.wildfly.security.auth.server.NameRewriter.IDENTITY_REWRITER is a simple identity name rewriter that does no rewriting. You can implement the NameRewriter interface and use your own rewriter.

Class org.wildfly.security.auth.principal.NamePrincipal represents principal comprised of a simple name. Handle for the identity can be obtained by passing the NamePrincipal instance to the getRealmIdentity or getRealmIdentityForUpdate method. Received identity for the given principal might or might not exist in the filesystem realm. Method exists can be called to check whether the received identity exists. If it does not, you cannot modify its credentials or attributes.

To add non existent identity to the filesystem realm, create method must be called. After this call, credentials and roles of this identity are empty. The exception will be thrown if method create is called on an existing identity.

ModifiableRealmIdentity modifiableIdentity = fsRealm.getRealmIdentityForUpdate(new NamePrincipal("alex"));
if (!modifiableIdentity.exists()) {
    modifiableIdentity.create();
}

To obtain read only instance of identity:

RealmIdentity identity = fsRealm.getRealmIdentity(identityPrincipal);

To obtain identity that can be modified:

ModifiableRealmIdentity modifiableIdentity = fsRealm.getRealmIdentityForUpdate(identityPrincipal);

ModifiableRealmIdentity handle must be cleaned up by a call to dispose when the modifying is done:

modifiableIdentity.dispose();

To delete the identity from the realm:

identity.delete();
Credentials

Supported password types for identity in filesystem realm are Bcrypt, Clear, Simple Digest, Salted Simple Digest, Scram Digest, Digest and OTP.

WildFly management CLI

To set clear password to the identity:

/subsystem=elytron/filesystem-realm=fsRealm:set-password(clear={password="alexPassword"}, identity=alex)

The above command will store password in clear text in identity’s file. It is not recommended to use clear passwords in a production set up.

To set digest password to the identity:

/subsystem=elytron/filesystem-realm=fsRealm:set-password(digest={algorithm=digest-md5,password="demoPassword",realm=demoRealm},identity=alex)

Note: Operation set-password` replaces any existing credential(s) with the new value.

Programmatic approach

When using Elytron API, working with passwords require interaction with the org.wildfly.security.password.PasswordFactory API. Here is the simplest example that will store password in clear text in identity’s file. Examples on how to work with other types of passwords can be found in Passwords section of this documentation.

PasswordFactory passwordFactory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR);
PasswordCredential clearPassword = new PasswordCredential(passwordFactory.generatePassword(new ClearPasswordSpec("alexPassword".toCharArray())));
identity.setCredentials(Collections.singleton(clearPassword));

Note: Operation setCredentials replaces any existing credential(s) with the new value.

Attributes

Attributes associated with identities can be read and updated through CLI as well as programmatically.

WildFly management CLI

To add attribute to the identity, you have to specify the name of the attribute and its values (values are of type LIST):

/subsystem=elytron/filesystem-realm=fsRealm:add-identity-attribute(
    identity="alex",
    name=Email,
    value=["alex@email.com", "alex_email@email.com"])
{"outcome" => "success"}

To read the identity with its attributes:

/subsystem=elytron/filesystem-realm=fsRealm:read-identity(identity="alex")
{
    "outcome" => "success",
    "result" => {
        "name" => "alex",
        "attributes" => {"Email" => [
            "alex@email.com",
            "alex_email@email.com"
        ]}
    }
}

To remove individual values of the attribute:

/subsystem=elytron/filesystem-realm=fsRealm:remove-identity-attribute(
    identity=alex,
    name=Email,
    value=[alex@email.com])
{"outcome" => "success"}
/subsystem=elytron/filesystem-realm=fsRealm:read-identity(identity="alex")
{
    "outcome" => "success",
    "result" => {
        "name" => "alex",
        "attributes" => {"Email" => ["alex_email@email.com"]}
    }
}

To remove the whole attribute:

/subsystem=elytron/filesystem-realm=fsRealm:remove-identity-attribute(identity=alex, name=Email)
{"outcome" => "success"}
/subsystem=elytron/filesystem-realm=fsRealm:read-identity(identity=alex)
{
    "outcome" => "success",
    "result" => {
        "name" => "alex",
        "attributes" => undefined
    }
}
Programmatic approach

Interface org.wildfly.security.authz.Attributes represents collection of string attributes. To get attributes associated with specific identity:

Attributes identityAttributes = identity.getAttributes();

To update attributes you can use ModifiableRealmIdentity instance. Class org.wildfly.security.authz.MapAttributes represents collection of attributes backed by java.util.Map:

ModifiableRealmIdentity modifiableIdentity = fsRealm.getRealmIdentityForUpdate(identityPrincipal);
MapAttributes attributes = new MapAttributes();
attributes.addLast("email","alex@email.com");
modifiableIdentity.setAttributes(attributes);
modifiableIdentity.dispose();
Converting legacy properties file into Filesystem realm

WildFly can use authentication with a properties file based identity store. One properties file maps users to passwords and another maps users to roles. You can use Elytron Tool to convert these properties files into a filesystem realm. Below is an example of how to run this tool from the command line:

$JBOSS_HOME/bin/elytron-tool.sh filesystem-realm -u conf/users.properties -r conf/roles.properties --output-location realms/example --summary -f example-fs-realm

This command creates new filesystem realm with users taken from users.properties file and roles taken from roles.properties file. Script example-fs-realm.sh that contains the commands for WildFly CLI is generated as well. The script adds this filesystem realm to the Elytron subsystem and also adds new security domain that uses this filesystem realm as a default realm.

Converting an unencrypted filesystem realm into an encrypted filesystem realm

It is possible to convert an unencrypted filesystem realm into an encrypted one using the Elytron Tool. In particular, the filesystem-realm-encrypt command can be used as shown below:

$JBOSS_HOME/bin/elytron-tool.sh filesystem-realm-encrypt -i ./standalone/configuration/fs-realm-plain -o ./standalone/configuration/fs-realm-enc -c ./mycredstore.cs

This command creates a new filesystem realm by taking the existing filesystem realm from the specified input location, ./standalone/configuration/fs-realm-plain, and encrypting its contents using the secret key with alias key from the specified credential store, ./mycredstore.cs. The new filesystem realm is stored in the specified output location, ./standalone/configuration/fs-realm-enc.

A .cli script will also be generated at the root of the filesystem realm. The script contains WildFly CLI commands that can be used to configure a secret-key-credential-store resource and a filesystem-realm resource in the Elytron subsystem that makes use of the newly encrypted realm content.

Adding Integrity Checking for an Existing Filesystem Realm

Integrity checking can also be added to an existing filesystem realm using the WildFly Elytron Tool. This takes an existing filesystem realm and creates a new filesystem realm with integrity checking support. The integrity checking uses a public and private key pair. The key pair will be used to create a new filesystem realm where each identity will be signed using the private key. This makes it possible to verify that identities in the realm haven’t been tampered with.

The feature also creates a CLI script that can be used to add a key-store resource and a filesystem-realm resource in the Elytron subsystem that makes use of the newly signed realm content.

In order to use this feature, we can make use of the elytron-tool.sh (or .bat for windows) script. The format for the command to add integrity checking is as follows:

$ WILDFLY_HOME/bin/elytron-tool.sh filesystem-realm-integrity --input-location <existing_path> --output-location <new_path> --keystore <ks_path> --password <ks_password> --summary

The required options include:

  • input-location: This is the path for the existing filesystem realm that does not have an integrity checking. This must be the path to the directory containing the filesystem. This can also be specified using -i. This must be an existing path.

  • output-location: This attribute will be the path for the new filesystem realm. This filesystem realm will include integrity checking. This can also be specified using -o. This path is also a path to a directory.

  • keystore: Path for the keystore that holds the key pair for integrity checking. Can also be specified using -k.

  • password: This is the password for the keystore. This can also be specified using -p.

The elytron tool also allows for the conversion to be done in bulk, as in multiple filesystems can be converted all at once. This can be done using the following command:

$ WILDFLY_HOME/bin/elytron-tool.sh filesystem-realm-integrity --bulk-convert <descriptor>

In this case <descriptor> is a file, which contains the instructions for bulk conversion. An example of a descriptor file is shown below. Notice that each block contains the options to use for converting a single realm. Additionally, in addition to specifying the input-location, we have specified the realm-name as well.

input-location:standalone/configuration/unsigned-realm-one
output-location:standalone/configuration/signed
keystore:myKeyStore
password:myPassword
key-pair:myKeyPair
realm-name:realm-one

input-location:standalone/configuration/unsigned-realm-two
output-location:standalone/configuration/signed
keystore:myKeyStore
password:myPassword
key-pair:myKeyPair
realm-name:realm-two

There are other options that can be specified for converting a filesystem realm, the details of which can be found using the ./bin/elytron-tool.sh filesystem-realm-integrity -h command.

Updating WildFly Elytron Tool Generated CLI Scripts for Domain Mode

As mentioned above, some of the WildFly Elytron Tool commands create a CLI script that can be used to add resources to the Elytron subsystem. Some examples of these CLI scripts include:

  1. The <FILESYSTEM-REALM-NAME>.cli script generated when the filesystem-realm-encrypt command is used to encrypt an existing filesystem realm.

  2. The <FILESYSTEM-REALM-NAME>.cli script generated when the filesystem-realm-integrity command is used to add integrity checking to existing filesystem realms.

  3. The <FILESYSTEM-REALM-NAME>.sh script generated when the filesystem-realm command is used to create a filesystem realm from a legacy properties file.

However, the generated CLI script can only be used directly if the server is running in Standalone mode. For Domain mode the script needs to be updated. The steps for updating the script are as follows:

  • Update the file contents to add a /profile=<PROFILE_NAME> in front of all occurrences of the /subsystem=…​ strings as found in the file (so in front of /subsystem=elytron…​); where <PROFILE_NAME> is the profile name that you are using in your WildFly domain.xml (Hence, <PROFILE_NAME> can be either default, ha, full, full-ha, or any other profile-name defined and used in the domain.xml.)

  • Save and Exit the file editor.

Now you can use the CLI script to update server configurations in domain mode.

Please note that this same basic procedure can also apply to a Host Controller configuration (i.e. host.xml content). However, in that case, instead of specifying profile=<PROFILE_NAME>, we would use host=<HC_NAME> to specify the Host Controller name.

4.5.6. JAAS Security Realm

A JAAS realm utilizes a LoginContext initialized from a JAAS configuration file to authenticate and authorize users with custom Login Modules. Flags and options can be specified in a JAAS configuration file according to the Java documentation.

The jaas-realm resource contains the following attributes: -

  • entry JAAS configuration file entry name

  • path Path to the JAAS configuration file. You can also specify the location of the configuration with java system property "java.security.auth.login.config" or with java security property "login.config.url"

  • relative-to Optional base folder for the path.

  • module The WildFly module with Login Module implementations and Callback Handler implementation.

  • callback-handler Callback handler to use with the Login Context. Security property "auth.login.defaultCallbackHandler" can be used instead. The default callback handler of the realm will be used if none of these are defined.

All attributes but entry are optional.

Example of jaas-realm configuration via WildFly CLI:

/subsystem=elytron/jaas-realm=theRealm:add(
    entry=Entry1,
    path=/path/to/jaas/config/JAASConfig.conf,
    module=myLoginModule,
    callback-handler=my.custom.MyCallbackHandler)
Subject’s Principals to attributes mapping

Login Modules use Subjects to represent the user currently being authenticated. Subject’s principals are mapped to user’s attributes with the following rule:

  • key of the attribute is principal’s simple classname, so the value of principal.getClass().getSimpleName())

  • value is principal’s name, so the result of principal.getName() call. For principals of the same type / key, the values will be appended to the collection under this attribute key.

Attributes can be used to associate roles in the Elytron subsystem. Roles is the default attribute name used for this purpose. You can configure a role-decoder to use a different attribute. This means you can add roles to authenticated user by associating principals with the Subject in a Login Module imlpementations.

4.5.7. JDBC Security Realm

The JDBC security realm is a security realm developed to support loading identities from a database with the option of multiple credentials and multiple attributes each with the option of containing multiple values.

When defining the JDBC security realm one or more principal queries can be defined, each of these can load a credential and / or attributes for the resulting identity. Each defined principal query is associated with it’s own datasource, this means quite a complex configuration can be created loading the different aspects of an identity from multiple locations.

Each of the examples documented within this section will be making use of pre-configured datasources, please refer to the datasources subsystem documentation for more information relating to how to define datasources.

Loading a Single Clear Text Password

The simplest configuration is to load a clear text password for an identity. This approach would not be recommended at all in a production set up, however it does make a suitable starting point to illustrate how the JDBC security realm can be configured.

Table 4. Example Table
NAME PASSWORD

test

myPassword

A JDBC security realm can be defined as: -

/subsystem=elytron/jdbc-realm=demo-realm:add(
    principal-query=[{data-source=Identities,
                      sql="select PASSWORD from IDENTITIES where NAME = ?",
                      clear-password-mapper={password-index=1}}])

This realm is defined within a single principal-query against the Identities datasource. For the user test the result of the query would be: -

Table 5. Query Results
1

myPassword

The principal query can be defined with password mappers which define which columns should be used to construct the password for the identity being loaded, in this example a clear-password-mapper is used: -

clear-password-mapper={password-index=1}
The index of the first column is 1
Alternative Password Mappers

The WildFly Elytron project supports a variety of password types as described within Passwords, some of these password types require multiple values to be loaded from the database to reconstruct the password so alternative password mappers are made available. More than one password mapper can be defined on a single principal-query to support loading multiple passwords simultaneously.

Where the various password mappers load encoded representations of passwords and salts from the database these can either be encoded using Base64 or Hexadecimal, by default unless specified Base64 is assumed.
clear-password-mapper

This mapper is used to load a clear text password directly from the database.

The following attribute is supported for this password mapper: -

  • password-index - The index of the column containing the clear text password.

bcrypt-password-mapper

The bcrypt-password-mapper can be used for passwords to be loaded using the bcrypt algorithm, as an iterated salted password type the iteration count and salt are also loaded from the database query.

  • password-index - The index of the column containing the encoded password.

  • hash-encoding - The encoding of the hash, either base64 or hex.

  • salt-index - The index of the column containing the encoded salt.

  • salt-encoding - The encoding of the salt, either base64 or hex.

  • iteration-count-index - The index of the column containing the iteration count.

modular-crypt-mapper

The modular-crypt-mapper can be used for passwords encoded using modular crypt, this encoding allows for multiple pieces of information to be encoded in single String such as the password type, the hash or digest, the salt, and the iteraction count.

Information on how to encode and decode modular crypt representations can be seen in Modular Crypt Encoding.

The following attribute is supported for this password mapper: -

  • password-index - The index of the column containing the modular crypt encoded password.

salted-simple-digest-mapper

The salted-simple-digest-mapper supports the password types hashed with a salt as described in Salted Digest, for this type of password the encoded form of the password is loaded in addition to the salt.

  • algorithm - The algorithm of the password type, the supported values are listed at Salted Digest.

  • password-index - The index of the column containing the encoded password.

  • hash-encoding - The encoding of the hash, either base64 or hex.

  • salt-index - The index of the column containing the encoded salt.

  • salt-encoding - The encoding of the salt, either base64 or hex.

simple-digest-mapper

The simple-digest-mapper supports the loading of passwords which have been simply hashed without any salt as described in Simple Digest.

  • algorithm - The algorithm of the password type, the supported values are listed at Simple Digest.

  • password-index - The index of the column containing the encoded password.

  • hash-encoding - The encoding of the hash, either base64 or hex.

scram-mapper

The scram-mapper supports the loading of SCRAM passwords which use both a salt and an interation count as described in Scram.

  • algorithm - The algorithm of the password type, the supported values are listed at Scram.

  • password-index - The index of the column containing the encoded password.

  • hash-encoding - The encoding of the hash, either base64 or hex.

  • salt-index - The index of the column containing the encoded salt.

  • salt-encoding - The encoding of the salt, either base64 or hex.

  • iteration-count-index - The index of the column containing the iteration count.

Hash Character Sets

The various password mappers allow loading multiples values from the database to hash the client provided password in order to compare against the password stored in the database.

The JDBC realm supports specifying the character set via the attribute hash-charset to use when converting the client provided password string to a byte array. This is useful when our database is storing hashed passwords using a charset other than UTF-8, as the JDBC realm assumes that is the charset being used by default.

Although more than one password mapper can be defined on a single principal-query, only one hash-charset can be defined across the whole realm.

For example, the following JDBC realm is configured using the GB2312 charset:

 /subsystem=elytron/jdbc-realm=exampleDbRealm:add(principal-query=[{sql="SELECT password FROM all_users WHERE user=?",data-source=exampleDS,simple-digest-mapper={algorithm=password-salt-digest-md5,password-index=1}}])
Using a Hashed Password Representation

The same approach can be taken for all hashed password representations, for illustration purposes this section will illustrate how a bcrypt password can be prepared to be stored in a database and the subsequent realm configuration to make use of it. Examples uising the APIs for the different password types can be found in the Passwords section of this documentation.

The following example takes the password myPassword, generates a random salt an produces a bcrypt representation of the password.

static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();

static final String TEST_PASSWORD = "myPassword";

public static void main(String[] args) throws Exception {
    PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PROVIDER);

    int iterationCount = 10;

    byte[] salt = new byte[BCryptPassword.BCRYPT_SALT_SIZE];
    SecureRandom random = new SecureRandom();
    random.nextBytes(salt);

    IteratedSaltedPasswordAlgorithmSpec iteratedAlgorithmSpec = new IteratedSaltedPasswordAlgorithmSpec(iterationCount, salt);
    EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(TEST_PASSWORD.toCharArray(), iteratedAlgorithmSpec);

    BCryptPassword original = (BCryptPassword) passwordFactory.generatePassword(encryptableSpec);

    byte[] hash = original.getHash();

    Encoder encoder = Base64.getEncoder();
    System.out.println("Encoded Salt = " + encoder.encodeToString(salt));
    System.out.println("Encoded Hash = " + encoder.encodeToString(hash));
}

This produces the following output, as the salt is randomly generated the output would differ each time the above code is executed.

Encoded Salt = 3bFOQwRU75to+yJ8Cv0g8w==
Encoded Hash = x9P/0cxfNz+Pf3HCinZ3dLCbNMnBeiU=

This could now be stored in a database table: -

Table 6. Example Table
NAME PASSWORD SALT ITERATION_COUNT

test

x9P/0cxfNz+Pf3HCinZ3dLCbNMnBeiU=

3bFOQwRU75to+yJ8Cv0g8w==

10

The JDBC security realm can instead be created with the following CLI command: -

/subsystem=elytron/jdbc-realm=demo-realm:add(
    principal-query=[{data-source=Identities,
                      sql="select PASSWORD, SALT, ITERATION_COUNT from IDENTITIES where NAME = ?",
                      bcrypt-mapper={password-index=1, salt-index=2, iteration-count-index=3}}])

For the user test the result of the query would be: -

Table 7. Query Results
1 2 3

x9P/0cxfNz+Pf3HCinZ3dLCbNMnBeiU=

3bFOQwRU75to+yJ8Cv0g8w==

10

The bcrypt-password-mapper is defined to load the encoded password, encoded salt and iteration count from the relevent columns in the query result.

bcrypt-mapper={password-index=1, salt-index=2, iteration-count-index=3}

Had the values been encoding using hexadecimal instead of Base64 the bcrypt-mapper could have been defined as: -

bcrypt-mapper={password-index=1, hash-encoding=hex, salt-index=2, salt-encoding=hex, iteration-count-index=3}
It is worth noting that as the hash-encoding and salt-encoding are specified separately one could use Base64 whilst the other uses hexadecimal.
Loading Passwords from Different Queries / Datasources

It is also possible to combine both of the example so far and define two separate principal-query instances to attempt to load both password types from different locations.

Here is an example configuration loading a clear text password from one datasource / table and loading a bcrypt password from a second datasource / table.

/subsystem=elytron/jdbc-realm=demo-realm:add(
    principal-query=[
        {data-source=LegacyIdentities,
         sql="select PASSWORD from LEGACY_IDENTITIES where NAME = ?",
         clear-password-mapper={password-index=1}},
        {data-source=NewIdentities,
         sql="select PASSWORD, SALT, ITERATION_COUNT from NEW_IDENTITIES where NAME = ?",
         bcrypt-mapper={password-index=1, salt-index=2, iteration-count-index=3}}
                    ])
It is not required that the identity is found from both of the queries, this can be useful in situations where identities are being migrated from one location to another or for aggregating two together.
Loading Attributes

The examples so far have focussed on the loading of passwords from the database, the principal queries can also be used to load attributes for the resulting identities.

The loading of attributes can either be defined to happen within the principal queries being used to load the passwords or attribute specific principal queries can be defined, as each principal-query can be defined with it’s own datasource reference this means attributes can also be loaded from alternative locations.

The loaded attributes can then be used for mapping to roles and permissions which should be granted to the identity or they can be obtained programatically within the deployment to identify information about the currently authenticated identity.

Loading Attributes with Passwords

For single valued attributes these can often be loaded using the same principal-query used to load an identities password, as an example if an identities e-mail address or department is to be loaded from the database these can be loaded at the same time as the password.

A table for this example could look like: -

Table 8. Example Table
NAME PASSWORD E_MAIL Department

test

myPassword

test@example.com

Sales

The realm can now be defined as: -

/subsystem=elytron/jdbc-realm=demo-realm:add(
    principal-query=[
        {data-source=Identities,
         sql="select PASSWORD, E_MAIL, DEPARTMENT from IDENTITIES where NAME = ?",
         clear-password-mapper={password-index=1},
         attribute-mapping=[{index=2, to=email},{index=3,to=department}]
        }])

For the user test the result of the query would be: -

Table 9. Query Results
1 2 3

myPassword

test@example.com

Sales

The configuration contained the following attribute mappings: -

attribute-mapping=[{index=2, to=email},{index=3,to=department}]

This means the contents of column 2 will be mapped to the email attribute and the contents of column 3 will be mapped to the department attribute.

Loading Attributes Separately.

For multi-valued attributes such as a list of groups it can often make sense to define a separate principal query.

A list of groups could be represented as follows in a table.

Table 10. Example Table
NAME TEAM

test

Users

test

Supervisors

A realm can now be defined with a second principal query to load the groups into an attribute.

/subsystem=elytron/jdbc-realm=demo-realm:add(
    principal-query=[
        {data-source=Identities,
         sql="select PASSWORD from IDENTITIES where NAME = ?",
         clear-password-mapper={password-index=1}
        },{data-source=Identities,
         sql="select TEAM from MEMBERSHIP where NAME = ?",
         attribute-mapping=[{index=1, to=groups}]
        }])

Within this definition the second principal-query will load the attribute groups: -

{data-source=Identities,
 sql="select TEAM from MEMBERSHIP where NAME = ?",
 attribute-mapping=[{index=1, to=groups}]
}

For the user test the results would be: -

Table 11. Query Results
1

Users

Supervisors

The end result would be that the identity contains the attribute groups with the values Users, and Supervisors

5. Using Elytron within WildFly

5.1. Using the Out of the Box Elytron Components

5.1.1. Securing Management Interfaces

You can find more details on the enabling WildFly to use the out of the box Elytron components for securing the management interfaces in the Default Management Authentication Configuration section.

5.1.2. Securing Applications

The elytron subsystem provides application-security-domain by default which can be used to secure applications. For more details on how application-security-domain is configured, see the Default Application Authentication Configuration section.

To configure applications to use application-security-domain, see Set Up and Configure Authentication for Applications. You can also override the default behavior of all applications using the steps in Override an Application’s Authentication Configuration.

5.1.3. Using SSL/TLS

WildFly does provide a default one-way SSL/TLS configuration using the legacy core management authentication but does not provide one in the elytron subsystem. You can find more details on configuring SSL/TLS using the elytron subsystem for both the management interfaces as well as for applications in Configure SSL/TLS

5.1.4. Using Elytron with Other Subsystems

In addition to securing applications and management interfaces, Elytron also integrates with other subsystems in WildFly.

Subsystem Details

batch-jberet

You can configure the batch-jberet to run batch jobs using an Elytron security domain.

datasources

You can use a credential store or an Elytron security domain to provide authentication information in a datasource definition.

messaging-activemq

You can secure remote connections to the remote connections used by the messaging-activemq subsystem.

iiop-openjdk

You can use the elytron subsystem to configure SSL/TLS between clients and servers using the iiop-openjdk subsystem.

mail

You can use a credential store to provide authentication information in a server definition in the mail subsystem.

undertow

You can use the elytron subsystem to configure both SSL/TLS and application authentication.

5.2. Undertow Subsystem

As a web application is deployed the name of the security domain required by that application will be identified, this will either be from within the deployment or if the deployment does not have a security domain the default-security-domain as defined on the Undertow subsystem will be assumed. An application-security-domain resource can be added to the Undertow subsystem which maps from the name of the security domain required by the application to the appropriate WildFly Elytron configuration.

As an example in it’s simplest form a mapping can be added as: -

/subsystem=undertow/application-security-domain=MyAppSecurity:add(security-domain=ApplicationDomain)

Which results in: -

<subsystem xmlns="urn:jboss:domain:undertow:7.0" ... default-security-domain="other">
...
    <application-security-domains>
        <application-security-domain name="MyAppSecurity" security-domain="ApplicationDomain"/>
    </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.

Here we are mapping from the applications security domain MyAppSecurity to the WildFly Elytron defined domain ApplicationDomain.

This simple form is suitable where a deployment is using the standard HTTP mechanisms as defined within the Servlet specification i.e. BASIC, CLIENT_CERT, DIGEST, FORM and authentication will be performed against the ApplicationDomain security domain. This form is also suitable where an application is not using any authentication mechanisms and instead is using programatic authentication or even wishes to obtain the SecurityDomain associated with the deployment and use it directly.

An advanced form of the mapping can be added as: -

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

Which results in: -

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

In this form of the configuration instead of referencing a security domain a http-authentication-factory is referenced instead, this is the factory that will be used to obtain the instances of the authentication mechansisms and is in turn associated with the security domain. The standard mechanisms as defined in the Servlet specification can be used in this way but this approach also allows for other mechanisms to be used such as SPNEGO which requires additional configuration or even plug-in custom mechanism implementations.

When the advanced form of the mapping is used a further configuration option is available: -

  • override-deployment-config

The referenced http-authentication-factory can return a complete set of authentication mechanisms, by default these are filtered to just match the mechanisms requested by the application - if this option is set to true then the mechanisms offered by the factory will override the mechanisms requested by the application. One example of where this could be useful is say an application has been developed to support FORM authentication, by overriding the mechanisms the application could be updated to support SPNEGO, and FORM authentication without any modifications to the deployment.

The application-security-domain resource also has one additional option enable-jacc, if this is set to true JACC will be enabled for any deployments matching against this mapping.

5.2.1. Runtime Information

Where an application-security-domain mapping is in use it can be useful to double check that deployments did match against it as expected, if the resource is read with include-runtime=true the deployments that are associated with the mapping will also be shown: -

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

In this output the referencing-deployments attribute shows that the deployment simple-webapp.war has been deployed using this mapping.

5.3. Jakarta Enterprise Beans Subsystem

Configuration can be added to the Jakarta Enterprise Beans subsystem to map a security domain name referenced in a deployment to an Elytron security domain:

/subsystem=ejb3/application-security-domain=MyAppSecurity:add(security-domain=ApplicationDomain)

Which results in:

<subsystem xmlns="urn:jboss:domain:ejb3:5.0">
...
    <application-security-domains>
        <application-security-domain name="MyAppSecurity" security-domain="ApplicationDomain"/>
    </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.

An application-security-domain has two main attributes:

  • name - the name of the security domain as specified in a deployment

  • security-domain - a reference to the Elytron security domain that should be used

There is another attribute called legacy-compliant-principal-propagation. If it is set to true and there is no incoming run-as identity, then the principal of the local unsecured bean is the current authenticated identity. This was the case for legacy PicketBox security. If this attribute is set to false, the behaviour will comply with the Elytron’s previous behaviour and if there is no incoming run-as identity then the principal of the local unsecured bean is anonymous. This attribute is optional and the default value is true.

When an application security domain mapping is configured for a bean in a deployment, this indicates that security should be handled by Elytron.

5.4. WebServices Subsystem

There is adapter in webservices subsystem to make authentication works for elytron security domain automatically. Like configure with legacy security domain, you can configure elytron security domain in deployment descriptor or annotation to secure webservice endpoint.

When Elytron security is enabled, JAAS subject or principal can be pushed to jbossws-cxf endpoint’s SecurityContext to propagate authenticated identity to Jakarta Enterprise Beans container. Here is a CXF interceptor example to propagate authenticated information to Jakarta Enterprise Beans container :

public class PropagateSecurityInterceptor extends WSS4JInInterceptor {

   public PropagateSecurityInterceptor() {
      super();
      getAfter().add(PolicyBasedWSS4JInInterceptor.class.getName());
   }

   @Override
   public void handleMessage(SoapMessage message) throws Fault {
      ...
      final Endpoint endpoint = message.getExchange().get(Endpoint.class);
      final SecurityDomainContext securityDomainContext = endpoint.getSecurityDomainContext();
      //push subject principal retrieved from CXF to ElytronSecurityDomainContext
      securityDomainContext.pushSubjectContext(subject, principal, null)
   }

}

6. Client Authentication with Elytron Client

WildFly Elytron uses the Elytron Client project to enable remote clients to authenticate using Elytron. Elytron Client has the following components:

Component Description

Authentication Configuration

Contains authentication information such as usernames, passwords, allowed SASL mechanisms, and the security realm to use during digest authentication.

MatchRule

Rule used for deciding which authentication configuration to use.

Authentication Context

Set of rules and authentication configurations to use with a client for establishing a connection.

When a connection is established, the client makes use of an authentication context, which gives rules that match which authentication configuration is used with an outbound connection. For example, you could have a rule that use one authentication configuration when connecting to server1 and another authentication configuration when connecting with server2. The authentication context is comprised of a set of authentication configurations and a set of rules that define how they are selected when establishing a connection. An authentication context can also reference ssl-context and can be matched with rules.

To create a client that uses security information when establishing a connection:

  • Create one or more authentication configurations.

  • Create an authentication context by creating rule and authentication configuration pairs.

  • Create a runnable for establishing your connection.

  • Use your authentication context to run your runnable.

When you establish your connection, Elytron Client will use the set of rules provided by the authentication context to match the correct authentication configuration to use during authentication.

You can use one of the following approaches to use security information when establishing a client connection.

IMPORTANT: When using Elytron Client to make Jakarta Enterprise Beans calls, any hard-coded programatic authentication information, such as setting Context.SECURITY_PRINCIPAL in the javax.naming.InitialContext, will override the Elytron Client configuration.

6.1. The Configuration File Approach

The configuration file approach involves creating an XML file with your authentication configuration, authentication context, and match rules.

custom-config.xml

<configuration>
    <authentication-client xmlns="urn:elytron:1.0">
        <authentication-rules>
            <rule use-configuration="monitor">
                <match-host name="127.0.0.1" />
            </rule>
            <rule use-configuration="administrator">
                <match-host name="localhost" />
            </rule>
        </authentication-rules>
        <authentication-configurations>
            <configuration name="monitor">
                <allow-sasl-mechanisms names="DIGEST-MD5" />
                 <use-service-loader-providers />
                 <set-user-name name="monitor" />
                 <credentials>
                     <clear-password password="password1!" />
                 </credentials>
                 <set-mechanism-realm name="ManagementRealm" />
             </configuration>

             <configuration name="administrator">
                <allow-sasl-mechanisms names="DIGEST-MD5" />
                 <use-service-loader-providers />
                 <set-user-name name="administrator" />
                 <credentials>
                     <clear-password password="password1!" />
                 </credentials>
                 <set-mechanism-realm name="ManagementRealm" />
             </configuration>
        </authentication-configurations>
    </authentication-client>
</configuration>

The above example shows how the password for authentication can be specified in the clear. However, it’s also possible to use a masked password instead.

custom-config.xml

<configuration>
    <authentication-client xmlns="urn:elytron:1.4">
        <authentication-rules>
            <rule use-configuration="monitor">
                <match-host name="127.0.0.1" />
            </rule>
            <rule use-configuration="administrator">
                <match-host name="localhost" />
            </rule>
        </authentication-rules>
        <authentication-configurations>
            <configuration name="monitor">
                <allow-sasl-mechanisms names="DIGEST-MD5" />
                 <use-service-loader-providers />
                 <set-user-name name="monitor" />
                 <credentials>
                     <masked-password iteration-count="100" salt="12345678" masked-password="/Nym2s/dssMrabfdIGsZfQ==" />
                 </credentials>
                 <set-mechanism-realm name="ManagementRealm" />
             </configuration>

             <configuration name="administrator">
                <allow-sasl-mechanisms names="DIGEST-MD5" />
                 <use-service-loader-providers />
                 <set-user-name name="administrator" />
                 <credentials>
                     <masked-password iteration-count="100" salt="12345678" masked-password="/Nym2s/dssMrabfdIGsZfQ==" />
                 </credentials>
                 <set-mechanism-realm name="ManagementRealm" />
             </configuration>
        </authentication-configurations>
    </authentication-client>
</configuration>

You can then reference that file in your client’s code by setting a system property when running your client.

$ java -Dwildfly.config.url=/path/to/the.xml .....

IMPORTANT: If you use the The Programmatic Approach, it will override any provided configuration files even if the wildfly.config.url system property is set.

When creating rules, you can look for matches on various parameters such as hostname, port, protocol, or username. A full list of options for MatchRule are available in the Javadocs. Rules are evaluated in the order in which they are configured.

Common Rules

Rule Description

match-domain

Takes a single name attribute specifying the security domain to match against.

match-host

Takes a single name attribute specifying the hostname to match against. For example, the host 127.0.0.1 would match on http://127.0.0.1:9990/my/path .

match-no-user

Matches against URIs with no user.

match-path

Takes a single name attribute specifying the path to match against. For example, the path /my/path/ would match on http://127.0.0.1:9990/my/path .

match-port

Takes a single name attribute specifying the port to match against. For example, the port 9990 would match on http://127.0.0.1:9990/my/path .

match-protocol

Takes a single name attribute specifying the protocol to match against. For example, the protocol http would match on http://127.0.0.1:9990/my/path .

match-purpose

Takes a names attribute specifying the list of purposes to match against.

match-urn

Takes a single name attribute specifying the URN to match against.

match-user

Takes a single name attribute specifying the user to match against.

6.2. The Programmatic Approach

The programatic approach configures all the Elytron Client configuration in the client’s code:

//create your authentication configuration
AuthenticationConfiguration adminConfig =
    AuthenticationConfiguration.empty()
      .useProviders(() -> new Provider[] { new WildFlyElytronProvider() })
      .allowSaslMechanisms("DIGEST-MD5")
      .useRealm("ManagementRealm")
      .useName("administrator")
      .usePassword("password1!");
 
//create your authentication context
AuthenticationContext context = AuthenticationContext.empty();
context = context.with(MatchRule.ALL.matchHost("127.0.0.1"), adminConfig);
 
 
//create your runnable for establishing a connection
Runnable runnable =
    new Runnable() {
      public void run() {
        try {
           //Establish your connection and do some work
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    };
 
//use your authentication context to run your client
context.run(runnable);

The above example shows how the password for authentication can be specified in the clear. However, it’s also possible to use a masked password instead.

//create your authentication configuration
AuthenticationConfiguration adminConfig =
    AuthenticationConfiguration.empty()
      .useProviders(() -> new Provider[] { new WildFlyElytronProvider() })
      .allowSaslMechanisms("DIGEST-MD5")
      .useRealm("ManagementRealm")
      .useName("administrator")
      .usePassword("/Nym2s/dssMrabfdIGsZfQ==", null, null, "100", "12345678", null);
 
//create your authentication context
AuthenticationContext context = AuthenticationContext.empty();
context = context.with(MatchRule.ALL.matchHost("127.0.0.1"), adminConfig);
 
 
//create your runnable for establishing a connection
Runnable runnable =
    new Runnable() {
      public void run() {
        try {
           //Establish your connection and do some work
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    };
 
//use your authentication context to run your client
context.run(runnable);

When adding configuration details to AuthenticationConfiguration and AuthenticationContext, each method call returns a new instance of that object. For example, if you wanted separate configurations when connecting over different hostnames, you could do the following:

//create your authentication configuration
AuthenticationConfiguration commonConfig =
    AuthenticationConfiguration.empty()
      .useProviders(() -> new Provider[] { new WildFlyElytronProvider() })
      .allowSaslMechanisms("DIGEST-MD5")
      .useRealm("ManagementRealm");
 
AuthenticationConfiguration administrator =
    commonConfig
      .useName("administrator")
      .usePassword("password1!");


AuthenticationConfiguration monitor =
    commonConfig
      .useName("monitor")
      .usePassword("password1!");
 
 
//create your authentication context
AuthenticationContext context = AuthenticationContext.empty();
context = context.with(MatchRule.ALL.matchHost("127.0.0.1"), administrator);
context = context.with(MatchRule.ALL.matchHost("localhost"), monitor);

Common Rules

Rule Description

matchLocalSecurityDomain(String name)

This is the same as match-domain in the configuration file approach.

matchNoUser()

This is the same as match-no-user in the configuration file approach.

matchPath(String pathSpec)

This is the same as match-path in the configuration file approach.

matchPort(int port)

This is the same as match-port in the configuration file approach.

matchProtocol(String protoName)

This is the same as match-port in the configuration file approach.

matchPurpose(String purpose)

Create a new rule which is the same as this rule, but also matches the given purpose name.

matchPurposes(String…​ purposes)

This is the same as match-purpose in the configuration file approach.

matchUrnName(String name)

This is the same as match-urn in the configuration file approach.

matchUser(String userSpec)

This is the same as match-user in the configuration file approach.

Also, instead of starting with an empty authentication configuration, you can start with the current configured one by using captureCurrent().

//create your authentication configuration
AuthenticationConfiguration commonConfig = AuthenticationConfiguration.captureCurrent();

Using captureCurrent() will capture any previously established authentication context and use it as your new base configuration. An authentication context is established once it’s been activated by calling run(). If captureCurrent() is called and no context is currently active, it will try and use the default authentication if available. You can find more details about this in The Configuration File Approach, The Default Configuration Approach, and Using Elytron Client with Clients Deployed to WildFly sections.

Using AuthenticationConfiguration.empty() should only be used as a base to build a configuration on top of and should not be used on its own. It provides a configuration that uses the JVM-wide registered providers and enables anonymous authentication.

When specifying the providers on top of the AuthenticationConfiguration.empty() configuration, you can specify a custom list, but most users should use WildFlyElytronProvider() providers.

When creating an authentication context, using the context.with(…​) will create a new context that merges the rules and authentication configuration from the current context with the provided rule and authentication configuration. The provided rule and authentication configuration will appear after the ones in the current context.

6.3. The Default Configuration Approach

The default configuration approach relies completely on the configuration provided by Elytron Client:

//create your runnable for establishing a connection
Runnable runnable =
    new Runnable() {
      public void run() {
        try {
           //Establish your connection and do some work
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    };
 
// run runnable directly
runnable.run();

To provide a default configuration, Elytron Client tries to auto-discover a wildfly-config.xml file on the filesystem. It looks in the following locations:

  • Location specified by the wildfly.config.url system property set outside of the client code.

  • The classpath root directory.

  • The META-INF directory on the classpath.

If it does not find one, it will try and use the default wildfly-config.xml provided in the $WILDFLY_HOME/bin/client/jboss-client.jar.

default wildfly-config.xml

<configuration>
  <authentication-client xmlns="urn:elytron:1.0">
    <authentication-rules>
      <rule use-configuration="default" />
    </authentication-rules>
    <authentication-configurations>
      <configuration name="default">
        <allow-all-sasl-mechanisms />
        <set-mechanism-properties>
          <property key="wildfly.sasl.local-user.quiet-auth" value="true" />
        </set-mechanism-properties>
        <use-service-loader-providers />
      </configuration>
    </authentication-configurations>
  </authentication-client>
</configuration>

6.4. Using Elytron Client with Clients Deployed to WildFly

Clients deployed to WildFly can also make use of Elytron Client. In cases where you have included a wildfly-config.xml with your deployment or the system property has been set, an AuthenticationContext is automatically parsed and created from that file.

To load a configuration file outside of the deployment, you can use the parseAuthenticationClientConfiguration(URI) method. This method will return an AuthenticationContext which you can then use in your client’s code using the The Programmatic Approach.

Additionally, clients will also automatically parse and create an AuthenticationContext from the client configuration provided by the elytron subsystem. The client configuration in the elytron subsystem can also take advantage of other components defined in the elytron subsystem such as credential stores. If client configuration is provided by BOTH the deployment and the elytron subsystem, the elytron subsystem’s configuration is used.

6.5. Client configuration using wildfly-config.xml

Prior to WildFly 11, many WildFly client libraries used different configuration strategies. WildFly 11 introduces a new wildfly-config.xml file which unifies all client configuration in a single place. In addition to being able to configure authentication using Elytron as described in the previous section, a wildfly-config.xml file can also be used to:

6.5.1. Configure Jakarta Enterprise Beans client connections, global interceptors, and invocation timeout

Example configuration:

wildfly-config.xml
<configuration>
...
    <jboss-ejb-client xmlns="urn:jboss:wildfly-client-ejb:3.0">
        <invocation-timeout seconds="10"/>
        <connections>
            <connection uri="remote+http://10.20.30.40:8080"/>
        </connections>
        <global-interceptors>
            <interceptor class="org.jboss.example.ExampleInterceptor"/>
        </global-interceptors>
    </jboss-ejb-client>
...
</configuration>

6.5.2. Configure HTTP client

Example configuration:

wildfly-config.xml
<configuration>
...
    <http-client xmlns="urn:wildfly-http-client:1.0">
        <defaults>
            <eagerly-acquire-session value="true" />
            <buffer-pool buffer-size="2000" max-size="10" direct="true" thread-local-size="1" />
        </defaults>
    </http-client>
...
</configuration>

6.5.3. Configure a remoting endpoint

Example configuration:

wildfly-config.xml
<configuration>
...
    <endpoint xmlns="urn:jboss-remoting:5.0">
        <connections>
            <connection destination="remote+http://10.20.30.40:8080" read-timeout="50" write-timeout="50" heartbeat-interval="10000"/>
        </connections>
    </endpoint>
...
</configuration>

6.5.4. Configure the default XNIO worker

Example configuration:

wildfly-config.xml

<configuration>
...
    <worker xmlns="urn:xnio:3.5">
        <io-threads value="10"/>
        <task-keepalive value="100"/>
        <stack-size value="5000"/>
    </worker>
...
</configuration>

6.5.5. Configure RESTEasy client

RESTEasy client will automatically load credentials, bearer token and SSL context from wildfly-config.xml. Credentials will be used for HTTP Basic authentication and bearer token for Bearer token authentication.

Example configuration:

wildfly-config.xml

<configuration>
    <authentication-client xmlns="urn:elytron:client:1.4">
    ...
        <set-user-name name="administrator" />
        <credentials>
            <bearer-token value="bearerTokenValue"/>
        </credentials>
        <key-stores>
            <key-store name="truststore" type="PKCS12">
                <file name="src/test/resources/org/jboss/resteasy/test/security/client-different-cert.truststore"/>
            </key-store>
        </key-stores>
        <ssl-contexts>
            <ssl-context name="client-context">
                <trust-store key-store-name="truststore"/>
            </ssl-context>
        </ssl-contexts>
        <ssl-context-rules>
            <rule use-ssl-context="client-context"/>
        </ssl-context-rules>
    ...
    </authentication-client>
</configuration>
Note that WildFly client libraries do have reasonable default configuration. Thus, adding configuration for these clients to wildfly-config.xml isn’t mandatory.

7. Elytron and Java Authorization Contract for Containers (JACC)

This document will guide you on how to enable JACC for your deployments using the default policy defined in the Elytron subsystem.

7.1. Defining a JACC Policy Provider

Elytron subsystem provides a built-in policy provider based on JACC specification. This policy provider is active by default in the default configuration.

7.2. Enabling JACC to a Web Deployment

You can enable JACC to web deployments by executing the following command:

[standalone@localhost:9990 /] /subsystem=undertow/application-security-domain=other:add(http-authentication-factory=application-http-authentication,enable-jacc=true)

The command above defines a default security domain for applications if none is provided in jboss-web.xml. In case you already have a application-security-domain defined and just want to enable JACC you can execute a command as follows:

[standalone@localhost:9990 /] /subsystem=undertow/application-security-domain=my-security-domain:write-attribute(name=enable-jacc,value=true)

7.3. Enabling JACC to a EJB Deployment

You can enable JACC to EJB deployments by executing the following command:

[standalone@localhost:9990 /] /subsystem=ejb3/application-security-domain=other:add(security-domain=ApplicationDomain,enable-jacc=true)

The command above defines a default security domain for EJBs. In case you already have an application-security-domain defined and just want to enable JACC you can execute a command as follows:

[standalone@localhost:9990 /] /subsystem=ejb3/application-security-domain=my-security-domain:write-attribute(name=enable-jacc,value=true)
References in this document to Java Authorization Contract for Containers (JACC) refer to Jakarta Authorization unless otherwise noted. References in this document to Enterprise JavaBeans (EJB) refer to the Jakarta Enterprise Beans unless otherwise noted.

8. Elytron and Java Authentication SPI for Containers (JASPI)

Starting from WildFly 15 an implementation of the Servlet profile from the Java Authentication SPI for Containers (JSR-196 / JASPI) is also provided by the WildFly Elytron subsystem allowing tighter integration with the security features provided by WildFly Elytron. This JASPI implementation is available out of the box with minimal steps required to use it for deployments, this section of the documentation describes how to make use of it and the features it provides.

8.1. Activation

Presently it is the Servlet profile from the JASPI specification which is supported by this integration, the following information applies to web applications deployed to WildFly.

For the JASPI integration to be enabled for a web application that web application needs to be associated with either an Elytron http-authentication-factory or a security-domain - by doing this the WildFly Elytron security handlers will be installed for the deployment and the WildFly Elytron security framework activated for the deployment. This is all that is required for a deployment to be 'securable' using a JASPI configuration.

Once the WildFly Elytron security framework is activate for a deployment at the time requests are being handled the globally registered AuthConfigFactory will be queried to identify if an AuthConfigProvider has been registered which should be used for that deployment - if an AuthConfigProvider is found then JASPI authentication will be used instead of the deployments authentication configuration. If no AuthConfigProvider is found then the authentication configuration for the deployment will be used instead, this could mean authentication mechanisms from a http-authentication-factory are used or mechanisms specified in the web.xml are used or it could even mean no authentication is performed if the application does not have any mechanisms defined.

Any updates made to the AuthConfigFactory are immediately available, this means that if an AuthConfigProvider is registered which is a match for an existing application it will start to be used immediately without requiring redeployment of the application.

All web applications deployed to WildFly have a security domain which will be resolved in the following order: -

  1. From the deployment descriptors / annotations of the deployment being deployed.

  2. The value defined on the default-security-domain attribute on the Undertow subsystem.

  3. Default to 'other'.

We assume that this security domain is a reference to a PicketBox security domain so the final step in activation is ensuring this is mapped to WildFly Elytron using an application-security-domain resource in the Undertow subsystem. This mapping can either reference a WildFly Elytron security domain directly or it can reference a http-authentication-factory resource to obtain instances of authentication mechanisms.

e.g.

/subsystem=undertow/application-security-domain=MyAppSecurity:add(security-domain=ApplicationDomain)

or

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

Although this latter form references a http-authentication-factory that in turn will reference a security domain - for both examples the referenced security domain is associated with the deployment.

The minimal steps to enable the JASPI integration are: - . Leave the default-security-domain attribute on the Undertow subsystem undefined so it defaults to 'other'. . Add an application-security-domain mapping from 'other' to a WildFly Elytron security domain.

All deployments that do not specify their own security domain will be assigned this default mapping automatically which will activate the WildFly Elytron handlers and subsequently make JASPI available for that deployment.

The security domain associated with a deployment in these steps is the security domain that will be wrapped in a CallbackHandler to be passed into the ServerAuthModule instances used for authentication.

8.1.1. Additional Options

On the application-security-domain resource two additional attributes have been added to allow some further control of the JASPI behaviour.

  • enable-jaspi - Can be set to false to disable JASPI support for all deployments using this mapping.

  • integrated-jaspi - By default all identities are loaded from the security domain, if set to false ad-hoc identities will be created instead.

8.2. Subsystem Configuration

One way to register a configuration which will result in an AuthConfigProvider being returned for a deployment is to register a jaspi-configuration in the Elytron subsystem.

The following command demonstrates how to add a configuration containing two ServerAuthModule definitions: -

./subsystem=elytron/jaspi-configuration=simple-configuration:add(layer=HttpServlet, application-context="default-host /webctx", \
        description="Elytron Test Configuration", \
        server-auth-modules=[{class-name=org.wildfly.security.examples.jaspi.SimpleServerAuthModule, module=org.wildfly.security.examples.jaspi, flag=OPTIONAL, options={a=b, c=d}}, \
            {class-name=org.wildfly.security.examples.jaspi.SecondServerAuthModule, module=org.wildfly.security.examples.jaspi}])

This results in the following configuration being persisted: -

<jaspi>
    <jaspi-configuration name="simple-configuration" layer="HttpServlet" application-context="default-host /webctx" description="Elytron Test Configuration">
        <server-auth-modules>
            <server-auth-module class-name="org.wildfly.security.examples.jaspi.SimpleServerAuthModule" module="org.wildfly.security.examples.jaspi" flag="OPTIONAL">
                <options>
                    <property name="a" value="b"/>
                    <property name="c" value="d"/>
                </options>
            </server-auth-module>
            <server-auth-module class-name="org.wildfly.security.examples.jaspi.SecondServerAuthModule" module="org.wildfly.security.examples.jaspi"/>
        </server-auth-modules>
    </jaspi-configuration>
</jaspi>

The name attribute is just a name that allows the resource to be referenced in the management model.

The layer and application-context attributes are used when registering this configuration with the AuthConfigFactory - both of these attributes can be omitted allowing wildcard matching. The description attribute is also optional and is used to provide a description to the AuthConfigFactory.

Within the configuration one or more server-auth-module instances can be defined with the following attributes. * class-name - The fully qualified class name of the ServerAuthModule. * module - The module to load the ServerAuthModule from. * flag - The control flag to indicate how this module operates in relation to the other modules. * options - Configuration options to be passed into the ServerAuthModule on initialisation.

Configuration defined in this way is immediately registered with the AuthConfigFactory so any existing deployments using the WildFly Elytron security framework that match against the layer and application-context will immediately start to make use of the configuration.

8.3. Programmatic Configuration

The APIs defined within the JASPI specification allow for applications to dynamically register custom AuthConfigProvider instances, however the specification does not provide the actual implementations to use or a standard way to create instances of the implementations, the WildFly Elytron project contains a simple utility that can be used by deployments to help with this: -

org.wildfly.security.auth.jaspi.JaspiConfigurationBuilder

The following piece of code illustrates how this API can be used to register a similar configuration to the one illustrated in the subsystem.

String registraionId = JaspiConfigurationBuilder.builder("HttpServlet", servletContext.getVirtualServerName() + " " + servletContext.getContextPath())
    .addAuthModuleFactory(SimpleServerAuthModule::new, Flag.OPTIONAL, Collections.singletonMap("a", "b"))
    .addAuthModuleFactory(SecondServerAuthModule::new)
.register();

As an example this code could be executed within the init() method of a Servlet to register the AuthConfigProvider specific for that deployment, in this code example the application context has also been assembled by consulting the ServletContext.

The register method returns the resulting registration ID that can also be used to subsequently remove this registration directly from the AuthConfigFactory.

As with the subsystem configuration this call has an immediate effect and will be live for all web applications using the WildFly Elytron security framework immediately.

8.4. Authentication Process

8.4.1. CallbackHandler

Based on the configuration on the application-security-domain resource in the Undertow subsystem the CallbackHandler passed to the ServerAuthModule in an integrated or non-integrated mode.

8.4.2. Integrated

When operating in integrated mode although the ServerAuthModule instances will be handling the actual authentication the resulting identity will be loaded from the referenced SecurityDomain using the SecurityRealms referenced by that SecurityDomain, it is still possible in this mode to override the roles that will be assigned within the Servlet container.

The advantage of this mode is that ServerAuthModules are able to take advantage of the WildFly Elytron configuration for the loading of identities so identities stored in usual locations such as databases and LDAP can be loaded without the ServerAuthModule needing to be aware of these locations, additionally other WildFly Elytron configuration can be applied such as role and permission mapping. The referenced SecurityDomain can also be referenced in other places such as for SASL authentication or other non JASPI applications all backed by a common repository of identities.

In this mode the CallbackHandlers operate as follows: -

  • PasswordValidationCallback

    The username and password will be used with the SecurityDomain to perform an authentication, if successful there is now an authenticated identity.
  • CallerPrincipalCallback

    This Callback is used to establish the authorized identity / the identity that will be seen once the request reached the web application.
    If an authenticated identity has already been established via the PasswordValidationCallback this Callback is interpreted as a run-as request and authorization checks are performed to ensure the authenticated identity is authorized to run as the identity specified in this Callback.  If no authenticated identity has been established by a PasswordValidationCallback it is assumed the ServerAuthModule has handled the authentication step so this Callback will cause the specified identity to be loaded from the SecurityDomain and an authorization check to verify this identity has the LoginPermission.
    If a Callback is received with a null Principal and name then if an authenticated identity has already been established authorization will be performed as that identity, if no identity has been established then authorization of the anonymous identity will be performed.  Where authorization of the anonymous identity is performed the SecurityDomain must have been configured to grant the anonymous identity the LoginPermission otherwise authorization will fail.
  • GroupPrincipalCallback

    By default in this mode the attribute loading, role decoding, and role mapping configured on the security domain will be used to establish the identity - if this Callback is received the groups specified will be taken as the roles that will be assigned to the identity whilst the request is in the servlet container.  These roles will be visible in the servlet container only.

8.4.3. Non Integrated

When operating in non-integrated mode the ServerAuthModules are completely responsible for all authentication AND identity management, the Callbacks specified in the specification can be used to establish an identity. The resulting identity will be created on the SecurityDomain but it will be independent of any identities stored in referenced SecurityRealms.

The advantage of this mode is that JASPI configurations that are able to 100% handle the identities can be deployed to the application server without requiring anything beyond a simple SecurityDomain definitions, there is no need for this SecurityDomain to actually contain the identities that will be used at runtime. The disadvantage of this mode is that the ServerAuthModule is now reposible for all identity handling potenitally making the implementation much more complex.

In this mode the CallbackHandlers operate as follows: -

  • PasswordValidationCallback

    The Callback is not supported in this mode, the purpose of this mode is for the ServerAuthModule to operate independently of the referenced SecurityDomain so requesting a password to be validated would not be suitable.
  • CallerPrincipalCallback

    This Callback is used to establish the Principal for the resulting identity, as the ServerAuthModule is handling all of the identity checking requirements no checks are performed to verify if the identity exists in the security domain and no authorization checks are performed.
    If a Callback is received with a null Principal and name then then the identity will be established as the anonymous identity, as the ServerAuthModule is making the decisions no authorizaton check will be performed with the SecurityDomain.
  • GroupPrincipalCallback

    As the identity is created in this mode without loading from the SecurityDomain it will by default have no roles assigned, if this Callback is received the groups will be taken and assigned to the resulting identity whilst the request is in the servlet container. These roles will be visible in the servlet container only.

8.4.4. validateRequest

During the call to validateRequest on the ServerAuthContext the individual ServerAuthModule instances will be called in the order they are defined. A control flag can also be specified for each module, this defines how the response should be interpreted and if processing should continue to the next auth module or return immediately.

Control Flags

Where the configuration was provided either within the WildFly Elytron subsystem or using the JaspiConfigurationBuilder API it is possible to associate a control flag with each ServerAuthModule - if one is not specified we assume REQUIRED. The flags have the following meanings depending on their result.

Flag

AuthStatus.SEND_SUCCESS

AuthStatus.SEND_FAILURE, AuthStatus.SEND_CONTINUE

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.

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.

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.

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.

For all ServerAuthModule instances if they throw an AuthException an error will be immediately reported to the client without further modules being called.

8.4.5. secureResponse

During the call to secureResponse each ServerAuthMdoule is called but this time in reverse order. Where a module only undertakes an action in secureResponse if it undertook an action in validateResponse it is the responsibility of the module to track this.

The control flag has no effect on secureResponse processing, processing ends when one of the following is true: - . All of the ServerAuthModule instances have been called. . A module returns AuthStatus.SEND_FAILURE. . A module throws an AuthException.

8.4.6. SecurityIdentity

Once the authentication process has completed a org.wildfly.security.auth.server.SecurityIdentity for the deployments SecurityDomain will have been created as a result of the Callbacks to the CallbackHandler, depending on the Callbacks this will either be an identity loaded directly from the SecurityDomain or will be an ad-hoc identity described by the callbacks. This SecurityIdentity will be associated with the request as we do for other authentication mechanisms

9. Elytron and Jakarta EE Security

Starting from WildFly 15 an implementation of the Servlet Profile from Java Authentication SPI for Containers (JSR-196 / JASPI) specification has been included in the application server, in addition to making JASPI available for deployments using WildFly Elytron it also makes it possible to make use of EE Security with WildFly Elytron.

9.1. Activation

EE Security with WildFly Elytron is available out of the box with just a couple of small steps required.

9.1.1. Define JACC Policy

The SecurityContext API makes use of JACC to access the current authenticated identity, if deployments are going to make use of this API then a JACC policy needs to be activated in the WildFly Elytron subsystem. If this API is not being used then the activation can be skipped.

/subsystem=elytron/policy=jacc:add(jacc-policy={})
:reload

As is shown in the example no specific configuration is required other than the presence of a default JACC policy, additionally after making these changes the server needs to be reloaded to ensure the new policy activates correctly.

9.1.2. Add application-security-domain mapping

As with all deployments a mapping is required from the security domain defined for the deployment to either a WildFly Elytron security domain or http authentication factory to activate security backed by WildFly Elytron.

All web applications deployed to WildFly have a security domain which will be resolved in the following order: -

  1. From the deployment descriptors / annotations of the deployment being deployed.

  2. The value defined on the default-security-domain attribute on the Undertow subsystem.

  3. Default to 'other'.

An application-security-domain resource then needs to be added to map from the deployment’s security domain.

The simplest approach is to add a mapping for other, then no further configuration will be required and provided the deployment does not define it’s own security domain and provided no alternative default is specified all deployments will match this mapping.

/subsystem=undertow/application-security-domain=other:add(security-domain=ApplicationDomain, integrated-jaspi=false)

The EE Security API is built on JASPI. Within JASPI we support two different modes of operation 'integrated', and 'non-integrated'. In integrated mode any identity being established during authentication is expected to exist in the associated security domain. With the EE Security APIs however it is quite likely an alternative store will be in use so configuration the mapping to use 'non-integrated' JASPI allows for identities to be dynamically created as required.

References in this document to Java Authorization Contract for Containers (JACC) refer to the Jakarta Authorization unless otherwise noted

10. Using Keycloak SAML

To secure applications deployed to WildFly using Keycloak SAML, a Galleon feature pack provided by the Keycloak project needs to be used. This feature pack automatically installs the Keycloak SAML adapter that results in the keycloak-saml subsystem being added to WildFly.

There are a few ways to install the Keycloak Galleon Feature Pack that provides the SAML adapter.

10.1. Galleon CLI

A server installation can be provisioned using the Galleon CLI.

More information can be found in the Provisioning WildFly with Galleon documentation, but assuming you have provisioned a WildFly installation containing the web-server layer with a command similar to the following:

galleon.sh install wildfly:current \
    --layers=web-server --dir=wildfly

The Keycloak SAML adapter can then be added to the WildFly installation with the following command:

galleon.sh install org.keycloak:keycloak-saml-adapter-galleon-pack:22.0.1 --layers=keycloak-client-saml --dir=wildfly

Unlike the WildFly feature pack, this Keycloak feature pack is not part of a universe and so a fully qualified group:artifact:version reference to the feature pack is required.

From this point, applications can be configured as described in the Keycloak documentation.

10.2. Bootable JAR

The next installation option is if you are creating a bootable JAR for your deployment and want to add the Keycloak SAML adapter to secure the deployment. More details about bootable JAR support can be found in the Bootable JAR Guide.

The following is an example plug-in configuration to create a bootable JAR for a web application using both the web-server and keycloak-client-saml layers:

<plugin>
    <groupId>org.wildfly.plugins</groupId>
    <artifactId>wildfly-jar-maven-plugin</artifactId>
    <version>${version.wildfly.jar.maven.plugin}</version>
    <configuration>
        <feature-packs>
            <feature-pack>
                <location>wildfly@maven(org.jboss.universe:community-universe):current</location>
            </feature-pack>
            <feature-pack>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-saml-adapter-galleon-pack</artifactId>
                <version>22.0.1</version>
            </feature-pack>
        </feature-packs>
        <layers>
            <layer>web-server</layer>
            <layer>keycloak-client-saml</layer>
        </layers>
        <context-root>simple-webapp</context-root>
        <cli-sessions>
            <cli-session>
                <script-files>
                    <script>configure-saml.cli</script>
                </script-files>
            </cli-session>
        </cli-sessions>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>package</goal>
            </goals>
        </execution>
    </executions>
</plugin>

As with using the CLI, the latest version of WildFly in the universe can be dynamically selected but the Keycloak feature pack requires the complete groupId, artifactId, and version to be specified.

The approaches to configure the web application are the same as described in the Keycloak documentation. In this example, a CLI script, configure-saml.cli, is executed to update the Keycloak SAML subsystem with relevant configuration.

A sample script is shown below:

/subsystem=keycloak-saml/secure-deployment=simple-webapp.war/:add

/subsystem=keycloak-saml/secure-deployment=simple-webapp.war/SP="http://localhost:8090/simple-webapp"/:add(sslPolicy=EXTERNAL,logoutPage="logout")

/subsystem=keycloak-saml/secure-deployment=simple-webapp.war/SP="http://localhost:8090/simple-webapp"/IDP=idp/:add( \
    SingleSignOnService={ \
        signRequest=false, \
        validateResponseSignature=false, \
        validateAssertionSignature=false, \
        requestBinding=POST, \
        bindingUrl=http://localhost:8080/realms/myrealm/protocol/saml}, \
    SingleLogoutService={ \
        signRequest=false, \
        signResponse=false, \
        validateRequestSignature=false, \
        validateResponseSignature=false, \
        requestBinding=POST, \
        responseBinding=POST, \
        postBindingUrl=http://localhost:8080/realms/myrealm/protocol/saml, \
        redirectBindingUrl=http://localhost:8080/realms/myrealm/protocol/saml} \
)

Instead of adding configuration in the Keycloak SAML subsystem, configuration can be added in the deployment instead. In particular, the auth-method of the web application could be set to KEYCLOAK-SAML and adapter configuration could be provided in a keycloak-saml.json descriptor placed within the WEB-INF directory of the deployment.

10.3. WildFly Maven Plugin

The final installation option is to use the wildfly-maven-plugin to provision a server containing the Keycloak SAML subsystem.

The following is an example plug-in configuration to create a server for a web application using both the web-server and keycloak-client-saml layers:

<plugin>
    <groupId>org.wildfly.plugins</groupId>
    <artifactId>wildfly-maven-plugin</artifactId>
    <version>4.0.0.Final</version>
    <configuration>
        <feature-packs>
            <feature-pack>
                <location>wildfly@maven(org.jboss.universe:community-universe):current</location>
            </feature-pack>
            <feature-pack>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-saml-adapter-galleon-pack</artifactId>
                <version>22.0.1</version>
            </feature-pack>
        </feature-packs>
        <layers>
            <layer>web-server</layer>
            <layer>keycloak-client-saml</layer>
        </layers>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>package</goal>
            </goals>
        </execution>
    </executions>
</plugin>

As with the bootable JAR configuration, note that the latest version of WildFly in the universe can be dynamically selected but the Keycloak feature pack requires the complete groupId, artifactId, and version to be specified.

10.4. Propagating the Security Context to EJBs

The sample configuration in the above sections has referenced the keycloak-client-saml layer. If the security context for the application that is being secured with Keycloak SAML needs to be propagated to the EJB tier, the keycloak-client-saml-ejb layer should be used instead. This layer adds an application-security-domain mapping in the EJB3 subsystem to map the default security domain name other to the KeycloakDomain that is installed by the Keycloak feature pack.

11. Using Keycloak with WildFly Elytron

The ability to secure applications using OpenID Connect is provided by the elytron-oidc-client subsystem. For details about securing applications deployed on WildFly with OpenID Connect, by using Keycloak as the OpenID provider, see the Elytron OIDC Client documentation.

12. Bearer Token Authorization

Bearer Token Authorization is the process of authorizing HTTP requests based on the existence and validity of a bearer token representing a subject and his access context, where the token provides valuable information to determine the subject of the call as well whether or not a HTTP resource can be accessed.

Elytron supports bearer token authorization by providing a BEARER_TOKEN HTTP authentication mechanism based on RFC-6750 and an specific realm implementation, the token-realm, to validate tokens using the JWT format (for instance, OpenID Connect ID Tokens) or opaque tokens issued by any OAuth2 compliant authorization server.

12.1. How it Works

When a HTTP request arrives to your application, the BEARER_TOKEN mechanism will check if a bearer token was provided by checking the existence of an Authorization HTTP header with the following format:

GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM

If no bearer token was provided, the mechanism will respond with a 401 HTTP status code as follows:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example"

When a bearer token is provided, the mechanism will extract the token from the request (in the example above, the token is represented by the string mF_9.B5f-4.1JqM) and pass it over to the token-realm in order to check if the token is valid and can be used to build a security context based on its information. Note that the BEARER_TOKEN mechanism is only responsible to check and extract bearer tokens from an HTTP request, whereas the token-realm is the one responsible for validating the token.

If validation succeeds, a security context will be created based on the information represented by the token and the application can use the newly created security context to obtain information about the subject making the request as well decide whether or not the request should be full filled. In case the validation fails, the mechanism will respond with a 403 HTTP status code as follows:

HTTP/1.1 403 Forbidden

12.2. Validating JWT Tokens

Elytron provides built-in support for JWT tokens, which can be enabled by defining a realm in the Elytron subsystem as follows:

 <token-realm name="JwtRealm" principal-claim="sub">
    <jwt issuer="as.example.com"
         audience="api.example.com"
         public-key="-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB-----END PUBLIC KEY-----"/>
 </token-realm>

In the example above, the token-realm is defined with a principal-claim attribute. This attribute specifies which claim within the token should be used to identity the principal. If you have a token as follows:

 {
   "iss": "as.example.com",
   "sub": "24400320",
   "aud": "s6BhdRkqt3",
   "exp": 1311281970,
   "nbf": 1311280970,
  }

Elytron will use the value associated with the sub claim as the identifier of the subject represented by the token.

The jwt element within the token-realm specifies that tokens should be validated as JWT and provides different configuration options on how they should be validated:

  • issuer

    A list of strings representing the issuers supported by this configuration. During validation JWT tokens must have an "iss" claim that contains one of the values defined here

  • audience

    A list of strings representing the audiences supported by this configuration. During validation JWT tokens must have an "aud" claim that contains one of the values defined here

  • public-key

    A public key in PEM Format. During validation, if a public key is provided, signature will be verified based on the key you provided here. Alternatively, you can define a key-store and certificate attributes to configure the public key. This key will be used to verify tokens without "kid" claim.

  • key-store

    As an alternative to public-key, you can also define a key store from where the certificate with a public key should be loaded from

  • certificate

    The name of the certificate with a public key to load from the key store in case you defined the key-store attribute

  • client-ssl-context

    The SSL context to be used if if you want to use remote JSON Web Keys. This enables you to use url from "jku" token claim to fetch public keys for token verification.

  • host-name-verification-policy

    A policy that defines how host names should be verified when using remote JSON Web Keys. Allowed values: "ANY", "DEFAULT".

For being able to use different key pairs for signing / verification and for easier key rotation you can define key map. This will take the "kid" claim from the token and use corresponding public key for verification.

 <token-realm name="JwtRealm" principal-claim="sub">
    <jwt issuer="as.example.com" audience="api.example.com">
        <key kid="1" public-key="-----BEGIN PUBLIC KEY-----MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANc4VlnN6oZwe1PoQQeJsTwu7LGS+eEbgYMNYXahidga4+BhdGKwzMZU54ABFQ11tUMJSENQ6o3n1YKVgMnxvcMCAwEAAQ==-----END PUBLIC KEY-----"/>
        <key kid="2" public-key="-----BEGIN PUBLIC KEY-----MFswDQYJKoZIhvcNAQEBBQADSgAwRwJAcNpXy6psxC21DdnTtAdlgsEwEuJh/earH3q7xJPjmsygmrlpC66MG4/A/J9Gai2Hp+QdCSEVpBWkIoVff3sIlwIDAQAB-----END PUBLIC KEY-----"/>
    </jwt>
 </token-realm>

This token-realm will verify tokens with "kid" claims of value "1" or "2" using corresponding key.

Tokens without "kid" claim will be verified using the 'public-key' attribute of 'jwt', or in this case invalidated, as there is no key specified.

The jwt validator performs different checks in order to determine the validity of a JWT:

  • Expiration checks based on the values of the exp and nbf claims

  • Signature checks based on the public key provided via public-key or certificate attributes, key map with named public keys, or by fetching remote JSON Web Key Set using provided client-ssl-context. You can skip token signature verification by not defining any of these.

It is strongly recommended that you use signed JWTs in order to guarantee authenticity of tokens and make sure they were not tampered.

During JWT validation, if the jku header is present in a token, it must exactly match one of the values from a configured string of allowed values or token validation will fail. The allowed jku values for a token-realm can be specified as a space separated string using the wildfly.elytron.jwt.allowed.jku.values.<realm-name> system property.

As an example, to set the allowed jku values for the JwtRealm to https://somehost.com and https://someotherhost.com, we’d need to set the wildfly.elytron.jwt.allowed.jku.values.JwtRealm system property to "https://somehost.com https://someotherhost.com".

12.3. Validating OAuth2 Bearer Tokens

Elytron provides built-in support for tokens issued by an OAuth2 compliant authorization server, where these tokens are validated using a token introspection endpoint as defined by OAuth2 specification.

<token-realm name="OAuth2Realm" principal-claim="sub">
    <oauth2-introspection client-id="my-client-id"
                          client-secret="keep_it_secret"
                          introspection-url="https://as.example.com/token/introspect"
                          client-ssl-context="user-defined-ssl-context"
                          host-name-verification-policy="ANY" />
</token-realm>

The auth2-introspection element within the token-realm specifies that tokens should be validated using an OAuth2 Token Introspection Endpoint and provides different configuration options on how they should be validated:

  • client-id

    The identifier of the client on the OAuth2 Authorization Server

  • client-secret

    The secret of the client

  • introspection-url

    The URL of token introspection endpoint

  • client-ssl-context

    The SSL context to be used if the introspection endpoint is using HTTPS.

  • host-name-verification-policy

    A policy that defines how host names should be verified when using HTTPS. Allowed values: "ANY", "DEFAULT".

12.5. CLI Examples on How to Create a Token Realm

Create a Token Realm to validate JWT tokens using a key store to retrieve the public key
# Create a Key Store
/subsystem=elytron/key-store=my-keystore:add(path=/path/to/keystore.pkcs12,credential-reference={clear-text=secret},type=PKCS12)

# Create the realm
/subsystem=elytron/token-realm=jwt-realm:add(principal-claim=sub, jwt={issuer=["as.example.com"], audience=["api.example.com"], key-store=my-keystore, certificate=as.example.com})
Create a Token Realm to validate OAuth2 tokens
# Create a Client SSLContext
/subsystem=elytron/key-store=default-trust-store:add(path=/path/to/keystore.pkcs12,credential-reference={clear-text=secret},type=PKCS12)
/subsystem=elytron/trust-manager=default-trust-manager:add(algorithm=PKIX, key-store=default-trust-store)
/subsystem=elytron/client-ssl-context=default-client-ssl-context:add(trust-manager=default-trust-manager)

# Create the realm
/subsystem=elytron/token-realm=oauth2-realm:add(principal-claim=preferred_username, oauth2-introspection={client-id=my-client-id, client-secret=keep_it_secret, client-ssl-context=default-client-ssl-context, introspection-url=https://as.example.com/token/introspect})

13. OpenSSL

13.1. Configuring WildFly to use the OpenSSL TLS provider

There are a couple ways you can configure WildFly to use the OpenSSL TLS provider.

13.1.1. Configure the Elytron subsystem to use the OpenSSL TLS provider by default

The following commands can be used to configure the Elytron subsystem so that the OpenSSL TLS provider is used by default:

/subsystem=elytron:write-attribute(name=initial-providers, value=combined-providers)
/subsystem=elytron:undefine-attribute(name=final-providers)
reload

13.1.2. Configure the OpenSSL TLS provider on an SSL context

Instead of configuring the Elytron subsystem to use the OpenSSL TLS provider by default, it is also possible to specify that the OpenSSL TLS provider should be used for a specific SSL context. For example, the following command can be used to create a server-ssl-context that will use the OpenSSL TLS provider:

/subsystem=elytron/server-ssl-context=httpsSSC:add(providers=openssl, ...)
reload

13.2. OpenSSL Library Location

WildFly will search for the OpenSSL library using the standard system library search path. If you’d like to specify a custom location for the OpenSSL library, the org.wildfly.openssl.path property can be specified during WildFly startup.

If OpenSSL was loaded successfully, a message similar to the one below will occur in the server.log file on startup:

15:37:59,814 INFO [org.wildfly.openssl.SSL] (MSC service thread 1-7) WFOPENSSL0002 OpenSSL Version OpenSSL 1.1.1d FIPS  10 Sep 2019

13.3. Enabling TLSv1.3

WARNING It is possible to use TLSv1.3 with the OpenSSL TLS provider when using OpenSSL 1.1.1 or higher and when running against JDK 11 or higher. However, if JDK 11 is in use and if there is a very large number of TLSv1.3 requests being made, it is possible that a drop in performance (throughput and response time) will occur compared to TLSv1.2. For this reason, the use of TLSv1.3 is currently disabled by default. TLSv1.3 can be enabled by configuring the cipher-suite-names attribute for an SSL context. See the sections on Configuring a server SSLContext and Configuring a client SSLContext for more details. It is recommended to test for performance degradation prior to enabling TLSv1.3 in a production environment.

13.4. Adding Additional Native Libraries

By default, WildFly includes WildFly OpenSSL native libraries for commonly used platforms (Linux x86_64, Windows x86_64, Mac OS X x86_64, and Linux s390). However, the WildFly OpenSSL Natives project contains modules that can be used to build WildFly OpenSSL native libraries for other platforms as well. The steps needed to build and make use of a WildFly OpenSSL native library for a different platform than the ones provided by default will be described below.

13.4.1. Building the Native Library

Make sure you have cloned the WildFly OpenSSL Natives project locally. We’ll use $WILDFLY_OPENSSL_NATIVES to denote the path to this project. Next, cd to the module that you want to build. In this example, we’ll use the linux-i386 module but similar steps can be used to build and make use of another module instead.

cd $WILDFLY_OPENSSL_NATIVES/linux-i386
mvn clean install

Notice that this now results in a wildfly-openssl-linux-i386-VERSION.jar in the $WILDFLY_OPENSSL_NATIVES/linux-i386/target directory. We’ll make use of this in the next step.

13.4.2. Overriding the Existing WildFly OpenSSL Module

We’re going to add a new module that will override the existing WildFly OpenSSL module that’s found in the $WILDFLY_HOME/modules/system/layers/base/org/wildfly/openssl directory. Notice that the existing module contains a Java artifact in its main directory and native libraries for the default platforms in its main/lib directory.

First, create a new module that contains the WildFly OpenSSL Java artifact from the existing module:

module add --name=org.wildfly.openssl --resources=$WILDFLY_HOME/modules/system/layers/base/org/wildfly/openssl/main/wildfly-openssl-java-VERSION.jar --dependencies=java.logging,jdk.unsupported

Next, add the native library that we already built to this newly created module:

mkdir $WILDFLY_HOME/modules/org/wildfly/openssl/main/lib
cd $WILDFLY_HOME/modules/org/wildfly/openssl/main/lib
jar xvf $WILDFLY_OPENSSL_NATIVES/linux-i386/target/wildfly-openssl-linux-i386-VERSION.jar linux-i386

You should now see a libwfssl.so file in the $WILDFLY_HOME/modules/org/wildfly/openssl/main/lib/linux-i386 directory.

That’s it, WildFly will now make use of the newly added WildFly OpenSSL module instead of the existing one, allowing it to make use of the new native library when running on the Linux i386 platform. Additional native libraries can be added to the $WILDFLY_HOME/modules/org/wildfly/openssl/main/lib directory if desired.

14. Web Single Sign-On

This document will guide on how to enable single sign-on across different applications deployed into different servers, where these applications belong to same security domain.

14.1. Create a Server Configuration Template

For this document, you’ll need to run at least two server instances in order to check single sign-on and how it affect usability in your applications. Users should be able to log in once and have access to any application using the same security domain.

All configuration described in the next sections should be done with a server instance using standalone-ha.xml (or standalone-full-ha.xml).

Run a server instance using the following command:

bin/standalone.sh -c standalone-ha.xml

14.1.1. Create a HTTP Authentication Factory

If you already have a http-authentication-factory defined in Elytron subsystem and just want to use it to enable single sign-on to your applications, please skip this section.{info} First, you need a security-domain which we’ll use to authenticate users. Please, execute the following CLI commands:
# Creates a FileSystem Realm, an identity store where users are stored in the local filesystem
/subsystem=elytron/filesystem-realm=example-realm:add(path=/tmp/example-realm)

# Creates a Security Domain
/subsystem=elytron/security-domain=example-domain:add(default-realm=example-realm, permission-mapper=default-permission-mapper,realms=[{realm=example-realm, role-decoder=groups-to-roles}]

# Creates an user that you can use to access your applications
/subsystem=elytron/filesystem-realm=example-realm:add-identity(identity=alice)
/subsystem=elytron/filesystem-realm=example-realm:add-identity-attribute(identity=alice, name=groups, value=["user"])
/subsystem=elytron/filesystem-realm=example-realm:set-password(identity=alice, clear={password=alice})

Now you can create a http-authentication-factory that you’ll use to actually protect your web applications using Undertow:

# Create a Http Authentication Factory
/subsystem=elytron/http-authentication-factory=example-http-authentication:add(security-domain=example-domain, http-server-mechanism-factory=global, mechanism-configurations=[{mechanism-name=FORM}]

14.1.2. Create a Application Security Domain in Undertow

If you already have a _application-security-domain_ defined in Undertow subsystem and just want to use it to enable single sign-on to your applications, please skip this section.

In order to protect applications using the configuration defined in Elytron subsystem, you should create a application-security-domain definition in Undertow subsystem as follows:

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

By default, if your application does not define any specific security-domain in jboss-web.xml, the application server will choose one with a name other.

14.1.3. Create a Key Store

In order to create a key-store in Elytron subsystem, first create a Java Key Store as follows:

keytool -genkeypair -alias localhost -keyalg RSA -keysize 2048 -validity 365 -keystore keystore.pkcs12 -dname "CN=localhost" -keypass secret -storepass secret

Once the keystore.pkcs12 file is created, execute the following CLI commands to create a key-store definition in Elytron:

/subsystem=elytron/key-store=example-keystore:add(path=keystore.pkcs12, relative-to=jboss.server.config.dir, credential-reference={clear-text=secret}, type=PKCS12)

14.1.4. Enable Single Sign-On

Single Sign-On is enabled to a specific application-security-domain definition in Undertow subsystem. It is important that the servers you will be using to deploy applications are using the same configuration.

To enable single-sign on, just change an existing application-security-domain in Undertow subsystem as follows:

/subsystem=undertow/application-security-domain=other/setting=single-sign-on:add(key-store=example-keystore, key-alias=localhost, domain=localhost, credential-reference={clear-text=secret})

After restarting the servers, users should be able to log in once and have access to any application using the same application-security-domain.

14.2. Create Two Server Instances

All configuration you did so far should be reflected in $JBOSS_HOME/standalone/standalone-ha.xml. You can now create two distinct server configuration directories_:\_

cp -r standalone standalone-a
cp -r standalone standalone-b

And you can run the two instances using the command below:

$JBOSS_HOME/bin/standalone.sh -c standalone-ha.xml -Djboss.node.name=node-a -Djboss.socket.binding.port-offset=200 -Djboss.server.base.dir=$JBOSS_HOME/standalone-a
$JBOSS_HOME/bin/standalone.sh -c standalone-ha.xml -Djboss.node.name=node-b -Djboss.socket.binding.port-offset=300 -Djboss.server.base.dir=$JBOSS_HOME/standalone-b ----

14.3. Deploy an Application

For the sake of simplicity, these are the minimum files you need in your application:

WEB-INF/web.xml
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">

    <security-constraint>
        <display-name>SecurityConstraint</display-name>
        <web-resource-collection>
            <web-resource-name>All Resources</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>user</role-name>
        </auth-constraint>
    </security-constraint>

    <login-config>
        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/login.html</form-login-page>
            <form-error-page>/login.html</form-error-page>
        </form-login-config>
    </login-config>

    <security-role>
        <role-name>user</role-name>
    </security-role>
</web-app>
login.html
<html>
    <body>
        <form method="post" action="j_security_check">
            <input type="text" name="j_username">
            <input type="password" name="j_password">
            <input type="submit" value="Log In">
        </form>
    </body>
</html>
Make sure you have at least a welcome file (e.g.: index.html\|jsp).

Deploy your application into both server instances and try to log in using the user you created at the beginning of this document:

  • Username: alice

  • Password: alice

15. Audit

WildFly Elytron supports audit using security event listeners - components which captures security events, like successful or unsuccessful login attempts.

15.1. File audit log

File audit log logs security events into a local file. It requires to define path to the log file, which can be relative-to a system property. It also allows to set the file format - human readable SIMPLE or JSON. The encoding used by the audit file can be set using encoding. The default value is UTF-8. Possible values: UTF-8, UTF-16BE, UTF-16LE, UTF-16, US-ASCII or ISO-8859-1. While in WildFly 14 there was only one attribute synchronized, which had influence on data reliability, now there is possible more fine grained performance and reliability tunning: * autoflush defines whether should be output stream flushed after every audit event (guarantees that the log message is passed to the operating system immediately) * synchonized defines whether should be file descriptor synchronized after every audit event (guarantees that all system buffers are synchronized with the underlying device)

15.2. Syslog Audit Logging

Syslog audit logging logs security events to a syslog server using a transmission protocol. WildFly Elytron supports using UDP, TCP, or TCP with SSL, with the latter protocol requiring a SSLContext to be defined. When syslog audit logging is first defined, Elytron will send an INFORMATIONAL priority event to the defined syslog server containing the message "Elytron audit logging enabled with RFC format: <format>", where <format> is the RFC format that the audit logging handler has been defined with, defaulting to RFC5424. If the given syslog server is not defined, resulting in Elytron being unable to send the message, then Elytron will keep track of the amount of attempts that sending a message has failed, up to a maximum defined by the reconnect-attempts parameter, before closing the endpoint and reporting an error. It is possible to define this value as infinite, by specifying -1, in which case Elytron will never close the endpoint and so will always attempt to send audit messages despite previous failures.

15.2.1. Required Parameters

Parameter Value

server-address

String consisting of the IP address, or a name recognized by Java’s InetAddress.getByName(), of the syslog server

port

int of the listening port on the syslog server

host-name

String of the host name that will be embedded into all events sent to the syslog server

ssl-context

The SSLContext used to connect to the syslog server, only required if transport is set to SSL_TCP

15.2.2. Optional Parameters

Parameter Description Possible Values Default Value

transport

The transport protocol to be used to connect to the syslog server

String of: UDP, TCP, or SSL_TCP

TCP

format

The format that audit events should be recorded in

String of SIMPLE or JSON

SIMPLE

syslog-format

The RFC format to be used for describing the audit event

String of RFC5424 or RFC3164

RFC5424

reconnect-attempts

The maximum number of times that elytron will attempt to send successive messages to a syslog server before closing the endpoint to disallow further attempts to send messages.

int of -1 (infinite attempts) or higher

0

15.2.3. Defining Syslog Audit Logging

Syslog audit logging can be defined under the elytron subsystem resource. Some examples syslog audit logging resources can be created with the following commands:

Minimum Resource Definition

Using the following command will generate a syslog audit logging resource that connects with TCP, records audit events in a simple format, and uses RFC5424 to describe the audit event.

/subsystem=elytron/syslog-audit-log=syslog-example:add(
    server-address=127.0.0.1,
    port=10857,
    host-name=localhost
)
Default Format and Reconnect Attempts with UDP and RFC3164

Using the following command will generate a syslog audit logging resource that connects with UDP, does not send any further messages to the syslog server if there is an error sending, records audit events in a simple format, and uses RFC3164 to describe the audit event.

/subsystem=elytron/syslog-audit-log=syslog-example:add(
    server-address=127.0.0.1,
    port=10837,
    host-name=localhost,
    transport=UDP,
    syslog-format=RFC3164
)
Full UDP Definition with RFC5424 Explicitly Set and 10 Reconnect Attempts

Using the following command will generate a syslog audit logging resource that connects with UDP, attempts to send messages 10 times if there is an error sending before no longer sending messages, records audit events in a simple format, and uses RFC5424 to describe the audit event.

/subsystem=elytron/syslog-audit-log=syslog-example:add(
    server-address=127.0.0.1,
    port=10837,
    host-name=localhost,
    transport=UDP,
    syslog-format=RFC5424,
    reconnect-attempts=10
)
Full UDP Definition with Infinite Reconnect Attempts and JSON Format

Using the following command will generate a syslog audit logging resource that connects with UDP, always attempts to send messages despite previous failures sending messages, records audit events in a JSON format, and uses RFC5424 to describe the audit event.

/subsystem=elytron/syslog-audit-log=syslog-example:add(
    server-address=127.0.0.1,
    port=10837,
    host-name=localhost,
    transport=UDP,
    format=JSON,
    reconnect-attempts=-1
)

16. Credential Store

One new component included with WildFly Elytron for the secure storage of credentials is the Credential Store. Previous versions of the application server made use of the Vault which was used for the secure storage of clear text strings; the credential store moves forward a step to focus on the secure storage of credentials. As with the Vault the stored credentials could be clear text passwords however other formats are also supported.

WildFly Elytron contains two default credential store implementations. The first implementation is the KeyStoreCredentialStore which is an implementation backed by a KeyStore. The KeyStoreCredentialStore implementation supports the storage of various credential types, PasswordCredential, KeyPairCredential, and SecretKeyCredential. The second implementation is the PropertiesCredentialStore. This credential store is dedicated to the storage of SecretKeyCredential instances using a properties file. The PropertiesCredentialStore does not offer any protection of the credentials it stores so its primary purpose is to provide an initial key to a server environment.

This documentation is primarily focused on the KeyStoreCredentialStore and PropertiesCredentialStore; however the section Custom CredentialStore describes the SPIs for implementing a custom credential store and the section Migrating Existing Vaults describes how to convert a vault to a credential store.

It is possible to operate on credential stores using either a standalone command line tool elytron-tool.sh which is included with WildFly or by using management operations through the jboss-cli.sh, management console or other management client calling the management interface directly.

The elytron-tool.sh script to execute the command line tool can be found within the bin folder of the application server installation.

The WildFly Elytron tool supports a number of commands, one of which being credential-store which operates on a credential store. The commands in this documentation are making use of the .sh script on linux; the elytron-tool.bat and elytron-tools.ps1 scripts can be used on Microsoft Windows.

The following command provides a summary of the command line arguments which can be used with the credential-store command:

]$ bin/elytron-tool.sh credential-store

16.1. Credential Store Creation

16.1.1. KeyStoreCredentialStore / credential-store

The KeyStoreCredentialStore supports the largest number of credential types which can be stored within a credential store, additionally as the implementation is backed by a Java KeyStore the storage is protected using the mechanisms provided by the KeyStore implementations.

Command Line

When using the credential-store command of the elytron-tool.sh script by default, this command assumes the type of the store is a KeyStoreCredentialStore backed by a JCEKS KeyStore.

A new credential store can be created using the following command: -

]$ bin/elytron-tool.sh credential-store --create --location=standalone/configuration/mycredstore.cs
Credential store password:
Confirm credential store password:
Credential Store has been successfully created

This command prompts twice for the password that should be used to secure the store. Alternatively the password can be passed in using the --password argument however that may mean the password is cached in the local command history or visible to other users viewing the list of running processes.

The command line tool only creates the instance of the credential store on the underlying file system; the management operation is still required to define the credential store in the elytron subsystem.
Management Operation

It is also possible to operate on a credential store using management operations against a running server using the application server’s CLI.

The following operation can be used to add a credential-store resource to the elytron subsystem referencing this newly created credential store.

/] /subsystem=elytron/credential-store=mycredstore:add(path=mycredstore.cs, relative-to=jboss.server.config.dir, credential-reference={clear-text=StorePassword})
{"outcome" => "success"}

In this example the resource was added to reference an existing credential store, however with a small change this command can also automatically create the store if it does not exist.

/] /subsystem=elytron/credential-store=mycredstore:add(path=mycredstore.cs, relative-to=jboss.server.config.dir, credential-reference={clear-text=StorePassword}, create=true)
{"outcome" => "success"}
The file representing the credential store is not created immediately, it will instead be created the first time a credential is added.

Previous versions of the application server supported a single vault definition only; credential stores however are cross-referenced by name so multiple credential stores can be defined simultaneously.

16.1.2. PropertiesCredentialStore / secret-key-credential-store

This is a simple credential store implementation which can only be used to store SecretKey instances. This credential store also does not offer any protection of the contents. Within an application server environment it is always possible to get into a cycle of how is an initial secret provided to unlock further resources, this is primarily the purpose of this credential store. The strategy in the past had been to use masking of a password using password based encryption, the down side of this approach was the password used for the masking was stored in the source code and being an open source project this means the password is well known. By using the PropertiesCredentialStore installations are back in control of the initial secret. Although this credential store does not offer its own protection filesystem level access control can still be used to restrict access ideally to just the application server process.

Command Line

A new credential store can be created using the following command: -

]$ bin/elytron-tool.sh credential-store --create --location=standalone/configuration/propcredstore.cs --type PropertiesCredentialStore
Credential Store has been successfully created
Management Operation

It is also possible to operate on a credential store using management operations against a running server using the application server’s CLI.

The following operation can be used to add a secret-key-credential-store resource to the elytron subsystem referencing this newly created credential store.

/] /subsystem=elytron/secret-key-credential-store=mycredstore:add(path=propcredstore.cs, relative-to=jboss.server.config.dir, create=false, populate=false)
{"outcome" => "success"}

In this example the resource was added to reference an existing credential store, however with a small change this command can also automatically create the store if it does not exist, and populate it with a generated SecretKey under the alias key if it does not already exist.

/] /subsystem=elytron/secret-key-credential-store=mycredstore:add(path=propcredstore.cs, relative-to=jboss.server.config.dir)
{"outcome" => "success"}
If you rely on automatic generation of a SecretKey be sure to create a backup of the key as soon as possible. If you were to loose the key we do not have a mechanism to decrypt anything encrypted with the lost key.

16.2. Credential Manipulation

The contents of the credential stores can be manipulated using either the command line tool or by using management operations. Unlike key stores the credential store APIs allow for multiple entries to be stored under a single alias provided each entry is of a different credential type.

16.2.1. Adding Credentials

The different credential store implementations support different credential types as illustrated in this table.

Credential Type KeyStoreCredentialStore PropertiesCredentialStore

PasswordCredential

Supported

Unsupported

KeyPairCredential

Supported

Unsupported

SecretKeyCredential

Supported

Supported

As with all the manipulation options it is possible to use either the command line tool or management operations against a running server to modify the contents of the store.

Care should be taken when using the command line tool to ensure the store is not currently in use by any other processes. If the store is in use by another process which updates the contents of the store changes made by the tool could be lost.
PasswordCredential

Using the tooling it is possible to add a clear text password as a credential.

Command Line

The following command adds a new credential with an alias of example to the store using the command line tool: -

]$ bin/elytron-tool.sh credential-store --add=example --location=standalone/configuration/mycredstore.cs
Credential store password:
Secret to store:
Confirm secret to store:
Alias "example" has been successfully stored
Management Operation

Using a management operation the following command can be used to add a new alias of example to the credential store.

[standalone@localhost:9990 /] /subsystem=elytron/credential-store=mycredstore:add-alias(alias=example, secret-value=ExamplePassword)
{
    "outcome" => "success",
    "result" => undefined
}
KeyPairCredential
Command Line

The following command allows you to import a key pair credential with an alias of example from a file containing a private key in OpenSSH format:

]$ bin/elytron-tool.sh credential-store --import-key-pair example --private-key-location /home/user/.ssh/id_rsa --location=standalone/configuration/mycredstore.cs
Credential store password:
Confirm credential store password:
Passphrase to be used to decrypt private key (can be nothing if no passphrase was used to encrypt the key): secret
Confirm passphrase to be used to decrypt private key (can be nothing if no passphrase was used to encrypt the key): secret
Alias "example" has been successfully stored

The following command allows you to import a key pair credential with an alias of example by specifying a private key in OpenSSH format :

]$ bin/elytron-tool.sh credential-store --import-key-pair example --private-key-string="-----BEGIN OPENSSH PRIVATE KEY-----
                                                   b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCdRswttV
                                                   UNQ6nKb6ojozTGAAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz
                                                   dHAyNTYAAABBBAKxnsRT7n6qJLKoD3mFfAvcH5ZFUyTzJVW8t60pNgNaXO4q5S4qL9yCCZ
                                                   cKyg6QtVgRuVxkUSseuR3fiubyTnkAAADQq3vrkvuSfm4n345STr/i/29FZEFUd0qD++B2
                                                   ZoWGPKU/xzvxH7S2GxREb5oXcIYO889jY6mdZT8LZm6ZZig3rqoEAqdPyllHmEadb7hY+y
                                                   jwcQ4Wr1ekGgVwNHCNu2in3cYXxbrYGMHc33WmdNrbGRDUzK+EEUM2cwUiM7Pkrw5s88Ff
                                                   IWI0V+567Ob9LxxIUO/QvSbKMJGbMM4jZ1V9V2Ti/GziGJ107CBudZr/7wNwxIK86BBAEg
                                                   hfnrhYBIaOLrtP8R+96i8iu4iZAvcIbQ==
                                                   -----END OPENSSH PRIVATE KEY-----"
                                                   --location=standalone/configuration/mycredstore.cs
Credential store password:
Confirm credential store password:
Passphrase to be used to decrypt private key (can be nothing if no passphrase was used to encrypt the key): secret
Confirm passphrase to be used to decrypt private key (can be nothing if no passphrase was used to encrypt the key): secret
Alias "example" has been successfully stored
If specifying your key in PKCS format rather than OpenSSH format, you must specify both the private and public key. The PKCS private key must also not be encrypted with a passphrase.

Alternatively to importing, you may use the command line tool to generate and store a key pair credential in a credential store. The following command allows you to generate and store a key pair credential under the alias example using the ecdsa algorithm:

]$ bin/elytron-tool.sh credential-store --generate-key-pair example --algorithm EC --location=standalone/configuration/mycredstore.cs
Credential store password:
Confirm credential store password:
Alias "example" has been successfully stored

You can then export the public key generated in OpenSSH format using the following command:

]$ bin/elytron-tool.sh credential-store --export-key-pair-public-key example
Credential store password:
Confirm credential store password:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMfncZuHmR7uglb0M96ieArRFtp42xPn9+ugukbY8dyjOXoi
cZrYRyy9+X68fylEWBMzyg+nhjWkxJlJ2M2LAGY=
SecretKeyCredential
Command Line

Each of the examples in this section uses the KeyStoreCredentialStore to use the PropertiesCredentialStore this can be specified on the command line by adding the --type PropertiesCredentialStore parameter to the command line.

A new secret key can be generated with the following command.

]$ bin/elytron-tool.sh credential-store --generate-secret-key=example --location=standalone/configuration/mycredstore.cs
Credential store password:
Alias "example" has been successfully stored

By default this will create a 256 bit secret key, if either a 128 bit or 192 bit secret key is desired this can be specified with the --size=128 or --size=192 parameters respectively.

An existing secret key can be exported with the following command.

]$ bin/elytron-tool.sh credential-store --export-secret-key=example --location=standalone/configuration/mycredstore.cs
Credential store password:
Exported SecretKey for alias example=RUxZAUucgH8RSMNvoUj/rMz+pBZddttGCuT9of4TgfYLnN5Z1w==
The exported key uses a custom representation to allow Elytron to recognise exported keys.

Finally a previously exported secret key can be imported with the following command.

]$ bin/elytron-tool.sh credential-store --import-secret-key=imported --location=standalone/configuration/mycredstore.cs
Credential store password:
SecretKey to import: RUxZAUucgH8RSMNvoUj/rMz+pBZddttGCuT9of4TgfYLnN5Z1w==
Alias "imported" has been successfully stored

It is possible to also specify the key to import on the command line e.g. --key=RUxZAUucgH8RSMNvoUj/rMz+pBZddttGCuT9of4TgfYLnN5Z1w== but this would be vieweable by others users that can consult running processes and might also be cached in the history of the shell executing the commands.

Management Operations

For secret key manipulation the same set of command are available for both the credential-store resource and the secret-key-credential-store resource.

A new secret key can be generated with the following command.

[standalone@localhost:9990 /] /subsystem=elytron/credential-store=mycredstore:generate-secret-key(alias=example)
{"outcome" => "success"}

To generate either a 128 bit key or 192 bit key the parameter key-size=128 or key-size=192 can be specified respectively.

An existing secret key can be exported with the following command.

[standalone@localhost:9990 /] /subsystem=elytron/credential-store=mycredstore:export-secret-key(alias=example)
{
    "outcome" => "success",
    "result" => {"key" => "RUxZAUs+Y1CzEPw0g2AHHOZ+oTKhT9osSabWQtoxR+O+42o11g=="}
}
Finally a previously exported secret key can be imported with the following commands.

[standalone@localhost:9990 /] history --disable
[standalone@localhost:9990 /] /subsystem=elytron/credential-store=mycredstore:import-secret-key(alias=imported, key="RUxZAUs+Y1CzEPw0g2AHHOZ+oTKhT9osSabWQtoxR+O+42o11g==")
{"outcome" => "success"}
[standalone@localhost:9990 /] history --enable

In this last example we also temporarily disable the history in the CLI to prevent the key from being cached in the CLI hisory.

16.2.2. Listing Aliases

It is possible to list the aliases contained within the credential store, however it is not possible to list the actual values stored.

Command Line

Using the command line tool will show a list of aliases stored within the credential store:

]$ bin/elytron-tool.sh credential-store --aliases --location=standalone/configuration/mycredstore.cs
Credential store password:
Credential store contains following aliases: example
Management Operation

The following management operation will also show the aliases contained within the credential store.

/] /subsystem=elytron/credential-store=mycredstore:read-aliases
{
    "outcome" => "success",
    "result" => ["example"]
}

16.2.3. Removing Credentials

Finally, it is also possible to remove an alias from the credential store.

Command Line

Using the WildFly Elytron Tool the following command will remove an alias from the store.

]$ bin/elytron-tool.sh credential-store --remove=example --location=standalone/configuration/mycredstore.cs
Credential store password:
Confirm credential store password:
Alias "example" has been successfully removed
Management Operation

The following management operation can be used to remove an alias from the credential store.

/] /subsystem=elytron/credential-store=mycredstore:remove-alias(alias=example)
{
    "outcome" => "success",
    "result" => undefined,
    "response-headers" => {"warnings" => [{
        "warning" => "Update dependent resources as alias 'example' does not exist anymore",
        "level" => "WARNING",
        "operation" => {
            "address" => [
                ("subsystem" => "elytron"),
                ("credential-store" => "mycredstore")
            ],
            "operation" => "remove-alias"
        }
    }]}
}

By default the credential-store resource assumes the type to be removed is PasswordCredential. If a different type is to be removed it can be specified with the entry-type=SecretKeyCredential parameter. The secret-key-credential-store only holds secret keys so the entry type never needs to be specified.

16.3. Referencing Credentials

After being able to populate and manipulate a credential store the next step is being able to reference the stored credential so that it can be used.

16.3.1. Management Model References

Various resources that make use of credentials across the application server’s management model contain credential-reference attributes that can be used either to specify a clear-password or to cross-reference a credential from within a configured credential store.

The following is an example of how to define a key-store within the Elytron subsystem specifying a clear text password to access the store.

/] /subsystem=elytron/key-store=exampleKS:add(relative-to=jboss.server.config.dir, path=example.keystore,    \
                                              type=JCEKS, credential-reference={clear-text=ExamplePassword})
{"outcome" => "success"}

To reference a credential from the previously defined credential store the following command could be used instead.

/] /subsystem=elytron/key-store=exampleKS:add(relative-to=jboss.server.config.dir, path=example.keystore, type=JCEKS, credential-reference={store=mycredstore, alias=example})
{"outcome" => "success"}

The above command assumes that the referenced credential already exists in the previously defined credential store. The next section will describe how credentials can automatically be added to the previously defined credential store.

16.3.2. Automatic Updates of Credential Stores

Instead of needing to add a credential to a previously defined credential store in order to reference it from a credential-reference, it is possible to have the credential get added automatically to the previously defined credential store by specifying both the store and clear-text attributes for the credential-reference. In particular, when adding a new credential-reference with both the store and clear-text attributes specified:

  • If the alias attribute is also specified, then one of the following will occur:

    • If the previously defined credential store does not contain an entry for the given alias, a new entry will be added to the credential store to hold the clear text password that was specified. The clear-text attribute will then be removed from the management model.

    • If the credential store does contain an entry for the given alias, the existing credential will be replaced with the clear text password that was specified. The clear-text attribute will then be removed from the management model.

  • If the alias attribute is not specified, an alias will be generated and a new entry will be added to the credential store to hold the clear text password that was specified. The clear-text attribute will then be removed from the management model.

As an example, the following CLI command will result in a new entry being added to the previously defined credential store, mycredstore, with alias myNewAlias and credential myNewPassword:

/subsystem=elytron/key-store=exampleKS:add(relative-to=jboss.server.config.dir, path=example.keystore, type=JCEKS, credential-reference={store=mycredstore, alias=myNewAlias, clear-text=myNewPassword})
{
    "outcome" => "success",
    "result" => {"credential-store-update" => {
        "status" => "new-entry-added",
        "new-alias" => "myNewAlias"
    }}
}

When updating an existing credential-reference that contains both the alias and store attributes to also specify the clear-text attribute:

  • The existing credential in the previously defined credential store will be replaced with the clear text password that was specified. The clear-text attribute will then be removed from the management model.

As an example, the following CLI command will result in updating the credential for the myNewAlias entry that was just added to the previously defined credential store:

/subsystem=elytron/key-store=exampleKS:write-attribute(name=credential-reference.clear-text,value=myUpdatedPassword)
{
    "outcome" => "success",
    "result" => {"credential-store-update" => {"status" => "existing-entry-updated"}},
    "response-headers" => {
        "operation-requires-reload" => true,
        "process-state" => "reload-required"
    }
}
If an operation that includes a credential-reference parameter fails for any reason, no automatic credential store update will take place, i.e., any credential store that was specified via the credential-reference attribute will contain the same contents as it did before the operation was executed.

16.3.3. wildfly-config.xml

If you are making use of the wildfly-config.xml descriptor it is also possible to define a credential store within this descriptor to obtain credentials without requiring them to be in-lined within the configuration.

As an example the CLI can be executed with a configuration:

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

Without using a credential store the username and credential can be specified in the clear e.g.

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

<configuration>
    <authentication-client xmlns="urn:elytron:1.0">
        <authentication-rules>
                    <rule use-configuration="default" />
        </authentication-rules>
        <authentication-configurations>
            <configuration name="default">
                <sasl-mechanism-selector selector="DIGEST-MD5" />
                <providers>
                    <use-service-loader/>
                </providers>
                <set-user-name name="User" />
                <credentials>
                    <clear-password password="UserPassword" />
                </credentials>
             </configuration>
        </authentication-configurations>
    </authentication-client>
</configuration>

However, it is possible to move this password to the credential store and update the configuration to load it from the store e.g.

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

<configuration>
    <authentication-client xmlns="urn:elytron:1.0">
        <credential-stores>
            <credential-store name="mycredstore">
                <attributes>
                    <attribute name="keyStoreType" value="JCEKS" />
                    <attribute name="location" value="standalone/configuration/mycredstore.cs" />
                </attributes>
                <protection-parameter-credentials>
                    <clear-password password="StorePassword" />
                </protection-parameter-credentials>
            </credential-store>
        </credential-stores>

        <authentication-rules>
            <rule use-configuration="default" />
        </authentication-rules>
        <authentication-configurations>
            <configuration name="default">
                <sasl-mechanism-selector selector="DIGEST-MD5" />
                <providers>
                    <use-service-loader/>
                </providers>
                <set-user-name name="User" />
                <credentials>
                    <credential-store-reference store="mycredstore" alias="User" />
                </credentials>
             </configuration>
        </authentication-configurations>
    </authentication-client>
</configuration>

Within this second example the key changes being the addition of the <credential-stores /> section and updating the <credentials/> section to use a <credential-store-reference/> to specify which credential store should be used and which alias from that credential store should be used.

In the above example, the credential store’s protection parameter is specified as a clear password, but it is also possible to specify it as a masked password.

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

<configuration>
    <authentication-client xmlns="urn:elytron:1.4">
        <credential-stores>
            <credential-store name="mycredstore">
                <attributes>
                    <attribute name="keyStoreType" value="JCEKS" />
                    <attribute name="location" value="standalone/configuration/mycredstore.cs" />
                </attributes>
                <protection-parameter-credentials>
                    <masked-password masked-password="M3loEZ7uua1X1PiYCYJDpg==" iteration-count="100" salt="12345678"/>
                </protection-parameter-credentials>
            </credential-store>
        </credential-stores>

        <authentication-rules>
                    <rule use-configuration="default" />
        </authentication-rules>
        <authentication-configurations>
            <configuration name="default">
                <sasl-mechanism-selector selector="DIGEST-MD5" />
                <providers>
                    <use-service-loader/>
                </providers>
                <set-user-name name="User" />
                <credentials>
                    <credential-store-reference store="mycredstore" alias="User" />
                </credentials>
             </configuration>
        </authentication-configurations>
    </authentication-client>
</configuration>

16.4. CredentialStore APIs

It is also possible to make use of the CredentialStore APIs directly. This could be useful for applications that require access to securely stored credentials. This could also be an option for an application to populate a credential store for use elsewhere.

The following code demonstrates how to obtain an initialised instance of KeyStoreCredentialStore so it can be used to store and retrieve credentials.

Password storePassword = ClearPassword.createRaw(ClearPassword.ALGORITHM_CLEAR, "StorePassword".toCharArray());
ProtectionParameter protectionParameter = new CredentialSourceProtectionParameter(IdentityCredentials.NONE.withCredential(new PasswordCredential(storePassword)));

CredentialStore credentialStore = CredentialStore.getInstance("KeyStoreCredentialStore", CREDENTIAL_STORE_PROVIDER);

Map<String, String> configuration = new HashMap<>();
configuration.put("location", "mystore.cs");
configuration.put("create", "true");

credentialStore.initialize(configuration, protectionParameter);

The following code illustrates how a couple of different credential types can be added to a credential store:

Password clearPassword = ClearPassword.createRaw(ClearPassword.ALGORITHM_CLEAR, "ExamplePassword".toCharArray());
credentialStore.store("clearPassword", new PasswordCredential(clearPassword));

KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256);
SecretKey secretKey = keyGenerator.generateKey();
credentialStore.store("secretKey", new SecretKeyCredential(secretKey));

These credentials can then be obtained again from the store:

Password password = credentialStore.retrieve("clearPassword", PasswordCredential.class).getPassword();
SecretKey secretKey = credentialStore.retrieve("secretKey", SecretKeyCredential.class).getSecretKey();
As the type is specified when retrieving a credential it is possible to store multiple credentials under the same alias.

Please use the published javadoc for more information in relation to the APIs and the credential types supported within WildFly Elytron.

16.5. Migrating Existing Vaults

If migrating from a prior version of the application server it is possible that you already are making use of a PicketBox vault for the storage of clear text passwords. The tooling provided can be used to convert the vault to the format used by the KeyStoreCredentialStore.

Within the WildFly Elytron command line tool an additional command vault is available specifically for the conversion of legacy vaults to a credential store. A complete vault can be converted to a credential store with the following command: -

]$ bin/elytron-tool.sh vault --enc-dir standalone/configuration/vault --keystore standalone/configuration/vault.keystore --iteration 44 --salt 00000000 --alias vault \
    --location standalone/configuration/newcredstore.cs
Vault password:
Confirm vault password:
Vault (enc-dir="standalone/configuration/vault";keystore="standalone/configuration/vault.keystore") converted to credential store "standalone/configuration/newcredstore.cs"

When executing this command the destination credential store must not already exist. The password used for the credential store will be the password originally used for the vault.

Entries stored within the vault would have been stored specifying a "block" and "alias" value; within the credential store the new alias will be block::alias.

16.6. Custom Credential Store

It is also possible to provide custom credential store implementations. Overall the pattern to implementing a custom credential store is very similar to the pattern that would be followed to implement a custom key store.

  • Extend the SPI

  • Implement a java.security.Provider to register the implementation.

The SPI to be extended is org.wildfly.security.credential.store.CredentialStoreSpi. The custom implementation will be required to implement the following methods.

public abstract void initialize(Map<String, String> attributes, CredentialStore.ProtectionParameter protectionParameter, Provider[] providers) throws CredentialStoreException;

This method is required to perform the initialisation of the credential store, by taking in a Map of attributes it allows for custom configuration to be provided as required by the store.

public abstract boolean isModifiable();

A credential store needs to advertise if it supports modifications so clients can determine if the modification APIs can be used.

public abstract <C extends Credential> C retrieve(String credentialAlias, Class<C> credentialType, String credentialAlgorithm, AlgorithmParameterSpec parameterSpec, CredentialStore.ProtectionParameter protectionParameter) throws CredentialStoreException;

The retrieve method is essential for all credential store implementations to retrieve credentials of a specific type using the alias specified.

In addition to retrieve there are two more methods that can optionally be implemented.

public boolean exists(String credentialAlias, Class<? extends Credential> credentialType) throws CredentialStoreException;

public Set<String> getAliases() throws UnsupportedOperationException, CredentialStoreException;

A default implementation of exists already is implemented which checks if a call to retrieve returns a credential as requested. However it could be optimal to check the existence of a credential without actually loading it. The getAliases method is optional as some implementations may only be able to retrieve a credential by name rather than query all available credentials.

The next set of methods to implement are the methods needed for updates to be applied to the underlying credential store.

public abstract void store(String credentialAlias, Credential credential, CredentialStore.ProtectionParameter protectionParameter)
            throws CredentialStoreException, UnsupportedCredentialTypeException;

public abstract void remove(String credentialAlias, Class<? extends Credential> credentialType, String credentialAlgorithm, AlgorithmParameterSpec parameterSpec) throws CredentialStoreException;

public void flush() throws CredentialStoreException;

The store and remove methods either add credentials to a credential store or remove them. Implementing the flush method is optional but this method can be used as a trigger for a store to persist its state.

The final stage is to provide an implementation of java.security.Provider which can return an instance of the SPI for the CredentialStore service type. The WildFly Elytron provider which makes the Elytron implementations available is org.wildfly.security.credential.store.WildFlyElytronCredentialStoreProvider. The source code for this provider can be used as an example.

16.7. Reference

The previous sections have made use of either the WildFly Elytron Tool or the management operations and specified the arguments and configuration options required for the action being performed. These operations and tools however support a variety of other options so this section provides some additional detail.

16.7.1. Elytron Tool - credential-store Command

Examples of how to structure calling the credential-store command were provided earlier. When using the credential-store command the following actions are possible: -

Table 12. credential-store Actions
Action Description

-a,--add <alias>

Add a new entry to the credential store using the specified alias.

-c,--create

Create a new credential store instance.

-e,--exists <alias>

Test if the specified alias already exists in the credential store.

-r,--remove <alias>

Remove the alias specified from the credential store.

-g,--generate-key-pair <alias>

Generate a new key pair credential and add it as an entry to the credential store using the specified alias.

--generate-secret-key <alias>

Generate a new secret key credential and add it as an entry to the credential store using the specified alias.

--export-secret-key <alias>

Export a secret key credential identified using the specified alias.

-xp,--export-key-pair-public-key <alias>

Display the public key of a key pair credential entry under the specified alias in OpenSSH format.

--import-secret-key <alias>

Import a secret key credential and add it as an entry to the credential store using the specified alias.

-ikp,--import-key-pair <alias>

Add a new key pair credential entry to the credential store using the specified alias.

--encrypt <alias>

Encrypt a clear text string using the secret key stored under the specified alias.

-v,--aliases

Display all aliases

-f,--summary

Print summary, especially command how to create this credential store

-h,--help

Get help with usage of this command

The following parameters can be provided for each action to specify how to load the store.

Table 13. credential-store Parameters
Parameter Description

-d,--debug

Print stack trace when error occurs.

-i,--iteration <arg>

Iteration count for final masked password of the credential store

-l,--location <loc>

Location of credential store storage file

-n,--entry-type <type>

Type of entry in credential store

-o,--other-providers <providers>

Comma separated list of Jakarta Connectors provider names. Providers will be supplied to the credential store instance. Each provider must be installed through java.security file or through service loader from properly packaged jar file on classpath.

-p,--password <pwd>

Password for credential store

-q,--credential-store-provider <cs-provider>

Provider name containing CredentialStoreSpi implementation. Provider must be installed through java.security file or through service loader from properly packaged jar file on classpath.

-s,--salt <arg>

Salt to apply for final masked password of the credential store

-t,--type <type>

Credential store type

-u,--properties <arg>

Implementation properties for credential store type in form of "prop1=value1; …​ ;propN=valueN"

-x,--secret <secret to store>

Password credential value

The following parameters can be provided for the generate-key-pair command:

Table 14. generate-key-pair Parameters
Parameter Description Default Value

-k, --algorithm <algorithm name>

The encryption algorithm to be used. One of: RSA, DSA, or EC

RSA

-j,--size <size in bytes>

Size of the private key in bytes

RSA: 2048, DSA: 2048, EC: 256

The following parameter can be provided for the generate-secret-key command:

Table 15. generate-secret-key Parameter
Parameter Description Default Value

--size

Size of the secret key in bits, can be one of 128, 192, or 256.

256

The following parameters can be provided for the import-key-pair command:

Table 16. import-key-pair Parameters
Parameter Description

-pvk, --private-key-string <private key to store>

The private key as a string. Alternative to private-key-location

-pvl, --private-key-location <path>

The path to a file containing a private key. Alternative to private-key-string

-pbk, --public-key-string <public key to store>

The public key as a string. Alternative to public-key-location

-pbl, --public-key-location <path>

The path to a file containing a public key. Alternative to public-key-string

-kp, --key-passphrase <passphrase>

The passphrase used to decrypt the private key if needed. Can also be specified via prompt

The following parameter can be provided for the import-secret-key command:

Table 17. import-secret-key Parameter
Parameter Description Default Value

--key

The secret key to be imported, if not specified the key will be prompted for.

N/A

The following parameters can be provided for the encrypt command:

Table 18. encrypt Parameters
Parameter Description Default Value

--clear-text <clear text>

The clear text string to encrypt, if omitted this wil be prompted for.

N/AThe following parameters can be provided for the generate-key-pair command:

.generate-key-pair Parameters

|Parameter |Description |Default Value

|-k, --algorithm <algorithm name> |The encryption algorithm to be used. One of: RSA, DSA, or EC |RSA

16.7.2. Elytron Tool - vault Command

The vault command is used to convert a legacy vault to a credential store and supports the following parameters.

Table 19. vault Parameters
Parameter Description

-b,--bulk-convert <description file>

Bulk conversion with options listed in description file.

-d,--debug

Print stack trace when error occurs.

-e,--enc-dir <dir>

Vault directory containing encrypted files (defaults to "vault")

-f,--summary

Print summary of conversion

-h,--help

Get help with usage of this command

-i,--iteration <arg>

Iteration count (defaults to "23")

-k,--keystore <keystore>

Vault keystore URL (defaults to "vault.keystore")

-l,--location <loc>

Location of credential store storage file (defaults to "converted-vault.cr-store" in vault encryption directory)

-o,--other-providers <providers>

Comma separated list of Jakarta Connectors provider names. Providers will be supplied to the credential store instance. Each provider must be installed through java.security file or through service loader from properly packaged jar file on classpath.

-p,--keystore-password <pwd>

Vault keystore password, used to open original vault key store, and used as password for new converted credential store

-q,--credential-store-provider <cs-provider>

Provider name containing CredentialStoreSpi implementation. Provider must be installed through java.security file or through service loader from properly packaged jar file on classpath.

-s,--salt <salt>

8 character salt (defaults to "12345678")

-t,--type <type>

Converted credential store type (defaults to "KeyStoreCredentialStore")

-u,--properties <arg>

Configuration parameters for credential store in form of: "parameter1=value1; …​ ;parameterN=valueN"

-v,--alias <arg>

Vault key alias within key store (defaults to "vault")

16.7.3. KeyStoreCredentialStore

When configuring the KeyStoreCredentialStore the following configuration options are supported.

Table 20. KeyStoreCredentialStore Configuration
Name Default Description

create

false

If the credential store does not exist should it be created?

cryptographicAlgorithm

AES/CBC/NoPadding

The algorithm to use when using an external store.

external

false

Should external storage be used?

externalPath

N/A

Path to external storage.

keyAlias

cs_key

The alias to use from the KeyStore when working with external storage.

keyStoreType

KeyStore.getDefault()

The type of the key store used for the credential store.

location

N/A

The location of the credential store.

modifiable

true

Should the store be modifiable via the exposed API.

17. Encrypted Expressions

WildFly Elytron provides support for handling encrypted expressions in the management model using a SecretKey from a CredentialStore to decrypt the expression at runtime.

For information regarding how to create, configure, and populate credential stores including the manipulation of secret keys please refer to the Credential Store chapter.

Within the elytron subsystem one or more resolvers can be defined to handle the decryption of a previously encrypted expression. Each resolver will reference a single secret key in a single credential store which it will use to decrypt the expressions. Encrypted expressions can take one of two forms:

  • ${ENC::ResolverName:RUxZAUMQXUj3qP1hbXyO5PpmsbgkepjoscIf3tKXvGiPDXYqNAc=}

  • ${ENC::RUxZAUMQXUj3qP1hbXyO5PpmsbgkepjoscIf3tKXvGiPDXYqNAc=}

In both cases the ENC prefix is used to identify the expression is an encrypted expression. This prefix is the default however it is possible to define an alternative prefix for use across the configuration.

Within the first example the ResolverName is the name of an individual resolver definition. The name of the resolver has been omitted in the second example as it is also possible to define a default resolver which will be used if no resolver is specified within the expression.

17.1. expression=encryption resource

Support for decrypting expressions is enabled by defining a singleton expression=encryption resource within the elytron subsystem.

This resource can be defined with the following attributes.

  • prefix (Default ENC) - The prefix used within the encrypted expressions.

  • default-resolver (Optional) - For expressions that do not define a resolver, the default resolver to use.

  • resolvers - A lit of one more named resolver definitions.

An individual resolver is defined with three attributes:

  • name - The name of the individual configuration used to reference it.

  • credential-store - Reference to the credential store instance that contains the secret key this resolver will use.

  • secret-key - The alias of the secret key within the credential store to use.

The following is an example CLI command to define an expression=encryption resource in the elytron subsystem with two resolver definitions one if which is a default and an alternative prefix.

[standalone@localhost:9990 /] /subsystem=elytron/expression=encryption:add(prefix=ENCRYPTED, default-resolver=one, \
    resolvers=[{name=one, credential-store=store-one, secret-key=key}, \
               {name=two, credential-store=store-two,secret-key=key}])
{"outcome" => "success"}

17.2. Creating Expressions

The following section illustrates how to create an encrypted expression. If you repeat the same command for the same clear text it is normal that a different result is returned for the same key. This is because a unique initialisation vector is used for each call.

17.2.1. Management Operation

Once the expression=encryption resource has been defined the create-expression management operation can be called to generate an expression using the referenced secret key.

When using management operations to create expressions from a clear-text string remember to disable the history in the CLI first otherwise the clear text string will be cached.

In these examples the expression=encryption resource has been configured to use the default prefix.

[standalone@localhost:9990 /] history --disable
[standalone@localhost:9990 /] /subsystem=elytron/expression=encryption:create-expression(resolver=two, clear-text=MyPassword)
{
    "outcome" => "success",
    "result" => {"expression" => "${ENC::two:RUxZAUMQcUVLxqjt8zh8FabcA+wcnux+mqfLE27sfmHfKNG9BcY=}"}
}

[standalone@localhost:9990 /] history --enable

When using the create-expression operation the resolver attribute can be ommited, in that case the default-resolver will be used instead.

[standalone@localhost:9990 /] history --disable
[standalone@localhost:9990 /] /subsystem=elytron/expression=encryption:create-expression(clear-text=MyPassword)
{
    "outcome" => "success",
    "result" => {"expression" => "${ENC::RUxZAUMQu7biKBAwtUi+to+BlQnbjK3URUHMUDh8ReTlN0Alao0=}"}
}

[standalone@localhost:9990 /] history --enable

In both cases the resulting expression can be used on any attribute of a resource which supports the use of expressions.

17.2.2. Command Line Tool

Once a credential store has been populated with a secret key the credential-store command of the elytron-tool can also be used to create the encrypted string to include in an expression.

]$ bin/elytron-tool.sh credential-store --location standalone/configuration/store-one.cs --type PropertiesCredentialStore --encrypt key
Clear text value:
Confirm clear text value:
Clear text encrypted to token 'RUxZAUMQvGzk6Vaadp2cahhZ6rlPhHOZcWyjXALlAthrENvRTvQ=' using alias 'key'.

The --encrypt action is used with the credential-store command, the argument to this action is the alias of the secret key to use. In this form the tool will prompt twice for the clear text which is being encrypted. When using the command line tool the output is just the Base64 encoded encrypted ciphertext. To use this in the management model it will need to be included in an expression as described earlier i.e. using the appropriate prefix and if required resolver name.

When using the --encrypt action it is also possible to pass in --clear-text parameter to pass in the clear text directly but this may be visible to other users and may also be cached in the command history of your shell.

17.3. Domain Mode

When using encrypted expressions in domain mode things are slightly different to how the legacy vault may have been used in the past.

To make use of encrypted expressions in the host controller configuration the expression=encryption resource and relevant *credential-store definitions must be defined within the elytron subsystem definition of the host controller i.e. in the same host.xml configuration.

For expressions within a domain profile being used to configure one or more servers the expression=encryption resource and relevant *credential-store definitions must be defined within the elytron subsystem definition of the same profile.

The runtime management operations are not supported against the expression=encryption or *credential-store when defined within a domain profile so for these environments the credential store and relevant expressions should be created offline before defining in the model. It is possible to reference a common credential store file shared between the host controller management model and the domain profile but after making any updates to the credential store from the host controller the application server processes will need to be restarted to force them to reload the credential store.

18. Custom Components

WildFly Elytron subsystem allows adding custom implementation of different components in form of WildFly modules into the WildFly instance and use them by the same way as built-in Elytron subsystem components. For example, you can create custom security event listener to develop custom audit mechanisms and store information about user authentication attempts in custom storage structure. Or you can create custom security realm to authenticate users against your own identities storage.

To find what types of custom components you can implement you can use Tab completion:

[standalone@localhost:9990 /] /subsystem=elytron/custom-
custom-credential-security-factory  custom-realm
custom-evidence-decoder             custom-realm-mapper
custom-modifiable-realm             custom-role-decoder
custom-permission-mapper            custom-role-mapper
custom-principal-decoder            custom-security-event-listener
custom-principal-transformer

18.1. Security event listener

This section describes how to add a custom security event listener. Similar steps can be followed to add another custom component type.

To create custom security event listener you need to implement java.util.function.Consumer<org.wildfly.security.auth.server.event.SecurityEvent> interface. Resulting class needs to be packed into JAR and WildFly module created. You can create appropriate directory structure and module descriptor manually, or you can use following command of WildFly CLI:

bin/jboss-cli.sh
module add --name=my.module --resources=my-listener.jar --dependencies=org.wildfly.security.elytron

Check Class loading doc for more information how to create WildFly module.

When appropriate module is on place, you can start using it by adding Elytron subsystem resource:

/subsystem=elytron/custom-security-event-listener=myListener:add(
        module=my.module, class-name=my.module.MyAuditListener)

Now you can start to use it as any other security event listener - typically to set it as listener of ApplicationDomain:

/subsystem=elytron/security-domain=ApplicationDomain:write-attribute(name=security-event-listener, value=myListener)

After server reload the listener will receive all security events from given security domain.

18.2. Configurable custom components

You can also provide some component configuration from the subsystem, if class of your component will implement following method:

public void initialize(Map<String, String> configuration)

Afterwards you can provide configuration into your component from the subsystem using attribute configuration:

/subsystem=elytron/custom-...=my-component:add(module=..., class-name=..., configuration={myAttribute="myValue"})

After the component construction, the initialize method will be called with the configuration.

19. Elytron WildFly Java Security Manager

19.1. Overview

19.1.1. General introduction

The Elytron WildFly Java Security Manager is build on top of and within the Java SE Platform Security Architecture.

The Java Enterprise Edition (EE) 7 specification introduced a new feature which allows application developers to specify a Java Security Manager (JSM) policy for their Java EE applications, when deployed to a compliant Java EE Application Server such as WildFly. Until now, writing JSM policies has been pretty tedious. Now a new tool has been developed which allows the generation of a JSM policy for deployments running on WildFly. It is possible that running with JSM enabled may affect performance, JEP 232 indicates the performance impact would be 10-15%, but it is still recommended to test the impact per application.

Elytron WildFly Java Security Manager basic enhancements are

  • Support of JavaEE 7 (and newer) Java EE application permissions schema to allow declarative security per application deployment.

  • Support of minimum and maximum permissions per server instance.

  • Support of a "log-only" mode to ease development and testing of permissions.

  • Output of debug information to detect missing permissions.

  • Output of trace information to analyse special situations with protection domains, classloaders, and codesources.

Why Run with the Java Security Manager Enabled?

Running a JSM will not fully protect the server from malicious attackers exploiting security vulnerabilities. It does, however, offer another layer of protection which can help reduce the impact of serious security vulnerabilities, such as deserialization attacks. For example, most of the recent attacks against Jackson Databind rely on making a Socket connection to an attacker-controlled JNDI Server to load malicious code. This article provides information on how this issue potentially affects an application written for JBoss EAP, which would be same for WildFly. The Security Manager could block the socket creation, and potentially thwart the attack.

JavaSE 17 Deprecate the Security Manager for Removal

The community decided to deprecate the Java Security Manager with Java 17. The full details are described in JEP 411: Deprecate the Security Manager for Removal. WildFly 32 still tests and actively supports the Security Manager. Used in conjunction with other tools such as Serialization Filtering the JSM is still a good defense in depth measure.

Multi-Layer Security

The Elytron WildFly Java Security Manager is just one piece in a multi-layer security with Defense in depth. Take care to harden your environment and minimize rights of the process for example with SELinux and systemd service hardening.

19.2. The Security manager subsystem

19.2.1. Enabling the Security Manager

You can enable the security manager with one of the following options:

./standalone.sh -secmgr # command line argument or
./standalone.sh SECMGR=true

The server startup log should indicate this

INFO  [org.jboss.as] (MSC service thread 1-8) WFLYSRV0235: Security Manager is enabled

The use of -Djava.security.manager as described in Managing Applets and Applications has been removed from WildFly.

19.2.2. Security Policy Support

You can still utilize a custom security policy file with the -Djava.security.policy option. This is useful for special cases, for example a java agent. The default is the JVM provided policy.

./standalone.sh -secmgr -Djava.security.policy=file://$JBOSS_HOME/standalone/configuration/customjsm.policy

-Djava.security.policy= with one equal sign (=) utilizes the default policy plus the custom policy. -Djava.security.policy== with two equal signs (==) utilizes only the custom policy, take care for the necessary rights of the JVM in this case.

Property replacement in security policies

You can use variables in custom security policies which are resolved at runtime. You have to specify them on the command line or in the $JBOSS_HOME/bin/standalone.conf file. Properties in the server configuration are not available at the time of variable resolution on startup for the JSM.

19.2.3. Log-only mode

The log-only mode could be described as JSM simulation mode: Every permission check will be done but the result will not be propagated to the system - no SecurityException will be thrown. It is NOT about enabling or disabling log entries, which is solely driven by the logging configuration. The log-only mode is controlled with the -Dorg.wildfly.security.manager.log-only option. The default is false.

./standalone.sh -secmgr -Dorg.wildfly.security.manager.log-only=true

Do NOT use this option in production environments.

19.2.4. Logging Debug and trace

You can find a detailed description and how to about the command line interface and the logging configuration in the Admin Guide. The logger name to configure for the Elytron WildFly Security Manager is org.wildfly.security.access.

WildFly Security Manager Debug logging

Debug is enabled via the following CLI commands

/subsystem=logging/logger=org.wildfly.security.access:add
/subsystem=logging/logger=org.wildfly.security.access:write-attribute(name=level,value=DEBUG)

A sample output will look like this:

DEBUG [org.wildfly.security.access] (Batch Thread - 1) Permission check failed (permission "("java.util.PropertyPermission" "java.io.tmpdir" "read")" in code source "(vfs:/content/batch-processing.war/WEB-INF/classes <no signer certificates>)" of "ModuleClassLoader for Module "deployment.batch-processing.war" from Service Module Loader")
WildFly Security Manager Trace logging

You might have a case where you need to find out what exactly caused a certain permission request. Or you have an error case with an null classloader or null codesource. To get a stacktrace you can enable org.wildfly.security.access on log level TRACE.

/subsystem=logging/logger=org.wildfly.security.access:add
/subsystem=logging/logger=org.wildfly.security.access:write-attribute(name=level,value=TRACE)

Attention: This generates a lot of log output and has a severe performance impact. It’s not intended for permanent activation in development or testing but for special cases only.

Logging Profiles

If you are using one or more logging profiles it is strongly recommended to configure the org.wildfly.security.access logger for the server AND all logging profiles. If not, you might miss a relevant output.

19.2.5. Minimum and maximum permissions

The security-manager subsystem configures a maximum-set with the AllPermission by default:

            <deployment-permissions>
                <maximum-set>
                    <permission class="java.security.AllPermission"/>
                </maximum-set>
            </deployment-permissions>

If you cannot configure a third-party blackbox deployment unit or you want to share a common set of permissions across multiple deployments you can add a minimum-set of permissions. You could modify the maximum-set, remove the AllPermission and setup further restrictions to permissions you are willing to grant to deployments.

19.2.6. Property replacement in permissions.xml

Expression resolution in EE security manager deployment descriptors (permissions.xml and jboss-permissions.xml) is supported since WildFly 19. You can use the familiar ${foo:true} syntax in these deployment descriptors to allow customization of settings at runtime.

Enable in standalone mode

[standalone@localhost:9990 /] /subsystem=ee:write-attribute(name=jboss-descriptor-property-replacement,value=TRUE)
[standalone@localhost:9990 /] /subsystem=ee:write-attribute(name=spec-descriptor-property-replacement,value=TRUE)

Enable in domain mode

[domain@localhost:9990 /] /profile=*/subsystem=ee:write-attribute(name=jboss-descriptor-property-replacement, value=TRUE)
[domain@localhost:9990 /] /profile=*/subsystem=ee:write-attribute(name=spec-descriptor-property-replacement, value=TRUE)

19.2.7. Sample permissions.xml

The permissions.xml file has to be placed below META-INF of deployment unit. The following example shows some entries, including property replacement. This is especially useful for immutable artifacts.

<?xml version="1.0" encoding="UTF-8"?>
<permissions xmlns="https://jakarta.ee/xml/ns/jakartaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
             https://jakarta.ee/xml/ns/jakartaee/permissions_10.xsd"
             version="10">
    <permission>
        <class-name>java.util.PropertyPermission</class-name>
        <name>*</name> <!-- many 3rd party APIs cache and require access to all properties -->
        <actions>read, write</actions>
    </permission>
    <permission>
        <class-name>java.lang.RuntimePermission</class-name>
        <name>getClassLoader</name>
    </permission>
    <permission>
        <class-name>java.io.FilePermission</class-name>
        <name>${install.app.home}/a/folder/-</name> <!-- recursive in and below folder -->
        <actions>read</actions> <!-- but not write, delete, execute -->
    </permission>
    <permission>
        <class-name>java.io.FilePermission</class-name>
        <name>${install.app.home}/b/folder</name> <!-- folder itself -->
        <actions>read, write, delete</actions> <!-- but not execute -->
    </permission>
    <permission>
        <class-name>java.io.FilePermission</class-name>
        <name>${install.app.home}/b/folder/*</name> <!-- all IN the folder -->
        <actions>read, write, delete</actions> <!-- but not execute -->
    </permission>
    <permission>
        <class-name>java.net.URLPermission</class-name>
        <name>${myserver.prot}://${myserver.hostname}:${myserver.port}/c/path/-</name> <!-- recursive in and below path -->
        <actions>POST,GET,DELETE:*</actions> <!-- refer to JavaDoc for more samples -->
    </permission>
</permissions>

19.3. How to generate a Java Security Manager Policy

19.3.1. Prerequisites

  • Java EE EAR or WAR file to add policies to;

  • Targeting WildFly 11 or later;

  • the Gradle build tool https://gradle.org/install/

  • Comprehensive test plan which exercises every "normal" function of the application.

If a comprehensive test plan isn’t available, a policy could be generated in a production environment, as long as some extra disk space for logging is available and there is confidence the security of the application is not going to be compromised while generating policies.

19.3.2. Setup 'Log Only' mode and 'debug' logging for the Security Manager

Please enable the log-only mode and debug logging.

19.3.3. Test the application to generate policy violations

For this example we’ll use the batch-processing quickstart. Follow the README to deploy the application and access it running on the application server at http://localhost:8080/batch-processing. Click the 'Generate a new file and start import job' button in the Web UI and notice some policy violations are logged to the $JBOSS_HOME/standalone/log/server.log file, for example:

DEBUG [org.wildfly.security.access] (Batch Thread - 1) Permission check failed (permission "("java.util.PropertyPermission" "java.io.tmpdir" "read")" in code source "(vfs:/content/batch-processing.war/WEB-INF/classes <no signer certificates>)" of "ModuleClassLoader for Module "deployment.batch-processing.war" from Service Module Loader")

19.3.4. Generate a policy file for the application

Checkout the source code for the wildfly-policygen project written by Red Hat Product Security.

git clone git@github.com:jasinner/wildfly-policygen.git

Set the location of the server.log file which contains the generated security violations in the build.gradle script, i.e.:

task runScript (dependsOn: 'classes', type: JavaExec) {
    main = 'com.redhat.prodsec.eap.EntryPoint'
    classpath = sourceSets.main.runtimeClasspath
    args '$JBOSS_HOME/standalone/log/server.log'
}

Run wildfly-policygen using gradle, i.e.:

gradle runScript

A permissions.xml file should be generated in the current directory. Using the example application, the file is called batch-processing.war.permissions.xml. Copy that file to src/main/webapp/META-INF/permissions.xml, build, and redeploy the application, for example:

cp batch-processing.war.permissions.xml $APP_HOME/src/main/webapp/META-INF/permissions.xml

Where APP_HOME is an environment variable pointing to the batch-processing application’s home directory.

19.3.5. Run with the security manager in enforcing mode

Recall that we set the org.wildfly.security.manager.log-only system property in order to log permission violations. Remove that system property or set it to false in order to enforce the JSM policy that’s been added to the deployment. Once that line has been changed or removed from bin/standalone.conf, restart the application server, build, and redeploy the application.

Also go ahead and remove the extra logging category that was added previously using the CLI, e.g.:

/subsystem=logging/logger=org.wildfly.security.access:remove

This time there shouldn’t be any permission violations logged in the server.log file.

19.4. Conclusion

While the Java Security Manager will not prevent all security vulnerabilities possible against an application deployed to WildFly, it will add another layer of protection, which could mitigate the impact of serious security vulnerabilities such as deserialization attacks. If running with Security Manager enabled, be sure to check the impact on the performance of the application to make sure it’s within acceptable limits. Finally, use of the wildfly-policygen tool is not officially supported by Red Hat, however issues can be raised for the project in Github or in the WildFly User Forum.

19.5. Further background

Additionally to the documentation of JBoss Modules and the Security Manager the following explanations have been extracted from an WildFly developer conversation.

19.5.1. Elytron WildFly Security Manager and Protection Domains

Within WildFly there are two types of ProtectionDomain:

  • Server module (everything under the modules folder).

  • Deployment.

Server modules automatically have the AllPermission granted. Deployments have a combination of their permission.xml as well as possibly the minimal permission set from the security manager subsystem.

Permission checks

As a call progresses from class to class, module to module, deployment to module a list of all of the protection domains of each of these builds up. When a permission check is performed it will only succeed if each and every protection domain on the call stack has been granted the permission.

This is where doPrivileged comes in, this is effectively saying "At this point in the callstack it is verified safe, forget the protection domains that called me." So when a deployment calls into a server module and that server module calls doPrivileged then the deployments module will not longer be a part of the permission check.

Adding a doPrivileged seems an obvious solution but when added one also need to think about how deployments could abuse this to get their protection domain dropped from the permissions check.

Privileged blocks in dependencies

Adding the privileged block in a dependency used by WildFly works because the protection domain of the server modules has all the permissions. It is important to understand how module all permissions and doPrivileged checks work combined for a deployment.

One point to start with there is nothing special about a PrivilegedAction: PrivilegedAction is an interface which allows a Runnable class to be passed in and also has a return type. If this was added later it could have been implemented with the functional interfaces. The special part is the doPrivileged call. Under normal circumstances every jar would be represented by it’s own protection domain and each jar could be assigned it’s own permissions. When the security manager performs it’s permissions check it would make sure every protection domain in the call stack has been granted that permission.

The WildFly modules case is slightly special as all the modules just get granted AllPermission, so we end up in a situation where deployments have a defined set of permissions and the WildFly modules have all - by default the security manager checks the permissions of both of these during a permission check that spans them - the WildFly module will of course always pass.

Because of WildFly’s model it is easy to get into the assumption that it is the doPrivileged call which is doing something special to bypass the security manager permissions check, it is not - all it is doing is dropping the protection domains from the call stack prior to that point, so we just end up with the protection domains for WildFly modules on the call stack which have the all permission.

So where you say the current protection domain where the dependency is has all permissions granted - yes that is it - the remaining protections domains on the call stack have the appropriate permissions granted.

The documentation might lead to the view the doPriviledge does some magical things that allowed your code run as trusted code that can bypass the permissions, for example at What It Means to Have Privileged Code, with sentences as:

Marking code as "privileged" enables a piece of trusted code to temporarily enable access to more resources than are available directly to the code that called it. This is necessary in some situations. For example, an application may not be allowed direct access to files that contain fonts, but the system utility to display a document must obtain those fonts, on behalf of the user. In order to do this, the system utility becomes privileged while obtaining the fonts.

This is all part of the argument for removing it, the confusion around how to apply the APIs - one could say the following sentence is correct but hides the important detail. "Marking code as "privileged" enables a piece of trusted code to temporarily enable access to more resources than are available directly to the code that called it."

If we have module A calling module B - by default the permissions check checks both. If module B contains a doPrivileged before the permission check then only module B’s protection domain will be checked.

The reason for using the doPrivileged is the assumption module B has the greater (or more appropriate permissions) and just want these compared without those from A.

But it is also possible that A actually had the required permissions and B does not do even though one adds a doPrivileged it still fails as B does not have sufficient permissions anyway.

The reason for using the doPrivileged is the assumption module B has the greater (or more appropriate permissions) and just want these compared without those from A.

Privileged blocks implications for third party libraries

It is not a requirement for a developer of a third-party library to add a doProviledge block and configure the corresponding protection domain with the permissions the library requires for doing the work.

A third-party developer of a library could add a doPrivileged block to allow the consumers of the library to have the freedom to not require all the permissions needed available on all the protection domains. However, consumers have to at least give the required permissions to the protection domain where the third-party library is. This is just to add a possibility for the consumers of the library.

Privileged blocks implications for WildFly modules

Without WildFly’s special AllPermission assignment each module should specify the permissions that it needs.

But this is where the permissions get even more complex. Let’s say in that example module B needs to read a file, the developer of module B may not know where that file will exist as it is not until the module is used in another project (like an app server we know file locations).

One way is avoiding doPrivileged:

  • Module B has the permission to access all files.

  • Module A has the permission to access just a specific file.

Combined a permission check would pass.

Second is module B contains a doPrivileged.

If module B still has the permission to access all files but module A has no file permission. If module A can pass the path of the file to module B one now has the problem that modules can potentially use this to bypass permissions and get access to all files.

The next variation is the permissions for module B need adjusting in context, i.e. it now needs to specify which file module B can access so there is no way for A to abuse it.

WildFly developers should not need to specify permissions for all modules in the app server, and if they need context that would be even more difficult.

The first example would be the equivalent of all deployments now needing the permissions granted but deployments are not supposed to need to be aware of the inner workings of the application server to decide what permissions they need.

So it ends up in that middle ground where if WildFly developers are not careful it could be open to abuse from deployments.

20. Migrate Legacy Security to Elytron Security

20.1. Authentication Configuration

20.2. Properties Based Authentication / Authorization

20.2.1. 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.

20.2.2. Migrating to FileSystemRealm Based Authentication

An alternative to using a legacy properties-realm in Elytron is to use the new filesystem-realm. An Elytron filesystem-realm will use file-backed authentication methods to secure the server. It is now easy to migrate from a legacy properties-realm to an Elytron filesystem-realm by using the Elytron Tool. The new Elytron Tool command, FileSystemRealmCommand, will convert the given properties files and create an Elytron FileSystemRealm, along with a script with the WildFly CLI commands for registering the FileSystemRealm and Security Domain on the WildFly server. After using the tool, it will still be necessary to configure an authentication-factory and an application-security-domain, as in the steps above.

Single User-Roles Conversion

To convert a single user-roles properties files combination, the parameters can be passed directly into the command:

./bin/elytron-tool.sh filesystem-realm --users-file users.properties --roles-file roles.properties --output-location filesystem_realm_dir

This will then configure a filesystem-realm in filesystem_realm_dir and will create a script converted-properties-filesystem-realm.sh in filesystem_realm_dir with the WildFly CLI commands to register the filesystem-realm and the security-domain, with the security-domain named converted-properties-security-domain. To customize the filesystem-realm name and the security-domain name, the --filesystem-realm-name and --security-domain-name parameters can be used.

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

Notes:
  • The short form options, as shown in the --help option, can be used, such as -u in place of --users-file.

  • When the --summary parameter is specified, an output of operations performed during conversion, including warnings and errors, will be shown once the command finishes conversion.

  • When the --silent parameter is specified, Elytron Tool will not give no information output, as compared to normal operation where warnings are shown. The --silent command will not override --summary, resulting in the ability to hide output until the command finishes conversion.

  • Elytron Tool will not configure the filesystem-realm and security-domain within WildFly itself, it will just provide the necessary commands in the output script file.

Bulk User-Roles Conversion

It is possible to convert multiple users-roles files combinations at once by using --bulk-convert parameter with a descriptor file.

An example descriptor-file from our tests is:

users-file:/home/jucook/Documents/WildFly/Git_Projects/WildFly-Elytron/wildfly-elytron-tool/target/test-classes/filesystem-realm/users/users-5.properties
roles-file:/home/jucook/Documents/WildFly/Git_Projects/WildFly-Elytron/wildfly-elytron-tool/target/test-classes/filesystem-realm/roles/roles-5.properties
output-location:./target/test-classes/filesystem-realm/output-5-bulk
filesystem-realm-name:nameOfFileSystemRealm5
security-domain-name:nameOfSecurityDomain5

users-file:/home/jucook/Documents/WildFly/Git_Projects/WildFly-Elytron/wildfly-elytron-tool/target/test-classes/filesystem-realm/users/users-6.properties
roles-file:/home/jucook/Documents/WildFly/Git_Projects/WildFly-Elytron/wildfly-elytron-tool/target/test-classes/filesystem-realm/roles/roles-6.properties
output-location:/home/jucook/Documents/WildFly/Git_Projects/WildFly-Elytron/wildfly-elytron-tool/target/test-classes/filesystem-realm/output-6-bulk
filesystem-realm-name:nameOfFileSystemRealm6
security-domain-name:nameOfSecurityDomain6

Each blank line starts a new conversion operation. As with a single conversion, the users-file, roles-file, and output-location are required parameters while the filesystem-realm-name and security-domain-name are optional parameters.

Execute the following command to convert with the descriptor file:

./bin/elytron-tool.sh filesystem-realm --bulk-convert descriptor-file
Notes:
  • For bulk conversion, only the long form option can be used, unlike the CLI mode where both long and short form options can be used.

  • The --summary and --silent parameters can be used here too. However, they must be specified while executing the command and apply to all conversions specified in the descriptor file.

  • If the --summary parameter is used, then a summary will be provided after each execution as opposed to after the command finishes all conversions.

  • As with the single conversion, absolute or relative paths can be used for users-file, roles-file, and output-location.

  • Each execution of the command will produce a separate script in the given output-location directory.

  • Repeated output-location paths can result in an error

  • If there is an error in one users-roles files combination then Elytron Tool will report the issue, such as a missing required parameter, and continue with the conversion of all remaining combinations.

20.2.3. 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>

20.3. 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.

20.3.1. 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>

20.3.2. 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>

20.3.3. 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>

20.4. 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.

20.4.1. PicketBox Based Configuration

./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.

./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.

20.4.2. 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 realm definition:

<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.

20.5. 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.

20.5.1. 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>

20.5.2. 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.

20.5.3. 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>

20.6. 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>

20.6.1. 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.

20.6.2. 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.

20.7. 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.

20.7.1. 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>

20.7.2. 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. When credentials are updated externally in the LDAP server, the user’s obsolete credential will be removed when the user successfully authenticates with the updated credential.

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>

20.8. Clients

20.9. Application Client Migration

20.9.1. Naming Client

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

Original Configuration

An InitialContext backed by the org.wildfly.naming.client.WildFlyInitialContextFactory 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.wildfly.naming.client.WildFlyInitialContextFactory");
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);

20.9.2. 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);
References in this document to Enterprise JavaBeans (EJB) refer to the Jakarta Enterprise Beans unless otherwise noted. :leveloffset: -1

20.9.3. General Utilities

20.9.4. 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 implemnent SPI which allows to deploy custom implemenations 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.

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

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:

# 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.

20.9.5. 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>

20.9.6. SSL Migration

20.9.7. Simple 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 PKCS12:

    /subsystem=elytron/key-store=LocalhostKeyStore:add(path=server.keystore,relative-to=jboss.server.config.dir,credential-reference={clear-text="keystore_password"},type=PKCS12)
  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="PKCS12"/>
                        <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"