This hello world guide explores the deployment of IBM Security Verify Access OIDC Provider (ISVAOP) within Docker containers.

This tutorial details deployment of Verify Access OIDC Provider using native Docker.

High Level Architecture

The high-level architecture for the environment described in this document may be summarized as follows:

1047

Deployment machine

The deployment machine is a physical or virtual machine which has the following components installed:

  • Docker - This provides the container services used to explore native Docker installation of ISVAOP. It includes a command line tool (docker) for management.
  • Browser - A browser is required for accessing the Verify Access Reverse Proxy.

The deployment machine needs to have outbound connectivity to the Internet so that it can connect to IBM Cloud Container Registry.

IBM Cloud Container Registry

IBM Cloud Container Registry(ICR) is a repository of Docker images which can be used to create Docker containers. The official images for IBM Security Verify Access are hosted on ICR. The as-is "Verify Access ready" image PostgreSQL is also hosted here. Internet-connected Docker systems can automatically pull images from ICR as required.

  • Verify Access - icr.io/isva/verify-access:10.0.4.0_IF1
  • Verify Access WRP - icr.io/isva/verify-access-wrp:10.0.4.0_IF1
  • Verify Access Postgres - icr.io/isva/verify-access-postgresql:10.0.4.0_IF1
  • Verify Access OpenLDAP - icr.io/isva/verify-access-openldap:10.0.4.0_IF1
  • Verify Access OIDC Provider - icr.io/isva/verify-access-oidc-provider:23.12

See Software Downloads > Containers for more information.

Verify Access Trial Center

In order to activate an IBM Security Verify Access system, you will need a trial certificate. A certificate for a 90-day trial can be obtained via the Verify Access Trial Center. You will need to register for an IBMid (if you don't already have one) to access this site. Link

Getting Started

Prerequisites

Database update

  • Verify Access OIDC Provider database schema is identical to Verify Access expect for an additional table used for jti validation.

    • Run the following command to add the additional table.
    [demouser@centos ~]$ docker exec -ti postgresql bash
    [postgresql ~]$ psql -d isva -U postgres
    isva# CREATE TABLE IF NOT EXISTS OAUTH20_JTI ( 
        JWT_TYPE   INT          NOT NULL, 
        JWT_ID     VARCHAR(200) NOT NULL, 
        EXPIRED_AT BIGINT       NOT NULL, 
        CONSTRAINT PK_JTIS PRIMARY KEY(JWT_TYPE, JWT_ID) 
    );
    
    CREATE INDEX IF NOT EXISTS IX_JTIS_EXPIRED ON OAUTH20_JTI (EXPIRED_AT);
    

Test the Database

- Run the following command to add the additional table.

    ```shell
    [demouser@centos ~]$ docker exec -ti postgresql bash
    [postgresql ~]$ psql -d isva -U postgres
    isva# \dt;
        public | oauth20_dynamic_client         | table | postgres
        public | oauth20_token_cache            | table | postgres
        public | oauth20_token_extra_attribute  | table | postgres
        public | oauth20_jti                    | table | postgres
        public | oauth_authenticators           | table | postgres
        public | oauth_scope                    | table | postgres
        public | oauth_trusted_client           | table | postgres
    ```

Generate Certificate and Key Files

  • In your Docker environment you need keys and certificates to start up the ISVAOP containers. You can use the following openssl command to generate key and certificate files.

    • To generate RSA private key, you can use the following command:

      [demouser@demovm ~]$ mkdir $HOME/git/isvaop-container-deployment 
      [demouser@demovm ~]$ export DOCKERKEYS=$HOME/git/isvaop-container-deployment/dockerkeys
      [demouser@demovm ~]$ mkdir $DOCKERKEYS
      [demouser@demovm ~]$ mkdir $DOCKERKEYS/server_keys
      [demouser@demovm ~]$ mkdir $DOCKERKEYS/server_keys/personal
      [demouser@demovm ~]$ mkdir $DOCKERKEYS/server_keys/signer
      [demouser@demovm ~]$ openssl req -newkey rsa:2048 -subj "/CN=iamlab/O=ibm/C=us" -nodes -keyout $DOCKERKEYS/server_keys/personal/server_key.pem -x509 -days 365 -out $DOCKERKEYS/server_keys/signer/server_cert.pem
      
      
  • In ISVAOP containers you need keys to sign the id_token. You can use the following openssl to genrate key.

    • To generate RSA private key, you can use the following command:

      [demouser@demovm ~]$ openssl genrsa -out rsakey.pem 2048 
      
    • Create the keystore directory :

      [demouser@demovm ~]$ mkdir isvaop_signing
      
      [demouser@demovm ~]$ cd isvaop_signing
      
      [demouser@demovm ~]$ mkdir personal
      
      [demouser@demovm ~]$ mkdir signer
      
    • Copy the files into the path:

      
      [demouser@demovm ~]$ cp rsakey.pem isvaop_signing/personal
      

