Search code examples
azurepowershellrestazure-servicebus-queuessas-token

Powershell HTTP Post to Service Bus Queue returns 401


Trying to submit a message to a service bus queue I have set up, and keep getting a 401 Unauthorized return.

I've tried configuring the SAS token myself using this method

$ResourceGroupName = 'myResourceGroup'
$NameSpaceName = "serviceBusNameSpace"
$QueueName = "myQueueName"
$PolicyName = "RootManageSharedAccessKey"
$body = "test message"

$Namespace = (Get-AzServiceBusNamespace -ResourceGroupName $ResourceGroupName -Name $namespacename).Name
$key = (Get-AzServiceBusKey -ResourceGroupName $ResourceGroupName -Namespace $namespacename -Name $PolicyName).PrimaryKey

$origin = [DateTime]"1/1/1970 00:00" 
$Expiry = (Get-Date).AddMinutes(5)    

#compute the token expiration time.
$diff = New-TimeSpan -Start $origin -End $Expiry 
$tokenExpirationTime = [Convert]::ToInt32($diff.TotalSeconds)

#Create a new instance of the HMACSHA256 class and set the key to UTF8 for the size of $Key
$hmacsha = New-Object -TypeName System.Security.Cryptography.HMACSHA256
$hmacsha.Key = [Text.Encoding]::UTF8.GetBytes($Key)

$scope = "https://$Namespace.servicebus.windows.net/"
#create the string that will be used when cumputing the hash
$stringToSign = [Web.HttpUtility]::UrlEncode($scope) + "`n" + $tokenExpirationTime

#Compute hash from the HMACSHA256 instance we created above using the size of the UTF8 string above.
$hash = $hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($stringToSign))
#Convert the hash to base 64 string
$signature = [Convert]::ToBase64String($hash)
$fullResourceURI = "https://$Namespace.servicebus.windows.net/$QueueName"
#create the token
$token = [string]::Format([Globalization.CultureInfo]::InvariantCulture, `
         "SharedAccessSignature sr={0}sig={1}&se={2}&skn={3}", `
         [Web.HttpUtility]::UrlEncode($fullResourceURI), `
         [Web.HttpUtility]::UrlEncode($signature), `
         $tokenExpirationTime, $PolicyName) 

$headers = @{ "Authorization" = "$token"; "Content-Type" = "application/atom+xml;type=entry;charset=utf-8" }
$uri = "https://$Namespace.servicebus.windows.net/$QueueName/messages"
$headers.Add("BrokerProperties", "{}")

#Invoke-WebRequest call.
Invoke-WebRequest -Uri $uri -Headers $headers -Method Post -Body $body -UseBasicParsing

I've also tried generating it through a built in cmdlet in Az.ServiceBus

$ResourceGroupName = 'myResourceGroup'
$NameSpaceName = "serviceBusNameSpace"
$QueueName = "myQueueName"
$PolicyName = "RootManageSharedAccessKey"

$body = "test message"
$expiry = (Get-Date).AddHours(2)
$authRule = Get-AzServiceBusAuthorizationRule -ResourceGroupName $ResourceGroupName -Namespace $NamespaceName
$token = New-AzServiceBusAuthorizationRuleSASToken -AuthorizationRuleId $authRule.Id -KeyType Primary -ExpiryTime $Expiry

$headers = @{ "Authorization" = "SharedAccessSignature $($token.SharedAccessSignature)"; "Content-Type" = "application/atom+xml;type=entry;charset=utf-8" }
$uri = "https://$Namespace.servicebus.windows.net/$QueueName/messages"
$headers.Add("BrokerProperties", "{}")

#Invoke-WebRequest call.
Invoke-WebRequest -Uri $uri -Headers $headers -Method Post -Body $body -UseBasicParsing

Both give me a 401 unauthorized error

Invoke-WebRequest : The remote server returned an error: (401) Unauthorized.
At line:9 char:17
+ ... $response = Invoke-WebRequest -Uri $uri -Headers $headers -Method Pos ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

I'm not sure what else to do. Is there a setting I need to configure for my queue within the azure portal?

Have found solution. UTC time was originally expiring token before even sending, in addition to malformed SAS signature

Final code edit below

$key = (Get-AzServiceBusKey -ResourceGroupName $ResourceGroupName -Namespace $namespacename -Name $PolicyName).PrimaryKey

$origin = [DateTime]"1/1/1970 00:00" 
$Expiry = (Get-Date).AddMinutes(20)
$Expiry = $Expiry.ToUniversalTime()    

#compute the token expiration time.
$diff = New-TimeSpan -Start $origin -End $Expiry 
$tokenExpirationTime = [Convert]::ToInt32($diff.TotalSeconds)


$uri = "https://$Namespace.servicebus.windows.net/$QueueName/messages"
$scope = "https://$Namespace.servicebus.windows.net/$QueueName"
#create the string that will be used when cumputing the hash
$stringToSign = [Web.HttpUtility]::UrlEncode($scope) + "`n" + $tokenExpirationTime

#Create a new instance of the HMACSHA256 class and set the key to UTF8 for the size of $Key
$hmacsha = New-Object -TypeName System.Security.Cryptography.HMACSHA256
$hmacsha.Key = [Text.Encoding]::UTF8.GetBytes($Key)


#Compute hash from the HMACSHA256 instance we created above using the size of the UTF8 string above.
$hash = $hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($stringToSign))
#Convert the hash to base 64 string
$signature = [Convert]::ToBase64String($hash)

#create the token
$token = [string]::Format([Globalization.CultureInfo]::InvariantCulture, `
        "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", `
        [Web.HttpUtility]::UrlEncode($scope), `
        [Web.HttpUtility]::UrlEncode($signature), `
        $tokenExpirationTime, $PolicyName) 

$headers = @{ "Authorization" = "$token"}
$headers.Add("Content-Type", "application/atom+xml;type=entry;charset=utf-8")

#Invoke-WebRequest call.
Invoke-WebRequest -Uri $uri -Headers $headers -Method Post -Body $body -UseBasicParsing

Solution

  • See post edit.

    Token expiration time wasn't converted to UTC, making it always expired, in addition to not having the SaS token configuration string formed correctly.