7

I am trying to build the SAS token required for a blob download URL in Python, following the instructions from MSDN.

My string to sign looks like:

r\n
2016-12-22T14%3A00%3A00Z\n
2016-12-22T15%3A00%3A00Z\n
%2Fblob%2Fmytest%2Fprivatefiles%2F1%2Fqux.txt\n
\n
\n
https\n
2015-12-11\n
\n
\n
\n
\n
_

I've added the newline symbols for clarity and the last line is supposed to be an empty line (with no newline at the end).

The Python method I use for signing the string is:

def sign(self, string):
    hashed = hmac.new(base64.b64decode(self.account_key), digestmod=sha256)
    hashed.update(string)
    base64_str = base64.encodestring(hashed.digest()).strip()
    return base64_str

The final URL I build looks like:

https://mytest.blob.core.windows.net/privatefiles/1/qux.txt?sv=2015-12-11&st=2016-12-22T14%3A00%3A00Z&se=2016-12-22T15%3A00%3A00Z&sr=b&sp=r&spr=https&sig=BxkcpoRq3xanEHwU6u5%2FYsULEtOCJebHmupUZaPmBgM%3D

Still, the URL fails with a 403. Any idea on what I am doing wrong?

adrianp
  • 2,491
  • 5
  • 26
  • 44
  • 1
    Can you try by removing the URL encoding in your string to sign? For example, canonical resource should be `/blob/mytest/privatefiles/1/qux.txt`. – Gaurav Mantri Dec 22 '16 at 14:55
  • Hi, any update yet? – Gary Liu Jan 03 '17 at 07:35
  • @GauravMantri Yep, that seems to do the trick, don't know where I got the idea that the fields must be encoded; could you add that as an answer so I can accept it? – adrianp Jan 03 '17 at 12:28
  • I get what the OP meant now... Here is a link to code that does what is wanted https://github.com/yokawasa/azure-functions-python-samples/blob/master/v1functions/blob-sas-token-generator/function/run.py. I am not author!! – Jordan Simba Dec 16 '19 at 16:37

4 Answers4

10

Update, with the latest storage python library, this is what I used to generate the sas token:

def generate_sas_token(file_name):
    sas = generate_blob_sas(account_name=AZURE_ACC_NAME,
                            account_key=AZURE_PRIMARY_KEY,
                            container_name=AZURE_CONTAINER,
                            blob_name=file_name,
                            permission=BlobSasPermissions(read=True),
                            expiry=datetime.utcnow() + timedelta(hours=2))

    logging.info('https://'+AZURE_ACC_NAME+'.blob.core.windows.net/'+AZURE_CONTAINER+'/'+file_name+'?'+sas)
    sas_url ='https://'+AZURE_ACC_NAME+'.blob.core.windows.net/'+AZURE_CONTAINER+'/'+file_name+'?'+sas
    return sas_url

Python 3.6 and azure-storage-blob package.

kgalic
  • 2,441
  • 1
  • 9
  • 21
8

The easiest way to generate SAS token in python is to leverage Azure Storage SDK for Python. Please consider following code snippet:

import time
import uuid
import hmac
import base64
import hashlib
import urllib
from datetime import datetime, timedelta
from azure.storage import (
    AccessPolicy,
    ResourceTypes,
    AccountPermissions,
    CloudStorageAccount,
)
from azure.storage.blob import (
    BlockBlobService,
    ContainerPermissions,
    BlobPermissions,
    PublicAccess,
)

AZURE_ACC_NAME = '<account_name>'
AZURE_PRIMARY_KEY = '<account_key>'
AZURE_CONTAINER = '<container_name>'
AZURE_BLOB='<blob_name>'

def generate_sas_with_sdk():
    block_blob_service = BlockBlobService(account_name=AZURE_ACC_NAME, account_key=AZURE_PRIMARY_KEY)    
    sas_url = block_blob_service.generate_blob_shared_access_signature(AZURE_CONTAINER,AZURE_BLOB,BlobPermissions.READ,datetime.utcnow() + timedelta(hours=1))
    #print sas_url
    print 'https://'+AZURE_ACC_NAME+'.blob.core.windows.net/'+AZURE_CONTAINER+'/'+AZURE_BLOB+'?'+sas_url

