Pages

Friday, 26 March 2021

Code example to generate Secure Access Signature (SAS) for Azure API


So you wish to generate a SAS through Code, but you're stuck with the Microsoft Azure Documentation? My friend, you're in the right place, I've pieced it together for you.

With you shiny new SAS, you can apply it in such fancy ways as:

  • The PUT BLOB API to add files to a Storage Account.
  • The GET BLOB API to download from a Storage Account.
  • The AZ Copy tool for AzCopy operations.

You can generate a SAS from Azure Storage Explorer with a long expiry for testing. In production this is not secure, in the case that a Bad Actor might intercept the SAS and act badly, but their reward will be greater than a Razzie. The goal with this code generated SAS is that you can generate a few short-lived SASs per request that you wish to make.

Shared Access Signature (SAS) can be used as an alternative to the SharedKey method of Authentication for Azure API requests. Rather than suppling an Authentication header for SharedKey usage, with SAS, you just append the SAS to the Endpoint URL as a query string in the REST request.


Code Example

You, dear reader, can use the code example (below) to generate the SAS. The example (below) is in Java, but the principles can be used in any language. The core component is the ordering of the entries in the stringToSign and the azureApiVersion, so this needs to be preserved through the example being ported between languages.


Code Example's Inputs Explained

The storageAccountCanonicalizedResource string uses the Azure Canonical Resource format, that is /blob/storageAccountName/containerName. In Practice the storageAccountName can be sourced from the Azure Portal. The containerName would be the top level Container you wish to access.
This would result in an input such as "/blob/ndz123abc/myContainer".

The storageAccountKey can be found on the Access Keys screen for the Storage Account.
It is a long string that ends with two equals signs (==).


package com.lukegjpotter;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class SasKeyGeneration {

    /**
     * This method generates an SAS for a Container for Blob Operations, such as Put Blob API and Get Blob API.
     * It defaults to using the hardcoded values below, but these can be parameterised as needed.
     *
     * @param storageAccountCanonicalizedResource This is the string that denotes the Resource.
     *                                            It is available in the {@link PrivateConfiguration} class.
     *                                            It is of the format: /blob/storageaccount/container
     * @param storageAccountKey                   This is a long string that ends with ==.
     *                                            This can be sourced from the Azure Portal.
     *                                            In the Azure Portal, navigate to the Storage Account, and choose Access Keys.
     * @return The Shared Access Signature for the Storage Account's Container.
     */
    public String getSasToken(final String storageAccountCanonicalizedResource, final String storageAccountKey) {
        final String permissions = "rcwl"; // Read Write List
        final String resource = "c"; // Container c, Object o
        final String startDateString = "2021-03-11T12:00:00Z";
        final String expiryDateString = "2022-03-13T12:00:00Z";
        final String azureApiVersion = "2020-04-08";

        /* Source: Create Account SAS
         * Link: https://docs.microsoft.com/en-us/rest/api/storageservices/create-account-sas
         *       https://docs.microsoft.com/en-us/rest/api/storageservices/service-sas-examples#example-get-a-blob-using-a-containers-shared-access-signature */
        final String stringToSign = permissions + "\n" // signedpermissions
                + startDateString + "\n" // signedstart
                + expiryDateString + "\n" // signedexpiry
                + storageAccountCanonicalizedResource + "\n" // Canonical Resource
                + "\n" // 1
                + "\n" // 2
                + "\n" // 3
                + azureApiVersion + "\n" // signedversion
                + resource + "\n" // signedresourcetype
                + "\n" // 1
                + "\n" // 2
                + "\n" // 3
                + "\n" // 4
                + "\n"; // 5

        final String signature = getHMAC256(storageAccountKey, stringToSign);

        return "?"
                + "sv=" + azureApiVersion
                + "&st=" + URLEncoder.encode(startDateString, StandardCharsets.UTF_8)
                + "&se=" + URLEncoder.encode(expiryDateString, StandardCharsets.UTF_8)
                + "&sr=" + resource
                + "&sp=" + permissions
                + "&sig=" + URLEncoder.encode(signature, StandardCharsets.UTF_8);
    }

    private String getHMAC256(String accountKey, String stringToSign) {
        try {
            final String algorithm = "HmacSHA256";
            SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(accountKey), algorithm);
            Mac sha256HMAC = Mac.getInstance(algorithm);
            sha256HMAC.init(secretKey);
            return Base64.getEncoder().encodeToString(sha256HMAC.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)));
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }

        return "";
    }
}


Tweaking the Example

The getSasToken(...) method can choose to parameterise the hardcoded strings at the beginning of the method for added flexibility.

The expiryDateString could be parameterised to allow it to be generated to be valid for a short time, say 2 minutes, so that it cannot be used by a would be Bad Actor beyond this time.

The permissions String could be parameterised to allow for Write and Create permissions (permissions = "cw") for PUT operations, and Read and List permissions (permissions = "rl") for GET operations.


The Azure API versions (as specified in azureApiVersion variable) can be found on the Azure Documentation for Previous Azure Storage service versions. It is the your decision which version to use. The latest version can be received programmatically by HTML scraping the page, as at the time of writing there is not an API to get this version. The older version of the API are not Deprecated, so there is no need to upgrade. Earlier API versions expect a different format of the stringToSign, and later versions give better performance on the Azure side.
The example below is tested with the "2020-04-08" API Version.


If you wish to create a test harness prior to refactoring or changing this example, a simple expected vs actual comparison can be made in a Unit Test. The expected SAS can be generated from Azure Storage Explorer.


GitHub Example: lukegjpotter / AzureSharedAccessSignatureKeyGen.


No comments:

Post a Comment

Note: only a member of this blog may post a comment.