Ho to configure proxy settings in WildFly

This tutorial covers how to configure WildFly to use Proxy settings to manage connections through a Proxy and, if needed, Proxy authorization.

Configuring WildFly to use Proxy Host settings is not different from any other Java application. Basically you need to include the following System Properties in your start script:

  • http.proxyHost: the host name of the proxy server
  • http.proxyPort: the port number, the default value being 80.
  • http.nonProxyHosts:a list of hosts that should be reached directly, bypassing the proxy. This is a list of patterns separated by ‘|’.
  • http.proxyUser=The proxy user name if needed
  • http.proxyPassword=The proxy password if needed.

Adding Proxy Settings to WildFly

As an example, we will add the proxy settings to WildFly standalone server through standalone.conf file:

JAVA_OPTS="$JAVA_OPTS -Djboss.modules.system.pkgs=$JBOSS_MODULES_SYSTEM_PKGS -Djava.awt.headless=true -Dexo.product.developing=true -Denvironment=production -Dhttp.proxyHost=192.168.10.1 -Dhttp.proxyPort=8080 -Dhttp.nonProxyHosts=localhost\|127.0.0.1\|10.10.10.* -Dhttp.proxyUser=user
-Dhttp.proxyPassword=password"

LDAP and WildFly part 1: securing the management console

This is a two-part tutorial about using LDAP on WildFly application server. In this first one we will learn how to configure the management console to use LDAP for authentication. In the next one we will learn how to use KeyCloak to authenticate and authorize application users against the LDAP server.

We will use ApacheDS as LDAP server. The simplest way to get started is downloading ApacheDS Studio which features a rich Eclipse based IDE and an embedded server: https://directory.apache.org/studio/downloads.html

Once downloaded, start ApacheDS studio and launch the ApacheDS server:

Now create a new connection using the default parameters:

  • Host: localhost
  • Port 10389
  • User: uid=admin,ou=system
  • Password: secret

The next thing we will do is creating an LDAP partition. As I want to use the partition dc=keycloak,dc=org, we need to create it. You can easily do it through ApacheDS, by clicking on the Add button:

Now import a set of users that will let you authenticate. Here is a sample LDIF script taken from https://github.com/keycloak/keycloak/blob/master/examples/ldap/ldap-example-users.ldif

dn: ou=People,dc=keycloak,dc=org
objectclass: top
objectclass: organizationalUnit
ou: People

dn: ou=RealmRoles,dc=keycloak,dc=org
objectclass: top
objectclass: organizationalUnit
ou: RealmRoles

dn: ou=FinanceRoles,dc=keycloak,dc=org
objectclass: top
objectclass: organizationalUnit
ou: FinanceRoles

dn: uid=jbrown,ou=People,dc=keycloak,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: jbrown
cn: James
sn: Brown
mail: jbrown@keycloak.org
postalCode: 88441
userPassword: password

dn: uid=bwilson,ou=People,dc=keycloak,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: bwilson
cn: Bruce
sn: Wilson
mail: bwilson@keycloak.org
postalCode: 88441
postalCode: 77332
postalCode: 66221
street: Elm 5
userPassword: password

dn: cn=ldap-user,ou=RealmRoles,dc=keycloak,dc=org
objectclass: top
objectclass: groupOfNames
cn: ldap-user
member: uid=jbrown,ou=People,dc=keycloak,dc=org
member: uid=bwilson,ou=People,dc=keycloak,dc=org

dn: cn=ldap-admin,ou=RealmRoles,dc=keycloak,dc=org
objectclass: top
objectclass: groupOfNames
cn: ldap-admin
member: uid=jbrown,ou=People,dc=keycloak,dc=org

dn: cn=accountant,ou=FinanceRoles,dc=keycloak,dc=org
objectclass: top
objectclass: groupOfNames
cn: accountant
member: uid=bwilson,ou=People,dc=keycloak,dc=org

Now Choose File | Import LDIF into LDAP. Point to the ldif file. At the end, the following structure will be created with two users loaded in the o.u. “People”:

Great, now let’s configure WildFly to use these users for accessing to the management console. The first change will be creating a new Realm named LDAP Realm which contains a base-dn (“ou=People,dc=keycloak,dc=org“) where users are stored and the prefix to them (in our case “uid“). We need also to define an outbound connection for the initial connection to the LDAP. We can use the default connection String for this purpose:

<security-realms> 
               . . .
               <security-realm name="LDAPRealm">
                 <authentication>
                    <ldap connection="ldapconnection" base-dn="ou=People,dc=keycloak,dc=org">
                       <username-filter attribute="uid"/>
                    </ldap>
                 </authentication>
             </security-realm>

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

Last change, is setting the LDAPRealm on the management-interfaces:

<management-interfaces>
            <http-interface security-realm="LDAPRealm" http-upgrade-enabled="true">
                <socket-binding http="management-http"/>
            </http-interface>
</management-interfaces>

