Cloud Pak for Business Automation

 View Only

API access tokens in Cloud Pak for Automation 21.0.3

By Jens Engelke posted Thu December 30, 2021 05:14 AM

  
Cloud Pak for Automation 21.0.3 brings a fundamental shift in architecture: Version 21.0.3 introduces the "Platform UI" component - a foundational service that is also used in other Cloud Paks like Cloud Pak for Data.

As @DA GUANG SUN describes in Default hostnames change for Cloud Pak for Business Automation 21.0.3 this new Platform UI component impacts the way how browser users access the system as all URLs change to the one and only hostname of the Platform UI route and in many cases, also change the URL path to include a path-prefixes.

The other significant impact of this architectural change is ...

Authentication and session management

Before 21.0.3, there was User Management Service (UMS) providing multiple capabilities: Teams, SSO, and SCIM. With UMS SSO, end users were prompted for their password just once, but each capability managed its own session with the end user based on cookies. The result was a lack of single sign-out and a series of redirects when moving from one capability to another. Essentially, the various capabilities were related with one another like many independent web sites all using the "log me in using my facebook account" option from an authentication perspective.

With 21.0.3 and the shared Platform UI component, this experience improves: All capabilities share a single hostname, which is technically implemented by a common "frontdoor" component that handles authentication and session management even before a request is forwarded to a pod for actual processing. This frontdoor component is the one and only OpenID Connect client of foundational service Identity and Access Management (IAM), hence reducing the number redirects for authentication to one (instead of one per capability). After logging out, the frontdoor component will not forward traffic to its backend pods, hence there is an implicit single sign-out.

With that central management of authentication, there is an additional opportunity to introduce support for authentication schemes shared by all capabilities as this blog post will explain.

Cookie based authentication

Most obvious is cookie based authentication: As an end user, you point your browser to https://cpd-{icp4acluster-name}.apps.myocpcluster.com for example, https://cpd-production.apps.ocptim1.cp.fyre.ibm.com. This will cause a redirect to the default landing page at /zen/#/homepage, which is a protected resource, so authentication is triggered by redirecting to IAM. Right after installation, you probably only have "Openshift authentication" and "IBM provided credentials (admin only)" options. You can obtain these admin credentials with oc level access:
iamadmin=$(oc get secret -n ibm-common-services platform-auth-idp-credentials -o jsonpath='{.data.admin_username}' | base64 -d)
iampass=$(oc get secret -n ibm-common-services platform-auth-idp-credentials -o jsonpath='{.data.admin_password}' | base64 -d)
echo "User: $iamadmin"
echo "Password: $iampass"

Of course, IAM may be connected to one or more LDAP servers and provide the "Enterprise LDAP" authentication scheme option or you may have configured an upstream SAML identity provider, which adds the "Enterprise SAML" option. The set of options presented to users is configurable and will be memorized using a cookie.


Once you successfully authenticated, there will be two relevant cookies:
  • ibm-private-cloud-session
  • ibm-private-cloud-session-id
In some use cases, it may be convenient to just copy these two cookies from your browser and pass them as a HTTP header in cURL as in the example for a Platform UI user management API below:
cp4ahost="https://bts.apps.jens.cp.fyre.ibm.com"
cookievalue='ibm-private-cloud-session=eyJh...; ibm-private-cloud-session-id=a95ffe15-cc60-4679-8f5c-3dc7ce50d7fb'
curl -skH "Cookie: $cookievalue" "$cp4ahost/usermgmt/v1/users" | jq

The expected output is a JSON structure about the current user as shown below. If that's not the case, omit the | jq and replace the the cURL option -s with -v to see the actual response.

