Using IG to Protect IDM For Secure and Standards-Based Integration

ForgeRock Identity Management (IDM) has a rich set of REST APIs capable of performing many actions; this is one of the great values that IDM has to offer. However, with such a broad set of APIs available out of the box, it is reasonable to want to limit which of those REST APIs are directly available to the public. The most common way to enforce that limit is to use an API gateway to act as a reverse proxy. In this way, only the requests which the gateway has been configured to allow will be sent to IDM.

In addition to offering a filter, API gateways can offer many powerful options for authentication and authorization which are not available directly within IDM. One important example of this need is for OAuth2 clients to be able to request IDM REST APIs using an access token that was obtained from ForgeRock Access Management (AM). In this way, the IDM endpoints would be able to be treated as standard OAuth2 resource server endpoints; the gateway can validate the presence of the access token and introspect it to ensure that there are appropriate scopes for the request. Being able to rely on an access token as the means by which clients work with the IDM REST API is often easier, more secure, and more inter-operable than other means available.

In this article I’m going to focus on how you could configure ForgeRock Identity Gateway (IG) to accomplish both of these goals. You will be able to use this configuration as a pattern for deploying IDM functionality in a secure and standards-based way, making it easy to integrate with your own applications. Some basic knowledge about installation and typical use for each product is assumed; please review the product documentation if you are unfamiliar with either product.

Securing the connection between IDM and IG

When IG is responsible for securing the requests to IDM, it is important to configure IDM so that it will only accept connections which originate from IG. One way to accomplish this is to configure your network topology and firewall rules to prevent anything besides IG from being able to make a connection; doing so will be specific to your environment, so I won’t cover how to do that here; ask your network administrator for help. In addition to the network level security, you should also secure the connection with an encrypted channel using client SSL certificates. Both methods used together will provide the most robust security.

Configuring IDM to accept connections from IG

Since IG will be handling the direct end-user authentication, IDM only needs to handle the authentication of the requests originating from IG. As they are separate processes, there must be a standard HTTPS interface between them (IG proxing the client requests to the IDM server). The main security challenge is to ensure that only IG is able to make these direct HTTPS requests to IDM. Essentially, IDM needs a way to first authenticate IG itself so that it can then operate on behalf of the actual user. There are a few details that need to considered in order to do this.

Static Users

First is the simple fact that IG is not a “user” in the sense that IDM normally expects – it is more of a service, and as such will probably not have a corresponding record available on the IDM router (in the way that users normally do).  Because authentication in IDM requires a route for every authenticated subject, we have to find a way to supply one. One simple way to do this is to create a custom endpoint that provides a sort of pseudo-route. For example, you can create a file named “conf/endpoint-staticuser.json” with this content:

{
    "type" : "text/javascript",
    "context" : "endpoint/static/user/*",
    "source" : "request.method === 'query' ? ([{_id: request.additionalParameters._id}]) : ({_id: context.security.authenticationId})"
}

This handy endpoint is designed to work with the authentication service. It simply reflects back the id provided (either as a query parameter or from the security context). Using this, we can define authentication modules for “users” which don’t have any other router entry, such as the case with IG.

Client Certificate Authentication

The next step is to actually configure the authentication module that IG will be using. In this case, we are going use the “CLIENT_CERT” authentication module, following a similar process to the one described in “Configuring Client Certification Authentication“. Example openssl commands and more background detail can be found there.

This module requires that the “client” (in this case, IG) has a particular SSL certificate that it is able to present to IDM when it connects via HTTPS. Using our above “static/user” endpoint, here’s the most basic example JSON configuration showing how this could be done:

{
    "name": "CLIENT_CERT",
    "properties": {
        "queryOnResource": "endpoint/static/user",
        "defaultUserRoles": [
            "openidm-cert"
        ],
        "allowedAuthenticationIdPatterns": [
            "CN=ig, O=forgerock"
        ]
    },
    "enabled": true
}

This declaration authenticates any request which has supplied a client certificate that meets two conditions:

  1. It is trusted in terms of SSL (either directly imported in the IDM truststore or signed by a CA which is within the IDM truststore).
  2. It has a “subject” which matches one of the patterns listed under “allowedAuthenticationIdPatterns”. In this example, the subject needs to exactly match “CN=ig, O=forgerock”.

If both of those conditions are met, then the request will be authenticated, and it will be recognized as a “static/user” type of entity with the “openidm-cert” role. The intent behind this configuration is that requests from IG are recognized within this context.

