Azure Storage: SAS Token Expiry check and Generation with PowerShell & Python

Prashanth Kumar
9 min readFeb 18, 2024

Recently, one of my colleagues approached me with a concern: someone had generated a SAS token, but he/she doesn't have access to Azure Key Vault. Meanwhile Business need to verify whether the SAS token was still active or if it required renewal. Additionally, there’s another issue with SAS tokens: when granted full Contributor-level permissions on a Subscription or Resource Group, anyone can generate SAS tokens and it leads to misuse as people forget to add them onto Keyvault or any other secure vaults. Unfortunately, this can lead to breach.

In this article, we’ll guide you through the process of checking for any existing SAS tokens in use. We’ll find out their expiry date/time stamps and then automate the generation of Shared Access Signature (SAS) tokens for Azure Blob Storage using both PowerShell and Python scripts. SAS tokens are crucial for providing limited access to resources in Azure Storage, enabling applications to securely access storage resources without needing to share account keys.

Prerequisites

Before you begin, make sure you have the following:

  • An Azure subscription.
  • Access to Azure PowerShell.
  • Access to Python module & should have valid permissions to install library(s).
  • Permissions to create and manage resources in Azure Key Vault and Azure Blob Storage.

Step 1: Setup Azure Environment and Install PowerShell Modules

  1. Install Azure PowerShell modules if you haven’t already. You can install them using the following command:
Install-Module -Name Az -AllowClobber -Scope CurrentUser

Step 2: Define Key Vault and Storage Account Details

Define the names of your Azure Key Vault and Azure Blob Storage account, along with other required details like resource group and container name. Replace the placeholders with your actual values.

$keyVaultName = "YourKeyVaultName"
$secretName = "YourSecretName"
$storageAccountName = "YourStorageAccountName"
$resourceGroupName = "YourResourceGroupName"
$containerName = "YourBlobContainerName"

Step 3: Make sure to set Storage Context

A storage context is just an object containing references to the storage account, so yes, just deleting the variable containing the storage context is all you need to do. It is Important to set an object in PowerShell to encapsulate the storage credentials.

$storageAccountKey = (Get-AzStorageAccountKey -ResourceGroupName $resourceGroupName -AccountName $storageAccountName)[0].Value
$storageContext = New-AzStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $storageAccountKey
$storageContext

Step 4: Check if SAS Token Exists in Key Vault

Next step we need to check if the SAS token already exists in Azure Key Vault. If it exists, extract the expiry time and determine if it needs to be renewed.

$secret = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $secretName -ErrorAction SilentlyContinue

if ($secret) {
# Retrieve the secret value as a SecureString
$secretSecureString = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $secretName
$secretValue = $secretSecureString.SecretValue | ConvertFrom-SecureString -AsPlainText

# Extract the expiry time from the secret value
$expiryTimeString = ($secretValue -split '&') | Where-Object { $_ -like 'se=*' }

# Extract the actual expiry time value from the string
$expiryTimeValue = ($expiryTimeString -split '=')[1]

# Decode the URL-encoded datetime string
$decodedExpiryTimeValue = [System.Web.HttpUtility]::UrlDecode($expiryTimeValue)

# Parse the decoded expiry time value as a datetime
$expiryTime = [datetime]::Parse($decodedExpiryTimeValue)

if ($expiryTime -lt (Get-Date).AddHours(1)) {
Write-Host "Secret '$secretName' has expired or will expire in less than 1 hour. Generating a new SAS token..."
$generateNewToken = $true
} else {
Write-Host "Secret '$secretName' is still valid and set to expire at $expiryTime"
$generateNewToken = $false
}
} else {
Write-Host "Secret '$secretName' does not exist in Key Vault '$keyVaultName'"
$generateNewToken = $true
}

One of the Important point to remember when you pull data from Keyvault PowerShell should parse the output and it should you the expiry key in right format, from below output you can see this is a system generated format however we want it in readable format.

2024–02–18T05%3A55%3A52Z

for that I have parsed the output using below command

# Here I am extracting only SE using split, if you want Signature you can additional Pipe cwith where condition
$expiryTimeString = ($secretValue -split '&') | Where-Object { $_ -like 'se=*' }

# Extract the actual expiry time value from the string
$expiryTimeValue = ($expiryTimeString -split '=')[1]

# First ste of converting it to Right format
$decodedExpiryTimeValue = [System.Web.HttpUtility]::UrlDecode($expiryTimeValue)

# This step will decode to right format
$expiryTime = [datetime]::Parse($decodedExpiryTimeValue)

Now this is how my output looks

PS /home/prashanth_kumar4> $decodedExpiryTimeValue = [System.Web.HttpUtility]::UrlDecode($expiryTimeValue)
PS /home/prashanth_kumar4> $decodedExpiryTimeValue
2024-02-18T05:55:52Z