generate_sas_with_sdk()

Furthermore, to generate SAS token via plain python script, you can refer to the source code at https://github.com/Azure/azure-storage-python/blob/master/azure/storage/sharedaccesssignature.py#L173 for more hints.

Gary Liu
  • 13,758
  • 1
  • 17
  • 32
  • 1
    Looked over their code (the more precise reference is [this class](https://github.com/Azure/azure-storage-python/blob/master/azure/storage/sharedaccesssignature.py#L555)) but I could not figure out what they are doing differently; I want to have my code working and not use the SDK for objective reasons. – adrianp Jan 03 '17 at 12:13
  • 1
    Broken link... :( – jtlz2 May 20 '19 at 07:23
  • What are good reasons to not use the SDK if already using their services? – Jordan Simba Dec 16 '19 at 10:08
  • @jordan simba **//What are good reasons to not use the SDK//** because using pure HTTP REST-API approach has fewer dependencies and is arguably "closer to the metal" [closer to the metal](https://www.quora.com/Does-closer-to-the-metal-mean-what-people-think-it-means?share=1) – dreftymac Nov 24 '20 at 03:44
  • 1
    @dreftymac The way I think about it is similar to why I would use a library to URL encode a string, rather than replicate a standard already implemented and maintained in a library/ module. Less room for mistake, less maintenance, less area to test etc... – Jordan Simba Nov 27 '20 at 09:39
  • 1
    @JordanSimba Yep, that makes sense. You are actually making a good point in favor of **not** using an SDK. Why? Because, in some cases, the SDK can actually have *higher* maintenance requirements compared to using a pure REST API based approach. Of course, it depends on the SDK (how new it is, which language, etc) but it is not always the case that the SDK approach entails less maintenance. – dreftymac Nov 27 '20 at 15:22
  • 1
    This is true. But SDKs exist for convenience, if one makes use of any of th SDKs offerings, you might as well use all! So if the OP is using a completely HTTP approach, then I agree they should stay in keeping with that, otherwise use the tool already in the toolkit you've paid for. – Jordan Simba Nov 27 '20 at 16:42
  • 1
    @JordanSimba good points all around. Thanks for the feedback and perspective. – dreftymac Dec 19 '20 at 20:32
5

Here's an updated code snippet for Python3 and the updated Azure Storage Blob SDK:

from datetime import datetime, timedelta
from azure.storage.blob import (
    BlockBlobService,
    ContainerPermissions,
    BlobPermissions,
    PublicAccess,
)

AZURE_ACC_NAME = '<account_name>'
AZURE_PRIMARY_KEY = '<account_key>'
AZURE_CONTAINER = '<container_name>'
AZURE_BLOB='<blob_name>'

block_blob_service = BlockBlobService(account_name=AZURE_ACC_NAME, account_key=AZURE_PRIMARY_KEY)
sas_url = block_blob_service.generate_blob_shared_access_signature(AZURE_CONTAINER,AZURE_BLOB,permission=BlobPermissions.READ,expiry= datetime.utcnow() + timedelta(hours=1))
print('https://'+AZURE_ACC_NAME+'.blob.core.windows.net/'+AZURE_CONTAINER+'/'+AZURE_BLOB+'?'+sas_url)
Tommy Falgout
  • 76
  • 1
  • 3
2

Based on the documentation (Please see Constructing the Signature String section), the parameters passed to string to sign must be URL decoded. From the link:

To construct the signature string of a shared access signature, first construct the string-to-sign from the fields comprising the request, then encode the string as UTF-8 and compute the signature using the HMAC-SHA256 algorithm. Note that fields included in the string-to-sign must be URL-decoded.

Please use un-encoded parameter values in your string to sign and that should fix the problem.

Community
  • 1
  • 1
Gaurav Mantri
  • 128,066
  • 12
  • 206
  • 241