{
  "users": [
    {
      "uid": "1000331004",
      "username": "...​

Token based authentication

Working with "programmatic" clients like your own Java based REST client or even cURL, copying from a browser is not at all practical. There is an API to obtain an authentication token for the Platform UI based on a valid IAM token:
cp4atoken=$(curl -sk "$cp4ahost/v1/preauth/validateAuth" \
  -H "username:$username" \
  -H "iam-token: $iamaccesstoken" | jq -r .accessToken)

The resulting token in bash variable cp4atoken can be used for API invocations by passing it as a regular OAuth access_token header:

Authorization: Bearer <token>

Let's explore how to obtain an IAM token first: IAM is an OpenID Connect (OIDC) Offering Party (OP) and can be used with various standards based flows. Some of these flows requires registration of an OIDC client representing the client app asking for tokens. There are more flows available (as defined in the OIDC standard). this blog post just outlines some common examples.

Resource Owner Password Credentials flow (ROPC or just "password")

For a user account that exists in an LDAP server connected to IAM, the password flow can be used to obtain an API token. For cases where password based authentication is not allowed, consider API keys.

The IAM access token is obtained by POSTing username and password along with grant_type=password and scope=openid to /idprovider/v1/auth/identitytoken.
The IAM token is then exchanged for a Platform UI token, which can be used for API invocations.

This script is an end-to-end example

  • Obtain an IAM token representing the user (authenticated using password)
  • Exchange the IAM token for a Platform UI token
  • Call a Platform UI user management REST API, passing the token for authentication
myuser='someone@ibm.com'
mypass='********************'
iamhost=https://$(oc get route -n ibm-common-services cp-console -o jsonpath="{.spec.host}")
cp4ahost=https://$(oc get route cpd -o jsonpath="{.spec.host}")

iamaccesstoken=$(curl -sk -X POST -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" \
  -d "grant_type=password&username=$myuser&password=$mypass&scope=openid" \
  $iamhost/idprovider/v1/auth/identitytoken | jq -r .access_token)

cp4atoken=$(curl -sk "$cp4ahost/v1/preauth/validateAuth" -H "username:$myuser" -H "iam-token: $iamaccesstoken" | jq -r .accessToken)
printf "CP4A token:\n\n$cp4atoken\n\n"

curl -skH "Authorization: Bearer $cp4atoken" "$cp4ahost/usermgmt/v1/users" | jq

Registering an IAM OIDC client

Flows other than ROPC require registration of an OIDC client at IAM. This is a one-time set up to prepare your client to obtain tokens. Keep the client_id and client_secret for reuse.

There are three approaches for registering an OIDC client at IAM documented at Authentication onboarding and single sign-on
Method 3 is the easiest - assuming you have oc level access:

oc apply -f - <<EOF
apiVersion: oidc.security.ibm.com/v1
kind: Client
metadata:
  name: sampleclient
  namespace: cp4ba
spec:
  secret: sampleclient-credentials
  oidcLibertyClient:
    post_logout_redirect_uris: []
    redirect_uris: []
    trusted_uri_prefixes: []
  introspect_tokens: true
  grant_types:
  - "client_credentials"
EOF


This command applies a YAML resource description directly from standard input. You could as well use the + button in the OCP console to create a new resource or apply it from file.
The expected output of this command is client.oidc.security.ibm.com/sampleclient created
The IAM operator watches resources of this kind and updates their status once they are processed. It will also pick up updates, of course. You can view the updated status using:

oc get client.oidc.security.ibm.com/sampleclient -o jsonpath='{.status}' -n cp4ba | jq

Consider lastTransitionTime to determine if your most recent change was processed in case of updates.

{
    "conditions": [{
            "lastTransitionTime": "2021-12-28T12:31:52Z",
            "message": "OIDC client registration successful",
            "reason": "CreateClientSuccessful",
            "status": "True",
            "type": "Ready"
        }
    ]
}


CLIENT_ID and CLIENT_SECRET of the newly created client are stored in the k8s secret specified in the resource sampleclient-credentials in the sample above. As in other kubernetes secrets, values are base64 encoded. the following commands will obtain the cleartext values into bash variables:

clientid=$(oc get secret sampleclient-credentials -o jsonpath='{.data}' -n cp4ba | jq .CLIENT_ID -r | base64 -d)
clientsecret=$(oc get secret sampleclient-credentials -o jsonpath='{.data}' -n cp4ba | jq .CLIENT_SECRET -r | base64 -d)

IAM as a standards based OIDC OP provides a regular registration endpoint, which can be obtained from its discovery endpoint:  https://cp-console.apps.jens.cp.fyre.ibm.com/oidc/endpoint/OP/registration in my environment.

iamhost=https://$(oc get route -n ibm-common-services cp-console -o jsonpath="{.spec.host}")
curl -sk $iamhost/idprovider/v1/auth/.well-known/openid-configuration | jq .registration_endpoint -r


Here is a full example for registering a client using cURL and storing client_id and client_secret in bash variables:

iamreguser="oauthadmin"
iamregpwd=$(oc get secret -n ibm-common-services ibm-iam-bindinfo-oauth-client-secret -o jsonpath="{.data.WLP_CLIENT_REGISTRATION_SECRET}" | base64 -d)
iamhost=https://$(oc get route -n ibm-common-services cp-console -o jsonpath="{.spec.host}")

iamregresponse=$(curl -sku  "$iamreguser:$iamregpwd"  "$iamhost/idauth/oidc/endpoint/OP/registration"  -X POST -H "Content-Type: application/json" -d @- <<EOF | jq
 {
   "scope": "openid",
   "preauthorized_scope": "openid",
   "response_types": [ "token" ],
   "grant_types": ["client_credentials"]
 }
 EOF
 )

clientid=$(echo $iamregresponse | jq .client_id -r)
clientsecret=$(echo $iamregresponse | jq .client_secret -r)


Note that in both cases, the registration is minimal - allowing some default flows only. You will need to extend this registration for using any of the other flows as explained below.

Client credentials flow

The client credential flow is useful in scenarios where a client application interacts with APIs in its own name. An OIDC client_id is used as a system account instead of calling as an existing user. As several capabilities in Cloud Pak for Automation rely on additional user information from a connected user repository (LDAP), this approach is only applicable to some APIs.

The prerequisite to obtaining a token for the Platform UI is "User Onboarding" (which deserves its own blog post). That is: adding a user record representing the client_id as a Platform UI user in to the Platform UI database and associate it with a role.
The sample below assumes the client was registered using the Client kind of custom resource and your client_id and client_secret are available in kubernetes secret sampleclient-credentials.

iamhost=https://$(oc get route -n ibm-common-services cp-console -o jsonpath="{.spec.host}")
cp4ahost=https://$(oc get route cpd -o jsonpath="{.spec.host}")
clientid=$(oc get secret sampleclient-credentials -o jsonpath='{.data}' -n cp4ba  | jq .CLIENT_ID -r | base64 -d)
iamadmin=$(oc get secret -n ibm-common-services platform-auth-idp-credentials -o jsonpath='{.data.admin_username}' | base64 -d)
iampass=$(oc get secret -n ibm-common-services platform-auth-idp-credentials -o jsonpath='{.data.admin_password}' | base64 -d)
iamaccesstoken=$(curl -sk -X POST -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -d "grant_type=password&username=$iamadmin&password=$iampass&scope=openid" $iamhost/idprovider/v1/auth/identitytoken | jq -r .access_token)

cp4atoken=$(curl -sk "$cp4ahost/v1/preauth/validateAuth" -H "username:$iamadmin" -H "iam-token: $iamaccesstoken" | jq -r .accessToken)

curl -sk -X POST \
  $cp4ahost/usermgmt/v1/user \
  -H "Authorization: Bearer $cp4atoken" \
  -H "Content-Type: application/json" \
  -d "{
    \"username\": \"$clientid\",
    \"displayName\": \"system-account-$clientid\",
    \"user_roles\": [
      \"zen_user_role\"
    ]
  }" | jq

{
  "uid": "1000331006",
  "_messageCode_": "success",
  "message": "success"
}


This is a one-time setup to make this client_id eligible for obtaining Platform UI tokens.

The script below assumes the client was registered using the Client kind of custom resource and your client_id and client_secret are available in kubernetes secret sampleclient-credentials.
An IAM token is obtained by POSTing client_id and client_secret to /idprovider/v1/auth/token along with grant_type=client_credentials.
The IAM token is then exchanged for a Platform UI token, which can be used for API invocations.

This script is an end-to-end example

  • Obtain an IAM token representing this client
  • Exchange the IAM token for a Platform UI token
  • Call a Platform UI user management REST API, passing the token for authentication

iamhost=https://$(oc get route -n ibm-common-services cp-console -o jsonpath="{.spec.host}")
cp4ahost=https://$(oc get route cpd -o jsonpath="{.spec.host}")
clientid=$(oc get secret sampleclient-credentials -o jsonpath='{.data}' -n cp4ba  | jq .CLIENT_ID -r | base64 -d)
clientsecret=$(oc get secret sampleclient-credentials -o jsonpath='{.data}' -n cp4ba  | jq .CLIENT_SECRET -r | base64 -d)

iamtokenresponse=$(curl -sk -X POST -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" \
  -d "grant_type=client_credentials&scope=openid&client_id=$clientid&client_secret=$clientsecret" \
  "$iamhost/idprovider/v1/auth/token" )

iamaccesstoken=$(echo $iamtokenresponse | jq -r .access_token)
printf "IAM token:\n\n$iamaccesstoken\n\n"

cp4atoken=$(curl -sk "$cp4ahost/v1/preauth/validateAuth" -H "username:$clientid" -H "iam-token: $iamaccesstoken" | jq -r .accessToken)
printf "CP4A token:\n\n$cp4atoken\n\n"
curl -skH "Authorization: Bearer $cp4atoken" "$cp4ahost/usermgmt/v1/users" | jq

{
  "users": [
    {
      "uid": "1000331006",
      "username": "wcqiq2csm9

Implicit flow

If your client allows end user interactions, such as a mobile app, web app or native fat client (eclipse), the authorization code flow can allow your users to authenticate in a browser (including redirects to upstream identity providers, multi-factor authentication, time and location based restrictions etc.) and obtain a token for use in your client app.

The overall set up includes

  1. OIDC client registration (one-time)
  2. Sending the user to IAM's authorize endpoint
  3. Receiving the callback including an access_token
  4. Obtain username (either prompt the user or by calling userinfo)
  5. Obtain CP4A token
  6. Call API
During OIDC client registration the grant_type "implicit" must be listed to use the implicit flow. In order to obtain the callback including an access_token you specify a URL of your web application (containing JavaScript to parse the URL bar) or you register a custom protocol to trigger any other native app on the operating system. For this demo, sample-app:// will point to a sample Java program.
iamreguser="oauthadmin"
iamregpwd=$(oc get secret -n ibm-common-services ibm-iam-bindinfo-oauth-client-secret -o jsonpath="{.data.WLP_CLIENT_REGISTRATION_SECRET}" | base64 -d)
iamhost=https://$(oc get route -n ibm-common-services cp-console -o jsonpath="{.spec.host}")
curl -sku  "$iamreguser:$iamregpwd"  "$iamhost/idauth/oidc/endpoint/OP/registration"  -X POST -H "Content-Type: application/json" -d @- <<EOF | jq
{ "scope": "openid", 
  "preauthorized_scope": "openid", 
  "redirect_uris": [ "sample-app://authenticate"], 
  "grant_types": ["authorization_code", "client_credentials", "implicit"], 
  "response_types": ["code", "token"] 
}
EOF

The user flow is triggered by sending the user to IAM's authorize endpoint. Just open the default browser with a URL pattern 
https://$iamhost/idprovider/v1/auth/authorize?client_id=$clientid&response_type=token&scope=openid&redirect_uri=sample-app://authenticate&state=$state such as 
https://cp-console.apps.jens.cp.fyre.ibm.com/idprovider/v1/auth/authorize?client_id=d99a3420280b4ea3bb3de788fb9ba5e1&response_type=token&scope=openid&redirect_uri=sample-app://authenticate&state=3rZXHPIgi13pAKo
state is a random token that will be provided with the callback - helping the client application to verify that it had actually requested authentication and to find state that was available at the time of sending the user to IAM for authentication.

After authentication completed in the browser, the final redirect will point to the requested (and registered) redirect_uri, in this example:
sample-app://authenticate/#scope=openid&access_token=YPoEp7bTmFpGuJhXrhvZnBBB8fv9B8ErTm9F8Jh6Wwu0IJaBvkATZAWSR2s72h96V2xvBMy5qmN7Zx1Xd6BcARQUyOyhxml4F9XnnwAszpT99diR8sRyKayhShdD8e1cGk6ewovtbqu7I2MmBk3KU2et7zm50axj4oXeFfaH6QZlapDJ3UTKAL2rW9QKSdxEZZzOMr1CuudQKoDwCQXinhQ8CaRHXtQbleDSR26nX6lvt3pNPHAKEbVrExihuKts25m3mhhNz01mWXDTMZOJ6MD5k5Syww3kKmJMIZCGxRugiVY8JRl6kJatqrlBaDrRIoznh0QO3pxI9Gk7urREdDdDUBGPO8dxW6oAd2eMUyAMb8sjaWAb5kgszPZx7IvsBH4HXxv1kklBXAqVSRwZj5mP4A5FNKhSyAgjN2cBRSuOuzbhKazyc08BMLHlTwoWL7fbrLHO7p8ucTHvyIfZirBKONsGGkqcX5QlrSIKomWR8eSmVbn7&token_type=Bearer&expires_in=43199&state=3rZXHPIgi13pAKo

The client app (triggered by sample-app:// or a server when using https://) can parse the URL fragment to verify the value of the state parameter and to obtain the IAM access_token.

Exchanging the IAM access_token for a Platform UI token requires providing the IAM token and the matching username. Your client app may either prompt the user for this username or invoke IAM's introspect API:

curl -skH "Authorization: Bearer $iamtoken" $iamhost/idprovider/v1/auth/userinfo | jq

The username will be returned in the sub field.

The API is for obtaining the CP4A token is the same in the client_credentials flow:

cp4atoken=$(curl -sk "$cp4ahost/v1/preauth/validateAuth" -H "username:$username" -H "iam-token: $iamaccesstoken" | jq -r .accessToken)

It can be used to invoke APIs:

curl -skH "Authorization: Bearer $cp4atoken" "$cp4ahost/usermgmt/v1/users" | jq



0 comments
150 views

Permalink