Content Management and Capture

Content Management and Capture

Come for answers. Stay for best practices. All we’re missing is you.

 View Only

A sample LTPA/OAuth/OIDC SSO with FileNet Content Management (FNCM) services on container having Keycloak as an identity provider.

By Leonardo Modeo posted Tue August 01, 2023 01:41 PM

  
A sample LTPA/OAuth/OIDC SSO with FileNet Content Management (FNCM) services on container having Keycloak as an identity provider.

INTRODUCTION.
This article describes a working sample configuration for LTPA/OAuth/OIDC SSO with FileNet Content Management (FNCM) services on container, featuring CPE (Content Platform engine), IBM Content Navigator (ICN) and GraphQL components. For privacy reasons, some pictures in the article may have a red ban over some parts of it. Keycloak (aka RedHat SSO) is also an increasingly popular IdP (Identity Provider) tool, which turns out to be used in several situations where OAuth, OIDC or SAML implementations are required. It also offers the possibility to integrate other popular IdPs (e.g. Google, Facebook and many other...) as clients in its configuration. Keycloak is available as both traditional deployment and containerised deployment.
Hopefully, this article may serve as a "recipe" to implement some basic OAuth/OIDC integration playground, to practice with the "guts" of the needed configurations.
This sample scenario is composed of:
- traditional RedHat Keycloak 18.0.2 as an Identity Provider (IAM) for OIDC/OAuth
- an OpenShift 4.11.42 cluster to host the FNCM container-based deployment, with Content Engine 5.5.8 and ICN 3.0.11
- traditional DB2 11.5.7 database and OpenLDAP as a local LDAP provider

The proposed sample implementation has not been tested for containerised FNCM as part of Cloud Pak for Business Automation.

This article is meant to be a follow-on of the following two blogs, which in turn cover the LTPA/OAuth/OIDC topic for FNCM in a traditional WebSphere deployment environment:

The proposed scenario mirrors a typical deployment, in that RDBMS, local LDAP and Identity Provider are usually implemented as traditional server, outside the OpenShift cluster. A typical requirement here is to have CPE acce login still based on "basic auth" authentication model, while preserving OIDC/OAuth for ICN and GraphQL. This is a compelling requirement when administrative users (i.e. "cpe-admin-user" like) are not mapped to the Identity Provider, as opposed to business users who need to be authenticated with SSO.

The following picture describes the design of this sample implementation:

The dashed arrow between OpenLDAP and Keycloak IAM is there to describe that in this specific sample implementation there is a user federation between LDAP and IAM. However this is not mandatory to have, as long as the JAAS user subject coming from IAM can be mapped to the local OpenLDAP.
The terms IAM (Identity and access management) and IdP (Identity Provider) in this article are used interchangeably, as having the same meaning.

USE CASE SCENARIO.
Let's examine the use case scenario in this design, for both Content Navigator and GraphQL.

Content Navigator authentication by OIDC will flow like the following:

1) User lands on ICN page (e.g. ICN desktop...).
2) If there's no available OIDC cookie in the session, the ICN logon page will display a button that will redirect the user to the IAM, and the user will authenticate.
3) If authentication is successful, cookies will be released to the user session, conveying user identity, that will allow to finally land over to the ICN desktop. Likely if you inspect ICN landing page with the available browser tool, you'll see "WASOidcCode" cookie among the other cookies, when the authentication process completes successfully.
4) The JAAS user subject is propagated from ICN to CPE via LTPA, the subject is cross-checked over the local OpenLDAP user directory, for access to the CPE Object Store repository.

GraphQL authentication will follow OAuth2.0 protocol, like the following:

1) GraphQL client application will interact with IAM to get an authentication bearer token.
2) GraphQL client application will do an HTTP request towards the GraphQL, and will have to extend request headers with: (1) authorization header (similar to: "Bearer .....") and (2) "auth-token-realm" header with the value of the IdP realm (in this example, the value of the identity realm will be "ExShareGID", as shown ahead in this article).
3) The JAAS subject is propagated from GraphQL to CPE via LTPA; the subject is cross-checked over the local OpenLDAP user directory, for the end-user to be granted access to the ECM object in an Object Store.
ECM Administrators need to access CPE (e.g. via "acce" console). In real-life scenario, it can be the case that such administrative accounts are not meant to be configured in IAM (or IdP), as such accounts are only configured inside the local LDAP (openLDAP in this case), and will still be managed via basic authentication against the local LDAP.

IMPLEMENTATION DETAILS.
The IBM reference guide to implement the proposed solution can be found at this link:

and this article covers what the above link says in the view of a practical implementation, based on Keycloak as the reference IAM/IdP system.
Let's examine all the steps of the documentation and see what/how it applies to the practical implementation.

Step 1. Prepare a draft redirect URL value. What in the documentation is referred to as redirect url "https://ingress.es-<ingress_host>/oidcclient/redirect/<Provider ID for each instance>" will likely in our case be based on the values of the OpenShift routes of the three FNCM components. Here follows a description of how the URLs are built.
for ICN, redirect URL will become: "https://navigator-<your-namespace-and-cluster>/oidcclient/redirect/<Provider-ID-for-ICN>"
for CPE, that will become: "https://cpe-<your-namespace-and-cluster>/oidcclient/redirect/<Provider-ID-for-CPE>"
and for GraphQl that will become: "https://graphql-<your-namespace-and-cluster>/oidcclient/redirect/<Provider-ID-for-GraphQL>"
Where the <your-namespace-and-cluster> are the values of the corresponding OpenShift cluster configuration and namespace.
You can check the following OpenShift routes: "fncmdeploy-navigator-route", "fncmdeploy-graphql-route" and "fncmdeploy-cpe-route" in your OpenShift namespace to see how <your-namespace-and-cluster> is built.

The reference documentation for "Step 1" is:

We will examine in practice what <Provider-ID-for-...> prefix is, and it will be seen that it is the same as the client ID in Keycloak, or "ExShareGID" in this example, as well as in the IBM documentation.
Step 2. Create your identity provider application. In this section the Keycloak configuration will be described.
From Keycloak administration, (1) create a new Keycloak realm, like in the following two pictures, with "Add realm" button. Then (2) name your realm and click "create" button.


(1)
(2)


Step 3. Create and configure a client in Keycloak. This is needed to have a client configuration, for the sake of the implementation of the interaction between Keycloak and the FNCM components.  First select your realm (1) and click "Clients", then create a new client. Name your new client (2): you can name it "ExShareGID" if you want to make it the same as the proposed sample configuration. Set  "Client Protocol" as "openid-connect" and "Save". Do not fill the "root url" part of the form. Open the client configuration and set the values as proposed in (3).
(1) (2) (3)


And it can be seen, as of picture (3), that the redirect URIs, mentioned in "Step 1", are finally built as per the following settings:

<Provider-ID-for-ICN>="ExShareGIDNAV"
<Provider-ID-for-CPE>="ExShareGIDCPE"
<Provider-ID-for-GraphQL>="ExShareGIDGRAPHQL"
Step 4. Create identity provider secret.
In Keycloak, the identity provider secret can be found in the "Credentials" section of the client, as shown below:

Just copy the value at the "Secret" part of the form and use it to create the OpenShift secret as per the main guideline  section 4 ("create an identity prover secret"). 
Step 5. Create additional secret with the certificate from the identity provider.
Secrets are needed in OpenShift, to acknowledge the Keycloak certificate. it is possible to download Keycloak certificate by accessing any Keycloak page with https, and downloading from the browser certificate management. For example, in Firefox browser, that would look like the following:

and the download start by clicking on "PEM (chain)". Then the certificate secret can be created in OpenShift with the following OpenShift command:

oc create secret generic <Keycloak-secret-name> --from-file=tls.crt=<pem-chain-file-name>.crt  -n <your-namespace>
where <Keycloak-secret-name> is the secret name, <pem-chain-file-name>.crt is the PEM Chain file after download completes and <your-namespace> is the namespace of your FNCM OpenShift deployment.
The secret will also be part of the "trusted_certificate_list" section in "shared_configuration" part of the CR of the FNCM OpenShift deployment, as described in the following YAML snippet:
....
shared_configuration:
     trusted_certificate_list:
     - <Keycloak-secret-name>
....
Step 6. Compile the OIDC provider section in FNCM CR YAML.
In this step, the working configuration of the "shared_configuration" => "open_id_connect_providers" part of the FCNM CR is described. In main guideline  section 6, a sample configuration is proposed, based on Google as an Identity Provider. Let's examine how is it with Keycloak (angle brackets below are parameters that may change according to the naming of one's own environment):
    open_id_connect_providers:
    - authn_session_disabled: "false"
      authorization_endpoint_url: https://<Keycloak-server:Keycloak-port>/realms/<your-realm>/protocol/openid-connect/auth
      client_oidc_secret:
        cpe: <your-Keycloak-client-id-and-secret>
        graphql: <your-Keycloak-client-id-and-secret>
        nav: <your-Keycloak-client-id-and-secret>
      disable_ltpa_cookie: "false"
      discovery_endpoint_url: https://<Keycloak-server:Keycloak-port>/realms/<your-realm>/.well-known/openid-configuration
      display_name: Single Sign on
      https_required: "true"
      inbound_propagation: supported
      issuer_identifier: https://<Keycloak-server:Keycloak-port>/realms/<your-realm>
      map_identity_to_registry_user: "true"
      provider_name: ExShareGID
      response_type: code
      scope: openid email profile
      signature_algorithm: RS256
      token_endpoint_url: https://<Keycloak-server:Keycloak-port>/realms/<your-realm>/protocol/openid-connect/token
      unique_user_identifier: email
      user_identifier: email
      user_identity_to_create_subject: email
      validation_method: introspect
