mdie
mdie

Reputation: 77

Azure Storage Rest-API via Powershell to list container content

i try to list content of our storage accounts by the example of the "liveworkerstorage". I have created an auth header and it was possible to create a file on a container but when i want to only list the content via Powershell i get an error message which tells me:

Invoke-RestMethod : AuthenticationFailedServer failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:781ec136-101e-0012-0620-f6ebe4000000
Time:2020-03-09T14:40:50.3148026ZThe MAC signature found in the HTTP request '16lBcDgjTWNtqInwWSghnHT0ae7yc5OS/05B72fVS4E=' is not the same as any computed signature. Server used following string to sign: 'GET
x-ms-blob-type:BlockBlob
x-ms-date:Mon, 09 Mar 2020 15:40:52 GMT
x-ms-version:2014-02-14
/liveworkerstorage/curltestdonotdelete/
restype:container'.
In C:\temp\Powershell\StoragePing\StoragePimg3.ps1:40 Zeichen:1
+ Invoke-RestMethod -method $method -Uri $Url -Headers $headers
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

Here is my code... You can see that there are commented out values where i made the PUT Method and that worked.

$method = "GET"
#$method = "PUT"
$headerDate = '2014-02-14'
$headers = @{"x-ms-version" = "$headerDate" }
$StorageAccountName = "xxXXxx"
$StorageContainerName = "xxXXxx"
$StorageAccountKey = "xxXXxxXX"
#$Url = "https://$StorageAccountName.blob.core.cloudapi.de/$StorageContainerName/Test.txt"
$Url = "https://$StorageAccountName.blob.core.cloudapi.de/$StorageContainerName/?restype=container"
#$body = "Hello world"
$xmsdate = (get-date -format r).ToString()
$headers.Add("x-ms-date", $xmsdate)
$bytes = ([System.Text.Encoding]::UTF8.GetBytes($body))
$contentLength = $bytes.length
$headers.Add("Content-Length", "$contentLength")
$headers.Add("x-ms-blob-type", "BlockBlob")

$signatureString = "$method$([char]10)$([char]10)$([char]10)$contentLength$([char]10)$([char]10)$([char]10)$([char]10)$([char]10)$([char]10)$([char]10)$([char]10)$([char]10)"
#Add CanonicalizedHeaders
$signatureString += "x-ms-blob-type:" + $headers["x-ms-blob-type"] + "$([char]10)"
$signatureString += "x-ms-date:" + $headers["x-ms-date"] + "$([char]10)"
$signatureString += "x-ms-version:" + $headers["x-ms-version"] + "$([char]10)"


#Add CanonicalizedResource
$uri = New-Object System.Uri -ArgumentList $url
$signatureString += "/" + $StorageAccountName + $uri.AbsolutePath                   

$dataToMac = [System.Text.Encoding]::UTF8.GetBytes($signatureString)

$accountKeyBytes = [System.Convert]::FromBase64String($StorageAccountKey)

$hmac = new-object System.Security.Cryptography.HMACSHA256((, $accountKeyBytes))
$signature = [System.Convert]::ToBase64String($hmac.ComputeHash($dataToMac))

$headers.Add("Authorization", "SharedKey " + $StorageAccountName + ":" + $signature);
write-host -fore green $signatureString
#Invoke-RestMethod -Uri $Url -Method $method -headers $headers -Body $body
Invoke-RestMethod -method $method -Uri $Url -Headers $headers

Thank you in advance

Best Regards


I have an update. Thanks so far for your answers!! It still doesn't work...

I changed my code for the get query.

But it tells me that the container doesn't exist. How is it possible to only list the root part of the storage?

I found this

$root?restype=container

So here is my code and the error message i get when i execute it...

#[CmdletBinding()]
#Param(
  #[Parameter(Mandatory=$true,Position=1)] [string] $StorageAccountName,
  #[Parameter(Mandatory=$True,Position=2)] [string] $FilesystemName,
  #[Parameter(Mandatory=$True,Position=2)] [string] $AccessKey
#)
$StorageAccountName = "XXX"
#$StorageAccountName = "XXX"
#$FilesystemName = "XXX"
$FilesystemName = "XXX"
#$AccessKey = "XXX"
$AccessKey = "XXX"


$date = [System.DateTime]::UtcNow.ToString("R") 

$n = "`n"
$method = "GET"