Now start WildFly and check the you are able to log in the management console (http://localhost:9990 by default) with any of the two users you have created.

In the next tutorial  LDAP and WildFly part 2: Using Keycloak we will learn how to manage your application authentication and authorization on LDAP through KeyCloak server. Stay tuned!

How to create a custom Elytron Realm

In this tutorial we will learn how to create a custom Realm in Elytron, which is the equivalent of the old legacy Login Modules, and we will test it with a sample Web application.

The starting point for creating a custom Ream in Elytron is the interface SecurityRealm which contains the contract for a realm backed by a single homogeneous store of identities and credentials.

We will not cover all interface methods available in this interface, but the most important one which is:

public RealmIdentity getRealmIdentity(final Principal principal) throws RealmUnavailableException {
}

Within this method, you can either return a RealmIdentity if the login/password match or a RealmIdentity.NON_EXISTENT.

Most Realm implementations are an extension of the CacheableSecurityRealm which supports caching of org.wildfly.security.auth.server.RealmIdentity instances.

In the next section we will show how to implement a CacheableSecurityRealm to define a custom Realm which contains a set of hardcoded credentials that can be combined with entries from the configuration.

Creating a custom Realm

To get started, we will create a custom Realm named com.example.customrealm.ExampleRealm which implements CacheableSecurityRealm and Configurable to allow realm configuration. Here is our implementation:

public class ExampleRealm implements CacheableSecurityRealm, Configurable {
    
    private Map<String, String> users;
    private Map<String, Set<String>> roles;

    
    public ExampleRealm() {
        // nothing
    }
    
    public ExampleRealm(Map<String,String> map) {
        // test
        initialize(map);
    }
    
    @Override
    public void initialize(Map<String, String> map) {
        users = new HashMap<>();
        roles = new HashMap<>();

        //Adding Guest users
        String guest[] = new String[] { "Guest"};
        for (Map.Entry<String, String> entry : map.entrySet()) {
            // user and password
            users.put(entry.getKey(),entry.getValue());
            roles.put(entry.getKey(),new HashSet(Arrays.asList(guest)));
        }

        // Adding other users
        String array[] = new String[] { "Admin", "Guest"};
        users.put("admin","admin");
        roles.put("admin",new HashSet(Arrays.asList(array)));

        System.out.println("I've loaded "+users.size()+ " users ");
    }
    
    @Override
    public void registerIdentityChangeListener(Consumer<Principal> cnsmr) {
        // nothing
    }
    

    @Override
    public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName,
            AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
        return SupportLevel.UNSUPPORTED;
    }


    @Override
    public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName)
            throws RealmUnavailableException {
        return PasswordGuessEvidence.class.isAssignableFrom(evidenceType)? SupportLevel.POSSIBLY_SUPPORTED : SupportLevel.UNSUPPORTED;
    }

    @Override
    public RealmIdentity getRealmIdentity(final Principal principal) throws RealmUnavailableException {
        // just search the user in the configured users
        String password = users.get(principal.getName());
        if (password != null) {
            return new ExampleRealmIdentity(principal, password, roles.get(principal.getName()));
        }
        return RealmIdentity.NON_EXISTENT;
    }

    @Override
    public String toString() {
        return "ExampleRealm: " + this.users.keySet().stream().collect(Collectors.toList());
    }
    
}

So, the purpose of this Custom Realm is to create some cached user/password entries which are bound to a set of Roles. We have added only one user with credentials “admin”/”admin” bound to the Roles “Admin” and “Guest”. In addition, the initialize method collects a set of key/values attributes from the configuration which are allowed only the “Guest” Role.

Realm Identities need to implement the org.wildfly.security.auth.server.RealmIdentity to store credentials and roles. The basic implementation of our RealmIdentity follows here:

public class ExampleRealmIdentity implements RealmIdentity {

    private final Principal principal;
    private final String password;
    private final Set<String> roles;
    
    public ExampleRealmIdentity(Principal principal, String password, Set<String> roles) {
        this.principal = principal;
        this.password = password;
        this.roles = roles;
    }
    
    @Override
    public Principal getRealmIdentityPrincipal() {
        return principal;
    }

    @Override
    public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType,
            String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
        // do not support credential acquire
        return SupportLevel.UNSUPPORTED;
    }

    @Override
    public <C extends Credential> C getCredential(Class<C> credentialType) throws RealmUnavailableException {
        // do not return credentials
        return null;
    }

    @Override
    public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName)
            throws RealmUnavailableException {
        return PasswordGuessEvidence.class.isAssignableFrom(evidenceType)? SupportLevel.SUPPORTED : SupportLevel.UNSUPPORTED;
    }

    @Override
    public boolean verifyEvidence(Evidence evidence) throws RealmUnavailableException {
        if (evidence instanceof PasswordGuessEvidence) {
            PasswordGuessEvidence guess = (PasswordGuessEvidence) evidence;
            return Arrays.equals(password.toCharArray(), guess.getGuess());
        }
        return false;
    }

    @Override
    public boolean exists() throws RealmUnavailableException {
        return true;
    }
    
    @Override
    public Attributes getAttributes() throws RealmUnavailableException {
        if (roles == null || roles.isEmpty()) {
            return Attributes.EMPTY;
        }
        MapAttributes map = new MapAttributes();
        map.addAll("Roles", roles);
        return map;
    }
    
    @Override
    public AuthorizationIdentity getAuthorizationIdentity() throws RealmUnavailableException {
        return AuthorizationIdentity.basicIdentity(getAttributes());
    }
}

Building and deploying the Custom Realm

The Realm is part of a Maven project which is avaiable here: https://github.com/fmarchioni/mastertheboss/tree/master/security/example-elytron-realm

You can build it as any maven project:

$ mvn install

Then, copy the generated jar file in a folder, for example in WildFly’s bin folder:

$ cp target/elytron-custom-realm-1.0.0.jar $JBOSS_HOME/bin

