このエントリは2021/11/29現在の情報に基づいています。将来の機能追加や変更に伴い、記載内容との乖離が発生する場合があります。
問い合わせ
例によって、とある人から以下のような問い合わせがあった。
Blob storageに外部からファイルをアップロードしてもらおうと考えている。確かにPublic previewでSFTPアクセスも始まっているが、今回プロトコルをすべてHTTPベースにしているため、SFTPを使いたくない。また、Blob storageをパブリックネットワークに公開したくない。そのため、利用者の制限やDDoS攻撃などからの保護を見据え、ゲートウェイとしてAPI Managementを挟んでBlob storageにファイルを投入したいのだが、そういうことは実現可能か?
仕組み自体は非常にシンプルである。図示すると以下のよう。

環境で注意すべきことは以下のあたり。
- API ManagementはBasic SKUのインスタンスなので、VNet内に配置できない。
- 対象のBlob storage(Blobコンテナー)は、1個のストレージアカウントに紐付く。この段階では1個のコンテナーだが、将来複数コンテナーを対象にしなければならない可能性もある。
- Blob storageはVNetとすでにPrivate Linkを構成済みである。
- Blob storageのアクセスキーを使ってAPI Managementからアクセスすることは罷り成らない(SASキーはOK)
考慮すべきこと
コンテンツの取り扱い
ファイル(テキスト、バイナリとも)やJSON自体もblobとして投入することはそれほど難しくはない。エンコードした形式で受け入れることも可能だが、バックエンドサービスがそのまま受け入れないのであれば、結局API Managementでデコードせざるを得ず、ちょっと面倒。というわけで、今回はバイナリやテキストなどもアップロードしたいので、Content-Typeを正しく指定してそのまま送信することにする(もちろんどうしようもない場合は、application/octet-stream
を使う場合もある)。
バックエンドサービスの呼び出し
バックエンドサービスとして利用するBlob storage REST APIでは認証が必要であり、OAuth 2.0トークンをどのように取得するかを検討しておく必要がある。もちろん、ユーザー/パスワードをAPI Managementに保持したくないので、できるだけ運用の手間にならない方法を考慮しておきたい。SASトークンを使うこともできるが、その場合、逐一InboundセクションでSASトークンを生成しなければならず、さらにSASトークン自体は認証のしくみではないことから、今回はSASトークンの利用は除外しておく。
方針
以下の方針で実現することにした。
- APIは PUT
/blobs/{blob}
とし、Path parameterでファイル名を指定する。コンテンツはContent-Typeで指定する。 - 作成したAPIではポリシーでRate limitやThrottlingなどを構成しておく。
- APIリクエストに対する認証はAPI Gatewayでトークンをチェックする。必要であれば、API Keyを使って利用承認も構成する。
- Blob storageのREST APIをバックエンドサービスとして利用するAPIをAPI Managementでホストする。
- API ManagementではManaged Identityを生成しておき、Blob storageでRBACを構成する。さらに、Blob storage(ストレージアカウント)では、信頼されたAzureサービスにアクセスを許可しておく。

ストレージアカウントは自身でFirewallを持つが、System Managed Identityを使ってAPI Managementがアクセスする場合、このFirewallを回避できるしくみがある。これはPrivate Linkとは独立しているため、Private LinkでVNetに引き込まれていたとしても影響を受けない。
システム割り当てマネージド ID に基づく信頼されたアクセス / Trusted access based on system-assigned managed identity
https://docs.microsoft.com/azure/storage/common/storage-network-security?toc=%2Fazure%2Fstorage%2Fblobs%2Ftoc.json&tabs=azure-portal#trusted-access-based-on-system-assigned-managed-identity
このしくみは明示的に有効化する必要があるので、ストレージアカウントのFirewallを確認し、まだ有効になっていないようであれば有効にしておく。
- セキュリティとネットワーク > ネットワーク > ファイアウォールと仮想ネットワークを開く
- 【許可するアクセス元】は選択されたネットワーク
- 【例外】で、「信頼されたサービスの一覧にある Azure サービスがこのストレージ アカウントにアクセスすることを許可します。」にチェックを入れる
- 【保存】をクリック

