Posted in:

Why Shared Access Signatures?

Whenever you’re working with resources in the cloud, whether you’re reading and writing data in blob storage or publishing and subscribing to messages on a queue, it’s always best to work with the “principle of least privilege”. In Microsoft Azure this is achieved by using “Shared Access Signatures”

A Shared Access Signature gives the holder of that signature access to a particular resource (like a blob or a queue), for a limited time, and with limited permissions (e.g. read only). The advantage is that you don’t need to give someone the full “connection string” just to allow them write to a specific blob, or post to a particular queue.

I’ve already posted about how you can control access to an Azure Blob container with Shared Access Signatures, and it is relatively straightforward. However, when I tried to do the same for a Service Bus namespace, it turned out to be a bit trickier than I hoped, as the MSDN documentation omits certain key details.

So in this post, I’ll demonstrate how we can create a SAS token that we can give someone to allow them to read messages off a specific queue.

Creating Shared Access Signature

First of all, we need to create an authorization rule for our queue. You don’t need to create one for every SAS token you want to generate, just one for each set of permissions you want to grant.

Typically you’d create one with “Listen” rights, and one with “Send” rights. The following code snippet shows us checking if the queue exists, and creating it if needed, and then checking if the shared access authorization rule exists, and creating it if needed. (Obviously this rule is created by a process that does have the privileges to create shared access authorization rules).

// service bus connection string
var connectionString = "Endpoint=sb://mynamespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=sgjasdhgjkasdhgawe3857238212";
const string queueName = "MyTestQueue";

// first ensure the queue exists
var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);
if (!namespaceManager.QueueExists(queueName))
{
    namespaceManager.CreateQueue(queueName);
}

// make a shared authorization rule which will have listen privileges
var sharedAccessAuthorizationRuleKeyName = "MyQueue_Listen"; 

// find out if the authorization rule already exists:
var queueDescription = namespaceManager.GetQueue(queueName);
var authRule = queueDescription.Authorization
    .OfType<SharedAccessAuthorizationRule>()
    .FirstOrDefault(a => a.KeyName == sharedAccessAuthorizationRuleKeyName);
    
// if it doesn't:
if (authRule == null)
{
    // create a new authorization rule, with listen rights 
    authRule = new SharedAccessAuthorizationRule(
        sharedAccessAuthorizationRuleKeyName,
        SharedAccessAuthorizationRule.GenerateRandomKey(), // generate a primary key for this rule
        new[] { AccessRights.Listen }); // this rule only allows listening
        
    // add this to the list of authorization rules
    queueDescription.Authorization.Add(authRule);
    // actually update the queue with the new list of rules
    namespaceManager.UpdateQueue(queueDescription);
}

Now we’re ready to create the shared access signature itself (thanks to Brent for pointing me in the right direction here). We can choose an expiry time for the signature, which ideally would be fairly short. And we generate the Shared Access Signature like this:

var expiry = TimeSpan.FromMinutes(30);
var serviceUri = ServiceBusEnvironment.CreateServiceUri("https", "mynamespace", queueName).ToString().Trim('/');
string generatedSaS = SharedAccessSignatureTokenProvider.GetSharedAccessSignature(sharedAccessAuthorizationRuleKeyName, 
                            authRule.PrimaryKey, serviceUri, expiry);

Now we have a signature which will look something like this. This is the string we can give to the client we want to grant access to. Obviously it’s still a secret, but if it does leak, the potential damage is limited to only listening on this one queue, and only for the specified duration.

SharedAccessSignature sr=https%3a%2f%2fmynamespace.servicebus.windows.net%2fMyTestQueue&sig=fFWmdMmWjsdTqPyhyvRS9LQqLjJNPc87xhInhYai9OM%3d&se=1453286209&skn=MyListenKey

Using the Shared Access Signature

Now we’re ready to actually use the Shared Access Signature, and we do that with the TokenProvider and MessagingFactory. These will allow us to create a QueueClient which we can use to receive a message (since our Shared Access Authorization Rule specified listen permissions).

var tp = TokenProvider.CreateSharedAccessSignatureTokenProvider(generatedSaS);
var mf = MessagingFactory.Create("sb://mynamespace.servicebus.windows.net", tp);
var listenClient = mf.CreateQueueClient(queueName);

// receive a message
var rxMessage = listenClient.Receive(TimeSpan.FromSeconds(10));

And for completeness, let’s check that our permissions really worked. We’ll try to send a message on this listen client. And we’ll try to create a new listen client for another queue that we weren’t granted access to:

try
{
    listenClient.Send(new BrokeredMessage("should not be sent"));
    throw new InvalidOperationException("SHOULD HAVE BEEN BLOCKED");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Very good - can't send");
}

try
{
    var listenClient2 = mf.CreateQueueClient("AnotherQueue");
    var m = listenClient2.Receive();
    throw new InvalidOperationException("SHOULD HAVE BEEN BLOCKED");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Very good - can't listen on another queue");
}

Anyway, that’s it for SAS tokens with Azure Service Bus queues. For topics, it’s a little more involved as you’ll want to grant Manage rights to allow the holder of the SAS token to create a subscription, but otherwise the process is roughly the same.

Comments

Comment by DisqusTech

If we give the client the string
SharedAccessSignature sr=https%3a%2f%2fmynamespace.servicebus.wi...%2fMyTestQueue&sig=fFWmdMmWjsdTqPyhyvRS9LQqLjJNPc87xhInhYai9OM%3d&se=1453286209&skn=MyListenKey
Since they could manipulate the se value, aren't you giving the client the ability to generate a very long lasting token? Aka, wouldn't they be able to get a token that last for over a year? Even though we only want to give out tokens that last 1 day? (or whatever long term/short term values you might use)

DisqusTech
Comment by Johan

The signature part sig=fFWmdMmWjsdTqPyhyvRS9LQqLjJNPc87xhInhYai9OM%3d
contains a cryptographic sigature based on the uri, expire date and the secret
authRule.PrimaryKey. So unless you have access to access policy keys of the Service Bus
then you cant forge any new Shared Access Signatures that are valid.

Johan
Comment by Jignesh

I am using "WindowsAzure.ServiceBus" nuget package and try to send message on topic using topic level shared access policy(send only) but it will give me below error.
40103: Invalid authorization token signature, Resource:sb://testsb.servicebus.wind....
But when I tried to use "Microsoft.Azure.ServiceBus" nuget same connection string working fine any idea on this issue.
and it's working fine if I used service bus level shaared access policy connection string(send only).
How I can send topic message using topic level shared access policy and "WindowsAzure.ServiceBus" nuget package.
Thanks in advance.

Jignesh
Comment by Mark Heath

sorry, can't tell what's going wrong from that description. I'd suggest asking on GitHub or StackOverflow

Mark Heath