We will now install the Custom Realm on Elytron and bind it to an HTTP Authentication Factory so that Web application can reference it as an Application security domain:

#Add module for the custom realm
module add --name=com.example.customrealm  --resources=elytron-custom-realm-1.0.0.jar --dependencies=org.wildfly.security.elytron,org.wildfly.extension.elytron

#Creates a custom elytron Realm and initialize one guest identity with guest/password
/subsystem=elytron/custom-realm=example-realm:add(module=com.example.customrealm, class-name=com.example.customrealm.ExampleRealm, configuration={"guest" => "password"})

#Defines a Security Domain named example-domain based om the example-realm
/subsystem=elytron/security-domain=example-domain:add(realms=[{realm=example-realm}], default-realm=example-realm, permission-mapper=default-permission-mapper)

#creates an HTTP Authentication factory based on the example-domain which uses HTTP Basic authentication
/subsystem=elytron/http-authentication-factory=example-http-auth:add(http-server-mechanism-factory=global, security-domain=example-domain, mechanism-configurations=[{mechanism-name=BASIC, mechanism-realm-configurations=[{realm-name=example-domain}]}])

#Adds the example-domain to undertow's security domain
/subsystem=undertow/application-security-domain=example-domain:add(http-authentication-factory=example-http-auth)

In addition, if you are running applications based on the ejb subsystem, the security domain needs to be added to the ejb3 subsystem as well:

/subsystem=ejb3/application-security-domain=example-domain:add(security-domain=example-domain)

Testing the Elytron custom Realm

To test our Realm, we will deploy a simple Web application which is configured to use the above Security Realm:

<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.jboss.org/schema/jbossas
    http://www.jboss.org/schema/jbossas/jboss-web_7_2.xsd">
 
   <security-domain>example-domain</security-domain> 
</jboss-web>

This example Web application is available at: https://github.com/fmarchioni/mastertheboss/tree/master/security/ee-security-elytron

Deploy it as follows:

$ mvn install wildfly:deploy

Then, login at http://localhost:8080/ee-security-elytron using the credentials “admin”/”admin”. You will be granted access:

Congratulations! You just managed to create,deploy and test a Custom Elytron Realm on WildFly!

Configuring TLS 1.3 on WildFly application Server

This tutorial will teach you how to configure Transport Layer Security (TLS) v.1.3 on WildFly application server.

TLS 1.3 offers improved speed compared to TLS 1.2. The earlier version of TLS (1.2) required two round-trips to finish a TLS handshake. On the other hand, TLS 1.3 only needs to complete a single round-trip. This substantially reduces encryption latency and users will be able to browse websites faster and with greater security.

Besides that, TLS 1.3 has removed some deprecated features (SHA-1, RC4, DES and AES-CBC) that caused vulnerabilities to attacks such as the RC4 and BEAST exploits issues.

In order to configure TLS 1.3 on WildFly, we need an available ssl-context. You can check this tutorial to get started with WildFly and SSL: How to configure SSL/HTTPS on WildFly

Once you have an https listener available, you can check the SSL connection with cURL as follows:

curl -v -k https://localhost:8443
* Rebuilt URL to: https://localhost:8443/

* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384

As you can see, the SSL connection has been established using TLS 1.2. Now let’s switch to TLS 1.3.

In order to that, we will update our ssl-context configuration in the Elytron subsystem to specify the cipher-suite-names attribute. The format of this attribute is colon separated list of the TLS 1.3 cipher suites that you would like to enable.

You can check the list of available ciphers with the openssl command:

openssl ciphers -tls1_3 -v -s 
TLS_AES_256_GCM_SHA384  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any  Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(128) Mac=AEAD
TLS_AES_128_CCM_SHA256  TLSv1.3 Kx=any      Au=any  Enc=AESCCM(128) Mac=AEAD

Now let’s first switch the demoSSLContext to TLS 1.3:

[standalone@localhost:9990 /] /subsystem=elytron/server-ssl-context=demoSSLContext:write-attribute(name=protocols,value=[TLSv1.3])
{
    "outcome" => "success",
    "response-headers" => {
        "operation-requires-reload" => true,
        "process-state" => "reload-required"
    }
}

Then, we will choose the list of cipher suites for our TLS 1.3 connection:

[standalone@localhost:9990 /] /subsystem=elytron/server-ssl-context=demoSSLContext:write-attribute(name=cipher-suite-names,value=TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256)
{
    "outcome" => "success",
    "response-headers" => {
        "operation-requires-reload" => true,
        "process-state" => "reload-required"
    }
}

That’s it. The following server-ssl-context will be available:

<server-ssl-contexts>
    <server-ssl-context name="demoSSLContext" cipher-suite-names="TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256" protocols="TLSv1.3" key-manager="demoKeyManager"/>
</server-ssl-contexts>

Reload the server configuration and check again the SSL connection with cURL as follows:

curl -v -k https://localhost:8443
* Rebuilt URL to: https://localhost:8443/

* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, [no content] (0):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS handshake, [no content] (0):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

As you can see, the connection has been established using TLS 1.3 and the cipher suite TLS_AES_256_GCM_SHA384.

That’s it. You can apply the same configuration also to create the Client SSL Context, which can be used, for example by the mod_cluster subsystem when connecting to the load balancer using SSL/TLS:

    <client-ssl-context name="demoClientSSLContext" cipher-suite-names="TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256" protocols="TLSv1.3" key-manager="demoClientKeyManager"/>