Step 5: Generate and Store New SAS Token

If the SAS token doesn’t exist or is expired, generate a new SAS token with the desired expiry time and store it in Azure Key Vault.

if ($generateNewToken) {
# Generate a new SAS token with a 2-hour expiry for testing
$expiryTime = (Get-Date).AddHours(2).ToUniversalTime()

$sasToken = New-AzStorageBlobSASToken -Container $containerName -Blob '*' -Permission 'rw' -Context $storageContext -ExpiryTime $expiryTime
$secretValue = ConvertTo-SecureString $sasToken -AsPlainText -Force
Set-AzKeyVaultSecret -VaultName $keyVaultName -Name $secretName -SecretValue $secretValue

Write-Host "New SAS token generated and stored in Key Vault '$keyVaultName' with name '$secretName' and expiry set to 2 hours"
}

In Action

Lets check everything in Action

  1. lets create a new Azure Keyvault and from below screenshot you can see I dont have any new key yet.

2. Next when I run it should first check if any key exists if not it is going to create a new secret “TestKey”.

3. Here you can see I dont have any secret keys exist so it returned null.

4. Lets run the script and now it should create a new secret key

you can see new Secret “TestKey” is ready for us, and when I open new version is ready.

shows new secret value “sv=2023–08–03&se=2024–02–18T06%3A42%3A33Z&sr=b&sp=rw&sig=AbwX5XX0ooF7XXIieiqXXlXS5X0Xza48AbcDEfgh3qw%3D”

Here you can see se is set to 2024–02–18T06%3A42%3A33Z

as in the script I have created a new token with + 2 hours expiry from current time.

5. Now lets go further and check our script with 1 more condition in my script I have set the condition to check keys if they are about to expire which are less than 1 hour. So let me go ahead and generate a new SAS token manually on Storage account and set its expiry to 8 hours.

sp=r&st=2024–02–18T04:48:06Z&se=2024–02–18T12:48:06Z&spr=https&sv=2022–11–02&sr=c&sig=AbC12OdD910eFgHijk34L9mnOpqRS56tUV0wx%2Yzwfc%3D

6. Lets add this to Keyvault, now you can see I have 2 versions

7. Now when I run my script it should first validate if there is a key already exists or not, if yes then it should proceed checking secret content and check se (end time) and if it is less than 1 hour then it should proceed generating a new one. You can see it has done validation as my key is set to expire in 8 hours so it has just validated and returned with the message saying “‘TestKey’ is still valid and set to expire at 02/18/2024 12:48:06”

8. One last testing now let me generate a new SAS token manually with 30mins as an expiry and we should be running script again to generate a new token and add a new entry in Azure Key vault.

New SAS token: sp=r&st=2024–02–18T07:38:01Z&se=2024–02–18T19:58:01Z&spr=https&sv=2022–11–02&sr=c&sig=AB%2XXjCXXXXh7b%2BfE%2XXX8hXahvXX%2XXXd5XjdfatpuFAo%3X

New Added version: 3a94edb391574fae9e092d668430bf54

After execution.

Conclusion

In this guide, we have demonstrated how to automate the generation and management of SAS tokens for Azure Blob Storage using PowerShell scripts. By leveraging Azure Key Vault and PowerShell, you can ensure secure access to your storage resources while automating the process of token generation and renewal.

Feel free to customize the script according to your specific requirements and integrate it into your Azure automation workflows.

Here is the full working script.

# full working powershell script.

az login --debug --service-principal -u "username" -p "password" --tenant "tenantid"
az account set --subscription "subscriptionid"

#variables declaration
$keyVaultName = "pkdemokeyvault01"
$secretName = "TestKey"
$storageAccountName = "storageaccountname"
$resourceGroupName = "yourresourcegroupname"
$containerName = "container-for-sastoken-generation"

#Getting storage key and setting context
$storageAccountKey = (Get-AzStorageAccountKey -ResourceGroupName $resourceGroupName -AccountName $storageAccountName)[0].Value
$storageContext = New-AzStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $storageAccountKey


# Check if the secret exists in Key Vault
$secret = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $secretName -ErrorAction SilentlyContinue

