Programmatically Create SAS tokens in .Net
Introduction
In today's digital landscape, data security and controlled access are critical concerns for businesses. Azure Blob Storage is a popular cloud storage solution, allowing you to store and manage unstructured data. To safeguard your data while granting controlled access to authorized users, one effective approach is to generate Shared Access Signature (SAS) tokens programmatically in .NET. In this blog post, we'll explore how to create SAS tokens in .NET, to enhance the security of your Azure Blob Storage.
All the code used in this blob post can be found in Github. Please check it out if you would like to have an example of this running locally in a Console app.
Disclaimer
While Shared Access Signature (SAS) tokens provide a valuable mechanism for granting temporary, controlled access to specific resources in Azure Blob Storage, it is essential to acknowledge that SAS is not always the first option for security measures. In many scenarios, Role-Based Access Control (RBAC) using Azure Active Directory (Azure AD) or other identity providers should be the primary choice for access control and security enforcement.
Use Case for SAS over RBAC
SAS tokens are particularly useful in scenarios where you need to grant time-limited, fine-grained access to specific resources without granting broader permissions. For example, when collaborating with external users or granting access to temporary services, SAS tokens offer a secure approach for limited access without the need for permanent user accounts. Here are some additional use cases for using SAS if you are interested in learning more.
What is a SAS token?
A SAS token is a secure, time-limited URL that grants limited access to specific resources in your Azure Blob Storage account. When you create a SAS token, you can define its permissions, validity period, and which resources can be accessed. This provides a powerful mechanism to grant temporary, controlled access to your Blob Storage without compromising your account's primary access keys.
When generating SAS tokens, adhering to the least privileged principle is crucial. Only provide the necessary permissions required for the specific task. Granting excessive permissions in the SAS token could expose your data to unnecessary risks. By defining precise permissions (read, write, list, delete, etc.) and setting an appropriate expiration time, you can ensure that the SAS token is valid only for the required duration and with limited access. If you are interested in learning more about SAS tokens, you can find more information in this document Storage SAS Overview (be sure to check out the Best Practices section which contains a few of the features we have and will discuss in this blog post).
SAS Signature options
Every SAS token is signed with a key. You can sign a SAS token with a user delegation key or with a storage account key (Shared Key).
A user delegation SAS token is signed with a user delegation key created using Azure AD credentials. Microsoft recommends that you use this approach when signing SAS tokens as it is more secure than using the account key. If it is not possible to use user delegation, then move to the Shared Key signing method. To learn more take a look at Create a user delegation SAS. We will cover this method in a future blog post.
A service or account SAS token is signed with the storage account key (Shared Key), granting access based on the SAS tokens' signing method. One thing to keep in mind with this approach is the rotation of these keys as a security best practice. Should your key get rotated after you have created a SAS token and before it has expired it will invalidate the SAS. To learn more take a look at Create a service SAS or Create an account SAS. This will be our focus of this blog post.
Creating SAS tokens programmatically in .NET
To generate the SAS tokens programmatically in .NET, you can utilize the Azure.Storage.Blobs library, a part of the Azure SDK for .NET. In this blog post we will focus on using the account key to sign the SAS tokens.
var blobServiceClient = new BlobServiceClient(connectionString);
var containerClient = blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
// set properties on BlobSasBuilder class
var sasBuilder = new BlobSasBuilder()
{
BlobContainerName = containerName,
//add IPRange using a string ip address
IPRange = new SasIPRange(IPAddress.Parse(startIPAddress), IPAddress.Parse(endIPAddress)),
StartsOn = DateTimeOffset.UtcNow,
ExpiresOn = DateTimeOffset.UtcNow.AddSeconds(60), // Expiration time for the SAS token
};
// set the required permissions on the SAS token
sasBuilder.SetPermissions(BlobSasPermissions.Read | BlobSasPermissions.Write);
// add resource specific properties and generate the SAS
if (sasResourceType == SasResourceType.Container)
{
//Create token at the container level
sasBuilder.Resource = "c";
sasToken = containerClient.GenerateSasUri(sasBuilder);
}
else if (sasResourceType == SasResourceType.Blob)
{
//Create token at the blob level
sasBuilder.Resource = "b";
sasBuilder.BlobName = blobName;
sasToken = blobClient.GenerateSasUri(sasBuilder);
}
Console.WriteLine($"Generated SAS token: {sasToken}");
- As you can see in the code above, we first create a new BlobServiceClient using the connectionString parameter which internally uses the account key for signing the SAS.
-
- It is important to understand that this account key is a primary security credential provided by Azure, granting full access to your Blob Storage account. Make sure you protect and handle the account key with utmost care. Something like Azure Key Vault would work nicely.
- You will need to get this connection string from the Azure portal for an existing Azure Storage Account. Go to your selected storage account and under the 'Security + networking' section there is a blade for 'Access keys'. You will copy one of those keys (key1 or key2 it doesn't matter which).
- Next, we are using the BlobSasBuilder class to create the basics of the SAS token. In our case we want to only allow a certain IPRange, a start date of now, and it will expire in 60 seconds. A best practice here would be to use a near-term expiration so even if SAS is compromised, it is only valid for a short time. Please see Best practices when using SAS for a full list of best practices.
- Then we set the permissions on the token. Here is a visual example of most of the options available to you in this screen shot from the portal. Here is a full list of all the properties for BlobSasBuilder.
- Finally, we add specific properties to the SAS based on the resource you are trying to secure with the SAS. In our sample code, we provide the ability to create a Container secured SAS or a Blob secured SAS. Then we create the SAS using GenerateSasUri. Note that this is a complete Uri that includes the SAS token with the container or blob depending on how it was created.
Testing newly created SAS token
Now that we have created the SAS token, let's confirm it's working how we would expect. We will create a blob programmatically using the newly created SAS with a string for content. Let's look at the code.
static void TestSasToken(Uri uri, IConfiguration configuration)
{
Console.WriteLine("Testing the SAS token...");
try
{
var testFileName = configuration.GetSection("AzureStorageConfig")["BlobNameForTest"] ?? string.Empty;
var testContent = configuration.GetSection("AzureStorageConfig")["BlobContentAsString"] ?? string.Empty;
var containerName = configuration.GetSection("AzureStorageConfig")["ContainerName"] ?? string.Empty;
// Create BlobClient with the URI containing the SAS token
var blobServiceClient = new BlobServiceClient(uri, new BlobClientOptions { Transport = new HttpClientTransport() });
var blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = blobContainerClient.GetBlobClient(testFileName);
var blobContent = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(testContent));
blobClient.Upload(blobContent);
}
catch (Exception ex)
{
Console.WriteLine($"Error while testing the SAS token: {ex.Message}");
}
Console.WriteLine("Completed testing the SAS token");
}
- The first thing we do is pull in the file name, content, and container name from our environment variables.
- Next, we create the blob. Notice how the URI which was created as the SAS token is used in the BlobServiceClient instantiation.
- The final step we create the MemoryStream for the content and upload it using the blobClient.Upload() function.
Again, the complete code for this blob post can be found in Github. Once you have followed the instructions in the README to get it running locally you will execute the following command to test creating a SAS token for a specific named blob. This will allow you to create the blob and read the blob.
dotnet run create-blob test
Here is the output:
And if we look in the container, we will see the bar.txt blob successfully created:
Now if you try to access another blob in that container (say test.json) with that same SAS token you will get the following:
Next, we will test creating the container SAS using the container command.
dotnet run create-container test
Here is the output:
When we combine the SAS token from our output with the file path, we can access any blob in the container. As a test, I am using `test.json` as my blob to request and its content is rendered in the browser as expected. Here is that:
Conclusion
Securing your Azure Blob Storage is crucial for protecting your valuable data. By creating SAS tokens programmatically in .NET using the account key as the backing mechanism, you can grant controlled and time-limited access to authorized users or applications without exposing your storage account's primary access keys. Stay tuned for our upcoming blog, where we'll delve into the enhanced security and access control offered by user-delegated keys for generating SAS tokens in Azure Blob Storage.