BlobをSASでダウンロードする【Azure,Python】

# はじめに

自分用の備忘録です。

# 環境
- Python3.6(Anaconda)
- Azureのストレージアカウントを持っている

ストレージアカウントのアクセスキーを確認しておいてください（[参考](https://docs.microsoft.com/ja-jp/azure/storage/common/storage-account-keys-manage?tabs=azure-portal)）。

# やったこと

コードを掲載しておきます。myaccesskey,myaccount,mycontainerはそれぞれ自分のに書き換えてください。

```python
# 必要なライブラリのimport
import base64
import hashlib
import hmac
from urllib.parse import quote, unquote
import requests
import datetime

# 自分のアクセスキーで置き換える
access_key = 'myaccess_key'
# 操作したいblobのurl
url = 'https://myaccount.blob.core.windows.net/mycontainer/test.png'

now = datetime.datetime.utcnow()

token_string = {}

canonicalizedResource = '/blob/myaccount/mycontainer/test.png' # リソースを指定

token_string['sp'] = 'r' # signedPermissions
token_string['st'] = '' # signedStart
token_string['se'] = (now+datetime.timedelta(minutes=10)).strftime('%Y-%m-%dT%H:%M:%SZ') # signedExpiry* ex-> 2020-11-16T04:25:03-05:00
token_string['si'] = '' # signedIdentifier
token_string['sip'] = '' # signedIP
token_string['spr'] = '' # signedProtocol
token_string['sv'] = "2020-04-08" # signedVersion
token_string['sr'] = 'b' # signedResource BLOB->'b', container->'c'
token_string['ss'] = '' # signedSnapshotTime?
token_string['rscc'] = '' # リクエストヘッダの指定(Cache-Control)
token_string['rscd'] = '' # リクエストヘッダの指定(Content-Disposition)
token_string['rsce'] = '' # リクエストヘッダの指定(Content-Encoding)
token_string['rscl'] = '' # リクエストヘッダの指定(Content-Language)
token_string['rsct'] = '' # リクエストヘッダの指定(Content-Type)
token_string['sig'] = '' # 後述

# 順番厳守、これをもとに署名を作る
StringToSign = token_string['sp'] + "\n" +  \
               token_string['st'] + "\n" +  \
               token_string['se'] + "\n" +  \
               canonicalizedResource + "\n" +  \
               token_string['si'] + "\n" +  \
               token_string['sip'] + "\n" +  \
               token_string['spr'] + "\n" +  \
               token_string['sv'] + "\n" +  \
               token_string['sr'] + "\n" + \
               token_string['ss'] + "\n" +\
               token_string['rscc'] + "\n" +  \
               token_string['rscd'] + "\n" +  \
               token_string['rsce'] + "\n" +  \
               token_string['rscl'] + "\n" +  \
               token_string['rsct']

# 使う関数の定義
def url_quote(url):
    return quote(url)

def encode_base64(data):
    if isinstance(data, str):
        data = data.encode('utf-8')
    encoded = base64.b64encode(data)
    return encoded.decode('utf-8')

def decode_base64_to_bytes(data):
    if isinstance(data, str):
        data = data.encode('utf-8')
    return base64.b64decode(data)

def decode_base64_to_text(data):
    decoded_bytes = decode_base64_to_bytes(data)
    return decoded_bytes.decode('utf-8')

def decode_base64_to_text(data):
    decoded_bytes = decode_base64_to_bytes(data)
    return decoded_bytes.decode('utf-8')

def sign_string(key, string_to_sign, key_is_base64=True):
    if key_is_base64:
        key = decode_base64_to_bytes(key)
    else:
        if isinstance(key, str):
            key = key.encode('utf-8')
    if isinstance(string_to_sign, str):
        string_to_sign = string_to_sign.encode('utf-8')
    signed_hmac_sha256 = hmac.HMAC(key, string_to_sign, hashlib.sha256)
    digest = signed_hmac_sha256.digest()
    encoded_digest = encode_base64(digest)
    return encoded_digest

# 署名の作成
signature = sign_string(access_key,StringToSign)
token_string['sig'] = signature

url = url + '?' + '&'.join(['{}={}'.format(k, url_quote(v)) for k,v in token_string.items() if v!=''])
print(url)
```

表示されたURLをGETするとBlobが取得できます。

canonicalizedResourceは**[ここ](https://docs.microsoft.com/ja-jp/rest/api/storageservices/create-service-sas#specifying-the-signature)**を参考にしてください。
token_stringのキーの意味は**[ここ](https://docs.microsoft.com/ja-jp/rest/api/storageservices/create-service-sas)**をご覧ください。必須のものはこれです

- signedversion　(sv、[参考](https://docs.microsoft.com/ja-jp/rest/api/storageservices/versioning-for-the-azure-storage-services))
- signedresource　(sr、Blobかfileのみ、[書き方](https://docs.microsoft.com/ja-jp/rest/api/storageservices/create-service-sas#specifying-the-signed-resource-blob-service-only))
- tablename　(tn、tableのみ、[書き方](https://docs.microsoft.com/ja-jp/rest/api/storageservices/create-service-sas#specifying-the-table-name-table-service-only))
- signedexpiry　(se、[書き方](https://docs.microsoft.com/ja-jp/rest/api/storageservices/formatting-datetime-values)、上のプログラムでは10分間)
- signedpermissions　(sp、[書き方](https://docs.microsoft.com/ja-jp/rest/api/storageservices/create-service-sas#specifying-permissions))
- signature　(sig、上記のsign_stringで生成)

関数はAzure SDK for Pythonを参考にしてます([リンク](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/__init__.py))。

# おわりに

SASや認証に種類があってややこしいと思いました。Pythonでやるとして、azure-storage-blobライブラリが入れられるなら入れたほうが早いと思います。AWS Lambdaから呼び出したくてやってみましたが、Layerの制限に余裕があるなら素直にライブラリを追加しましょう。

間違い等あればご指摘お願いします。

# 参考

- [Azure SDK for Python (azure-storage-blob)](https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/storage/azure-storage-blob/azure/storage/blob)
- [サービス SAS を作成する](https://docs.microsoft.com/ja-jp/rest/api/storageservices/create-service-sas)
- [DateTime 値の書式設定](https://docs.microsoft.com/ja-jp/rest/api/storageservices/formatting-datetime-values)