How to configure server to server EJB security propagation starting with EAP 7.1+

Solution Verified - Updated

Environment

  • Red Hat JBoss Enterprise Application Platform (EAP)
    • 8.x
    • 7.4
    • 7.3
    • 7.2
    • 7.1

Issue

  • How to configure server to server EJB security propagation starting with EAP 7.1

Resolution

Using Authorization identity forwarding option

Elytron was designed for and contains implementation to support trusted use of identities between peers in addition to credential forwarding. Cases in which one would prefer this approach include:

  • Scenarios where the user has requirements to not send passwords over the wire. Notably credential forwarding requires TLS and/or secure networks
  • Setups where an authentication type that does not support credential forwarding are used (credential forwarding is limited to Plain, Form, and OAuth, all other mechanisms, including the out of the box Digest auth of EAP are not)
  • Environments where its desired to limit which systems are allowed to receive requests which are propagated
  1. create a user / password on each for each server or use the same for all servers
$JBOSS_HOME/bin/add-user.sh -a -u server -p 'redhat1!' -e

Note: the example below will show the client side and server side configuration needed for security propagation. Typical infrastructures would have several servers which all send EJB calls to each other, so for simplicity configure all servers using the client and server side configurations as they will all act as a client and server in typical use cases. The user/authentication-name server is used for the server instances to authenticate with each other. Different users can be used for each server, but for simplicity this example will use the same authentication-name for all server instances and all server instances will have the application-users.properties file in their configuration directory with the server user.

Client configuration:

  1. client side configuration which should be on every node if all nodes will be talking to each other

elytron - configure to authentication configuration / context to forward

  • For standalone Mode :
/subsystem=elytron/http-authentication-factory=application-http-authentication:add(security-domain=ApplicationDomain,http-server-mechanism-factory=global,mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=ApplicationRealm}]}])
/subsystem=elytron/authentication-configuration=forwardit:add(authentication-name=server, security-domain=ApplicationDomain, forwarding-mode=authorization, realm=ApplicationRealm, credential-reference={clear-text=redhat1!})
/subsystem=elytron/authentication-context=forwardctx:add(match-rules=[{match-no-user=true,authentication-configuration=forwardit}])

/subsystem=elytron:write-attribute(name=default-authentication-context, value=forwardctx)

undertow subsystem - configure to use application-http-authentication

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

Server configuration:

ejb3 subsystem

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

elytron subsystem - add permission for remote server to run as user