Download the starter kit

  • IBM Security Verify Access OIDC Provider (ISVAOP) uses a prescribed configuration directory structure that is loaded into /var/isvaop/config directory within the container filesystem.

  • The starter kit provides a boilerplate structure for the configuration. It can be downloaded from Github Releases of the resources repository.

Verify Access OIDC Provider initial configuration

  • The ISVAOP configuration contains the following:

    • A set of YAML files that contain configuration settings for the OIDC Provider, storage, attribute sources etc.
    • Keystores and certificates
    • JavaScript customization in the form of mapping rules and access policies
    • Static OAuth and OIDC client configuration
  • All the YAML documents in the /var/isvaop/config directory are combined when the container starts. There are different ways to split the YAML documents and their references to folders. You can read more about configuration.

An example configuration is:

/var/isvaop/config
 |
 - provider.yml
 - storage.yml
 - rules.yml
 - clients.yml
 - keystore.yml
  • Create ISVAOP_Volume directory.

        [demouser@demovm ~]$ mkdir ISVAOP_Volume
    
  • Unzip the starer kit and copy the contents into the ISVAOP_Volume directory.

        [demouser@demovm ~]$ unzip config_starter_kit.zip -d config_starter_kit
        [demouser@demovm ~]$ cp -r config_starter_kit/* ISVAOP_Volume
    
  • Copy the server_keys into the ISVAOP_Volume/keystore directory.

        [demouser@demovm ~]$ cp -r $DOCKERKEYS/server_keys ISVAOP_Volume/keystore
    
  • Copy the isvaop_signing into the ISVAOP_Volume/keystore directory.

        [demouser@demovm ~]$ mkdir  ~/ISVAOP_Volume/keystore/isvaop_signing
        [demouser@demovm ~]$ cp https_keys/* ~/ISVAOP_Volume/keystore/isvaop_signing/
    
  • Copy the public certificates that will verify the Postgres Database container connection, into isvaop_signing/signer directory. If you are using the cookbook, the file should be git/container-deployment/local/dockerkeys/postgresql/postgres.crt .

        [demouser@demovm ~]$ cp postgres.crt isvaop_signing/signer/
    
  • Update keystore.yml

    keystore:
      - name: server_keys
        type: pem
        certificate:
          - label: server_cert
            content: "@keystore/server_keys/signer/server_cert.pem"

        key:
          - label: server_key
            content: "@keystore/server_keys/personal/server_key.pem"
      - name: isvaop_signing
        type: pem
        key:
          - label: rsakey
            content: "@keystore/isvaop_signing/personal/rsakey.pem"
        
  • Update rules.yml
    rules:
      mapping:
        - name: isvaop_pretoken
          rule_type: javascript
          content: |
            importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
            importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils);

            IDMappingExtUtils.traceString("Starting Pre Token JS");

            /**
            * Use this mapping rule to enrich the session:
            * - Resolve the claims requested for id_token (and userinfo)
            *   Populate resolved claims into 'idtokenData' context
            * - Adding extra claims for token introspection (and JWT access token)
            *   Populate the extra claims into 'tokenData' context
            * 
            * User Metadata:
            * Depending on the flow, user metadata may come from 'iv-jwt' or populated in
            * ROPC javascript mapping rule or by CIBA endpoints / javascript mapping rule.
            * But all the user metadata collected will be made availabe in STSUU attribute container.
            * 
            * Attribute Source:
            * The system will resolve any attribute source mappings prior to this mapping rule execution.
            * If the mapping should contain a value, it will be available in STSUU attribute container.
            * 
            * Claims to be resolved:
            * The 'claims' context contains some helper method to retrieve id_token/userinfo
            * voluntary or essential claims that need to be resolved.
            */

            IDMappingExtUtils.traceString("STSUU content: " + stsuu.toString());

            var requestType = stsuu.getContextAttributes().getAttributeValueByName("request_type");
            var grant_type = stsuu.getContextAttributes().getAttributeValueByName("grant_type");

            for (const claim of claims.getAllClaims()) {

                IDMappingExtUtils.traceString("Resolving claim: " + claim);
                if (claim == "email_verified") {
                    idtokenData["email_verified"] = true;
                } else if (claim == "updated_at") {
                    idtokenData["updated_at"] = Date.now();
                } else {
                    /**
                    * Trying to resolve the requested claim using user metadata or attribute source mapping
                    * that is made available in STSUU attribute container
                    */
                    var value = stsuu.getAttributeContainer().getAttributeValueByName(claim);
                    if (value != null) {
                        idtokenData[claim] = value;
                    }
                }
            }

            /**
            * Example of adding 'acr' and 'amr' claim based on AUTHENTICATION_LEVEL attribute
            */
            var authLevel = stsuu.getAttributeValueByName("AUTHENTICATION_LEVEL");
            if (authLevel == null || authLevel == "1") {
                idtokenData.amr = "password";
                idtokenData.acr = "urn:ibm:basic";
            } else {
                idtokenData.amr = "mmfa";
                idtokenData.acr = "urn:ibm:mmfa";
            }

            if (requestType == "authorize") {
                /**
                * In OpenBanking requirement, the 'openbanking_intent_id' received in the 'claims'
                * need to appear in the id_token.
                */
                var intentId = claims.getIDTokenClaimValues("openbanking_intent_id");
                if (intentId != null && intentId != undefined) {
                    idtokenData.openbanking_intent_id = intentId[0];
                }

                /**
                * Example of extra validation that can be done in this mapping rule
                */
                var state = stsuu.getContextAttributes().getAttributeValueByName("state");
                if (state != null && state.length > 255) {
                    OAuthMappingExtUtils.throwSTSCustomUserMessageException("State is too long", 400, "invalid_request");
                } 
            }

            /**
            * Example of enriching introspection result
            */
            tokenData.groups = ["support", "user"];
        - name: isvaop_posttoken
          rule_type: javascript
          content: |
            importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
            importClass(Packages.com.tivoli.am.fim.fedmgr2.trust.util.LocalSTSClient);
            importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils);

            IDMappingExtUtils.traceString("Starting Post Token JS");

            var requestType = stsuu.getContextAttributes().getAttributeValueByName("request_type");

            /**
            * Use this mapping rule to enrich the response.
            * In general there should not be a lot of customization here.
            * You can enrich the response parameter or header.
            * For example in FAPI there's requirement to output certain HTTP header received in the header.
            */

            var interactionID = stsuu.getContextAttributes().getAttributeValueByName("x-fapi-interaction-id");
            if (interactionID != null) {
                headersOverride["x-fapi-interaction-id"] = interactionID;
            }
  • Update the storage.yml.

        [demouser@demovm ~]$ vi  ~/ISVAOP_Volume/storage.yml
    
        # Copyright contributors to the IBM Security Verify Access OIDC Provider Resources project
        runtime_db: hvdb                              # Configuration of runtime database. Points to the database server connection
        session_cache:
        type: db                                      # Specifies the type of session cache: in-memory, redis, or db
        server_connections:
          - name: hvdb                                  # Connection name
            type: postgresql                          # Connection type: `redis`, `ldap`, `postgresql`, `oracle`, `db2`
            database_name: isva                       # Specifies the database name. For database types only
            hosts:                                    # List of host information (IP and port).
              - hostname: postgresql                    # Server's hostname.
                hostport: 5432                        # Server's host port.
            credential:                               # Credential information to connect to the server.
            username: postgres                        # Specifies the username to access the server.
            password: Passw0rd                        # Specifies the password to access the server. It is recommended to obfuscate this.
    
            ssl:
              certificate:
                - '@keystore/isvaop_keys/signer/postgres.crt'
            disable_hostname_verification: true
    