if ($secret) {
Write-Host "Secret '$secretName' exists in Key Vault '$keyVaultName'"

$secretSecureString = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $secretName
$secretValue = $secretSecureString.SecretValue | ConvertFrom-SecureString -AsPlainText
$expiryTimeString = ($secretValue -split '&') | Where-Object { $_ -like 'se=*' }
$expiryTimeValue = ($expiryTimeString -split '=')[1]

# First step convert it to Right format
$decodedExpiryTimeValue = [System.Web.HttpUtility]::UrlDecode($expiryTimeValue)

# This step will decode to right format
$expiryTime = [datetime]::Parse($decodedExpiryTimeValue)


if ($expiryTime -lt (Get-Date).AddHours(1)) {
Write-Host "Secret '$secretName' has expired or will expire in less than 1 hour. Generating a new SAS token..."
$generateNewToken = $true
} else {
Write-Host "Secret '$secretName' is still valid and set to expire at $expiryTime"
$generateNewToken = $false
}
} else {
Write-Host "Secret '$secretName' does not exist in Key Vault '$keyVaultName'"
$generateNewToken = $true
}

if ($generateNewToken) {
# Generate a new SAS token with a 2-hour expiry for testing, you can change this based on your requirement
$expiryTime = (Get-Date).AddHours(2).ToUniversalTime()

$sasToken = New-AzStorageBlobSASToken -Container $containerName -Blob '*' -Permission 'rw' -Context $storageContext -ExpiryTime $expiryTime
$secretValue = ConvertTo-SecureString $sasToken -AsPlainText -Force
Set-AzKeyVaultSecret -VaultName $keyVaultName -Name $secretName -SecretValue $secretValue

Write-Host "New SAS token generated and stored in Key Vault '$keyVaultName' with name '$secretName' and expiry set to 2 hours"
}



#make sure to install these libraries I just added as a part of script
pip install azure-identity
pip install azure-keyvault-secrets
pip install azure-storage-blob

#Using Python script.
from datetime import datetime, timedelta
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from azure.storage.blob import BlobServiceClient, ResourceTypes, AccountSasPermissions, generate_blob_sas

key_vault_name = "pkdemokeyvault01"
secret_name = "TestKey"
storage_account_name = "storageaccountname"
resource_group_name = "yourresourcegroupname"
container_name = "container-for-sastoken-generation"

credential = DefaultAzureCredential()


key_vault_uri = f"https://{key_vault_name}.vault.azure.net/"
key_vault_client = SecretClient(vault_url=key_vault_uri, credential=credential)

# Base Logic
try:
retrieved_secret = secret_client.get_secret(secret_name)
secret_value = retrieved_secret.value

expiry_time_str = [item.split('=')[1] for item in secret_value.split('&') if item.startswith('se=')][0]
expiry_time = datetime.strptime(expiry_time_str, "%Y-%m-%dT%H:%M:%SZ")

if expiry_time < datetime.utcnow() + timedelta(hours=1):
print(f"Secret '{secret_name}' has expired or will expire in less than 1 hour. Generating a new SAS token...")
generate_new_token = True
else:
print(f"Secret '{secret_name}' is still valid and set to expire at {expiry_time}")
generate_new_token = False

except Exception as e:
print(f"Secret '{secret_name}' does not exist in Key Vault '{key_vault_name}'")
generate_new_token = True

if generate_new_token:
# IN this line we generate/set 2 hour expiry
expiry_time = datetime.utcnow() + timedelta(hours=2)

blob_service_client = BlobServiceClient(account_url=f"https://{storage_account_name}.blob.core.windows.net", credential=credential)
user_delegation_key = blob_service_client.get_user_delegation_key(datetime.utcnow(), datetime.utcnow() + timedelta(hours=2))

# Here in this block we are generating a new SAS token
sas_token = generate_blob_sas(
account_name=storage_account_name,
container_name=container_name,
blob_name="*",
permission=BlobSasPermissions(read=True, write=True),
expiry=expiry_time,
user_delegation_key=user_delegation_key
)

secret_value = f"sp=r&st={datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')}&se={expiry_time.strftime('%Y-%m-%dT%H:%M:%SZ')}&spr=https&sv=2022-11-02&sr=c&sig={sas_token}"

# Adding this key to Azure Key vault
secret_client.set_secret(secret_name, secret_value)

print(f"New SAS token generated and stored in Key Vault '{key_vault_name}' with name '{secret_name}' and expiry set to 2 hours")

Reference articles:

Some of the reference articles from which I have used to create this script.

https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview?toc=%2Fazure%2Fstorage%2Fblobs%2Ftoc.json&bc=%2Fazure%2Fstorage%2Fblobs%2Fbreadcrumb%2Ftoc.json#how-a-shared-access-signature-works

https://devblogs.microsoft.com/scripting/adding-and-subtracting-dates-with-powershell/

https://learn.microsoft.com/en-us/dotnet/api/system.datetime.parse?view=net-8.0

https://learn.microsoft.com/en-us/dotnet/api/system.web.httputility.urldecode?view=net-8.0

https://learn.microsoft.com/th-th/powershell/module/microsoft.powershell.security/?view=powershell-5.1

--

--