TLS 1.3 vs TLS 1.3 Performance Test

Finally, I had a round of tests with JMeter to compare the throughput of a basic Web application using HTTPS and TLS 1.2/TLS 1.3 to see if the reduced number of round-trips actually works to improve the application performance.

The first test I have run, has been executed using the default WildFly 22 profile and default https configuration (TLS 1.2):

So, on average, requests were carried out in 35 ms with a Throughput of 269.1.

Next, I’ve configured WildFly to use TLS 1.3 with the set of ciphers discussed in this article. Here’s the JMeter report:

On average, using TLS 1.3, each request was carried out with 25ms with a Throughput of 365.7. Although this test is not rocket science, it still shows an improvement in application throughput around 35%. Which is not bad at all, isn’t it?

How to use failover and distributed Realms in Elytron

In this tutorial we will learn how to create failover and distributed Elytron Realms to add resilience and distribution to your identity lookup.

The option to stack multiple login modules is already available in the legacy Security Model. As the legacy security model is soon going to be deprecated, all the missing features are now available also in Elytron.

The first Realm we will discuss is the Failover Realm which allows to failover the Identity Lookup to another Identity Store in case of failure. Let’s see how this can be configured.

We will be using the following Identity Store set up:

  • LDAP Realm: Base Realm (called “delegate realm” in Elytron)
  • File System Realm: Failover Realm

Let’s start adding the LDAP Realm. Login to the CLI and execute the following commands:

if (outcome != success) of /subsystem=elytron/simple-role-decoder=from-roles-attribute:read-resource
 /subsystem=elytron/simple-role-decoder=from-roles-attribute:add(attribute=Roles) 
end-if

batch

/subsystem=elytron/dir-context=exampleDC:add(url="ldap://localhost:389",principal="cn=admin,dc=wildfly,dc=org",credential-reference={clear-text="secret"})

/subsystem=elytron/ldap-realm=demoLdapRealm: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"}]})

/subsystem=elytron/security-domain=ldapSD:add(realms=[{realm=demoLdapRealm,role-decoder=from-roles-attribute}],default-realm=demoLdapRealm,permission-mapper=default-permission-mapper)

/subsystem=elytron/http-authentication-factory=example-ldap-http-auth:add(http-server-mechanism-factory=global,security-domain=ldapSD,mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=RealmUsersRoles}]}])

run-batch

So the first block of instructions will create an LDAP realm based on the BaseDN “ou=Users,dc=wildfly,dc=org” running on localhost:389. Don’t care about starting up an LDAP Server as this connection is supposed to fail.

Now let’s add a File System Realm which will be our fallback Realm:

batch

/subsystem=elytron/filesystem-realm=demoFsRealm:add(path=demofs-realm-users,relative-to=jboss.server.config.dir)

/subsystem=elytron/filesystem-realm=demoFsRealm:add-identity(identity=frank)
/subsystem=elytron/filesystem-realm=demoFsRealm:set-password(identity=frank,clear={password="password123"})
/subsystem=elytron/filesystem-realm=demoFsRealm:add-identity-attribute(identity=frank,name=Roles, value=["Admin","Guest"])

/subsystem=elytron/security-domain=fsSD:add(realms=[{realm=demoFsRealm,role-decoder=from-roles-attribute}],default-realm=demoFsRealm,permission-mapper=default-permission-mapper)

/subsystem=elytron/http-authentication-factory=example-fs-http-auth:add(http-server-mechanism-factory=global,security-domain=fsSD,mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=RealmUsersRoles}]}])

run-batch

Finally, create the Failover Realm which will be a wrapper around the two base Realms:

# Create a Failover Realm
/subsystem=elytron/failover-realm=failoverRealm:add(delegate-realm=demoLdapRealm,failover-realm=demoFsRealm)

# Create a Security Domain for the Failover Realm
/subsystem=elytron/security-domain=failoverSD:add(default-realm=failoverRealm,permission-mapper=default-permission-mapper,realms=[{realm=failoverRealm,role-decoder="from-roles-attribute"}])

# Add an HTTP Authentication Factory for our Security Domain
/subsystem=elytron/http-authentication-factory=example-failover-http-auth:add(http-server-mechanism-factory="global",mechanism-configurations=[{mechanism-name="BASIC",mechanism-realm-configurations=[{realm-name="RealmUsersRoles"}]}],security-domain=failoverSD)

# Add the Failover Domain to the HTTP Authentication Factory
/subsystem=undertow/application-security-domain=httpLdapSD:add(http-authentication-factory=example-failover-http-auth)
run-batch

We are done with the configuration. Reload for changes to take effect:

reload

The most interesting bit is the failover-realm configuration, which holds a reference to the two wrapped Realms:

 <failover-realm name="failoverRealm" delegate-realm="demoLdapRealm" failover-realm="demoFsRealm"/> 

As a proof of concept, you can deploy the following application: https://github.com/fmarchioni/wildfly-admin-guide/tree/master/chapter15/servlet-security

This application references the Security Domain known to undertow’s Authentication Factory:

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

