Creating a custom client or application of SAS Logon Manager is normally used to allow end-users to authenticate with the new application and perform actions. This could be a custom script or your own application you are integrating with SAS Viya. In this blog I want to explore what can be done with the custom client application of SAS Logon Manager itself. So, in this case we will be using the client_credentials grant rather than the authorization_code or password grant types. This is not the standard approach. The standard approach would be to ensure all end-users and service accounts are correctly configured in your Identity and Authentication provider. These end-user and service accounts would then be the parties that run code or processes. However, in this case it will be the custom application that runs code or process outside the context of any specific user and often (but not always) outside of any user request.
A Reminder: Grant Types
SAS Logon Manager supports authorization_code, client_credentials, refresh_token, and password as valid grant_types. The grant_type defines the process that will be used to obtain an Access Token from SAS Logon Manager. The authorization_code grant requires the end-user to authenticate in their browser and return the code to the client to then exchange for the Access Token for the end-user. Examples of this flow are the standard login to the SAS Viya web applications and the SAS Viya CLI with the command auth loginCode. The client_credentials grant obtains an Access Token just for the custom application and not for the end-user, it uses the client_secret registered with SAS Logon Manager when the custom application was defined. The refresh_token grant obtains the Access Token by presenting an existing valid Refresh Token. Then the password grant uses the end-user username and password to authenticate to SAS Logon Manager and obtain the Access Token for the end-user. Remember the password grant is only applicable if an LDAP Provider is configured for your SAS Viya environment.
Use Cases
Before we dive into further details it is useful to understand some use cases when operating as the custom application using the client_credentials grant is appropriate or required. The first use case is specific to the internal services within the SAS Viya environment. As the different services and applications that make up the SAS Viya environment bootstrap, they need to create authorization rules, define their configurations etc. Internal SAS Viya services and applications during bootstraping leverage the client_credentials grant to allow them to complete this process. Another use case is application-to-application interfaces such as SCIM. The SCIM client, perhaps Azure Active Directory, needs to authenticate to SAS Viya to provision the users and groups. The SCIM client presents an Access Token to authenticate this connection.
Other use cases are more specific to your own usage of the SAS Viya environment. Perhaps you have an on-prem SAS 9.4 environment that needs to connect to SAS Cloud Analytic Services as part of an ETL process. The ETL process is part of your standard batch processing, and a corresponding end-user does not exist in your cloud-based SAS Viya environment. As such you want to configure a custom application on the SAS Viya side and authenticate as this for the connection to SAS Cloud Analytic Services.
Alternatively, you might have an external application that presents generalized modelling results. You want to be able to regularly connect to SAS Viya run a model in SAS Cloud Analytic Services and return the results to the external application. Since this is a regular process that runs unattended you do not want to run this in the context of a specific end-user. So again, you want to run this as a custom application registered with SAS Logon Manager.
Authentication and Authorization
Using the client_credentials grant results in an Access Token for the custom application and not an end-user. Once an Access Token has been obtained SAS Viya at the most basic level does not distinguish between how the Access Token was obtained. Access Tokens obtained with client_credentials do not contain any user claims, so the client ID in the Access Token will be accepted as the authenticated principal name. This is for all practical purposes treated like a username. This means that any connection providing a valid Access Token will be as an "Authenticated User". Most SAS Viya APIs do not distinguish between user and client Access Tokens. However, some services will inspect the Access Token itself to see if it is an end-user or a custom application that is connecting and presenting the Access Token. These services will inspect the Access Token looking for user specific claims which are not present when the Access Token is obtained using the client_credentials grant.
Authorization requires one of the following:
A rule granting access to the endpoint to "Everyone".
A rule granting access to the endpoint for all "Authenticated Users", where "users" in this context includes custom applications.
A rule granting access to a User principal where the principal name is the client ID.
A rule granting access to a Group principal where the custom application is registered with the Group in its list of authorities.
Within SAS Environment Manager only the Rules page will allow you to type the client ID to define authorization rules that will apply to your custom application. In other authorization screens, such as CAS authorization, or Folder’s authorization, you are only able to select Users, Groups, and Custom Groups as provided by the Identities microservice. Therefore, it is not possible to assign direct permissions to your ClientID to say a folder or a caslib. Instead, you need to create a Custom Group for your custom applications and assign authorization information to the Custom Group. For the examples in this blog, we will create a Custom Group with the name and ID of CustomApps. Then we can apply authorization information to the CustomApps group, for example allowing access to the Public folder:
Select any image to see a larger version. Mobile users: To view the images, select the "Full" version at the bottom of the page.
Adding a Custom Application to a Custom Group
Adding a custom group to a custom application is very straightforward. When the custom application is registered, the authorities parameter defines a list of the groups that will be present in the Access Token obtained when using the client_credentials grant. So, all you need to do is include the group ID for your custom group in the authorities parameter when you register your custom application.
Registering an Example Custom Client
Now we can create our custom client application to SAS Logon Manager. There are two ways to register the custom application, you can either use the SAS Viya CLI or you can manually register the custom application by calling the SAS Logon Manager APIs. Here we will demonstrate manually registering the custom application. To register the custom application, you need to obtain an Access Token from SAS Logon Manager as a member of the SAS Administrators group. For example, the following could be used to obtain a token for the sasadm user, who is a member of the SAS Administrators group:
export BEARER_TOKEN=`curl -sk -X POST \
"${INGRESS_URL}/SASLogon/oauth/token" \
-u "sas.cli:" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'grant_type=password&username=sasadm&password={{myPassword}}' | awk -F: '{print $2}'|awk -F\" '{print $2}'`
Where you replace {{myPassword}} with the password for the sasadm user. The same could be used for any other member of SAS Administrators if you have an LDAP Provider defined. Otherwise, if your environment is configured for SCIM you could use the SAS Viya CLI to obtain the initial Access Token using the auth loginCode command. You could then extract the Access Token from the credentials.json file.
This token will then enable you to register your custom application:
curl -k -X POST "${INGRESS_URL}/SASLogon/oauth/clients" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BEARER_TOKEN" \
-d '{
"client_id": "cust1.app",
"client_secret": "myclientsecret",
"scope": ["uaa.none"],
"authorities": ["CustomApps"],
"authorized_grant_types": ["client_credentials"],
"access_token_validity": 900
}'
Note: You should specify a complex random string as the client_secret, a simple value is shown here to make the examples easy to read.
You should see something like the following for a successful response, we have split this output onto multiple lines to make it easier to read:
{"scope":["uaa.none"],
"client_id":"cust1.app",
"resource_ids":["none"],
"authorized_grant_types":["client_credentials"],
"autoapprove":[],
"access_token_validity":900,
"authorities":["CustomApps"],
"lastModified":1673609094057,
"required_user_groups":[]}
Notice that the authorized_grant_types only include client_credentials; this means that this client will never be able to obtain an Access Token for an end-user. Since this custom application will never obtain credentials for an end-user we have also set the scope to uaa.none. In addition, for our example the access_token_validity has been set to 900, which is 10 minutes, so we will need to reauthenticate every 10 minutes. You can define the access_token_validity to whatever value makes sense for your custom application, the value is the number of seconds that the Access Token will be valid for.
When using the client_credentials grant the authorities will represent the custom group, so our custom application will be in the CustomApps custom group.
Updating an Example Custom Client
If you need to change the details of the custom client, perhaps to change the client secret or to change the access_token_validity you can again use the SAS Logon Manager API. As with registering you will need to have an Access Token as a member of SAS Administrators. For example, to update the client secret of our already registered custom application we would use the following command:
curl -k -X PUT "${INGRESS_URL}/SASLogon/oauth/clients/cust1.app" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BEARER_TOKEN" \
-d '{
"client_id": "cust1.app",
"client_secret": "myclientsecret1234",
"scope": ["uaa.none"],
"authorities": ["CustomApps"],
"authorized_grant_types": ["client_credentials"],
"access_token_validity": 900
}'
You should see something like the following for a successful response, we have split this output onto multiple lines to make it easier to read:
{"scope":["uaa.none"],
"client_id":"cust2.app",
"resource_ids":["none"],
"authorized_grant_types":["client_credentials"],
"autoapprove":[],
"access_token_validity":900,
"authorities":["CustomApps"],
"lastModified":1673610132049,
"required_user_groups":[]}
Deleting/Unregistering an Example Custom Client
Finally, if you want to remove the registration for your custom application you would use the SAS Logon Manager API again. As with registering you will need to have an Access Token as a member of SAS Administrators. For example, to delete our custom application we would use the following command:
curl -k -X DELETE "${INGRESS_URL}/SASLogon/oauth/clients/cust1.app" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BEARER_TOKEN"
You should see something like the following for a successful response, we have split this output onto multiple lines to make it easier to read:
{"scope":["uaa.none"],
"client_id":"cust2.app",
"resource_ids":["none"],
"authorized_grant_types":["client_credentials"],
"autoapprove":[],
"access_token_validity":900,
"authorities":["CustomApps"],
"lastModified":1673610132049,
"required_user_groups":[]}
Then use the following command to check there is no output:
curl -k "${INGRESS_URL}/SASLogon/oauth/clients/cust1.app" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BEARER_TOKEN"
Custom Applications and Credentials Microservice
The Credentials microservice inspects the Access Token when deciding if the secret that is stored can be accessed. When the Access Token has the grant_type of client_credentials the Credentials microservice identifies the caller as a client and not an end-user, since there are no user scopes within the Access Token. The credentials microservice uses three types of identity:
Identity Type = User
Identity Type = Group
Identity Type = Client
Within SAS Environment Manager credentials can be managed for Users and Group. However, credentials for Clients cannot be managed in SAS Environment Manager. Therefore, it makes sense to register the custom application as a member of a custom group so that credentials can be easily managed for that custom application. These credentials could be a user and password in a Password Authentication Domain, a token credential in a Token Authentication Domain, or an encryption key in an Encryption Domain.
If we store credentials in either a Password Authentication Domain or a Token Authentication Domain, these can be used by the Launcher microservice when launching a SAS Compute Server pod. So, the SAS Compute Server pod will run with this credential. Remember, by default the Launcher microservice will look in the DefaultAuth Password Authentication Domain. But this can be changed by editing the sas.compute.domain.default property. If we don’t store a credential, then the Launcher microservice will prevent the SAS Compute Server pod from being launched.
You can then validate that the ClientID has access to the stored credential by performing a GET request on /credentials/domains/{domainID}/secrets and presenting the Access Token for the custom application as the Bearer Token. If this returns the credentials, then it is setup correctly.
Client Credentials and SAS Cloud Analytic Services
SAS Cloud Analytic Services inspects the Access Token when launching the CAS session. Since the Access Token contains grant_type=client_credentials SAS Cloud Analytic Services recognises that this is a client application and with TRACE logging will display the messages:
DEBUG 2022-12-02 12:02:04.736 +0000 [cas-shared-default] - REST authentication request
DEBUG 2022-12-02 12:02:04.736 +0000 [cas-shared-default] - oauthAuthenticate(): Validating token.
DEBUG 2022-12-02 12:02:04.737 +0000 [cas-shared-default] - Service request: cust1.app
TRACE 2022-12-02 12:02:04.737 +0000 [cas-shared-default] - Oauth token zid: uaa
TRACE 2022-12-02 12:02:04.737 +0000 [cas-shared-default] - Identity is service account
As such, the CAS session will always run as the CAS service account. The session can never be host-launched and credentials in the authentication domain given by the sas.compute.domain.default property cannot be used to launch the CAS session. Once running, the CAS session can be leverage both authentication and encryption domains to access data.
Client Credentials and Launcher Service
The Launcher service will launch SAS Compute pods for the custom client. The Launcher service recognises that the Identities microservice will not generate a UID and GID for the grant_type=client_credentials. As such by default the Launcher will look for the reserved sasapp in the authorities, which means that the client is part of the SAS Viya environment. If it is part of the SAS Viya environment the SAS Compute Server pod runs with the default user, which is sas. Otherwise, for custom applications that do not have the reserved sasapp in the authorities, the Launcher service attempts to look-up credentials in the authentication domain given by the sas.compute.domain.default property for launching the SAS Compute Server pod. If there is no stored credential launching the SAS Compute Server pod will fail.
Therefore, if you associate a credential with the Custom Group your custom application is a member of, the Launcher service will use those to launch the SAS Compute Server pod. This can be validated by examining the debug logging for the Launcher service, which for example will show:
DEBUG 2022-12-01 16:03:28.519 +0000 [launcher] - Getting credential supplier: credentialType=USER_LAUNCH [0:USER_LAUNCH]
DEBUG 2022-12-01 16:03:28.519 +0000 [launcher] - Checking if host credential is required: user=cust1.app operatingSystem=linux [0:cust1.app 1:linux]
DEBUG 2022-12-01 16:03:28.519 +0000 [launcher] - Host credential not required: user=cust1.app operatingSystem=linux reason=["Operating system is linux.", "sas.compute.kerberos.enabled property is set to false."] [0:cust1.app 1:linux 2:["Operating system is linux.", "sas.compute.kerberos.enabled property is set to false."]]
DEBUG 2022-12-01 16:03:28.519 +0000 [launcher] - Found authentication domain: user=cust1.app authenticationDomain=DefaultAuth [0:cust1.app 1:DefaultAuth]
DEBUG 2022-12-01 16:03:28.519 +0000 [launcher] - Searching for OAuth2 credential: user=cust1.app authenticationDomain=DefaultAuth [0:cust1.app 1:DefaultAuth]
DEBUG 2022-12-01 16:03:28.559 +0000 [launcher] - Found stored credential: user=cust1.app authenticationDomain=DefaultAuth [0:cust1.app 1:DefaultAuth]
DEBUG 2022-12-01 16:03:28.559 +0000 [launcher] - Using password credential: user=cust1.app operatingSystem=linux credentialUserId=sastest1 [0:cust1.app 1:linux 2:sastest1]
DEBUG 2022-12-01 16:03:28.559 +0000 [launcher] - Assigned value clientToken from a UserPassCredential
WARN 2022-12-01 16:03:29.015 +0000 [launcher] - #{...} syntax is not required for this run-time expression and is deprecated in favor of a simple expression string
WARN 2022-12-01 16:03:29.095 +0000 [launcher] - #{...} syntax is not required for this run-time expression and is deprecated in favor of a simple expression string
DEBUG 2022-12-01 16:03:29.128 +0000 [launcher] - Is Workload orchestrator enabled?: false [0:false]
DEBUG 2022-12-01 16:03:29.129 +0000 [launcher] - User sastest1 currently owns 0 processes in a non-terminal state [0:sastest1 1:0]
DEBUG 2022-12-01 16:03:29.129 +0000 [launcher] - User sastest1 is a non-super user and has a limit of 10: enabled=true [0:sastest1 1:10 2:true]
DEBUG 2022-12-01 16:03:29.137 +0000 [launcher] - Using podTemplate sas-compute-job-config in namespace gelenv [0:sas-compute-job-config 1:gelenv]
DEBUG 2022-12-01 16:03:29.138 +0000 [launcher] - Requested command in the launch request unwrapped: /opt/sas/viya/home/bin/compsrv_start.sh [0:/opt/sas/viya/home/bin/compsrv_start.sh]
DEBUG 2022-12-01 16:03:29.138 +0000 [launcher] - The allow list with the replaceable parameters replaced: [/opt/sas/viya/home/bin/compsrv_start.sh] [0:[/opt/sas/viya/home/bin/compsrv_start.sh]]
This shows the cust1.app custom client connecting and then fetching the credentials for the sastest1 user from the Credentials microservice.
Example Custom Client: Using Job Execution Service
Now that we have the following:
A Custom Group defined
An authorization rule allowing the Custom Group access to the Public folder
A custom client defined with the scope linking the Custom Group
We can attempt to create a folder inside the Public folder, create a job definition inside the new folder, submit the job definition to the Job Execution service, check the job execution status, and fetch the resulting job execution files. To perform this test, I used Postman and the sample provided by Joe Furbee available here: https://github.com/sassoftware/rest-api-use-cases/tree/main/postman/job-execution. This is an end-to-end example for Job Execution and interacts with the following endpoints:
/folders/folders
/jobDefinitions/definitions
/jobExecution/jobs
/jobDefinitions/definitions/{{job_ID}}
/jobExecution/jobs/{{execution_ID}}/state
/jobExecution/jobs/{{execution_ID}}
/{{files_location}}/content
After authenticating with the client_ID and client_secret I was able to run Joe’s example without any further changes. The custom client, because it had access to the Public folder, was able to create and submit the job to SAS Viya.
Example Custom Client: Accessing CAS
Next, I wanted to see if the custom client would be able to interact with SAS Cloud Analytic Services using the REST API. For this use-case I wanted to generate a CAS session, load a table from the Samples global CASLIB and then fetch the first 10 rows from that table. The authorization for the Samples CASLIB was not changed from the default which is:
Again, I used Postman to interact with the CAS REST API, using the following endpoints:
/cas-shared-default-http/cas/sessions – to start a session
/cas-shared-default-http/cas/sessions/{{cas_session}}/actions/table.loadTable – to issue the loadTable action
/cas-shared-default-http/cas/sessions/{{cas_session}}/actions/table.fetch – to fetch the first 10 rows
/cas-shared-default-http/cas/sessions/{{cas_session}} – to terminate the session
Information from the documentation was used to construct the CAS REST API calls.
After authenticating with the client_ID and client_secret I was able run this flow successfully and return 10 rows of data from SAS Cloud Analytic Services to the REST API client.
Example Custom Client: Running SAS Code
Finally, I wanted to test running a SAS Compute Server pod and submitting code to it via the SAS Compute REST API. The documentation for the SAS Compute REST API was used to build out this test using Postman. This test leveraged the following endpoints:
/compute/contexts – to find the ID for the SAS Studio Compute Context
/compute/contexts/{{contextId}}/sessions – to launch a SAS Compute Server session
/compute/sessions/{{computeId}}/jobs – to submit a job to the SAS Compute Server session
/compute/sessions/{{computeId}}/jobs/{{jobId}}/state – to check the status of the submitted job
/compute/sessions/{{computeId}}/jobs/{{jobId}}/listing – to fetch the listing from the completed job
/compute/sessions/{{computeId}}/jobs/{{jobId}}/log – to fetch the log from the completed job
/compute/sessions/{{computeId}} – to terminate the SAS Compute Server session
After authenticating with the client_ID and client_secret I was able to complete this test. Running through this example a SAS Compute Server pod was launched, since credentials were stored for the custom application, these were used to launch the pod. Then some simple code to do a PROC PRINT, launch a CAS Session, report on the CAS nodes, and terminate the CAS session was run within the SAS Compute Server session. All this code completed successfully and then the listing and log output was fetched back to Postman.
Conclusion
In this blog, we have explored a possible option for some use-cases when it is impossible to configure service accounts in your Identity and Authentication providers. This can assist in uses-cases where some Viya related jobs/tasks are required to be automated or where a real user credential cannot be used. The expected scenario is that processes are run within your SAS Viya environment as real users; these users could be your actual end-users, or they could be service accounts correctly configured in your Identity and Authentication providers. It is only this scenario that is fully tested with all the SAS Viya applications. What we have explored here is a possible option for some use-cases when it is impossible to configure service accounts in your Identity and Authentication providers.
If you want to explore this topic in more detail, you can refer to Course: Advanced Topics in Authentication on SAS Viya.
Find more articles from SAS Global Enablement and Learning here.
... View more