Welcome back! This second post serves as a follow-up to our previous post, where we initiated discussions on DevOps processes with SAS Viya CLI container images, available here, Introducing New SAS Viya CLI container image in Azure DevOps (March 2024 Update). In this two-part blog, we delve deeper into security customizations, addressing aspects such as managing refresh tokens, handling certificates not chained to public Certificate Authorities (CAs), and enhancing security measures within Azure Pipelines and Kubernetes clusters. These enhancements build upon our previous discussions, aiming to fortify your DevOps pipelines against security vulnerabilities while ensuring seamless execution. Let's explore these advancements in detail.
Here's an illustrative diagram presenting the functional flow involved.
Access tokens play a pivotal role in facilitating secure communication between various components. However, the limitations of access tokens, such as their short lifespan of 1 hour (default for the SAS Viya) and irreversibility, often pose challenges. .
Here is a link from my colleague Stuart Rogers that explain the SAS Viya CLI Token Expiry (additional note, starting with 2023.11 the default refresh token expiration is 14 days).
Unlike access tokens, refresh tokens have a longer validity period of 14 days and are revocable, providing better control and flexibility in managing authentication within DevOps pipelines.
In this example, we will show you how to consume the SAS Refresh Token (retrieved from your Azure Key Vault) while running your Azure pipeline.
This Azure Pipeline parameter allows you to specify whether you want to use the refresh token during pipeline execution, providing flexibility in authentication.
- name: USE_REFRESH_TOKEN
displayName: Do you want to use the refresh token (valid 14 days)?
type: boolean
default: true
Enhance the step titled "Get JSON Web Token from Azure Key Vault" to extract the refresh token and its expiry date:
- task: AzureCLI@2
name: TgetJWTfromAKV
displayName: Get JSON Web Token from Azure Key Vault
inputs:
azureSubscription: '$(KV_SERVICE_CONNECTION)'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az keyvault secret download \
--name ${{ parameters.AZURE_KEYVAULT_KEY }} \
--vault-name ${{ parameters.AZURE_KEYVAULT }} \
--file $PWD/credentials.json
export JWT_TOKEN=$(cat $PWD/credentials.json | jq -r '.Default."access-token"')
export REFRESH_TOKEN=$(cat $PWD/credentials.json | jq -r '.Default."refresh-token"')
export EXPIRY=$(cat $PWD/credentials.json | jq -r '.Default."expiry"')
echo "##vso[task.setvariable variable=JWT_TOKEN;isOutput=true]$JWT_TOKEN"
echo "##vso[task.setvariable variable=REFRESH_TOKEN;isOutput=true]$REFRESH_TOKEN"
echo "##vso[task.setvariable variable=EXPIRY;isOutput=true]$EXPIRY"
Add the variable to the context of the container job:
variables:
- name: JWT_TOKEN
value: $[ dependencies.getJWTfromAKV.outputs['TgetJWTfromAKV.JWT_TOKEN'] ]
- name: REFRESH_TOKEN
value: $[ dependencies.getJWTfromAKV.outputs['TgetJWTfromAKV.REFRESH_TOKEN'] ]
- name: EXPIRY
value: $[ dependencies.getJWTfromAKV.outputs['TgetJWTfromAKV.EXPIRY'] ]
This step creates a credentials file containing the refresh token, access token, and expiry date, allowing the use of the refresh token mechanism.
- task: Bash@3
displayName: Generate credentials.json
enabled: ${{ eq(parameters.USE_REFRESH_TOKEN, true) }}
inputs:
targetType: 'inline'
script: |
jq -n \
--arg access-token $JWT_TOKEN \
--arg expiry $EXPIRY \
--arg refresh-token $REFRESH_TOKEN \
'{Default: $ARGS.named}'>~/.sas/credentials.json
Reminder: In my previous post, I was leveraging the JWT_TOKEN environment variable as the authentication mechanism. Now, I will be using the credentials.json file.
With these improvements in place, you should be able to use the refresh token mechanism.
Revoking refresh tokens after use is a security practice aimed at mitigating the risk of unauthorized access to resources. Refresh tokens are long-lived credentials used to obtain new access tokens without requiring the user to re-enter their credentials. However, if a refresh token is compromised or falls into the wrong hands, it can be abused to gain ongoing access to sensitive data or perform unauthorized actions.
Here are the steps needed to implement the revocation of the refresh token when you have completed all your DevOps tasks with your SAS Viya CLI.
Define an Azure Pipeline parameter to determine whether you want to revoke the refresh token after use:
- name: REVOKE_REFRESH_TOKEN
displayName: Do you want to revoke the refresh token after use ?
type: boolean
Include a step to log out using the SAS Viya CLI and, subsequently, revoke the refresh token:
- task: Bash@3
displayName: Log out (revoke the Refresh Token)
enabled: ${{ eq(parameters.REVOKE_REFRESH_TOKEN, true) }}
inputs:
targetType: 'inline'
script: |
/opt/sas/viya/home/bin/sas-viya auth logout
The log out will sign you out and revoke your refresh token.
Note: If your Access token is still valid it will remain valid until its expiration.
By incorporating these practices, you can effectively manage the use and revocation of refresh tokens within your DevOps processes, enhancing security and control over authentication mechanisms.
By default, the SAS Viya platform is configured to trust the CA certificates that are distributed by mozilla.org. By including this bundle of trusted CA certificates, you will be able to connect to your platform (signed by one of these CA) without any additional configuration.
If you are using a self/site-signed certificates, you will need to add your CA to your trust store in order to validate the certificate that will be send by your SAS Deployment.
When your SAS Viya platform's certificate is not chained to a public CA, it poses a challenge to secure communication. Without proper validation, your system may reject connections, leading to potential disruptions in your pipeline. But fear not, as we've outlined steps to navigate this challenge seamlessly.
The issue of untrusted authority can rear its head at two critical junctures in your pipeline process:
During the initial stages of setting up your DevOps pipeline, you may encounter verification failures when generating credentials. Let's break down the steps to address this issue:
Using the check_certificatescript, you can identify verification failures. Here's a snippet of code illustrating this step (full code is presented in Annex 1):
# Set some variables
SAS_SERVICES_DNS=yoursasviyaurl.company.com
# Can be retrieved automatically if you have configured kubectl
# NS=gelenv; SAS_SERVICES_DNS=$(kubectl -n ${NS} get configmap -o=jsonpath='{.items[*].data.SAS_SERVICES_URL}')
# Example usage: check_certificate <hostname> <port>
check_certificate "$SAS_SERVICES_DNS" 443
# Example usage: check_certificate <hostname> <port> <truststore>
check_certificate "$SAS_SERVICES_DNS" 443 $HOME/trustedcerts.pem
log output:
fradae@cldlgn01:~$ check_certificate "$SAS_SERVICES_DNS" 443
Certificate verification: Failed (Unable to verify the first certificate)
fradae@cldlgn01:~$ check_certificate "$SAS_SERVICES_DNS" 443 $HOME/trustedcerts.pem
Certificate verification: OK
Downloading the SAS Viya Platform trust store is crucial for establishing secure connections. Use the provided command to retrieve it:
# Set some variables
NS=gelenv
# Extract the trust store file and store it in the $HOME directory
kubectl -n $NS cp $(kubectl get pod -n $NS | grep "sas-logon-app" | head -1 | awk -F" " '{print $1}'):security/trustedcerts.pem ~/trustedcerts.pem
Ensure seamless authentication by mounting the trust store as a volume in your Docker container:
docker run --rm -it -v /$HOME:/security -v $PWD:/cli-home/.sas -e SAS_SERVICES_ENDPOINT $DOCKER_REGISTRY/viya-4-x64_oci_linux_2-docker/sas-viya-cli:latest auth loginCode
Note: Adding /$HOME:/security as a mounted volume ensures that trustedcerts.pem is readily accessible. Without the trust store, login attempts may fail due to unrecognized authorities. Your trust store must be named exactly trustedcerts.pem.
If you try without the trust store, you'll receive the following ERROR message: Login failed due to an error with the security certificate. The certificate is signed by an unknown authority. Run with the ‘–verbose' global option to see additional details.
In your Azure DevOps pipelines, additional steps are necessary to ensure smooth execution when certificates are not chained to public CAs.
Define a parameter to determine whether importing the trust store from your SAS Viya platform is necessary:
- name: PUB_SIGN_CERT
displayName: TLS certificate is chained to a public CA?
type: boolean
default: true
Add a Bash task to retrieve the trusted certificates just before executing the SAS Viya CLI command:
- task: Bash@3
displayName: Get the Trusted Certs (if not publicly available)
enabled: ${{ eq(parameters.PUB_SIGN_CERT, false) }}
inputs:
targetType: 'inline'
script: |
kubectl -n ${K8S_NAMESPACE} cp $(kubectl get pod -n ${K8S_NAMESPACE} | grep "sas-logon-app" | head -1 | awk -F" " '{print $1}'):security/trustedcerts.pem /tmp/trustedcerts.pem
Modify the pipeline step titled "Retrieve all configuration definition list" to utilize the downloaded trust store:
- task: Bash@3
displayName: Retrieve all configuration definition list
inputs:
targetType: 'inline'
script: |
if [ -f /tmp/trustedcerts.pem ]; then
export SSL_CERT_FILE=/tmp/trustedcerts.pem
fi
cd /opt/sas/viya/home/bin
/opt/sas/viya/home/bin/sas-viya-wrapper --output text configuration configurations list
By completing these steps, you ensure secure communication with your SAS Viya platform, even when using certificates not chained to a public CA.
By effectively managing refresh tokens, handling certificates and enhancing security measures within Azure pipelines and Kubernetes clusters, organizations can fortify their DevOps pipelines against potential security threats. These practices not only ensure security but also ensure the reliability and integrity of your applications and data in cloud environments.
#!/bin/bash
# Function to check server certificate verification status
check_certificate() {
hostname="$1"
port="$2"
trusted_ca_file="${3:-}"
# Prepare CAfile option if trusted_ca_file is provided
cafile_option=""
if [ -n "$trusted_ca_file" ]; then
cafile_option="-CAfile $trusted_ca_file"
fi
# Run openssl s_client command and capture output
output=$(echo X | openssl s_client -connect "$hostname":"$port" -servername "$hostname" $cafile_option 2>&1)
# Look for the "Verify return code" line and extract the return code
verify_return_code=$(echo "$output" | grep -oP "Verify return code: \K\d+")
# Check the verification status and print detailed information
case "$verify_return_code" in
0)
echo "Certificate verification: OK"
;;
10)
echo "Certificate verification: Failed (Certificate has expired)"
;;
20)
echo "Certificate verification: Failed (Unable to get local issuer certificate)"
;;
21)
echo "Certificate verification: Failed (Unable to verify the first certificate)"
;;
18)
echo "Certificate verification: Failed (Self-signed certificate)"
;;
*)
echo "Certificate verification: Failed (Unknown reason, Verify return code: $verify_return_code)"
;;
esac
}
David Estreich (myself) related posts with Azure DevOps:
Bogdan Teleuca's other posts about SAS Viya with Azure DevOps:
Catch the best of SAS Innovate 2025 — anytime, anywhere. Stream powerful keynotes, real-world demos, and game-changing insights from the world’s leading data and AI minds.
The rapid growth of AI technologies is driving an AI skills gap and demand for AI talent. Ready to grow your AI literacy? SAS offers free ways to get started for beginners, business leaders, and analytics professionals of all skill levels. Your future self will thank you.