Enabling WildFly Elytron for Management Security

Version 2

    This article captures how to enable Elytron defined security for management of WildFly Core based on our incubation branch.  This may be updated in the short term but should not be required in the long term once we switch to Elytron based configuration by default.

     

    Builds

     

    Build WildFly Core

     

    The first step is to build the remoting5_integration branch of WildFly Core available from here: -

     

    https://github.com/wildfly-security-incubator/wildfly-core/tree/remoting5_integration

     

    Note: As a development branch double check which SNAPSHOT dependencies are currently required.

     

    Currently the following SNAPSHOT builds are required: -

     

    RepositoryBranch
    GitHub - wildfly-security-incubator/wildfly-naming-client: WildFly Naming Client libraryelytron_integration

     

    Deploy Elytron Subsystem

     

    The Elytron subsystem can be obtained from the following repository: -

     

    https://github.com/wildfly-security/elytron-subsystem

     

    The approach taken for the Elytron subsystem is that is is entirely capabilities and requirements based for chaining together an assembled configuration, this also means that requirements could be satisfied by other subsystems.

     

    The following command will deploy the subsystem to your WildFly Core build: -

     

    mvn -P install2wildfly-core install -Dwildfly.core.home={jboss.home}
    
    
    

     

    At this point the WildFly Core installation can be started.

     

    Configuration

     

    With the WildFly Core server running the commands described in this section can all be executed using the CLI.

     

    Provider Registration

     

    A number of the components within WildFly Elytron are loaded using security providers, to make it easier to eliminate picking up the default implementations included with the JVM we make the Elytron provider implementation available as a capability.

     

    ./subsystem=elytron/provider-loader=elytron:add(register=false, providers=[{module="org.wildfly.security.elytron", load-services="true"}])
    
    
    

     

    Security Realm

     

    Within the terminology adopted for WildFly Elytron a security realm is the representation of the access to the repository of users and during the authentication process returns a raw relatively unmodified view of the account used for authentication.

     

    For this example we are just using a security realm backed by the same properties files currently used by default within the application server, this means they can be manipulated by the existing add-user utility.

     

    ./subsystem=elytron/properties-realm=ManagementRealm:add(users-properties={path="mgmt-users.properties", relative-to="jboss.server.config.dir"},groups-properties={path="mgmt-groups.properties", relative-to="jboss.server.config.dir"})
    
    
    

     

    It generally will not be seen by users or administrators but after authentication the security realm will make available an objected called the AuthorizationIdentity, this is the raw representation of the underlying identity.  The later configuration will now take this and convert it to a SecurityIdentity which will contain both roles and permissions.

     

    Utility Components

     

    Next to make the raw identity from the security realm usable some additional components need to be defined to perform appropriate mapping.

     

    ./subsystem=elytron/simple-role-decoder=groups-to-roles:add(attribute=groups)
    
    

     

    By default the properties realm will load the group information from the configured properties file and it will make those groups available in the attributes of the AuthorizationIdentity under the key 'groups', this definition is to define a simple role decoder that performs a 1:1 mapping between the names of the groups and the names of the roles.

     

    Elytron also supports the ability to define role mappers that operate after the role decoders to perform different mapping operations on the decoded roles, the first one we define is a constant role mapper that ensures all identities are granted the role "authenticated".

     

    ./subsystem=elytron/constant-role-mapper=authenticated:add(roles=["authenticated"])
    
    

     

    The final utility to be defined is a permission mapper: -

     

    ./subsystem=elytron/simple-permission-mapper=login-permission-mapper:add(permission-mappings=[{roles=["authenticated"], permissions=[{class-name="org.wildfly.security.auth.permission.LoginPermission"}]}])
    
    

     

    Any identity establishing a connection to the server must have the LoginPermission, the simple permission mapper is based on role membership information so to prevent adding a requirement for all accounts to have a specific group that will map to an appropriate role this is why the constant role mapper was defined previously.  In general however it would be recommended to use a real role that can be identified by using a role decoder so identities in a repository of users can selectively be given the ability to establish the connection to the server.

     

    Security Domain(s)

     

    For the majority of configurations used for securing management access to the server it wold be expected that a single security domain definition should be sufficient - however this example is also testing inflow between security domains so two different security domains are defined, one domain is defined for the authentication step as the connection to the server is established and the second defines the security domain that is used to obtain the identity executing the management operations.

     

    The first domain to be added is the domain that will be used for authentication as the connection to the server is established: -

     

    ./subsystem=elytron/security-domain=ManagementAuthenticationDomain:add(default-realm=ManagementRealm, realms=[{realm=ManagementRealm}], role-mapper=authenticated, permission-mapper=login-permission-mapper)
    
    

     

    This first security domain is only used for establishing the connection to the server so we don't reference our previously defined role decoder, it is sufficient to reference the role mapper that will always assign the 'authenticated' role and the permission mapper that will take this to grant the LoginPermission.

     

    The second security domain is the domain that will be used to obtain the SecurityIdentity for the invocation of the management operations.

     

    ./subsystem=elytron/security-domain=ManagementDomain:add(default-realm=ManagementRealm, realms=[{realm=ManagementRealm, role-decoder=groups-to-roles}], trusted-security-domains=[ ManagementAuthenticationDomain ] )
    
    

     

    A few of points to note about this domain, firstly the role decoder is referenced as these will subsequently be used for mapping into the management RBAC access control provider, secondly we have no need for the LoginPermission here as authentication on connecting to the server is happening sooner and thirdly the ManagementAuthenticationDomain is listed as a trusted domain as we will be inflowing the identity that was obtained during authentication into this domain.

     

    The Inflow Process

     

    The identity inflow process works as follows.

     

    On establishing a connection to the server authentication will be performed against the 'ManagementAuthenticationDomain' which is backed by the ManagementRealm.  Assuming a successful authentication regardless of the contents of the AuthorizationIdentity returned by the security realm the security domain will return a SecurityIdentity that contains a single role 'authenticated' and will also have the permission LoginPermission.

     

    A call would then continue to the ModelController which as will be shown below will be associated with the ManagementDomain.

     

    As the call reaches the ModelController the identity created during authentication will be obtained and the ModelController will attempt to inflow it into it's own security domain 'ManagementDomain'.  Firstly ManagementDomain trusts ManagementAuthenticationDomain so the inflow is allowed, secondly it will be identified that both domains contain the realm that was used so the inflowed SecurityIdentity can now be converted back to the raw AuthorizationIdentity.

     

    The ManagementDomain is configured with a role decoder so each group represented in the attributes in the AuthorizationIdentity will now be represented as a role on the SecurityIdentity returned by this security domain.  The ManagementDomain does not contain the constant role mapper or the permission mapper so neither of those apply in this case.

     

    The end result is that a single identity can now look very different depending on which security domain it is being used with.

     

    This will be a much more powerful feature where we have identities flowing across different deployment potentially each using their own security domain and even more powerful if we flow between application code and management code.

     

    SASL Authentication

     

    After the security domains are defined we need to define the SASL authentication policies that will be used for management access on the server.

     

    ./subsystem=elytron/provider-sasl-server-factory=elytron:add(provider-loader=elytron)
    
    

     

    This first definition is a simple SaslServerFactory that will make use of the provider registered in the first step to locate and load the actual authentication factories, as mentioned previously this prevents the JVM supplied mechanisms from being used and instead will just use the Elytron implementations.

     

    ./subsystem=elytron/sasl-authentication-factory=ManagementAuthentication:add(sasl-server-factory=elytron, security-domain=ManagementAuthenticationDomain, mechanism-configurations=[{mechanism-name="DIGEST-MD5", mechanism-realm-configurations=[{realm-name="ManagementRealm"}]}])
    
    

     

    This second definition is where the authentication policy lives, the previously defined sasl-server-factory is referenced for obtaining the mechanism factories and the DIGEST-MD5 mechanism has some configuration added to define the mechanism specific realm name it should use in the challenges to the client.

     

    Management Configuration

     

    The steps up until this point have been defining the Elytron components required to enable Elytron for management security, the next steps are now the management specific configuration to make use of this.

     

    ./core-service=management/management-interface=http-interface:write-attribute(name=http-upgrade, value={enabled=true, sasl-authentication-factory=ManagementAuthentication})
    ./core-service=management/access=identity:add(security-domain=ManagementDomain, inflow-security-domains=[ManagementAuthenticationDomain])
    
    

     

    The first line modifies the http management interface so that where HTTP Upgrade is used the ManagementAuthentication sasl-server-factory defined in the previous section will be used to obtain the SASL authentication policy that should be used.

     

    The second line then updates the ModelController to use the ManagementDomain to obtain the identity for the current request.  An attempt to inflow from another security domain will only happen in the ManagementDomain does not already have an identity established, if not there will be an attempt to inflow from the identity used to establish the connection.  For this specific example techinically the inflow-security-domains attribute is not required but as a final step if there is still no established identity each of the inflow-security-domain instances will be queried in turn to see if there is an identity we can inflow - this would be more appropriate in cases such as applications accessing management so that their identities can be handled correctly.

     

    For the purpose of creating this example the following groups have been used in the entries added to the properties file: -

    • monitorGroup
    • operatorGroup
    • administratorGroup

     

    For management access control there will be opportunities at a later point to tightly integrate with the Elytron SecurityIdentity permission mapping, at it's core management RBAC is heavily based on permissions.  However for now the existing role mappings supported for RBAC are used to map from the roles returned from the SecurityIdentity to the roles used by RBAC.

     

    The following set of mappings are added based on the groups in use: -

     

    ./core-service=management/access=authorization/role-mapping=Monitor:add
    ./core-service=management/access=authorization/role-mapping=Monitor/include=monitors:add(name=monitorGroup, type=GROUP)
    ./core-service=management/access=authorization/role-mapping=Operator:add
    ./core-service=management/access=authorization/role-mapping=Operator/include=operators:add(name=operatorGroup, type=GROUP)
    ./core-service=management/access=authorization/role-mapping=Administrator:add
    ./core-service=management/access=authorization/role-mapping=Administrator/include=administrators:add(name=administratorGroup, type=GROUP)
    
    

     

    The final step then is to enable RBAC for management access: -

     

    ./core-service=management/access=authorization:write-attribute(name=provider, value=rbac)
    
    

     

    After ensuring the users and their groups are added to the properties files the server can be restarted and this configuration will be in place.

     

    :whoami

     

    After restarting the server and connecting as one of the users if you execute the whoami operation you should see the successful role mapping but also some additional information such as the raw attributes coming from the SecurityIdentity and how the groups were mapped 1:1 to the roles of the SecurityIdentity.

     

    [standalone@localhost:9990 /] :whoami(verbose=true)
    {
        "outcome" => "success",
        "result" => {
            "identity" => {"username" => "administrator"},
            "roles" => ["administratorGroup"],
            "attributes" => {"groups" => ["administratorGroup"]},
            "mapped-roles" => ["Administrator"]
        }
    }
    
    

    Resulting XML

     

    After applying this configuration here are some extracts of the XML in the standalone.xml configuration.

     

            <management-interfaces>
                <http-interface security-realm="ManagementRealm">
                    <http-upgrade enabled="true" sasl-authentication-factory="ManagementAuthentication"/>
                    <socket-binding http="management-http"/>
                </http-interface>
            </management-interfaces>
    
    

     

     

            <subsystem xmlns="urn:wildfly:elytron:1.0">
                <provider-loaders>
                    <provider-loader name="elytron" register="false">
                        <provider module="org.wildfly.security.elytron" load-services="true"/>
                    </provider-loader>
                </provider-loaders>
                <security-domains>
                    <security-domain name="ManagementAuthenticationDomain" default-realm="ManagementRealm" permission-mapper="login-permission-mapper" role-mapper="authenticated">
                        <realm name="ManagementRealm"/>
                    </security-domain>
                    <security-domain name="ManagementDomain" default-realm="ManagementRealm" trusted-security-domains="ManagementAuthenticationDomain">
                        <realm name="ManagementRealm" role-decoder="groups-to-roles"/>
                    </security-domain>
                </security-domains>
                <security-realms>
                    <properties-realm name="ManagementRealm">
                        <users-properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
                        <groups-properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
                    </properties-realm>
                </security-realms>
                <mappers>
                    <simple-permission-mapper name="login-permission-mapper">
                        <permission-mapping roles="authenticated">
                            <permission class-name="org.wildfly.security.auth.permission.LoginPermission"/>
                        </permission-mapping>
                    </simple-permission-mapper>
                    <simple-role-decoder name="groups-to-roles" attribute="groups"/>
                    <constant-role-mapper name="authenticated" roles="authenticated"/>
                </mappers>
                <sasl>
                    <sasl-authentication-factory name="ManagementAuthentication" sasl-server-factory="elytron" security-domain="ManagementAuthenticationDomain">
                        <mechanism-configuration>
                            <mechanism mechanism-name="DIGEST-MD5">
                                <mechanism-realm realm-name="ManagementRealm"/>
                            </mechanism>
                        </mechanism-configuration>
                    </sasl-authentication-factory>
                    <provider-sasl-server-factory name="elytron" provider-loader="elytron"/>
                </sasl>
            </subsystem>