In this post I want to examine the use case of needing to authenticate a service account into a SAS Viya environment. Specifically, we’ll examine how an external process like a CI/CD pipeline might authenticate a non-personal service account to the SAS Viya environment to be able to run code in that environment.
Use Case
We’ve previously discussed the concept of authenticating using a custom application. However, as we identified in that post, we still need to provide a credential set to be able to launch a SAS Compute Server instance. So, what are our options if we want to build a pipeline that will at some point run SAS code where we eliminate or minimize user interaction.
The specific use case that prompted me to write this post is where a CI/CD pipeline will deploy SAS Viya, configure SCIM as the identity provider, configure OIDC authentication, and then run SAS code. This pipeline should be able to complete all steps automatically without any user interaction.
Select any image to see a larger version. Mobile users: To view the images, select the "Full" version at the bottom of the page.
We know that if our SAS Viya environment is configured with SCIM and OIDC the standard approach to authenticate a user will be within the browser. Even when using the SAS Viya CLI we would need to use the auth loginCode option to allow us to interactively authenticate in the browser.
Our first option to deal with the CI/CD pipeline builds upon this concept. We’d split the single pipeline into two phases. In the first phase the SAS Viya environment is deployed, and then SCIM and OIDC are configured. In the second phase any SAS code is run. In between the two phases we complete some manual interactive authentication. This authentication step results in making a Refresh Token available to use for the second phase of the CI/CD pipeline.
Our second option does not require us to split the CI/CD pipeline into two parts. Instead, the second option requires that we can authenticate to the third-party OIDC provider automatically using the service account. This might be by extracting the password for the service account from a vault and using that to authenticate to the OIDC provider. We’ll then use the Access Token from the third-party OIDC provider to authenticate to SAS Logon Manager in SAS Viya.
In the rest of this post, we’ll use Microsoft Entra ID as the SCIM client and OIDC provider. However, the same concepts will apply to other providers as well.
Option 1: Leveraging the Refresh Token
Once the SAS Viya environment is deployed, we can interactively authenticate to SAS Logon Manager and store the returned Refresh Token. Then we can use the Refresh Token to obtain a new Access Token. However, at this point the Refresh Token that is returned with the Access Token is the same as the one we used to gain the Access Token. As such, we then need to use the Access Token with the JWT Bearer Token grant to obtain a new Access Token and Refresh Token. This new Refresh Token will be valid for the standard refresh lifetime of 14 days. This process can then be repeated multiple times, so long as the Refresh Token has not expired or been revoked.
We need to use the JWT Bearer Token grant, and for this we will need to know both the client_id and client_secret. Therefore, we will need to create a custom application registered with SAS Logon Manager. To register the custom application with SAS Logon Manager we can either complete the steps with the SAS Viya CLI or using the REST API.
For example, to create a custom application with the REST API we could use the following series of CURL commands. First, we obtain a client registration token:
BEARER_TOKEN=$(curl -skX POST "${INGRESS_URL}/SASLogon/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=sas.cli&grant_type=password&username=sasboot&password=${myPass}" |jq -r ."access_token")
Here we are using the sasboot user since our assumption is that our environment is configured with SCIM as the identity provider, and we cannot authenticate as a member of SAS Administrators using the password grant type. Also we will have set an initial password for the sasboot user with the sitedefault.yaml.
Next, we register the custom application using the BEARER_TOKEN we just obtained:
curl -sk -X POST "${INGRESS_URL}/SASLogon/oauth/clients" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BEARER_TOKEN" \
-d '{
"client_id": "customapp.jwt",
"client_secret": "clientsecret",
"scope": ["openid","uaa.user"], "autoapprove": ["openid","uaa.user"],
"authorities": ["uaa.none"],
"authorized_grant_types": ["urn:ietf:params:oauth:grant-type:jwt-bearer","refresh_token"],
"refresh_token_validity": 7776000
}'
You would use a much more complex client_secret than the example here of clientsecret. Notice that this custom application only supports the JWT Bearer Token grant type and refresh tokens. As such it will only accept existing Access Tokens and Refresh Tokens for authentication and nothing else.
We can then use the SAS Viya CLI, to authenticate interactively in the browser for the user we will want to run code in our CI/CD pipeline. Which means using:
/opt/sas/viya/home/bin/sas-viya auth loginCode
Then fetch the current Refresh Token from the local cache used by the SAS Viya CLI with the following:
MY_REFRESH=$(cat .sas/credentials.json |jq -r '.Default."refresh-token"')
This Refresh Token can then be used with the SAS Logon Manager REST API to obtain a new Access Token:
MY_Request=$(curl -sk -X POST "${INGRESS_URL}/SASLogon/oauth/token" \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "client_id=sas.cli" \
--data-urlencode "grant_type=refresh_token" \
--data-urlencode "response_type=token" \
--data-urlencode "scope=openid" \
--data-urlencode "refresh_token=${MY_REFRESH}")
MY_ACCESS_TOKEN1=$(echo $MY_Request|jq -r '."access_token"')
MY_REFRESH_Expiry=$(echo $MY_Request|jq -r '."refresh_expires_in"')
If you look at the expiry of the Refresh Token returned in this request, you’ll notice that it does not change from the original Refresh Token obtained with the SAS Viya CLI. So, here we use the custom application we registered to obtain another new Access Token and Refresh Token using the JWT Bearer Token grant.
curl -sk -X POST "${INGRESS_URL}/SASLogon/oauth/token" \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "client_id=customapp.jwt" \
--data-urlencode "client_secret=clientsecret" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
--data-urlencode "response_type=token" \
--data-urlencode "scope=openid" \
--data-urlencode "assertion=${MY_ACCESS_TOKEN1}" \
|jq -r
From this request you’ll notice the refresh_expires_in value has re-set to the system default, normally 14 days, or the value used when we registered the client.
Moving forward with this new Refresh Token it can be used to request a new Access Token as we did above. However, the Refresh Token is tied to the client_id we used on the token request. This is why we also registered the refresh_token grant type against our custom application. Therefore, on future request using the new Refresh Token we’d change the MY_Request code above to use client_id=customapp.jwt and it's associated client_secret.
That Refresh Token could then be stashed in a vault that is accessible to our CI/CD pipeline. Then as long as the REST API calls to re-new the Refresh Token are run every two weeks; we can continue to leverage the Refresh Token to gain access to the SAS Viya environment.
Option 2: Leveraging External OIDC Authentication
As discussed above our second option of leveraging external OIDC authentication means that we don’t need to split the CI/CD pipeline into two parts. The pipeline job can move seamlessly from deployment to configuration and onto running SAS code. However, it is dependent on being able to authenticate to the third-party OIDC provider automatically using the service account.
Again, this option will be dependent on leveraging the JWT Bearer Token grant. But this time we will be presenting an Access Token obtained from the third-party OIDC provider. So again, we need to register a custom application with SAS Logon Manager. To register the custom application with SAS Logon Manager we can either complete the steps with the SAS Viya CLI or using the REST API.
For example, to create a custom application with the REST API we could use the following series of CURL commands. First, we obtain a client registration token:
BEARER_TOKEN=$(curl -skX POST "${INGRESS_URL}/SASLogon/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=sas.cli&grant_type=password&username=sasboot&password=${myPass}" |jq -r ."access_token")
Here we are using the sasboot user since our assumption is that our environment is configured with SCIM as the identity provider, and we cannot authenticate as a member of SAS Administrators using the password grant type.
Next, we register the custom application using the BEARER_TOKEN we just obtained:
curl -sk -X POST "${INGRESS_URL}/SASLogon/oauth/clients" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BEARER_TOKEN" \
-d '{
"client_id": "customapp.extjwt",
"client_secret": "clientsecret",
"scope": ["openid","uaa.user"],
"authorities": ["uaa.none"],
"authorized_grant_types": ["urn:ietf:params:oauth:grant-type:jwt-bearer"],
"refresh_token_validity": 7776000
}'
You would use a much more complex client_secret than the example here of clientsecret. Notice that this custom application only supports the JWT Bearer Token grant type. As such it will only accept existing Access Tokens for authentication and nothing else. Each time we want to use this custom application we will separately authenticate to the third-party OIDC provider.
The part to authenticate our service account against the third-party OIDC provider will be specific to the provider. Here we will consider Microsoft Entra ID. Microsoft provides the Azure CLI which allows us to authenticate to Microsoft Entra ID. However, the basic authentication using the Azure CLI will only generate us a version 1 Access Token and we need to have a version 2 Access Token to use that to authenticate to SAS Viya.
To be able to request a version 2 Access Token with the Azure CLI we need to request a token using the command az account get-access-token and specify a scope. This means that we need to have an Application Registration in Microsoft Entra ID where we can change attributes as necessary. We should use the Application Registration that exists for the OIDC integration with Microsoft Entra ID, as this will ensure the audience (aud) will be set to the correct application.
In the Azure Portal, as a user with the ability to make changes to the Application Registration we need to complete the following steps:
Expose a scope in the web API registration – covered in the Microsoft documentation
From the App registration page in the Azure Portal expand Manage and select Expose an API
Select Add for the Application ID URI, you can accept the default value or specify your own unique URI and select Save
Select Add a scope, specify something like CLI_login as name, CLI Login as Admin consent display name and description, then select Add scope
Select Add a client application, enter 04b07795-8ddb-461a-bbee-02f9e1bf7b46 as Client ID, select CLI_login as authorized scopes and select Add application – this is the client ID for the Azure CLI
Ensure any required claims are included in the Access Token
From the App registration page in the Azure Portal expand Manage and select Token configuration
Select Add optional claim
Select Access as Token type
Select the additional claims, ensure you have selected the claim you are using as the userID in SAS Viya
Select Add
Update the manifest and set accessTokenAcceptedVersion to 2
From the App registration page in the Azure Portal expand Manage and select Manifest
Scroll down to the “api” section and update the value of requestedAccessTokenVersion from null to 2
Select Save
With these steps complete we can then obtain an Access Token from Microsoft Entra ID with the following Azure CLI commands:
az login --user serviceaccount@company.com --password ${myPass}
AZ_TOKEN=$(az account get-access-token --scope api://<UNIQUE_URI>/CLI_login |jq -r ."accessToken")
That Microsoft Entra ID Access Token can then be used with the SAS Logon Manager API to obtain the SAS Viya Access Token.
curl -sk -X POST "${INGRESS_URL}/SASLogon/oauth/token" \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "client_id=customapp.extjwt" \
--data-urlencode "client_secret=clientsecret" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
--data-urlencode "response_type=token+id_token" \
--data-urlencode "scope=openid" \
--data-urlencode "assertion=${AZ_TOKEN}" \
|jq -r
The returned SAS Viya Access Token can then be used by the CI/CD pipeline to run SAS Code. Notice with this option we are not relying on Refresh Tokens; the only long-term item would be the credentials for the service account with Microsoft Entra ID. Each time the pipeline runs those credentials are pulled into the pipeline and the two Access Tokens generated.
Conclusion
Here we have examined two approaches for authenticating to SAS Viya as a service account, when it’s not appropriate to use a custom application to run the code. We’ve illustrated how we can leverage the JWT Bearer Token grant and a custom application registration to either renew an Access Token and Refresh Token. Or move from an external Access Token to a SAS Viya Access Token using the JWT Bearer Token grant.
Find more articles from SAS Global Enablement and Learning here.
... View more