Create the ISVAOP container

  • Create the container using the following command.
[demouser@demovm ~]$ docker run --hostname isvaop-test --name isvaop-test \
  --publish 8436:8436 \
  --volume /home/ISVAOP_Volume:/var/isvaop/config \
  icr.io/isva/verify-access-oidc-provider:23.12

Test the configuration

  • Use a browser to access the .well-known/openid-configration endpoint.
https://127.0.0.1:8436/oauth2/.well-known/openid-configration
910

Configure Web reverse proxy to act as point of contact for ISVAOP

  • Follow the steps to configure Web reverse proxy as the point of contact.
  • Configure the advanced tuning parameter to enable the point-of-contact (POC) wizard for ISVAOP: Navigate to System -> Advanced Tuning Parameter, and add a parameter verify_oidc_provider.enabled and set it to true. Setting this parameter enables an additional API endpoint oauth2_config. This API automates the process of setting up junctions, access control lists and other policies for the ISVAOP.
827
  • Retrieve the reverse proxy name. This is referenced as {rpID} in the steps below.
1191
  • Retrieve the admin user credentials for the Verify Access Local Management Interface (LMI). This is referenced in the following steps as {user} and {password}
  • Retrieve the IP address where the ISVAOP is running. Update the {junction} in the payload below to a valid name that acts as a standard junction path. For example: /isvaop. Update the {lmi_user}:{lmi_password}. Also, set reuse_acls to true to ensure ACLs are not detached and deleted from existing endpoints.
