Scope

  • Access to private Azure Blob Storage requires authentication.
  • Shared Access Signatures (SAS) represent one of the authentication options.
  • Scripts are provided for PowerShell releases 5.1, 6.x and 7.x

Example

Download: azure_authentication_shared_access_signatures_sample.ps1


List, read and write blobs with Shared Access Signatures
# inspired by:
#   https://stackoverflow.com/questions/53653473/generating-an-sas-token-in-java-to-download-a-file-in-an-azure-data-storage-cont?rq=1
#   https://docs.microsoft.com/en-us/rest/api/storageservices/create-account-sas#constructing-the-signature-string

function Create-SASToken( [string] $Account, [string] $AccessKey, [string] $Version, [DateTime] $Now, [DateTime] $Expires, [string] $Permissions='r', $ResourceTypes='o', [string] $Services='b' )
{
    [Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null

    [string] $nowIso = (Get-Date (Get-Date $Now).ToUniversalTime() -Format "u").Replace( ' ', 'T' )
    [string] $expiresIso = (Get-Date (Get-Date $Expires).ToUniversalTime() -Format "u").Replace( ' ', 'T' )

    $stringToSign = $Account + "`n" `
                             + $Permissions + "`n" `
                             + $Services + "`n" `
                             + $ResourceTypes + "`n" `
                             + $nowIso + "`n" `
                             + $expiresIso + "`n" `
                             + "`n" `
                             + "https" + "`n" `
                             + $Version + "`n"

    $hmac = New-Object System.Security.Cryptography.HMACSHA256
    $hmac.key = [Convert]::FromBase64String( $AccessKey )

    $signature = $hmac.ComputeHash( [Text.Encoding]::UTF8.GetBytes( $stringToSign ) )
    $signature = [Convert]::ToBase64String( $signature )
    
    $sasToken = "sv=$Version" `
              + "&ss=$Services" `
              + "&srt=$ResourceTypes" `
              + "&sp=$Permissions" `
              + "&se=" + [System.Web.HttpUtility]::UrlEncode( $expiresIso ) `
              + "&st=" + [System.Web.HttpUtility]::UrlEncode( $nowIso ) `
              + "&spr=https" `
              + "&sig=" + [System.Web.HttpUtility]::UrlEncode( $signature )

    Write-Debug "now: $nowIso"
    Write-Debug "expires: $expiresIso"
    Write-Debug "stringToSign: $stringToSign"
    Write-Debug "sasToken: $sasToken"

    $signature, $sasToken
}

function Invoke-BlobRequest( [Uri] $Uri, [DateTime] $Now, [string] $Version, [string] $Method='GET', [string] $FilePath='' )
{
    $requestParams = @{}
    $requestParams.Add( 'Uri', $Uri )
    $requestParams.Add( 'Method', $Method )
    
    if ( $FilePath )
    {
        $requestParams.Add( 'Infile', $FilePath )
    }
    
    $requestParams.Add( 'Headers', @{ `
                                      'x-ms-blob-type' = 'BlockBlob'; `
                                      'x-ms-date' = "$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')"; `
                                      'x-ms-version' = $Version `
                                    } `
                      )

    Write-Debug ".... Invoke-WebRequest: $Uri"
    
    $requestParams.Keys | % {
        if ( $_ -eq 'Headers' )
        {
            $item = $_
            $requestParams.Item($_).Keys | % {
                Write-Debug "...... Headers $_ : $($requestParams.Item($item).Item($_))"
            }
        } else {
            Write-Debug "...... $_  $($requestParams.Item($_))"
        }
    }

    # run the web service request
    $response = Invoke-WebRequest @requestParams
    $response
}


# ---------- ---------- ----------
# Main
# ---------- ---------- ----------

$ownerAccount     = '<storage account>'
$ownerAccessKey   = '<storage account access key>'
$requesterAccount = '<requester account>'
$version          = '2019-10-10'


# consider badly synchronized server time and start 3 minutes in the past
$now = (Get-Date).ToUniversalTime().AddMinutes(-3)
# set the SAS token lifetime to 1 day
$expires = $now.AddDays(1).ToUniversalTime()

# consider the following values when creating SAS tokens
#   Permission: (r)ead, (w)rite, (d)elete, (l)ist
#   ResourceTypes: (o)bject, (c)ontainer, (s)ervice
#   Services: (b)lob, (q)ueue, (t)able, (f)ile


Write-Host "`n"
Write-Host "# ++++++++++++++++++++++++++++++"
Write-Host "# Get Access Token"
Write-Host "# ++++++++++++++++++++++++++++++"