Some additional comments follow about some of the "open_id_connect_providers" configuration params:
- map_identity_to_registry_users: in this specific configuration, this is needed to be "true" to cater for cross-checking of authenticated user principals against the local LDAP (OpenLDAP in this case); this is especially useful to (1) allow "acce" access with plain basic auth against the local LDAP and (2) propagate JAAS user principal down to CPE, to manage user authorisation when accessing documents, folders and other ECM objects.
- user_identity_to_create_subject, user_identifier, unique_user_identifier: the user principal name that is used across the authentication chain;  if not specified, the default identifier is "sub". Now, this is quite relevant in this scenario, in that the "sub" identifier would be impractical to use. It is possible to verify which options are at hand in Keycloak by looking at the "Generated access token" section in Keycloak client configuration:

where you can pick an user from the "user" list (in this case the OpenLDAP federation came into play, for the LDAP users are "seen" by Keycloak too) and click the "evaluate" button to fetch the sample "Generated Access Token". Here's how the output will look like, in the text area that will appear in Keycloak:

{
  "exp": 1690222020,
  "iat": 1690221720,
  "jti": "86848412-d83b-479f-8710-34df2ac99e8b",
  "iss": "https://<Keycloak-server:Keycloak-port>/realms/CP4BA2",
  "aud": "account",
  "sub": "84287236-2f81-4b4a-83ad-2918c33067de",
  "typ": "Bearer",
  "azp": "ExShareGID",
  "session_state": "bbf29398-90d1-486b-a23c-93652137483a",
  "acr": "1",
  "realm_access": {
    "roles": [
      "offline_access",
      "uma_authorization",
      "default-roles-cp4ba2"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid email profile",
  "sid": "bbf29398-90d1-486b-a23c-93652137483a",
  "email_verified": false,
  "name": "an-user an-user",
  "preferred_username": "an-user",
  "given_name": "an-user",
  "family_name": "an-user",
  "email": "an-user"
}
and you can see that the "sub" identifier is quite impractical to use, whereas other attributes, such as "email" look better suited for our case: they convey the username principal. And that's why you see the "email" attribute in the "open_id_connect_providers" of FNCM CR.
- disable_ltpa_cookie: set to "false" to propagate LTPA cookie to CPE
- response_type: set to "code" to fix some "401 unauthorised" errors when accessing ICN


INSPECTING THE configDropins/overrides.
Inside each of the pods of the FNCM deployments, this directory: "/opt/ibm/wlp/usr/servers/defaultServer/configDropins/overrides" contains all the files that are relevant to the configuration of the WebSphere Liberty that is inside the FNCM pods: WebSphere Liberty is the java runtime env where CPE, ICN and GraphQL are running. All the configurations that have been described in the "open_id_connection_providers" section of the CR, are deployed inside the FNCM pods in the form of an xml file (namely "idp-oidc.xml") that is specifically injecting the configurations of the CR file into the Liberty runtime.
Let's examine the structure of the "idp-oidc.xml" file for each of ICN, GraphQL and CPE, so that we identify some parameters and values already mentioned in the previous sections.
- ICN:
<server description="idpoidc">
  <featureManager>
     <feature>openidConnectClient-1.0</feature>
  </featureManager>
  <webAppSecurity ssoCookieName="FileNetLtpaToken" overrideHttpAuthMethod="CLIENT_CERT" allowAuthenticationFailOverToAuthMethod="FORM" loginFormURL="/navigator/idplogin.jsp" loginErrorURL="/navigator/loginError.jsp" />
  <authFilter id="ExShareGIDNAVFilter" >
     <cookie id="ExShareGIDCookie" name="ExShareGID" matchType="contains" />
     <cookie id="ltpatokencookie" name="FileNetLtpaToken" matchType="notContain"  />
  </authFilter>
  <openidConnectClient id="ExShareGIDNAV"
     tokenReuse="true"
     authFilterRef="ExShareGIDNAVFilter"
     realmName="ExShareGID"
     clientId="ExShareGID"
     clientSecret="<your-Keycloak-client-secret-value>"
     audiences="ALL_AUDIENCES"
     issuerIdentifier="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2"
     responseType="code"
     scope="openid email profile"
     mapIdentityToRegistryUser="true"
     authnSessionDisabled="false"
     inboundPropagation="supported"
     httpsRequired="true"
     validationMethod="introspect"
     disableLtpaCookie="false"
     signatureAlgorithm="RS256"
     userIdentifier="email"
     uniqueUserIdentifier="email"
     userIdentityToCreateSubject="email"
     discoveryEndpointUrl="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/.well-known/openid-configuration"
     authorizationEndpointUrl="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/protocol/openid-connect/auth"
     tokenEndpointUrl="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/protocol/openid-connect/token"
     >
  </openidConnectClient>
</server>
and it is possible to cross-check the configuration above with the inspection of some ICN HTTP session parameters, like for instance from .har file of Firefox or Chrome, as represented in this picture:

 
- GraphQL:
<?xml version="1.0" encoding="UTF-8"?>
<server description="idpoidc">
  <featureManager>
     <feature>openidConnectClient-1.0</feature>
  </featureManager>
  <webAppSecurity ssoCookieName="FileNetLtpaToken" overrideHttpAuthMethod="CLIENT_CERT" allowAuthenticationFailOverToAuthMethod="BASIC"/>
  <authFilter id="ExShareGIDGRAPHQLFilter" >
     <requestHeader id="ExShareGIDGRAPHQLHeader" name="auth-token-realm" value="ExShareGID" matchType="contains"/>
  </authFilter>
  <openidConnectClient id="ExShareGIDGRAPHQL"
     tokenReuse="true"
     authFilterRef="ExShareGIDGRAPHQLFilter"
     realmName="ExShareGID"
     clientId="ExShareGID"
     clientSecret="<your-Keycloak-client-secret-value>"
     audiences="ALL_AUDIENCES"
     issuerIdentifier="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2"
     responseType="code"
     scope="openid email profile"
     mapIdentityToRegistryUser="true"
     authnSessionDisabled="false"
     inboundPropagation="supported"
     httpsRequired="true"
     validationMethod="introspect"
     disableLtpaCookie="false"
     signatureAlgorithm="RS256"
     userIdentifier="email"
     uniqueUserIdentifier="email"
     userIdentityToCreateSubject="email"
     discoveryEndpointUrl="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/.well-known/openid-configuration"
     authorizationEndpointUrl="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/protocol/openid-connect/auth"
     tokenEndpointUrl="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/protocol/openid-connect/token"
     >
  </openidConnectClient>
</server>
In the specific case of GraphQL, it is worth noting the "auth-token-realm". It has to be placed in the headers of the GraphQL call, like the following excerpt from Postman Canary shows, when authorisation is OAuth2:

In the presence of multiple IdPs in the configuration, basically "auth-token-realm" says which IdP it is going to be authenticated from. If omitted in the GraphQL call, likely a "401 Unauthorised" will be returned in the call. That's because it turns out that no IdP is specified, therefore no authentication can take place.
- CPE:
<?xml version="1.0" encoding="UTF-8"?>
<server description="idpoidc">
  <featureManager>
     <feature>openidConnectClient-1.0</feature>
  </featureManager>
  <webAppSecurity ssoCookieName="FileNetLtpaToken" />
  <authFilter id="ExShareGIDCPEFilter" >
     <requestHeader id="ExShareGIDCPEHeader" name="auth-token-realm" value="ExShareGID" matchType="contains"/>
  </authFilter>
  <openidConnectClient id="ExShareGIDCPE"
     tokenReuse="true"
     authFilterRef="ExShareGIDCPEFilter"
     realmName="ExShareGID"
     clientId="ExShareGID"
     clientSecret="<your-Keycloak-client-secret-value>"
     audiences="ALL_AUDIENCES"
     issuerIdentifier="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2"
     responseType="code"
     scope="openid email profile"
     mapIdentityToRegistryUser="true"
     authnSessionDisabled="false"
     inboundPropagation="supported"
     httpsRequired="true"
     validationMethod="introspect"
     disableLtpaCookie="false"
     signatureAlgorithm="RS256"
     userIdentifier="email"
     uniqueUserIdentifier="email"
     userIdentityToCreateSubject="email"
     discoveryEndpointUrl="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/.well-known/openid-configuration"
     authorizationEndpointUrl="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/protocol/openid-connect/auth"
     tokenEndpointUrl="https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/protocol/openid-connect/token"
     >
  </openidConnectClient>
</server>
Since the aim here is to have basic auth CPE access, just do not pass any "auth-token-realm" in CPE calls (e.g. when accessing the "acce" console), and the effect will be to authenticate as basic auth against the local LDAP user directory.


OTHER RELEVANT CONFIGURATIONS IN THE FNCM CR.
There are other configurations that are worth describing, in regard to the FNCM CR. 
- ICN:

(1) The jvm options section of the "navigator_configuration" has to be configured as follows. This configuration is needed to pass the LTPA token from GraphQL and ICN pods to CPE by having the following defined. In particular:
....
jvm_customize_options: DELIM=;-Dcom.filenet.authentication.wsi.AutoDetectAuthToken=true;-Dcom.filenet.authentication.wsi.AuthTokenOrder=ltpa,oauth,oidc

....
Please note that the  -Dcom.filenet.authentication.wsi.AutoDetectAuthToken 

option might be set already in the operator generated "jvm.options" file in ""/opt/ibm/wlp/usr/servers/defaultServer/".

If so,  you don't need to set it again. The  -DFileNet.WSI.AutoDetectLTPAToken option in deprecated.

(2) The container image of ICN has to be configured as "navigator-sso" type of image:
....
repository: cp.icr.io/cp/cp4a/ban/navigator-sso
....
- GraphQL:
The jvm options section of the "graphql" configuration has to be configured as follows.
....
enable_graph_iql: false
jvm_customize_options: DELIM=;-Decm.content.graphql.xsrf.validate.disable=TRUE;-Dcom.filenet.authentication.wsi.AuthTokenOrder=ltpa,oauth,oidc
....
The "-Decm.content.graphql.xsrf.validate.disable" property has been set to "TRUE", just for the sake of simplifying the configuration, therefore this property configuration is optional, and has nothing to do with OIDC/OAuth. The "-Dcom.filenet.authentication.wsi.AuthTokenOrder" setting is needed to pass the LTPA token from GraphQL and ICN pods to CPE, pretty much the same as the configuration made for ICN above. 
TESTING.
In this section, some tips are described about how to test, debug and troubleshoot the proposed implementation. 
- JWT: jwt.io has some online utility to inspect the content of tokens. Here follows an excerpt of how the token looks like when processed by jwt.io (also available in the for of browser extension, e.g. for Firefox browser) debugging utilities.
In this specific case, the token has been fetched as a result of a GraphQL test with Postman Canary: details are described ahead in this section.

- Postman Canary: Postman is a  popular tool that provides an array of features to test REST APIs. Therefore, it turns out to be useful to test FNCM GraphQL. Among the other things, Postman provides the configuration required to get an OAuth token from the reference IAM/IdP, and first, "OAuth2.0" has to be selected among the available authentication options, in the "Authorisation" section of Postman. Here follows a sample of the configuration of the Authentication part of the Postman collection, to interact with Keycloak ("Configure new token"):



Here follows the expanded view of the banned links in the picture above:

- Callback URL: https://graphql-<your-namespace-and-cluster>/oidcclient/redirect/ExShareGIDGRAPHQL
- Auth URL: https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/protocol/openid-connect/auth
- Access token URL: https://<Keycloak-server:Keycloak-port>/realms/CP4BA2/protocol/openid-connect/token
Scrolling down the "Configure new token" part of the picture above, the following buttons can be seen:

- Get New Access Token: will interact with Keycloak to authenticate the user and fetch the token; the token will eventually be stored into the Postman configuration, for subsequent GraphQL calls. It will automatically value the "Bearer" property in the Headers section, like the following:

- Clear cookies: is there to allow full cleanup of all the cookies that carry the tokens.

As per ICN testing, start with an URL to any of the available ICN Desktops in your ICN configuration. If ICN configuration is OK, a redirect to the following landing page will occur:



And it is possible to identify "idplogin.jsp" page, as per what described in "idp-oidc.xml" of ICN too, as well as "Single Sign On" as the friendly name of the IdP ("display_name" property of "open_id_connect_providers" section in FNCM CR). Continue by clicking the "Authenticate with Single Sign on" button and a redirect to Keycloak authentication page will happen, if there are not already all the relevant session cookies. 
Testing can continue by enabling the "Developer tools" utilities from most popular browsers, as well as leveraging the above mentioned JWT.io browser extensions.
SPECIAL THANKS.
To @ROGER Bacalzo who supported me in this initiative.
REFERENCES AND USEFUL LINKS.
0 comments
72 views

Permalink