curl --location --request POST 'https://lmi.iamlab.ibm.com/wga/reverseproxy/rp1/oauth2_config' \
  --user {lmi_user}:{lmi_password} \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data-raw '{
    "hostname":"isvaop-test",
    "port":"8436",
    "junction":"isvaop",
    "reuse_acls": true,
    "reuse_certs": true
}'
  • Deploy pending changes and restart reverse proxy. In the case of a container deployment of Verify Access, you will also need to publish the container configuration before restarting the Web Reverse Proxy container.
1575

Update the provider.yml

  • The base_url configuration in the provider.yml is based on the point of contact and junction configured in the previous step.
  • Update the provider.yml base_url.
    # Copyright contributors to the IBM Security Verify Access OIDC Provider Resources project
    version: 23.12
    server:
      ssl:
        key: ks:server_keys/server_key
        certificate: ks:server_keys/server_cert
      pages:
        type: zip
        content: "@templates.zip"
    definition:
      id: 1
      name: OIDC Definition
      grant_types:
        - authorization_code
        - urn:openid:params:grant-type:ciba
      base_url: https://www.acme.com/isvaop
      token_settings:
        issuer: https:///www.acme.com
        signing_alg: RS256
        signing_keystore: isvaop_signing
        signing_keylabel: rsakey
      features:
        consent_prompt: ALWAYS_PROMPT
    jwks:
      signing_keystore: isvaop_signing

  • Restart the isvaop-test container
[demouser@demovm ~]$ docker restart isvaop-test

Test the configuration

  • Use a browser to access the .well-known/openid-configration endpoint.
https://www.iamlab.ibm.com/isvaop/oauth2/.well-known/openid-configuration
905

Configure a static client

  • Static OAuth and OIDC clients are configured within the clients.yaml file.

  • Create a clients.yaml file.

        [demouser@demovm ~]$ vi  ~/ISVAOP_Volume/clients.yml
    
  • Copy the following contents into a the file clients.yml

    # Copyright contributors to the IBM Security Verify Access OIDC Provider Resources project
    clients:
      - client_id: client_authcode
        client_secret: "ahwoaor82noawasg"
        client_name: "AuthorizationCode Client"
        enabled: true
        redirect_uris:
          - https://www.iamlab.ibm.com
        grant_types:
          - authorization_code
        response_types:
          - code
          - code token
        token_endpoint_auth_method: client_secret_post
        require_pkce: true
  • Restart the isvaop-test container
[demouser@demovm ~]$ docker restart isvaop-test

Test Updated Configuration

  • Open the browser to access
  • Use a browser to trigger an authorization code flow. Since PKCE is enabled the code_challenge and code_challenge_method are sent in the request.
https://www.iamlab.ibm.com/isvaop/oauth2/authorize?client_id=client_authcode&response_type=code&redirect_uri=https://www.iamlab.ibm.com&scope=openid&&code_challenge=vXpDWQ0Ev8KgDEJB77ui741_iu218_mtk6rsSU0gU9E&code_challenge_method=S256
  • Enter sec_master as the Username and Passw0rd as the Password. Click on Submit.