# step 1: create signature and SAS token for any operation (read, write, delete, list) on any resources (container, object) of the blob service
$signature, $sasToken = Create-SASToken -Account $ownerAccount -AccessKey $ownerAccessKey -Version $version -Now $now -Expires $expires `
                                        -Permissions 'rwdl' -ResourceTypes 'oc' -Services 'b'
    Write-Host "Signature: $signature"
    Write-Host "SAS Token: $sasToken"


Write-Host "`n"
Write-Host "# ++++++++++++++++++++++++++++++"
Write-Host "# List Blobs in Container"
Write-Host "# ++++++++++++++++++++++++++++++"

<#
# step 1: create signature and SAS token for list operation on container resources of the blob service
$signature, $sasToken = Create-SASToken -Account $ownerAccount -AccessKey $ownerAccessKey -Version $version -Now $now -Expires $expires `
                                        -Permissions 'l' -ResourceTypes 'c' -Services 'b'  
    Write-Host "Signature: $signature"
    Write-Host "SAS Token: $sasToken"
#>

# step 2: send web service request
$container = 'yade'
$uri       = "https://$($ownerAccount).blob.core.windows.net/$($container)?restype=container&comp=list"
$response  = Invoke-BlobRequest -Uri ($uri + "&" + $sasToken) -Now $now -Version $version -Method 'GET' 

    Write-Host "Status Code: $($response.StatusCode)"
    Write-Host $response.Content


Write-Host "`n"
Write-Host "# ++++++++++++++++++++++++++++++"
Write-Host "# Get Blob from Container"
Write-Host "# ++++++++++++++++++++++++++++++"

<#
# step 1: create signature and SAS token for read operation on object resources of the blob service
$signature, $sasToken = Create-SASToken -Account $ownerAccount -AccessKey $ownerAccessKey -Version $version -Now $now -Expires $expires `
                                        -Permissions 'r' -ResourceTypes 'o' -Services 'b'
    Write-Host "Signature: $signature"
    Write-Host "SAS Token: $sasToken"
#>

# step 2: send web service request
$container = 'yade'
$blob      = 'test.txt'
$uri       = "https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)"
$response  = Invoke-BlobRequest -Uri ($uri + '?' + $sasToken) -Now $now -Version $version -Method 'GET'

    Write-Host "Status Code: $($response.StatusCode)"
    Write-Host $response.Content


Write-Host "`n"
Write-Host "# ++++++++++++++++++++++++++++++"
Write-Host "# Write Blob to Container"
Write-Host "# ++++++++++++++++++++++++++++++"

<#
# step 1: create signature and SAS token for write operation on object resources of the blob service
$signature, $sasToken = Create-SASToken -Account $ownerAccount -AccessKey $ownerAccessKey -Version $version -Now $now -Expires $expires `
                                        -Permissions 'w' -ResourceTypes 'o' -Services 'b'
    Write-Host "Signature: $signature"
    Write-Host "SAS Token: $sasToken"
#>

# step 2: send web service request
$container = 'yade'
$filePath  = "/tmp/some_blob.txt"
$blob      = (Get-Item $filePath).Name
$uri       = "https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)"
$response  = Invoke-BlobRequest -Uri ($uri + '?' + $sasToken) -Now $now -Version $version -Method 'PUT' -FilePath $filePath

    Write-Host "Status Code: $($response.StatusCode)"
    Write-Host $response.Content

Explanations

The authentication using Shared Access Signature token does not provides unlimited access, i.e it expires after the provided time. Refer to the Azure article Create an account SAS for reference. The above PowerShell script creates the request for three operation LIST, GET, PUT.

Signature String

The Signature in the SAS token uses different parameters for different operation. The SAS token requires a signature string that has to be encoded and that is passed as a signature parameter to the token. 

The format of the signature string is:

StringToSign Format
StringToSign = accountname + "\n" +  
    signedpermissions + "\n" +  
    signedservice + "\n" +  
    signedresourcetype + "\n" +  
    signedstart + "\n" +  
    signedexpiry + "\n" +  
    signedIP + "\n" +  
    signedProtocol + "\n" +  
    signedversion + "\n"  

The StringToSIgn is then encoded using the HMAC-SHA256 algorithm for the UTF-8-encoded signature string which is then passed to the request URI as a sig parameter to SAS Token.

Encoding Signature String

After creating the Signature String it is required to encode the string to create a SAS Token. The algorithm used for encoding is HMAC-SHA256 over the UTF-8-encoded signature string. The PowerShell instructions to encode the StringToSign are:

Encoded StringToSign
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.key = [Convert]::FromBase64String( $AccessKey )
 
$signature = $hmac.ComputeHash( [Text.Encoding]::UTF8.GetBytes( $stringToSign ) )
$signature = [Convert]::ToBase64String( $signature )

where $AccessKey is the access key of the storage account that owns the Azure blob container. $stringToSign is the signature string which depends on the type of operation.

SAS Token

The SAS token is a query string that includes all of the required information about authorization, resources, permissions and time intervals. The SAS token has the following syntax:

SAS Token Syntax
$sasToken = "sv=$Version" `
              + "&ss=$Services" `
              + "&srt=$ResourceTypes" `
              + "&sp=$Permissions" `
              + "&se=" + [System.Web.HttpUtility]::UrlEncode( $expiresIso ) `
              + "&st=" + [System.Web.HttpUtility]::UrlEncode( $nowIso ) `
              + "&spr=https" `
              + "&sig=" + [System.Web.HttpUtility]::UrlEncode( $signature )