Run As

The final configuration necessary within IDM is to allow IG to make requests on behalf of the user it has authenticated. In IDM 6.0, there is a new feature available for every authentication module which supports this – “RunAs Authentication“. This feature allows privileged clients to supply an “X-OpenIDM-RunAs” header in order to specify the name of the user they would like to operate on behalf of. This is obviously a highly-sensitive type of operation – only the most trusted clients (such as IG) should be allowed to operate under the guise of other users. You will need to configure IDM in order to allow IG to do this. For example, you can expand the CLIENT_CERT authentication module configuration like so (added values in italics):

{
    "name": "CLIENT_CERT",
    "properties": {
        "queryOnResource": "endpoint/static/user",
        "defaultUserRoles": [
            "openidm-cert"
        ],
        "allowedAuthenticationIdPatterns": [
            "CN=ig, O=forgerock"
        ],
        "runAsProperties": {
            "adminRoles": [
                "openidm-cert"
            ],
            "disallowedRunAsRoles": [ ],
            "queryOnResource": "managed/user",
            "propertyMapping": {
                "authenticationId" : "userName",
                "userRoles": "authzRoles"
            },
            "defaultUserRoles" : [
                "openidm-authorized"
            ]
        }
    },
    "enabled": true
}

By using this configuration, any authenticated request which comes from IG should include an “X-OpenIDM-RunAs” header that identifies the “userName” value for the associated “managed/user” record. IG will be the only client capable of making this sort of request, because it is the only entity which has the private key associated with the trusted certificate.

Configuring IG to accept connections for IDM

Now that IDM is prepared to accept connections, IG needs to be configured to make them correctly.

Trusting IDM

Review the chapter in the IG configuration guide called “Configuring IG for HTTPS (client-side)“. If IDM is using a self-signed certificate, you will need to import that into IG’s truststore as described there.

Supplying a Client Certificate

All requests made by IG to a back-end are via a “ClientHandler“.  By default, there is no special behavior associated with a ClientHandler – it simply acts as a generic HTTP/S client. The best option for us is to define a new ClientHandler on the IG “heap” that is configured to perform client certification authentication. The most important declaration for this use is the “keyManager“; this is how you tell the ClientHandler where to get the keys to use when prompted for client certificates. Here is an example of a configured client that is ready to trust IDM’s certificate and supply its own:

{
    "name": "IDMClient",
    "type": "ClientHandler",
    "config": {
        "hostnameVerifier": "ALLOW_ALL",
        "sslContextAlgorithm": "TLSv1.2",
        "keyManager": {
            "type": "KeyManager",
            "config": {
                "keystore": {
                    "type": "KeyStore",
                    "config": {
                        "url": "file:///var/openig/keystore.jks",
                        "password": "changeit"
                    }
                },
                "password": "changeit"
            }
        },
        "trustManager": {
            "type": "TrustManager",
            "config": {
                "keystore": {
                    "type": "KeyStore",
                    "config": {
                        "url": "file:///var/openig/keystore.jks",
                        "password": "changeit"
                    }
                }
            }
        }
    }
}

In this example, the /var/openig/keystore.jks file contains both the public certificate needed to trust IDM and the private key needed to authenticate IG as a client. Make sure that all IG routes which forward requests to IDM use this ClientHandler.

Including the RunAs Header

Before the request is forwarded to IDM via the IDMClient, it needs to be modified to include the X-OpenIDM-RunAs header. This allows IDM to find the user that is actually making the request, via IG.

There are several ways to modify the request before it is sent to IDM, but all of them involve a filter on the route. The simplest example is to use the “HeaderFilter“, like so:

{
     "type": "HeaderFilter",
     "config": {
         "messageType": "REQUEST",
         "add": {
             "X-OpenIDM-RunAs": [ "${contexts.oauth2.accessToken.info.sub}" ]
         }
     }
}

If you have more complex logic around your user behavior, you might want to use a ScriptableFilter, within which you could set the header with code like so:

String sub = contexts.oauth2.accessToken.info.sub
request.getHeaders().add('X-OpenIDM-RunAs', sub)
return next.handle(context, request)

Operating as an OAuth2 resource server