$stringToSign = "$method$n" #VERB
$stringToSign += "$n" # Content-Encoding + "\n" +  
$stringToSign += "$n" # Content-Language + "\n" +  
$stringToSign += "$n" # Content-Length + "\n" +  
$stringToSign += "$n" # Content-MD5 + "\n" +  
$stringToSign += "$n" # Content-Type + "\n" +  
$stringToSign += "$n" # Date + "\n" +  
$stringToSign += "$n" # If-Modified-Since + "\n" +  
$stringToSign += "$n" # If-Match + "\n" +  
$stringToSign += "$n" # If-None-Match + "\n" +  
$stringToSign += "$n" # If-Unmodified-Since + "\n" +  
$stringToSign += "$n" # Range + "\n" + 
$stringToSign +=    
                    <# SECTION: CanonicalizedHeaders + "\n" #>
                    "x-ms-date:$date" + $n + 
                    "x-ms-version:2018-11-09" + $n # 
                    <# SECTION: CanonicalizedHeaders + "\n" #>

$stringToSign +=    
                    <# SECTION: CanonicalizedResource + "\n" #>
                    "/$StorageAccountName/$FilesystemName" + $n + 
                    "recursive:true" + $n +
                    "resource:filesystem"# 
                    <# SECTION: CanonicalizedResource + "\n" #>

$sharedKey = [System.Convert]::FromBase64String($AccessKey)

$hasher = New-Object System.Security.Cryptography.HMACSHA256
$hasher.Key = $sharedKey

$signedSignature = [System.Convert]::ToBase64String($hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($stringToSign)))

$authHeader = "SharedKey ${StorageAccountName}:$signedSignature"

$headers = @{"x-ms-date"=$date} 
$headers.Add("x-ms-version","2018-11-09")
$headers.Add("Authorization",$authHeader)

$URI = "https://$StorageAccountName.blob.core.cloudapi.de/" + $FilesystemName + "?recursive=true&resource=filesystem"

$result = Invoke-RestMethod -method GET -Uri $URI -Headers $headers

Error Message:

Invoke-RestMethod : ContainerNotFoundThe specified container does not exist.
RequestId:c652069a-301e-0027-5ae1-f645b1000000
Time:2020-03-10T13:41:22.8270890Z
In C:\temp\Powershell\StoragePing\StoragePingfromweb.ps1:60 Zeichen:11
+ $result = Invoke-RestMethod -method GET -Uri $URI -Headers $headers
+           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

Thank you so far!

Upvotes: 3

Views: 4286

Answers (2)

Jimmy
Jimmy

Reputation: 41

Simpler if you generate your sas token through Azure, but here's what I've found that works when using a sas token...(no need to send headers)

Here is the reference that helped me put it together: https://blog.zuehlke.cloud/2019/10/access-azure-blob-storage-with-rest-and-sas/

# The sas token used below is the one generated by Azure in the Storage Account settings and censored
# and not one generated and signed using other methods (eg. the method you consistently see on help sites)
# To Note, in order to utilize the sas token with filters, you need to change the preceeding '?' with '&'
# Apologies for the variable names, I just yanked them from a larger test script
# The biggest difference when using a filter for these REST operations WITH A SAS KEY seems to be the need 
# to define "&restype=container" whereas the same operation anonymously has no need for "&restype=container"
#
$sasTokenS1 = "?sv=<DATE>&ss=bfqt&srt=sco&sp=rwdlacupx&se=<DATEandTIME>&spr=https&sig=<Signature already in address format>" #included only for comparison
$sasTokenS1v2 = "&sv=<DATE>&ss=bfqt&srt=sco&sp=rwdlacupx&se=<DATEandTIME>&spr=https&sig=<Signature already in address format>"
$storageAccountS1 = "storageaccountname"  #Standard StorageV2 (general purpose v2), paired with $sasTokenS1
$containerName2 = "containername"
$filter1 = "?comp=list" #the filter to list/read
$method1 = "GET" #the REST method, must be all CAPS
#
# With a sas token
$blobUri = "https://$storageAccountS1.blob.core.windows.net/$containerName2/$filter1&restype=container$sasTokenS1v2"
# Anonymous
$blobUri = "https://$storageAccountS1.blob.core.windows.net/$containerName2/$filter1"
#
#
Invoke-RestMethod -Method $method1 -Uri $blobUri

Upvotes: 2

Gaurav Mantri
Gaurav Mantri

Reputation: 136366

One issue that I see in your code is that you're computing canonicalizedResource string incorrectly. As per the documentation here, you would need to include query string parameters there.

So basically this line of code:

$signatureString += "/" + $StorageAccountName + $uri.AbsolutePath

Should be:

$signatureString += "/" + $StorageAccountName + $uri.AbsolutePath + $([char]10) + "restype:container"

Also some other comments:

  • Since you're listing blobs, you really don't need x-ms-blob-type header.
  • Listing blob containers is a GET operation so you really don't need Content-Length header.

Upvotes: 0

Related Questions