where $expiresIso and $nowIso are parameter with expiry date and the current date. And $signature is the encoded signature. The parameter description are as follows:

  • sv: Storage Service Version, this parameter indicates the version to use.
  • ss: Service, this parameter specifies to the Blob and File services. The supported values are (b)lob, (q)ueue, (t)able, (f)ile.
  • srt: Resource Type, this parameter specifies the resource type to be used. The supported values are (o)bject, (c)ontainer, (s)ervice.
  • sp: Permission, this parameter grant access to read and write operations. The supported values are (r)ead, (w)rite, (d)elete, (l)ist.
  • se: Expiry, this parameters is used to set the expiry time for the signature. Specified in UTC
  • st: Start Time, this parameters is used to specify the start of validity of token. Specified in UTC.
  • spr: Protocol, only the request using https are permitted.
Note:  Fields included in the string-to-sign must be URL-decoded

The above script generates signature and SAS Token with permission for any operation (read, write, delete, list) on any resources (container, object) of the blob service for a validity of 24 hours.

The SAS Token and signature parameters differ according to the operations:

  1. LIST BLOB:
    The LIST BLOB operation lists the blobs from the container. The signature and SAS token for LIST BLOB should have permission to access the list of the resource on services.

    StringToSign for LIST BLOB operation
    $stringToSign  =   $Account + "`n" `
                                 + $Permissions + "`n" `
                                 + $Services + "`n" `
                                 + $ResourceTypes + "`n" `
                                 + $nowIso + "`n" `
                                 + $expiresIso + "`n" `
                                 + "`n" `
                                 + "https" + "`n" `
                                 + $Version + "`n"  
    SAS Token for LIST BLOB
    $sasToken = "sv=$Version" `
                  + "&ss=$Services" `
                  + "&srt=$ResourceTypes" `
                  + "&sp=$Permissions" `
                  + "&se=" + [System.Web.HttpUtility]::UrlEncode( $expiresIso ) `
                  + "&st=" + [System.Web.HttpUtility]::UrlEncode( $nowIso ) `
                  + "&spr=https" `
                  + "&sig=" + [System.Web.HttpUtility]::UrlEncode( $signature )


    where

    • $Account is the storage account for which the HTTPS request is generated.

    • $Permission will be 'l' to grant permission for listing

    • $Services will be 'b' to provide the blob service

    • $ResourceType will be 'c' to use resource type as container

    • $nowIso is the URL-Decoded current time in UTC

    • $expires is the URL_Decoded expiry time in UTC

  2. GET BLOB:
    The GTE BLOB operation retrieves the content of the blob. So, for the get operation it is required to have a read permission on the object to the blob service

    StringToSign for GET BLOB operation
    $stringToSign  =   $Account + "`n" `
                                 + $Permissions + "`n" `
                                 + $Services + "`n" `
                                 + $ResourceTypes + "`n" `
                                 + $nowIso + "`n" `
                                 + $expiresIso + "`n" `
                                 + "`n" `
                                 + "https" + "`n" `
                                 + $Version + "`n"    
    SAS Token for GET BLOB
    $sasToken = "sv=$Version" `
                  + "&ss=$Services" `
                  + "&srt=$ResourceTypes" `
                  + "&sp=$Permissions" `
                  + "&se=" + [System.Web.HttpUtility]::UrlEncode( $expiresIso ) `
                  + "&st=" + [System.Web.HttpUtility]::UrlEncode( $nowIso ) `
                  + "&spr=https" `
                  + "&sig=" + [System.Web.HttpUtility]::UrlEncode( $signature )


    where

    • $Account is the storage account for which the HTTPS request is generated.

    • $Permission will be 'r' to grant permission for listing

    • $Services will be 'b' to provide the blob service

    • $ResourceType will be 'o' to use resource type as container

    • $nowIso is the URL-Decoded current time in UTC

    • $expires is the URL_Decoded expiry time in UTC

  3. PUT BLOB
    The PUT BLOB operation creates a new block blob or updates an existing block blob. The PUT BLOB operation creates a BLOB from the content of a file, therefore it requires write permission to the resource object in the blob service.

    StringToSign for PUT BLOB operation
    $stringToSign  =   $Account + "`n" `
                                 + $Permissions + "`n" `
                                 + $Services + "`n" `
                                 + $ResourceTypes + "`n" `
                                 + $nowIso + "`n" `
                                 + $expiresIso + "`n" `
                                 + "`n" `
                                 + "https" + "`n" `
                                 + $Version + "`n"  	
    SAS Token for PUT BLOB
    $sasToken = "sv=$Version" `
                  + "&ss=$Services" `
                  + "&srt=$ResourceTypes" `
                  + "&sp=$Permissions" `
                  + "&se=" + [System.Web.HttpUtility]::UrlEncode( $expiresIso ) `
                  + "&st=" + [System.Web.HttpUtility]::UrlEncode( $nowIso ) `
                  + "&spr=https" `
                  + "&sig=" + [System.Web.HttpUtility]::UrlEncode( $signature )


    where

    • $Account is the storage account for which the HTTPS request is generated.

    • $Permission will be 'w' to grant permission for listing

    • $Services will be 'b' to provide the blob service

    • $ResourceType will be 'o' to use resource type as container

    • $nowIso is the URL-Decoded current time in UTC

    • $expires is the URL_Decoded expiry time in UTC

HTTPS Request to Azure

The HTTPS request to Azure is used to invoke the call. The URI depends on the type of operation we want to use in the HTTPS request. The Headers used in the HTTPS request are:

'x-ms-blob-type' = 'BlockBlob'; `
'x-ms-date' = "$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')"; `
'x-ms-version' = $Version; `