If you try to access the Secured Servlet (http://localhost:8080/demo-security/secure) when the LDAP Server is not available, the failover will happen:

17:32:54,019 WARN  [org.wildfly.security] (default task-1) ELY13001: Realm is failing over.: org.wildfly.security.auth.server.RealmUnavailableException: ELY01125: Ldap-backed realm failed to obtain context
	at org.wildfly.security.elytron-private@1.14.1.Final//org.wildfly.security.auth.realm.ldap.LdapSecurityRealm.obtainContext(LdapSecurityRealm.java:214)
	at org.wildfly.security.elytron-private@1.14.1.Final//org.wildfly.security.auth.realm.ldap.LdapSecurityRealm.access$600(LdapSecurityRealm.java:101)

You can login using the user added in the File System Realm (frank/password123).

Using a Distributed Realm

The other option for configuring Realm redundancy is the Distributed Realm. The Distributed Realm is not about failing over in the event of a Realm being unavailable (although it may be related). This Realm can be rather used when identities are located across multiple stores. Therefore, identity look up will be attempted sequentially across all Realms included in the list, until one succeeds.

You can add a distributed-realm element to your configuration as follows:

<distributed-realm name="distributedRealm" realms="demoLdapRealm demoFsRealm" />

Unlike the Failover Realm, the above configuration requires that the LDAP Server is available, although Identity lookup will be checked on the File System Realm if that failed on LDAP.

JBoss security framework

Before WildFly 11, the core security framework used by the application server was based on PicketBox framework. This security framework is now deprecated on newer versions of WildFly and it’s recommended to switch to Elytron.

Some tutorials available on this site which discuss Elytron are:

How to create a custom Elytron Realm

Creating an Elytron Security Realm for WildFly

Configuring LDAP based authentication with Elytron on WildFly

Configure an Elytron JDBC Realm on WildFly

Using Elytron Credential Stores in WildFly

If you are still using the legacy security for your WildFly applications keep reading!

Security is a fundamental part of any enterprise application .The JBoss component framework that handles security is the JBossSX extension framework. The JBossSX security extension provides support for both the role-based declarative J2EE security model as well as integration of custom security via a security proxy layer.

The default implementation of the declarative security model is based on Java Authentication and Authorization Service (JAAS) login modules and subjects.

JAAS delivers a framework for providing authentication and authorization for all the Java applications.
Authentication is a mechanism to verify the client
Authorization is a mechanism to ensure that the client has the permissions required to access a secured resource.

The four steps to enable JAAS:

1. Identify which resources needs to be secured: a Web Application ? an EJB ?

2. Identify a suitable Security Provider. In the case of JBOSS, the security is provided by the JBOSS security manager.

3. Use a Security Implementation to secure the identified resources.

4. Make the clients of the secured resources aware of the security implementation and usage mechanisms

The LoginModule

The JBOSS application server provides pluggable security managers. The Web and the EJB Containers use the security managers to perform authentication and authorization. The JAAS-based security manager is the default security manager provided with JBOSS

The LoginModule is the module is in charge to provide the security implementation that authenticates and authorizes the clients. A typical implementation involves validating the username/password combination

JBOSS provides some Login Modules out of the box :

  • UserRolesLoginModule: This is the default login module : it reads the username, password and role information from files that are packaged with the applications.
  • DatabaseServerLoginModule: This module reads the username, password and role information from the tables in a database. The database is accessed using JDBC and the JDBC driver needs to be available in the application classpath.
  • LDAPLoginModule: This module requires the username and password. This is used to connect to LDAP as a means of verification. If successful, the roles are based on the group memberships of the user. This module is not very configurable as it doesn’t expose enough configuration options to work with all LDAPs.
  • BaseCertLoginModule: This module uses client certificates to perform authentication. It cannot provide role information. This is typically used in conjunction with one of the other LoginModules to obtain the role memberships

Securing a Web Application with UserRolesLoginModule

In this first tutorial we’ll explore how to secure a Web application and an EJB application using the UserRolesLoginModule

Step 1: Add the Security Policy to your conf/login-config.xml

<application-policy name = "jboss-secure">
   <authentication>
   <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule"
 flag = "required">
     <module-option name="usersProperties">users.properties</module-option>
     <module-option name="rolesProperties">roles.properties</module-option>
   </login-module>
   </authentication>
</application-policy>

This tells JBoss to associate the UserRolesLoginModules for the policy named “jboss-secure”.

Note: Security domains are created on demand. Putting an entry in login config.xml doesn’t have any effect until an application tries to use it.

Step 2: Add security constraints to web.xml

<security-constraint> 
 
   <web-resource-collection> 
     <web-resource-name>Restricted to Secure role</web-resource-name> 
     <description>Declarative security</description> 
     <url-pattern>/*</url-pattern> 
     <http-method>HEAD</http-method> 
     <http-method>GET</http-method> 
     <http-method>POST</http-method> 
   </web-resource-collection> 
 
   <auth-constraint> 
     <role-name>Administrator</role-name> 
   </auth-constraint> 

 </security-constraint>

 <login-config>
   <auth-method>BASIC</auth-method>
   <realm-name>JBoss Secured Realm</realm-name>
 </login-config>

 <security-role>
   <role-name>Administrator</role-name>
 </security-role>

In this sample all resources of the web application are restricted to the “Administrator” role. Now you need only

Step 3: Add security domain to your jboss-web.xml

<jboss-web>
 
  <security-domain>java:/jaas/jboss-secure</security-domain>
 
</jboss-web>

Last configuration file is JBoss web’s deployment descriptors. This file is by default under the WEB-INF folder. To link to a specific security domain, you need to set the security-domain element to the JNDI name of the security domain to link to. Security domains are bound under java:/jaas in JNDI, so the todo domain would be java:/jaas/jboss-secure.

Step 4: Add users.properties and roles.properties

Usernames and password are stored in users.properties file (you can place it anywhere JBoss classloader can reach it for example under WEB-INF/classes)

A minimalist user.properties file can be:

admin=admin

The roles.properties associate the usernames to Security Roles.

A minimalist roles.properties file can be:

admin=Administrator

Securing an EJB Application 

Securing the EJB tier is not much different: the server configuration stays the same, we need to group the EJB methods based on the roles that can access these methods.

Step 1: add <method-permission> tag in the ejb-jar.xml file.

<method-permission> 
   <role-name>Administrator</role-name> 
   <method> 
     <ejb-name>SampleEJB</ejb-name> 
     <method-name>securedMethod</method-name> 
   </method> 
</method-permission> 
<method-permission> 
   <unchecked/> 
   <method> 
     <ejb-name>SampleEJB</ejb-name> 
     <method-name>unsecuredMethod</method-name> 
   </method> 
</method-permission> 

In the above example, the method “securedMethod” in the EJB “SampleEJB” is available only to the
client belonging to the “Secure” role. However, the method “unsecuredMethod” in the same bean is available to all the clients.

Step 2: Add security domain to your jboss.xml

During the application packaging, the administrator must choose the security domain used to protect
the application. This is exaclty the same as for the web tier except that the EJB tier uses another d.descriptor file called jboss.xml.

<jboss>
 
  <security-domain>java:/jaas/jboss-secure</security-domain>
 
</jboss>

Securing JBoss / WildFly Management Interfaces: the easy way

This is the second tutorial about securing WildFly. In the first one, we have discussed how to secure the HTTP channel for Web applications:  How to configure SSL/HTTPS on WildFly

In this tutorial we will learn how to secure JBoss / WildFly Management interfaces using Elytron.

Firstly, we will at first demonstrate how to create a JDBC Realm and use that for accessing the Management interfaces.

Then, we will show how to encrypt the communication between the Web Console or the CLI and the application server

Secure Management interfaces with a JDBC Realm

Firstly, to configuring a JDBC Realm, you need to define a Datasource. For this purpose we will create a PostgreSQL Datasource, define a table for USERS and add at least one Admin user in it.

Start by creating the PostgreSQL Datasource which connects to a local PostgreSQL DB using “postgres/postgres” as credentials:

module add --name=org.postgres --resources=postgresql-42.2.5.jar --dependencies=javax.api,javax.transaction.api

/subsystem=datasources/jdbc-driver=postgres:add(driver-name="postgres",driver-module-name="org.postgres",driver-class-name=org.postgresql.Driver)

data-source add --jndi-name=java:/PostGreDS --name=PostgrePool --connection-url=jdbc:postgresql://localhost/postgres --driver-name=postgres --user-name=postgres --password=postgres

reload

Next, verify that the Datasource is available:

/subsystem=datasources/data-source=PostgrePool:test-connection-in-pool

Finally, create a table USERS with one row in it:

$ psql postgres postgres
psql (11.2 (Debian 11.2-1.pgdg90+1))
Type "help" for help.

postgres=# CREATE TABLE USERS(login VARCHAR(64) PRIMARY KEY, password VARCHAR(64), role VARCHAR(64));
postgres=# INSERT into USERS (login,password,role) values('admin','secret','Admin');

Creating the JDBC Realm

With the Datasource in place, let’s create the JDBC Realm and Security Domain which maps our Database table:

#Define the JDBC Realm
/subsystem=elytron/jdbc-realm=demoJdbcRealm:add(principal-query=[{sql="SELECT password,role FROM USERS WHERE login=?",data-source=PostgrePool,clear-password-mapper={password-index=1},attribute-mapping=[{index=2,to=groups}]}])

#Define the Security Domain
/subsystem=elytron/security-domain=jdbcSD:add(realms=[{realm=demoJdbcRealm,role-decoder=groups-to-roles}],default-realm=demoJdbcRealm,permission-mapper=default-permission-mapper)

As our authentication will pass through the HTTP connectors, we will add an HTTP Authentication factory to our configuration:

/subsystem=elytron/http-authentication-factory=db-http-auth:add(http-server-mechanism-factory=global,security-domain=jdbcSD,mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=RealmUsersRoles}]}])

You should be able to see in your XML configuration:

<security-domains>
    <!-- default security domains -->

    <security-domain name="jdbcSD" default-realm="demoJdbcRealm" permission-mapper="default-permission-mapper">
        <realm name="demoJdbcRealm" role-decoder="groups-to-roles"/>
    </security-domain>

</security-domains>
<security-realms>
    <identity-realm name="local" identity="$local"/>
    <jdbc-realm name="demoJdbcRealm">
        <principal-query sql="SELECT password,role FROM USERS WHERE login=?" data-source="PostgrePool">
            <attribute-mapping>
                <attribute to="groups" index="2"/>
            </attribute-mapping>
            <clear-password-mapper password-index="1"/>
        </principal-query>
    </jdbc-realm>

    <!-- default Property realms -->
</security-realms>

Then, in the HTTP Management factory section, the following should have been added:

<http-authentication-factory name="db-http-auth" security-domain="jdbcSD" http-server-mechanism-factory="global">
    <mechanism-configuration>
        <mechanism mechanism-name="BASIC">
            <mechanism-realm realm-name="RealmUsersRoles"/>
        </mechanism>
    </mechanism-configuration>
</http-authentication-factory>

Binding Management interfaces to the HTTP Authentication factory

Finally, we can bind the management interface with the HTTP Authentication Factory. This can be done as follows:

/core-service=management/management-interface=http-interface:write-attribute(name=http-authentication-factory,value=db-http-auth)

As a proof of concept, verify that by logging into the management console (http://localhost:9990), you are able to connect using the users in the Database:

Enabling SSL for the Web console

The second step to secure our management interfaces will be enabling SSL. This can be done using the new security enable-ssl-management CLI command that allows creating the required key pairs and install them in your configuration. We will use this command with the –interactive options that requires passing the arguments on the command line:

 security enable-ssl-management --interactive
Please provide required pieces of information to enable SSL:

Certificate info:
Key-store file name (default management.keystore): 
Password (blank generated): wildfly
What is your first and last name? [Unknown]: Admin
What is the name of your organizational unit? [Unknown]: Administrators
What is the name of your organization? [Unknown]: Acme
What is the name of your City or Locality? [Unknown]: London
What is the name of your State or Province? [Unknown]: 
What is the two-letter country code for this unit? [Unknown]: UK
Is CN=Admin, OU=Administrators, O=Acme, L=London, ST=Unknown, C=UK correct y/n [y]?y
Validity (in days, blank default): 
Alias (blank generated): admin
Enable SSL Mutual Authentication y/n (blank n):n

SSL options:
key store file: management.keystore
distinguished name: CN=Admin, OU=Administrators, O=Acme, L=London, ST=Unknown, C=UK
password: wildfly
validity: default
alias: admin
Server keystore file management.keystore, certificate file management.pem and management.csr file will be generated in server configuration directory.

Do you confirm y/n :y
Unable to connect due to unrecognised server certificate
Subject    - CN=Admin,OU=Administrators,O=Acme,L=London,ST=Unknown,C=UK
Issuer     - CN=Admin, OU=Administrators, O=Acme, L=London, ST=Unknown, C=UK
Valid From - Wed Jan 08 10:15:46 CET 2020
Valid To   - Tue Apr 07 10:15:46 CEST 2020
MD5 : 7c:63:76:48:ec:8d:e2:2c:96:74:4d:19:7d:81:e1:6d
SHA1 : de:3e:ba:f5:9b:c1:9c:4c:e5:48:ca:cf:f4:e2:71:63:d3:20:19:1a


Accept certificate? [N]o, [T]emporarily, [P]ermanently : P
Server reloaded.
SSL enabled for http-interface
ssl-context is ssl-context-eb0e29ad-6cf6-4c28-aab2-ffba55eb1d86
key-manager is key-manager-eb0e29ad-6cf6-4c28-aab2-ffba55eb1d86
key-store   is key-store-eb0e29ad-6cf6-4c28-aab2-ffba55eb1d86

As you can see, the certificate has been installed in the configuration folder and the CLI connection already switched to port 9993.

In terms of configuration, the following ssl-context has been added to the management interface:

 <management-interfaces>
            <http-interface ssl-context="ssl-context-eb0e29ad-6cf6-4c28-aab2-ffba55eb1d86" security-realm="ManagementRealm">
                <http-upgrade enabled="true"/>
                <socket-binding http="management-http" https="management-https"/>
            </http-interface>
 </management-interfaces>

The SSL Context, in turn, is defined in the Elytron TSL section, which contains the key-store definition, the related key-manager and the server-ssl-context:

<tls>
    <key-stores>
        <key-store name="key-store-eb0e29ad-6cf6-4c28-aab2-ffba55eb1d86">
            <credential-reference clear-text="wildfly"/>
            <implementation type="JKS"/>
            <file required="false" path="management.keystore" relative-to="jboss.server.config.dir"/>
        </key-store>
    </key-stores>
    <key-managers>
        <key-manager name="key-manager-eb0e29ad-6cf6-4c28-aab2-ffba55eb1d86" key-store="key-store-eb0e29ad-6cf6-4c28-aab2-ffba55eb1d86">
            <credential-reference clear-text="wildfly"/>
        </key-manager>
    </key-managers>
    <server-ssl-contexts>
        <server-ssl-context name="ssl-context-eb0e29ad-6cf6-4c28-aab2-ffba55eb1d86" cipher-suite-filter="DEFAULT" protocols="TLSv1.2" want-client-auth="false" need-client-auth="false" authentication-optional="false" use-cipher-suites-order="false" key-manager="key-manager-eb0e29ad-6cf6-4c28-aab2-ffba55eb1d86"/>
    </server-ssl-contexts>
</tls>

As a proof of concept, you can now connect to the Management interface through https://localhost:9993

In order to disable SSL for your management interface, simply issue from the CLI:

 security disable-ssl-management 

Enabling SSL for the CLI

We will now show how to secure the CLI using mutual SSL authentication. We will still use the security enable-ssl-management command, however we will answer “yes” to Enable SSL Mutual Authentication” and provide the path where the Client Certificate is stored:

[standalone@localhost:9990 /] security enable-ssl-management --interactive
Please provide required pieces of information to enable SSL:

Certificate info:
Key-store file name (default management.keystore):
Password (blank generated): secret
What is your first and last name? [Unknown]:
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]?y
Validity (in days, blank default):
Alias (blank generated): localhost
Enable SSL Mutual Authentication y/n (blank n):y
Client certificate (path to pem file): /home/jboss/wildfly24.0.0.Final/standalone/configuration/client.crt
Validate certificate y/n (blank y): n
Trust-store file name (management.truststore):
Password (blank generated): secret

SSL options:
key store file: management.keystore
distinguished name: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=
Unknown
password: secret
validity: default
alias: localhost
client certificate: /home/jboss/wildfly-24.0.0.Final/standalone/config
uration/client.crt
trust store file: management.truststore
trust store password: secret
Server keystore file management.keystore, certificate file management.pem and management.csr file will be generated in server configuration directory.
Server truststore file management.truststore will be generated in server configuration directory.

Do you confirm y/n :y
Unable to connect due to unrecognised server certificate
Subject – CN=Unknown,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown
Issuer – CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Valid From – Fri Sep 03 14:27:48 CEST 2021
Valid To – Thu Dec 02 14:27:48 CET 2021
MD5 : e5:53:0e:99:65:9d:fa:23:b1:f2:9c:6a:d1:a4:25:2a
SHA1 : d6:60:06:5f:db:bb:13:1f:53:94:6d:ed:c3:d4:f8:7c:b5:18:78:19

Accept certificate? [N]o, [T]emporarily, [P]ermanently : P

Done with the certificate, we now need to include this information in the jboss-cli.xml file.

First, set the defaut protocol to “https-remoting“:

<default-protocol use-legacy-override="false">https-remoting</default-protocol>

Then, at the bottom of the file, specify the keystore and truststore settings, based on the information provided in the security enable-ssl-management command:

<ssl>
     <alias>localhost</alias>
     <key-store>/home/jboss/wildfly-24.0.0.Final/standalone/configuration/management.keystore</key-store>
     <key-store-password>secret</key-store-password>
     <trust-store>/home/jboss/wildfly-24.0.0.Final/standalone/configuration/management.truststore</trust-store>
     <trust-store-password>secret</trust-store-password>
     <modify-trust-store>true</modify-trust-store>
</ssl>

Now, reload your server and try connecting with the CLI:

$ ./jboss-cli.sh -c
[standalone@localhost:9993 /]

As you can see from the prompt, we are not using the secure 9993 port to connect with the CLI.

Securing WildFly Management Interfaces (legacy)

In the second part of this tutorial we will demonstrate how to secure access to the Administration console for older installations of WildFly / JBoss EAP which are not using Elytron to manage Secure Sockets Layer (SSL).

By default, the communication between the browser and the Management console happens in clear text. The only security applied is an authentication which is required before accessing the console. If you have strict security requirements, however you might need to encrypt the communication with the management console. For this purpose we will use a self-signed certificate. If you need to expose the Management console to other entities (for example outside your network) you might consider creating a Certificate Request which has to be signed by a CA.

So start by creating a keystore with the following keytool command:

keytool -genkeypair -alias serverkey -keyalg RSA -keysize 2048 -validity 7360 -keystore server.keystore -keypass mypassword -storepass mypassword -dname "cn=Server Administrator,o=Acme,c=GB"

Now copy the server.keystore under your server’s configuration folder (e.g. C:\wildfly-8.0.0.Final\standalone\configuration ).

Next, include in your ManagementRealm configuration a server-identities definition which references our keystore as follows:

<security-realm name="ManagementRealm">
    <authentication>
        <local default-user="$local"/>
        <properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
    </authentication>
    <authorization map-groups-to-roles="false">
        <properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
    </authorization>
    <server-identities>
        <ssl>
            <keystore path="server.keystore" relative-to="jboss.server.config.dir" keystore-password="mypassword" alias="serverkey"/>
        </ssl>
    </server-identities>
</security-realm>

Last tweak is needed in the management-interfaces section, where you have to replace the http socket binding with an https socket binding:

<management-interfaces>
    <http-interface security-realm="ManagementRealm" http-upgrade-enabled="true">
       <!-- <socket-binding http="management-http"/> -->
              <socket-binding https="management-https"/>
    </http-interface>
</management-interfaces>

Please note that the management-https in turn references a socket binding in your configuration which is by default included:

<socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>

So, as you can see, the management console, when using https will be bound on port 9993.

Restart your server and check that the management console is available on https://localhost:9993

As you can see from the above definition, WildFly is using https as communication protocol, although it is marked as unsecure site because the certificate is not signed by a CA.

How to run WildFly and JBoss EAP through a firewall

This tutorial will teach you how to configure WildFly or JBoss EAP to run through a firewall.

Linux Machine

To configure firewall rules on a Linux machine you can use the command-line tool firewall-cmd which is part of the firewalld application, that is installed by default. It can be used to make permanent and non-permanent runtime changes.

Typically, you would allow the HTTP port to be reached from outside. So, to allow the port 8080:

sudo firewall-cmd --zone=public --add-port 8080/tcp

The above rule will not however survive a server restart. In order to make it permanent, you would need to add the –permanent parameter:

sudo firewall-cmd --zone=public --add-port 8080/tcp --permanent

Windows machine

On a Windows machine go into Control Panel and click Windows Firewall. There select “Allow an app or feature through Windows Firewall” and it will open this window:

Next, click on “Change Settings” and select the “Allow another app...” button and in the next window choose the Browse button next to the Path field.

You will need to add the WildFly service so that it can run through the Windows Firewall