1605
  • Copy the authorization code from the browser.
1601
  • Exchange code for token. Using the cURL call. Replace the code value. Since PKCE is enforced the code_verifier needs to be sent
    curl --location --request POST 'https://www.iamlab.ibm.com/isvaop/oauth2/token' \
    --header 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode 'scope=openid' \
    --data-urlencode 'grant_type=authorization_code' \
    --data-urlencode 'code=8DwMLLy8JvM1aFdeq2PxZCrhtkiV6laTlfuARc_EK3Y.6zpLc-tMlO20gRcR02FETQPewka3sMszZEYCAMMX8ExSRji-_ZxOr00d18pA6e7By69EPdsReDCqi8dliSkrUw' \
    --data-urlencode 'client_id=client_authcode' \
    --data-urlencode 'client_secret=ahwoaor82noawasg' \
    --data-urlencode 'redirect_uri=https://www.iamlab.ibm.com' \
    --data-urlencode 'code_verifier=rwv_TkCRcP1re0APTQRdHLwabrNZzFJVhfBiwbTpirWYpYFM3v36mXPMNv7rslDcxuvas8HtrFygxhCPF86ePFstyOsLnyhVel9xriqnnbK06jB7EqJj4QajeeI7sT5Y'
  • Sample Response
    {
        "access_token": "EQ6pS5x9W2fNEg9FDiXPpR-LCX6zgpzXocvCobA_Gek.B1sSOHZGXvO_GhqXypYu4SKepAeRpA47Juy2YYdCafNxPPVIacYqEp9Ypp9QjJh5IZheGizNb8OWwK2gaYWg6w",
        "expires_in": 7199,
        "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Imh0dHBzZXJ2ZXJrZXkiLCJ0eXAiOiJKV1QifQ.eyJhY3IiOiJ1cm46aWJtOmJhc2ljIiwiYXRfaGFzaCI6InJFeDdPdTZyVHlzWm1LOVFTck0zNXciLCJhdWQiOlsiY2xpZW50X2F1dGhjb2RlIl0sImF1dGhfdGltZSI6MTY2Nzc4MDIwMSwiZXhwIjoxNjY3NzgzODAxLCJpYXQiOjE2Njc3ODAyMDEsImlzcyI6Imh0dHBzOi8vaXN2YW9wLmlibS5jb20iLCJqdGkiOiJiNjkxOTcyNC1kNTgyLTRkMjItOTZiMy1jMGY3Mjc5YjE1NmIiLCJyYXQiOjE2Njc3ODAxNzUsInN1YiI6InRlc3R1c2VyIn0.NrZ_aauVg8O-CskI5YFgs9PyjCSK9wo56lF-bV9mvhoQKrf-xqNVW7Mjvp7X5qWwe7Pog-AvjzJOeGn1iVt2TenGcLMSuPGe1ExJHFrAE4noMH5aETFG1Bt9tZU2GvbcJ5rPqoMH8Wxu1yHUiFFa0BefbNpHqZCUwpcPnwICaD7m7xis3IlKPp9JMt7NvVK-0GzeFvF1jo0Kvouqqy7OO8LsByk-RbzxzaS09eYeEa0lA0ZMCmcpUiURTFw6ctkWJt69OdQefHZKdyfm_pcUIQ0uuStDSWg5IUh1zWbOGSV-Zq1t7OcIJz2g1fHad5VfU6rWS4Bd8fef_Z7mXiZIgg",
        "scope": "openid",
        "token_type": "bearer"
    }
  • Introspect the access_token
    curl --location --request POST 'https://www.iamlab.ibm.com/isvaop/oauth2/introspect' \
    --header 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode 'token=EQ6pS5x9W2fNEg9FDiXPpR-LCX6zgpzXocvCobA_Gek.B1sSOHZGXvO_GhqXypYu4SKepAeRpA47Juy2YYdCafNxPPVIacYqEp9Ypp9QjJh5IZheGizNb8OWwK2gaYWg6w' \
    --data-urlencode 'client_id=client_authcode' \
    --data-urlencode 'client_secret=ahwoaor82noawasg'