Export Utility and Token Migration

Migrate ISVA API Definition configuration to IBM Security Verify Access OIDC Provider

If you have an ISVA 10.0.5.0 instance with API Protection Definition configured, you can migrate the configuration to IBM Security verify Access OIDC Provider(ISVAOP) using export utility. The export utility migrates a single API protection definition and its associated clients, mapping rules, keystores, and attribute sources. The utility also exports the runtime database configuration.

How to run the export utility

  • Retrieve all of the definitions using LMI admin account to determine the definition ID to be exported. Use the Local Management Interface (LMI) admin account username and password.
    curl --location --request GET 'https://{appliance_hostname}/iam/access/v8/definitions' --user admin:admin --header 'Accept: application/json'
    
  • Retrieve the definition_id that must be exported and run the following curl call for export.
    `curl --location --request GET '{appliance_lmi}/iam/access/v8/definitions/export/{definition_id}' --user admin:admin  --output 'exportedData.zip'`
    
  • Extract the exportedData.zip file into the exportedData folder. Details of the folder structure are in the resource doc.

How to use the data that is exported

  • The exported configuration is mounted to the ISVAOP container path /var/isvaop/config. The deployment pattern will determine how the configuration is read.

Steps to be completed before the container is started

  • Update the base_url configuration in the provider.yml.

  • Cleanup and fix your mapping rules.

    • Mapping rules in ISVAOP are meant only to enrich grants. The conformance related logic which exists in ISVA is no longer needed in ISVAOP since it's supported out of the box.
    • ISVAOP supports a subset of mapping rule utilites. Mapping rules exported from ISVA will not get executed as is; they need to be updated. The detailed document is under the mapping rule reference section. Here are some samples of commonly used mapping rule utilities.
    • Retrieving grant_type and logging the entire STSUU.
      importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
      importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils);
    
      IDMappingExtUtils.traceString("STSUU content: " + stsuu.toString());
    
      var grant_type = stsuu.getContextAttributes().getAttributeValueByName("grant_type");
    
    • Adding an additional claim to the id_token.
      importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
      importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils);
    
      let preferred_username = stsuu.getAttributeContainer().getAttributeValueByName("preferred_username");
      if (preferred_username !== null) {
          idtokenData["preferred_username"] = preferred_username;
      }
    
    • Using the UserLookupHelper to validate the incoming username and password during an ROPC flow.
      importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
      importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils);
      importClass(Packages.com.ibm.security.access.user.UserLookupHelper);
    
      IDMappingExtUtils.traceString("Starting ROPC JS");
    
      var username = stsuu.getContextAttributes().getAttributeValueByName("username");
      var password = stsuu.getContextAttributes().getAttributeValueByName("password");
    
      /**
      * This is an example of how you could verify the username and password with a
      * user registry before the access token is generated, therefore preventing
      * the scenario where access tokens are created for invalid users and stored in
      * the cache with no way to remove them until they expire.
      *
      * A prerequisite for using this example is configuring an ldap configuration
      * in ldapcfg.yml and an associated ldap server connection in storage.yml
      * 
      * This example is the default method for verifying the username and password.
      * To enable this example, change the "ropc_registry_validation" variable 
      * to "true".
      */
      var ropc_registry_validation = true;
    
      if (ropc_registry_validation) {
    
          // Assuming there is ldap configuration name "user_registry" in ldapcfg.yml.
          var userLookupHelper = new UserLookupHelper("user_registry");
    
          /**
          * Assuming the users have distinguished name pattern:
          * cn=<username>,dc=ibm,dc=com.
          */
          var user = userLookupHelper.getUserByNativeId("cn=" + username + ",dc=ibm,dc=com");
          if (user.hasError()) { // Check the error.
    
              /**
              * This is the recommended way to check and log the error.
              */
              IDMappingExtUtils.traceString("Throw exception - getUserByNativeId failed with error: " + user.getError());
              OAuthMappingExtUtils.throwSTSException("Unable to authenticate user.");
    
          } else if (!user.authenticate(password)) { // Check the password.
    
              IDMappingExtUtils.traceString("Throw exception - failed to authenticate user: " + user.getNativeId());
              OAuthMappingExtUtils.throwSTSException("Invalid user or password.");
    
          } else {
    
              /**
              * Populate user metadata in the case of successful authentication.
              * The metadata has credential attributes that can be used for grant enrichment.
              * The metadata at least should at least contain 'uid' claim or any claim indicated in 'subject_attribute_name'
              * under provider.yml 'authentication' stanza as this will be used as the token 'sub' claim .
              */
              IDMappingExtUtils.traceString("Authentication is successful.");
              userData.uid = username;
              userData.given_name = user.getAttribute("givenName");
              userData.family_name = user.getAttribute("sn");
              userData.preferred_username = username;
    
          }
    
      }
    
    • Using OAuthMappingExtUtils to store additional claims in the database for grant enrichment.
      importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
      importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils);
    
      var stateID = stsuu.getContextAttributes().getAttributeValueByName("requestId");
      var association = OAuthMappingExtUtils.associate(stateID, "associateKey", "associateValue");
    
    
    • Using OAuthMappingExtUtils to retrieve all associations for grant enrichment. For userinfo and introspect the code snippet should be added in the post_token mapping rule. The example indicates the scenario when tokens are migrated from ISVA to ISVAOP.

      • This is an sample of the DB table where claims are stored by ISVA.
        select * from oauth20_token_extra_attribute;

        state_idattr_nameattr_valuesensitiveread_only
        uuidcb16ba98-0184-12f9-b77f-80ae9ccb5582urn:ibm:names:ITFIM::oauth:saved:claim:fixedClaim["fixedClaimValue"]NN
        uuidcb16ba98-0184-12f9-b77f-80ae9ccb5582urn:ibm:names:ITFIM::oauth:saved:claim:groups["3952a3b6-710f-11ed-896a-0242ac130008"]NN
      • To be able to add the fixedClaim and groups claim to introspect or userinfo response use the following snippet in the post_token mapping rule.

        importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
        importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils);
      
          var stateID = stsuu.getContextAttributes().getAttributeValueByName("requestId");
          var attrs = OAuthMappingExtUtils.retrieveAllAssociations(stateID);
          for (const key in attrs) {
              if (key === "urn:ibm:names:ITFIM::oauth:saved:claim:fixedClaim" && requestType === "userinfo") {
                  paramsOverride.fixedClaim = attrs[key];
              } else if (key === "urn:ibm:names:ITFIM::oauth:saved:claim:groups" && requestType === "introspect") {
                  paramsOverride.groups = attrs[key];
              }
          }
      
      
    • Using OAuthMappingExtUtils to retrieve all of the associations for grant enrichment for refresh_token flow, the following pre_token mapping rule snippet can be used. The example indicates the scenario when tokens are migrated from ISVA to ISVAOP.

      • This is an sample of the DB table where claims are stored by ISVA.
        select * from oauth20_token_extra_attribute;

        state_idattr_nameattr_valuesensitiveread_only
        uuidcb16ba98-0184-12f9-b77f-80ae9ccb5582urn:ibm:names:ITFIM::oauth:saved:claim:fixedClaim["fixedClaimValue"]NN
        uuidcb16ba98-0184-12f9-b77f-80ae9ccb5582urn:ibm:names:ITFIM::oauth:saved:claim:groups["3952a3b6-710f-11ed-896a-0242ac130008"]NN
      • To be able to add the fixedClaim claim to the id_token and groups to the access token generated by the refresh token flow, use the following snippet.

        importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
        importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils);
        var requestType = stsuu.getContextAttributes().getAttributeValueByName("request_type");
        var grantType = stsuu.getContextAttributes().getAttributeValueByName("grant_type");
      
        if (requestType == "token" && grantType == "refresh_token") { 
            IDMappingExtUtils.traceString("Original StateID: " + tokenData.grant_id);
            var attrs = OAuthMappingExtUtils.retrieveAllAssociations(tokenData.grant_id);
      
            for (const key in attrs) {
                if (key === "urn:ibm:names:ITFIM::oauth:saved:claim:fixedClaim") {
                    idtokenData.fixedClaim = attrs[key];
                } else if (key === "urn:ibm:names:ITFIM::oauth:saved:claim:groups") {
                    tokenData.groups = attrs[key];
                }
            }
        }
      
    • Using an HTTPClient callout to enrich grants.

      importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);
      importClass(Packages.com.ibm.security.access.HttpClient);
      IDMappingExtUtils.traceString("HttpClientV2 "); 
    
      var headers = new Headers();
      headers.addHeader('Content-Type','application/json');
      headers.addHeader('Accept','application/json');
      var responseGet = HttpClientV2.httpGet("https://www.acme.com/resources", headers, null, null,null); 
      if(responseGet.getCode() == 200){
          body = JSON.parse(responseGet.getBody());
      }
    
  • The export utility exports the entire keystore that is used in the API Definition and client configuration. Review the keystores and retain only the keys and certificates required by ISVAOP.