where Header

  • x-ms-blob-type specifies the type of blob to create 

  • x-ms-date specifies the Coordinated Universal Time (UTC) for the request
  • x-ms-version specifies the version of the operation to use for this request

The details about the type of HTTPS request in the operations are:

  1. LIST BLOB
    The HTTPS request URI for the LIST BLOB operation just includes the container name. As the LIST operations lists all the blobs in the container. So the HTTPS request URI will be https://$($ownerAccount).blob.core.windows.net/$($container)?restype=container&comp=list&$($sasToekn) where $ownerAccount is the name of the storage account, $container is the name of the container and $sasToken is the the above generated Shared Access Signature Token.

    The HTTPS request and header syntax for LIST BLOB will be:

    Syntax for LIST BLOB
    Request Syntax
    GET https://$($ownerAccount).blob.core.windows.net/$($container)?restype=container&comp=list&$($sasToken)
    
    Request Headers
    x-ms-blob-type: BlockBlob
    x-ms-date: <date>
    x-ms-version: $Version
    


  2. GET BLOB
    The GET BLOB operation is used to read the blob content so it is required to pass the blob name with the URI. So the request URI for the GET BLOB will be https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)?$($sasToken) where $ownerAccount is the name of the storage account, $container is the name of the container, $blob is the name of the blob which is to be read and $sasToken is the the above generated Shared Access Signature Token
    The HTTPS request and Header syntax for GET BLOB will be:

    Syntax for GET BLOB
    Request Syntax
    GET https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)?$($sasToken)
    
    Request Headers
    x-ms-blob-type: BlockBlob
    x-ms-date: <date>
    x-ms-version: $Version
    
  3. PUT BLOB

    The PUT BLOB operation is used to create a blob. So the URI for the PUT BLOB operation includes the name of the blob to be created and the content of the blob is to be passed with the HTTPS request body. The URI will be https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)?$($sasToken) where $ownerAccount is the name of the storage account, $container is the name of the container, $blob is the name of the blob which is to be created in the container and $sasToken is the above generated Shared Access Signature Token

    Syntax for PUT BLOB
    Request Syntax
    PUT https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)?$($sasToken)
    
    Request Headers
    x-ms-blob-type: BlockBlob
    x-ms-date: <date>
    x-ms-version: $Version
    
    Request Body:
    <Content of the File>
    


  • No labels