Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • Access to private Azure Blob Storage requires authentication.
  • Shared Key authentication represents one of the authentication options.
  • Scripts are provided for PowerShell releases 5.1, 6.x and 7.x

Example

Download: azure_authentication_shared_key_sample.ps1

...

Code Block
languagepowershell
titleListPowerShell script to list, to read and to write blobs with Shared Key
linenumberstrue
collapsetrue
# 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://purple.telstra.com.au/blog/loading-and-querying-data-in-azure-table-storage-using-powershell
#   https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key

function Create-Signature( [string] $Operation, [string] $Container, [string] $Blob, [string] $Account, [string] $AccessKey, [string] $Version, [DateTime] $Now, [int] $ContentLength=-1 )
{
    [Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null

    if ( $ContentLength -gt -1 )
    {
        $contentLengthValue = $ContentLength
    } else {
        $contentLengthValue = ''
    }

    $partToSign =      "`n" `
                     + "`n" `
                     + "`n" `
                     + (($ContentLength -gt -1) ? $ContentLength : '') $contentLengthValue + "`n" `
                     + "`n" `
                     + "`n" `
                     + "`n" `
                     + "`n" `
                     + "`n" `
                     + "`n" `
                     + "`n" `
                     + "`n" `
                     + "x-ms-blob-type:BlockBlob" + "`n" `
                     + "x-ms-date:$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')" + "`n" `
                     + "x-ms-version:$Version" + "`n"

    switch ( $Operation )                 
    {                 
        'GET'       {
                        $stringToSign  = 'GET' `
                                       + $partToSign `
                                       + "/$Account/$Container/$Blob"
                        break
                    }
                    
        'PUT'       {
                        $stringToSign  = 'PUT' `
                                       + $partToSign `
                                       + "/$Account/$Container/$Blob"
                        break
                    }
                    
        'LIST'      { 
                        $stringToSign  = "GET" `
                                       + $partToSign `
                                       + "/$Account/$Container" + "`n" `
                                       + "comp:list" + "`n" `
                                       + "restype:container"
                        break
                    }
                    
        default     {
                        throw "invalid operation: $Operation"
                    }
    }

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

    Write-Debug "container: $Container"
    Write-Debug "Accoount: $Account"
    Write-Debug "AccessKey: $AccessKey"
    Write-Debug "stringToSign: $stringToSign"

    $signature
}

function Invoke-BlobRequest( [Uri] $Uri, [string] $Account, [string] $Signature, [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; `
                                      'Authorization' = "SharedKey $($Account):$($Signature)" `
                                    } `
                      )

    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)


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

# step 1: create signature
$container = 'yade'
$signature = Create-Signature -Operation 'LIST' -Container $container -Account $ownerAccount -AccessKey $ownerAccessKey -Version $version -Now $now

    Write-Debug "Signature: $signature"

# step 2: send web service request
$uri      = "https://$($ownerAccount).blob.core.windows.net/$($container)?restype=container&comp=list"
$response = Invoke-BlobRequest -Uri $uri -Account $requesterAccount -Signature $signature -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
$container = 'yade'
$blob      = 'test.txt'
$signature = Create-Signature -Operation 'GET' -Container $container -Blob $blob -Account $ownerAccount -AccessKey $ownerAccessKey -Version $version -Now $now

    Write-Debug "Signature: $signature"

# step 2: send web service request
$uri      = "https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)"
$response = Invoke-BlobRequest -Uri $uri -Account $requesterAccount -Signature $signature -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
$container = 'yade'
$filePath  = "c:/tmp/some_blob.txt"
$blob      = (Get-Item $filePath).Name
$signature = Create-Signature -Operation 'PUT' -Container $container -Blob $blob -Account $ownerAccount -AccessKey $ownerAccessKey -Version $version -Now $now -ContentLength (Get-Content $filePath -Raw).length

    Write-Debug "Signature: $signature"

# step 2: send web service request
$uri      = "https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)"
$response = Invoke-BlobRequest -Uri $uri -Account $requesterAccount -Signature $signature -Now $now -Version $version -Method 'PUT' -FilePath $filePath

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


Explanations

The Shared Key authentication enable enables unlimited access, i.e it does not have any expiryexpire. Refer to the Azure article Authorize with Shared Key for reference. The above PowerShell script creates the Shared Key for three operation LIST, GET, PUT. The three operation support three different processprocesses:

  1. LIST BLOB
    1. The LIST BLOB operation displays retrieves the list of blobs in the container. 

  2. GET BLOB
    1. The GET BLOB operation is used to get read the content in of a blob. 

  3. PUT BLOB
    1. The PUT BLOB operation is used to create a blob and adding a content to it or writing to a to add the content of a file or to write to an existing blob.

The Shared Key uses different parameters for different operation. The Shared Key requires a String which is signature string that has to be encoded and need to passed as a that is passed with the Authorization Header in the HTTPS request. 

The format for of the signature string is:

Code Block
titleStringToSign Format
collapsetrue
StringToSign = VERB + "\n" +  
               Content-Encoding + "\n" +  
               Content-Language + "\n" +  
               Content-Length + "\n" +  
               Content-MD5 + "\n" +  
               Content-Type + "\n" +  
               Date + "\n" +  
               If-Modified-Since + "\n" +  
               If-Match + "\n" +  
               If-None-Match + "\n" +  
               If-Unmodified-Since + "\n" +  
               Range + "\n" +  
               CanonicalizedHeaders +   
               CanonicalizedResource;  

The StringToSIgn is then encoded using using the HMAC-SHA256 algorithm over for the UTF-8-encoded signature string which is then used as an with the Authorization Header using the following syntax:

Code Block
titleAuthorization header format
collapsetrue
Authorization="SharedKey <AccountName>:<Signature>"

where SharedKey is the authorization scheme, Account name is the name of the storage account by which the HTTPS request is generated, Signature is the encoded StringToSign

...

The StringToSign parameters differ according to the operations:

  1. LIST BLOB:
    In the The LIST BLOB operation lists the blobs in from the container. The StringToSign for LIST BLOB will be:

    Code Block
    titleStringToSign for LIST BLOB operation
    collapsetrue
    $stringToSign  =     "GET" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + (($ContentLength -gt -1) ? $ContentLength : '') + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "x-ms-blob-type:BlockBlob" + "`n" `
                         + "x-ms-date:$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')" + "`n" `
                         + "x-ms-version:$Version" + "`n" `
                         + "/$Account/$Container" + "`n" `
                         + "comp:list" + "`n" `
                         + "restype:container"

    where $Account is the storage account by which the HTTPS request is generated. $Container is the name of the container.

  2. GET BLOB:
    The GTE BLOB operation displays retrieves the content of the blob. So, for the get operation it is required to pass the blob name whose content is to should be read/get. So, the The StringToSign for GET BLOB will be:


    Code Block
    titleStringToSign for GET BLOB operation
    collapsetrue
    $stringToSign  =     'GET' `
    					 + "`n" `
                         + "`n" `
                         + "`n" `
                         + (($ContentLength -gt -1) ? $ContentLength : '') + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "x-ms-blob-type:BlockBlob" + "`n" `
                         + "x-ms-date:$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')" + "`n" `
                         + "x-ms-version:$Version" + "`n" `
                         + "/$Account/$Container/$Blob"

    where $Account is the storage account by which the HTTPS request is generated. $Container is the name of the container. $Blob is the name of the blob whose content is to be Getread.

  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 length of content in a file so file, therefore it is required to pass the length of the blob so that it can allocate a that much memory space in the Container and can write the file content to the blob. The StringToSign for the PUT BLOB operation will be:

    Code Block
    titleStringToSign for PUT BLOB operation
    collapsetrue
    $stringToSign  = 	 'PUT' `
    				     + "`n" `
                         + "`n" `
                         + "`n" `
                         + (($ContentLength -gt -1) ? $ContentLength : '') + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "`n" `
                         + "x-ms-blob-type:BlockBlob" + "`n" `
                         + "x-ms-date:$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')" + "`n" `
                         + "x-ms-version:$Version" + "`n" `
                         + "/$Account/$Container/$Blob"

    where $Account is the storage account by which the HTTPS request is generated. $Container is the name of the container. $Blob is the name of the blob which is to that should be created. The $ContentLength for the LIST BLOB and GET BLOB operation will be empty but for the PUT BLOB operation it required to pass the Content Length for the file blob to be created.

Encoding Signature String

After creating the Signature string String it is required to encode the string to create a Shared Key which can then be passed to the Authorization header. The algorithm used for encoding is HMAC-SHA256 over the UTF-8-encoded signature string. The script used in PowerShell instructions to encode the StringToSign isare:

Code Block
titleEncoded StringToSign
collapsetrue
$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 for of the storage account that owns the Azure blob container. $stringToSign is the signature string which depends on the type of operation.

HTTPS Request to Azure

The HTTPS request to the azure Azure is used to invoke the call. This takes different types of URI for different operations. The URI depends on the type of operation we want to use in the HTTPS request. The header Headers used in the HTTPS request are:

Code Block
'x-ms-blob-type' = 'BlockBlob'; `
'x-ms-date' = "$(Get-Date (Get-Date $Now).ToUniversalTime() -Format 'R')"; `
'x-ms-version' = $Version; `
'Authorization' = "SharedKey $($Account):$($Signature)" `

where headerHeader

  • 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
  • Authorization specifies the authorization scheme, account name, and signature

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 just 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 where $ownerAccount is the name of the Account storage account and $container is the name of the container. The headers used by LIST BLOB operation are x-ms-blob-type, x-ms-date, x-ms-version, Authorization header. the name of the container.

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

    Code Block
    titleSyntax for LIST BLOB
    collapsetrue
    Request Syntax
    GET https://$($ownerAccount).blob.core.windows.net/$($container)?restype=container&comp=list
    
    Request Headers
    x-ms-blob-type: BlockBlob
    x-ms-date: <date>
    x-ms-version: $Version
    Authorization="SharedKey <AccountName>:<Signature>"
    


  2. GET BLOB
    As the The GET BLOB operation is used to read a the blob content so we need it is required to pass the blob name also with the URI. So the request URI for the GET BLOB will be https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob) where $ownerAccount is the name of the Account storage account and $container is the name of the container and $blob is the name of the blob which is to be read. The Get BLOB uses the same headers as LIST BLOB.
    The  
    The HTTPS request and header Header syntax for GET BLOB will be:

    Code Block
    titleSyntax for GET BLOB
    collapsetrue
    Request Syntax
    GET https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)
    
    Request Headers
    x-ms-blob-type: BlockBlob
    x-ms-date: <date>
    x-ms-version: $Version
    Authorization="SharedKey <AccountName>:<Signature>"
    
  3. PUT BLOB

    The PUT BLOB operation is used to create a blob. So the URI for the PUT BLOB requires to pass operation includes the name of the blob to be created in the URI and also the content of the file which will blob is to be passed as a body to with the HTTPS request body. So, the The URI will be https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob) where $ownerAccount is the name of the Account storage account and $container is the name of the container and $blob is the name of the blob which is to be created in the container. The PUT BLOB uses the same headers. 

    Code Block
    titleSyntax for PUT BLOB
    collapsetrue
    Request Syntax
    PUT https://$($ownerAccount).blob.core.windows.net/$($container)/$($blob)
    
    Request Headers
    x-ms-blob-type: BlockBlob
    x-ms-date: <date>
    x-ms-version: $Version
    Authorization="SharedKey <AccountName>:<Signature>"
    
    Request Body:
    <Content of the File>