閉域化したBlobStorageにAzure Functionsからアクセスしてデータを取得する

# はじめに
完成形はあくまでも半閉域化レベルです。
（そもそも記事書いてる本人が閉域化の学習中なので詳しい人いたら教えてほしいです）

前回、Azure FunctionsからBlob Storageにアクセスする記事を書きました。

https://qiita.com/t-sho/items/d1e9a50ba32767232da3

ただし、この構成はパブリックアクセスに対して制限をかけていません。
もちろん、Functionsは認証レベルをFUNCTIONにしているので、エンドポイント以外に関数キー等のキー情報が必要ですし、Blob Storageも匿名アクセスは許可していないので、接続文字列などのキー情報が必要です。
ただ、キー情報が漏れてしまえば世界中どこからでもアクセス可能です。
前回の記事の状態はざっくり書くとこんな感じかなと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/e20f2cb4-6054-76dd-9fde-38b71f0482ca.png)


企業内で利用するにはこれだとちょっとセキュリティ的にNGなことがあるので、今回は以下のように設定します。

* Blob Storage：パブリックアクセスを無効にしてプライベートエンドポイントを使ってアクセスする
* Functions：サービスエンドポイントで会社のグローバルIPのみ許可するようにする

# リソースの作成
## 仮想ネットワークを作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/b3012518-2c12-d258-0422-4c8a65f87f5c.png)

適当な名前で作成します。
セキュリティタブは今回はデフォルトのままにして、アドレス空間もデフォルトのまま（無駄が多いですが）にし、サブネットを追加します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/160aa802-f787-a94f-f5ac-7f91090fcb8f.png)

追加したサブネットの情報はこんな感じ（これも今回はデフォルトのまま）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/18a8eb8c-ee27-52e7-ee90-5e06a730e05c.png)
追加後はそのままリソースを作成する

## Blob storageを作成する
前回記事書いたときと同じ構成で作成します。基本的に名前決めたらあとはデフォルトのままでよいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/0c009e25-c8a6-ebc8-163c-e10d41cdccb4.png)

匿名アクセスのチェックがOFFになってることだけはちゃんと確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/5d3e8169-d37e-1880-1e08-4f2b9c3b0f88.png)

リソース作成後は前回記事と同様に「faq」というコンテナを作成しファイルをアップする。
※ファイルの中身については冒頭に貼ってる前回記事を参照してください。（全く同じなので）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/53bb46a8-ea05-fb0d-099f-4a3ef36a2a8b.png)

## Functionsを作成する
関数アプリを作成する際は、今回はApp Serviceプランを選択します。
flex Consumptionプランはこの後補足しますが、基本的にpremiumより右側しか仮想ネットワーク対応してないです。ただ、premiumより右側は正直インスタンスが常時稼働しているので、Serverlessとしてのうまみが薄れてしまうのが難点なんですよね（2024/5/22までは）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/37a61f46-6750-b1b8-59d8-a4de69e6e941.png)


### 補足：Flex Consumptionの登場
日本時間で2024年5月22日から始まったMicrosoft BuildのイベントでFlex Consumptionプランが発表されました。このプランでは仮想ネットワークがサポートされたことで、上記で記載していたServerlessのうまみが薄れてしまっていたところが解消されます。
現在パブリックプレビュー段階で、残念ながら日本リージョンでリソースを作成することが出来ませんが、この記事を読んでる方が「データレジデンシー(データをホストする物理的な場所)」を気にしない場合はFlex Consumptionを選ぶとよいです

pythonの環境として作成していきます。名前は適当に決めてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/e40d8593-eb38-0a2b-0b6a-6f20155468f2.png)

価格プランは新規に作成し、B1を選択しておきます。選択後はStorageタブへ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/aa42e4e6-32b5-12b1-ee97-74f2cbf12da2.png)

Function用のストレージも新規に作成しておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/84533f42-c161-7754-c247-e7351dca0a24.png)

次のネットワークも今の時点ではパブリックアクセスONで進めます（あとで制限入れます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/b3a1cec7-5c20-695a-c3b9-d6c1cf9cf184.png)

次の監視タブはInsightsを有効にしておきます（新規に作成します）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/873edb41-9f0b-f883-278e-1d451fc53607.png)

デプロイタブはデフォルトのまま進めます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/1a4d9090-dd7b-10f8-6f5e-43638b1480a7.png)

リソースを作成しましょう

# 関数を実装する
これは前回記事と同じ内容なので、下記を参照して実装し、デプロイしてください
※環境変数の設定も忘れずに

https://qiita.com/t-sho/items/d1e9a50ba32767232da3

Functionにデプロイし、テスト実行してBlobに保存したファイルが読み取れたら、ここから半閉域化をしていきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/ee60b36b-e848-a4dc-06f5-8be7ceeb4c5e.png)