While there are many potential benefits provided by IG, the main one explored in this article is to operate as an OAuth2 resource server. The core feature is well documented – see both the IG gateway guide and the configuration reference. The main detail to consider in this specific context is about how you want to manage scopes. In order for requests to successfully pass through the OAuth2ResourceServerFilter, the token must have the correct scopes associated with it. What constitutes as a “correct” scope value for any given request is up to you to decide. You will need to consider which IDM endpoints and methods you want to make available and how to express that availability in terms of a scope. For example, if you want to allow OAuth2 clients to be able to make calls to the “/openidm/endpoint/usernotifications” endpoint, you could define a scope called “notifications” and require it with a route like so:

{
    "name": "notificationsRoute",
    "baseURI": "https://idm.example.com:8444",
    "condition": "${matches(request.uri.path, '^/openidm/endpoint/usernotifications')}",
    "handler": {
        "type": "Chain",
        "config": {
            "filters": [
                {
                    "type": "OAuth2ResourceServerFilter",
                    "config": {
                        "scopes": [
                            "notifications"
                        ],
                        "accessTokenResolver": "AccessTokenResolver"
                    }
                },
                {
                     "type": "HeaderFilter",
                     "config": {
                         "messageType": "REQUEST",
                         "add": {
                             "X-OpenIDM-RunAs": [ "${contexts.oauth2.accessToken.info.sub}" ]
                         }
                     }
                }
            ],
            "handler": "IDMClient"
        }
    },
    "heap": [
        {
            "name": "AccessTokenResolver",
            "type": "TokenIntrospectionAccessTokenResolver",
            "config": {
                "endpoint": "https://am.example.com/openam/oauth2/introspect",
                "providerHandler": {
                    "type": "Chain",
                    "config": {
                        "filters": [
                            {
                                "type": "HeaderFilter",
                                "config": {
                                    "messageType": "request",
                                    "add": {
                                        "Authorization": [
                                            "Basic ${encodeBase64('openidmClient:openidmClient')}"
                                        ]
                                    }
                                }
                            }
                        ],
                        "handler": "ClientHandler"
                    }
                }
            }
        },
        {
            "name": "IDMClient",
            "type": "ClientHandler",
            "config": {
                "hostnameVerifier": "ALLOW_ALL",
                "sslContextAlgorithm": "TLSv1.2",
                "keyManager": {
                    "type": "KeyManager",
                    "config": {
                        "keystore": {
                            "type": "KeyStore",
                            "config": {
                                "url": "file:///var/openig/keystore.jks",
                                "password": "changeit"
                            }
                        },
                        "password": "changeit"
                    }
                },
                "trustManager": {
                    "type": "TrustManager",
                    "config": {
                        "keystore": {
                            "type": "KeyStore",
                            "config": {
                                "url": "file:///var/openig/keystore.jks",
                                "password": "changeit"
                            }
                        }
                    }
                }
            }
        }
    ]
}

The details about how to validate the access token are shown here via the “AccessTokenResolver” heap object. There are a lot of legitimate configuration options worth exploring for the “OAuth2ResourceServerFilter” filter; see the reference documentation to see the full range. In this example, it is calling out to a standard OAuth2 token introspection endpoint provided by AM, authenticated with a registered client called “openidmClient”. See the documentation regarding how to configure AM to operate as a token introspection service. Be sure that the client you register for the IG resource server filter has the “am-introspect-all-tokens” scope included within it.

You will want to define similar route entries as this for each scope requirement you want to declare. To reduce the amount of duplicated route configuration, I suggest you move the common parts between each route (such as the IDMClient and AccessTokenResolver heap objects) into the global IG “config.json” file. See more details about route definition in the IG gateway guide section on the subject.

Complete configuration example

There is a full sample configuration available as part of the “ForgeOps” project which follows these patterns. This sample brings up AM configured to operate as an OAuth2 AS, providing token creation and introspection. It configures IG to act as an OAuth2 RS, and configures IDM to accept connections exclusively from IG. By reviewing the “rs” and “idm” sub-folders included within that sample, you should recognize many of the points described in this article. See the included README.md file within that sample for details regarding how to start it up. This sample configuration will be used as the basis for subsequent articles which will discuss how you can use specific OAuth2 client libraries to make requests to IDM endpoints.

Go forth and build your secure, standards-based applications, integrated with the ForgeRock Identity Platform!

0 Comments

Leave a reply

©2018 ForgeRock - we provide an identity and access platform to secure every online relationship for the enterprise market, educational sector and even entire countries. Click to view our privacy policy and terms of use.

Log in with your credentials

Forgot your details?