Differences between ISVA and ISVAOP

  • In ISVA the configuration is written into the configuration database. In ISVAOP the configuration for the container is supplied as YAML files, template files, and JavaScript files along with other potential supporting files (e.g. PEM certificate files).
  • The definition or provider configuration and client configuration in ISVAOP contain additional configurations that adhere to various OAuth and OIDC specifications. Details can be found in the provider and client configurations.

Token migration

๐Ÿšง

Token migration from ISVA to ISVAOP is a one way step

While its possible to exchange a refresh token generated in ISVA for a new access token in ISVAOP, tokens generated in ISVAOP cannot be consumed by ISVA.

  • The pre-requisite for token migration is that ISVA and ISVAOP use the same database and the ISVA configuration is exported and mounted into the ISVAOP container.

  • ISVA and ISVAOP runtime database schema are identical except for an additional database table created for jti in ISVAOP, so access token's and refresh token's created in ISVA can be consumed in ISVAOP. Here are a few scenarios.

    • introspect an access_token.
    • Call userinfo with the access_token to retrieve user information.
    • Exchange a refresh token to generate a new access token.
    • Map claims to access_token or id_token.
  • The examples mapping rule snippet is provided in the previous section to retrieve claims stored in the database and map it to userinfo or introspect response.

  • The examples mapping rule snippet is provided in the previous section to retrieve claims stored in the database and map it during the refresh_token flow.