# Blob Storageのパブリックアクセスを無効にする
ストレージアカウントにアクセスし、ネットワークの設定を開き、パブリックアクセスを「無効」に変えて保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/198aba43-bc6b-a330-8fea-ee196da2651d.png)

これによってインターネットを経由したストレージへのパブリックなアクセスは完全にブロックされました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/e3a3610d-1d39-28c6-24bb-77dd7afd4c87.png)
試しに先ほどの関数をもう一度テスト実行してみるとエラーになることがわかります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/a269f810-1829-39e6-5555-28c9404cdd1e.png)


# Blob Storageのプライベートエンドポイントを作成する
先ほどのストレージのネットワーク設定で、プライベートエンドポイント接続タブを開き、プライベートエンドポイントを追加していきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/cd3fe2a1-082e-4e0b-1c8d-d2c285d6424e.png)

名前とかは適当に入れて、リソースをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/47ffa836-4657-bfd9-2d5c-8c9782bbd1da.png)

次のリソースでは対象サブリソースで「blob」を選択して次に進みます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/429eb8e9-230d-261b-8929-abe2e6d6213e.png)

次の仮想ネットワークでは、作成した仮想ネットワークのdefaultサブネットを指定して次に進みます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/66c6a5d6-37a1-9d43-fe98-6d453d6cfaeb.png)

次のDNSでは、プライベートDNSゾーンと統合する設定で進めます。設定値はデフォルトのままで良いです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/454594e4-c2d0-d7bf-0158-3cfe23770dfe.png)

これによって、プライベートエンドポイントように割り振られたIPアドレスと、先ほど入力して決めたプライベートエンドポイントのリソース名を紐づけるための名前解決を自動でやってくれるようになります。

次のタグは特に設定不要なので、そのままプライベートエンドポイントを作成してしまします。
リソースができるとこんな感じでプライベートエンドポイントが追加されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/4b06d678-a7f5-f512-b1b7-3ed6566d4cb5.png)

現在の構成はこんな形です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/36f4b2ba-bdcf-a3c6-3dbb-09db5a067092.png)

# Functionの受信トラフィックを制限する
今回はグローバルIPアドレスを指定する形で、指定したIP以外からのアクセスをブロックします。
いわゆるサービスエンドポイントによる制限です。

Functionのネットワークの設定画面を開き、公衆ネットワークアクセスの「アクセス制限なしで有効」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/029dbe01-adc2-f183-6d4f-b213b54ec788.png)

* 公衆ネットワークアクセス：選択した仮想ネットワークとIPアドレスから有効
* 一致しないルールのアクション：拒否

に設定し、追加アイコンからIPアドレスを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/532cd9a3-7f92-d722-4722-ba6070b1cbe3.png)

* 名前：適当に入れる
* アクション：許可
* 優先度：100
* 種類：IPv4
* IPアドレスブロック：自身が使っているネットワークのグローバルIPアドレス

を指定して追加します（他の項目はデフォルトのまま）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/d20a8788-2ca4-0b19-d973-64c2a8a55ba9.png)

リストに追加されたら忘れず保存ボタンをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/898f9237-8079-22a6-d5f8-62af9b0f54f6.png)

これによってFunctionへの社外IPからのアクセスがブロックされました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/1a46dcba-0424-ea57-d7a6-244200e2c738.png)

# FunctionのVNet統合を実施する
通常、Functionからの送信トラフィックはインターネットに向けて流れていくようになっていますが、VNet統合を行うことで、仮想ネットワークに向けて送信トラフィックを流すことで、仮想ネットワーク内のリソースにアクセスすることが出来るようになります。（あくまでもFunctionからの送信トラフィックの制御を行うためのものです）

Functionのネットワークの設定を開き、送信トラフィックの構成のところにある仮想ネットワーク統合の「未構成」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/4d9a329f-68e0-8a7e-beb8-60e0da481e47.png)

仮想ネットワーク統合の追加をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/af4bbe65-a8ca-c19c-8890-43b21af52e3a.png)

作成したVnetのVNetIntegrateのサブネットを選択し、接続をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/bc9ce079-8184-e7fb-c116-f44c75a1e520.png)

これによってFunctionから仮想ネットワーク経由でストレージにアクセスし情報を取得できるようになりました
雑なイメージですが、こんな形です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/cd3fbc2a-87e2-96f2-9872-9676bc9bf352.png)

# 動作確認
## 外部からアクセスしてエラーになることを確認
コマンドプロンプトから関数を実行してみる

```powershell
curl -X POST "<your function url>" -H "Content-Type: application/json" -d ""
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/dbfd98e4-584f-7396-cc57-503efa8cb778.png)


## 許可されたネットワークからアクセスして成功することを確認
同じコマンドを実行してエラーにならず情報が取得できることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261327/94b5549b-650e-28d5-4c84-43b9b58a1a04.png)