Important Note: the permission mapper for the server principal here is added with list-add(index=0 , so that it comes before the match-all="true" permission-mapper as the default mapping-mode is first and you want the mapping for the principal server to take effect and it will not if it comes after the match all.

/subsystem=elytron/simple-permission-mapper=default-permission-mapper:list-add(index=0, name=permission-mappings,\
  value={principals=[server],\
  permissions=[{class-name=org.wildfly.security.auth.permission.RunAsPrincipalPermission, target-name=*},\
    {class-name=org.wildfly.security.auth.permission.LoginPermission},\
    {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}\
  ]})

remoting subsystem - configure it to use application-sasl-authentication

/subsystem=remoting/http-connector=http-remoting-connector:write-attribute(name=sasl-authentication-factory, value=application-sasl-authentication)

Using Authorization identity forwarding option with Remoting Outbound

Add a socket-binding to specify the remote host and port to connect:

 /socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-socket-ejb:add(host=localhost, port=8080)

Add the remote-outbound-connection that uses the socket-binding specified above and uses the authentication-context defined above in the Authorization identity forwarding section (forwardctx)

The forwardctx configuration above uses a username / password for the server instances to authenticate with each other. This configuration is used for the remote-outbound-connection to authenticate for the remote EJB calls. The authentication-context specified here references the Elytron configuration and replaces the security-realm and the username is removed as it is provided in the forwardctx.

 /subsystem=remoting/remote-outbound-connection=remote-ejb-connection:add(outbound-socket-binding-ref=remote-socket-ejb, authentication-context=forwardctx)

Add any other remoting configuration options such as SSL

 /subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=SASL_POLICY_NOANONYMOUS:add(value=false)
 /subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=SSL_ENABLED:add(value=false)
  • For domain mode prefix above commands with /profile=PROFILE_NAME the changes will be reflected in domain.xml

Using PLAIN option configuration:

Note: this configuration is the least recommended option, as it sends plaintext passwords across the wire. Also note that is will not work if Single Sign On (SSO) is used as SSO does not keep the plaintext passwords and in the event a user session fails over the security propagation would no longer work due to SSO no longer having the plaintext password.

  • This option works similar to EAP 5, where the client server will take the plaintext username/password provided to the client server and send it to the remote server
  • The client server and remove server will both authenticate
  • Since the username/password is sent in plaintext, it is recommended that SSL be used between the client and server side or that the client and servers are running in a secure environment

Server configuration:

if (outcome == success) of /core-service=management/security-realm=ApplicationRealm/authentication=local:read-resource()
     echo "remove $local"
    /core-service=management/security-realm=ApplicationRealm/authentication=local:remove() 
end-if

echo "configure ejb3 subsystem to use the elytron ApplicationDomain for the other"
/subsystem=ejb3/application-security-domain=other:add(security-domain=ApplicationDomain)

if (outcome == success) of /subsystem=elytron/sasl-authentication-factory=application-sasl-authentication:read-resource()
  echo "reconfigure the application-sasl-authentication to use PLAIN instead of DIGEST"
  /subsystem=elytron/sasl-authentication-factory=application-sasl-authentication:remove()
end-if

echo "configure elytron application-sasl-authentication"
/subsystem=elytron/sasl-authentication-factory=application-sasl-authentication:add(sasl-server-factory=configured, security-domain=ApplicationDomain, mechanism-configurations=[{mechanism-name=PLAIN, mechanism-realm-configurations=[{realm-name=ApplicationRealm}]}])
 
echo "configure remoting http-connector to use application-sasl-authentication"
/subsystem=remoting/http-connector=http-remoting-connector:write-attribute(name=sasl-authentication-factory, value=application-sasl-authentication)
        <subsystem xmlns="urn:jboss:domain:ejb3:5.0">
...
            <application-security-domains>
                <application-security-domain name="other" security-domain="ApplicationDomain"/>
            </application-security-domains>
            ....
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:undertow:4.0">
...
            <application-security-domains>
                <application-security-domain name="other" http-authentication-factory="application-http-authentication"/>
            </application-security-domains>
        </subsystem>

Client server configuration

if (outcome == success) of /core-service=management/security-realm=ApplicationRealm/authentication=local:read-resource()
     echo "remove $local"
    /core-service=management/security-realm=ApplicationRealm/authentication=local:remove() 
end-if

echo "add elytron authentication configuration forwardit"
/subsystem=elytron/authentication-configuration=forwardit:add(security-domain=ApplicationDomain, sasl-mechanism-selector=#ALL)

echo "add elytron authentication context forwardctx using forwardit"
/subsystem=elytron/authentication-context=forwardctx:add(match-rules=[{match-no-user=true, authentication-configuration=forwardit}])

echo "configure default-authentication-context" 
/subsystem=elytron:write-attribute(name=default-authentication-context, value=forwardctx)

echo "configure undertow application-security-domain other to use the application-http-authentication"
/subsystem=undertow/application-security-domain=other:add(http-authentication-factory=application-http-authentication)

Client Application example code using the WildFlyInitialContextFactory

  • With security propagation enabled using one of the methods above on the JBoss client and server instances, the EJB client application can use the WildFlyInitialContextFactory to lookup and invoke the EJB proxy. The EJB invocation will be invoked as the user that authenticated with the servlet for example shown below. If you want to invoke it as a different user, you can set the Context.SECURITY_PRINCIPAL / Context.SECURITY_CREDENTIALS in the Naming context properties.
import org.jboss.ejb.client.EJBClient;

@WebServlet(name="SecurityWebClientServlet", urlPatterns={"/"}, loadOnStartup=1)
@DeclareRoles("securityWebClient")
@ServletSecurity(value = @HttpConstraint(
        rolesAllowed = {"securityWebClient"}),
        httpMethodConstraints = {
                @HttpMethodConstraint(value = "GET", rolesAllowed = {"securityWebClient"}),
                @HttpMethodConstraint(value = "POST")
})
public class SecurityWebClientServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        callRemoteEjb();
    }
    public void callRemoteEjb() {
        HelloRemote remote = getInitialContext(host, port, user, pass).lookup("ejb:helloWorld/helloWorld-ejb/HelloWorldSLSB!org.jboss.examples.ejb.HelloRemote");
      remote.helloWorld();
    }
    public static Context getInitialContext(String host, Integer port, String username, String password) {
        Properties props = new Properties();
        props.put(Context.INITIAL_CONTEXT_FACTORY,  "org.wildfly.naming.client.WildFlyInitialContextFactory");
        props.put(Context.PROVIDER_URL, String.format("%s://%s:%d", "remote+http", host, port));
        return new InitialContext(props);
    }
}

Using SSL Mutual Authentication


[How to configure server to server EJB security propagation with SSL mutual authentication in EAP 7.1](https://access.redhat.com/solutions/3672051)
Components
Category

This solution is part of Red Hat’s fast-track publication program, providing a huge library of solutions that Red Hat engineers have created while supporting our customers. To give you the knowledge you need the instant it becomes available, these articles may be presented in a raw and unedited form.