構成上のポイント
APIリクエストの認証は今回は省略する。
バックエンドサービスとして利用するAPI
Azure BlobサービスのREST APIにPut blobというオペレーションがあるので、このオペレーションをバックエンドサービスとして利用する。
Put Blob
https://docs.microsoft.com/rest/api/storageservices/put-blob
Managed Identity
API ManagementでSystem Managed Identityを生成しておく。
- セキュリティ > マネージドID > 【システム割り当て済み】を開く
- 【状態】をオンにする
- 【保存】をクリック

Blob storageでは、このManaged Identityに対するRBACを構成する。このとき、複数のBlobコンテナーに同じ権限を付与したいのであれば、ストレージアカウントに対して構成すると簡単。もしBlobコンテナーごとに割り当て権限が違うのであれば、Blobコンテナーごとに指定する。今回は1個のコンテナーを対象にするので、Blobコンテナーに対して指定する。
- RBACを構成したいコンテナーで、アクセス制御 (IAM) を開く
- 【追加】をクリックして、【ロールの割り当ての追加】を選択

- Managed Identityに付与したい権限を選択する
- 書き込みが必要であれば、ストレージ BLOB データ共同作成者
- 読み取りだけでよければ、ストレージ BLOB データ閲覧者
- 【次へ】をクリック

- 【ロールの割り当ての追加】では
- 【アクセスの割り当て先】では、マネージドIDを選択
- 【+ メンバーを選択する】をクリックすると、右側に画面が現れるので、System Managed Identityを作成したAPI Managementインスタンスを選択し、【選択】ボタンをクリック
- 【次へ】をクリック
- 【レビューと割り当て】をクリック

これで、API ManagementインスタンスにBlob storageへのアクセス権限付与が完了。ストレージに対する設定はこれでおしまい。
バックエンドサービスが必要とするHTTPヘッダー
Put blobを利用するにあたって設定が必要なHTTP Headerはドキュメントに記載がある。
要求ヘッダー (すべての BLOB の種類) / Request Headers (All Blob Types)
https://docs.microsoft.com/rest/api/storageservices/put-blob#request-headers-all-blob-types
このうち、明示的に設定する必要があるものは以下の通り。
HTTP Header | 値 | APIリクエストにある場合 |
---|---|---|
Authorization | バックエンドサービス用のBearer token | |
x-ms-version | 2020-10-02 (2021/11/29現在) | オーバーライド |
x-ms-blob-type | BlockBlob | オーバーライド |
Content-Type | application/octet-stream (デフォルト) | スキップ |
x-ms-blob-content-disposition | attachment; filename=<ファイル名> ファイル名はAPIリクエストでPath parameterで指定したもの | スキップ |
Content-Disposition
をAPIリクエスト時に指定していれば、x-ms-blob-content-disposition
ではContent-Disposition
の内容を使えばよいが、未指定時には作る必要があるので、ポリシー式を使って設定する必要がある。今回の場合、最後の/
以後に現れるPath parameter(ファイル名)を取得し、Set-Headerポリシーを使ってx-ms-blob-content-disposition
を構成している。なお、以下の例ではファイル名を後で使うためにコンテキスト変数に格納しているが、もちろんSet-Headerポリシー内で直接使ってもかまわない。
<set-variable name="blob" value="@((string)context.Request.Url.Path.Split('/').Last())" />
<set-header name="x-ms-blob-content-disposition" exists-action="skip">
<value>@($"attachment; filename=\"{(string)context.Variables["blob"]}\"")</value>
</set-header>
Managed Identityを使った認証ではauthentication-managed-identityポリシーを使うが、リソースは以下の例の通り https://storage.azure.com/ を指定する。
<authentication-managed-identity resource="https://storage.azure.com/" />
これでInboundセクションでの設定はおしまい。
テスト
Blobコンテナー(apim)には何も存在しない状態からスタート。

作成したAPIをPostmanで呼び出す。まず、JSONを入れてみる。ファイル名はtest123.jsonとしておく。

もう一度Blobコンテナーを確認すると、指定したファイル名のBlobが作成されていることがわかる。

ファイルの内容は以下の通り。

続いて、バイナリイメージを送ってみる。今回はIMG_3585.JPGというファイル。

再度Blobコンテナーを確認すると、ファイルが生成されていることがわかる。

中身はこの通り。
