Private Endpointを使用してAzureFunctionからCosmosDBへセキュアに接続する
前提
概要
本記事ではAzureFunctionとCosmosDBをPrivateEndpointを使ってセキュアに接続する方法をまとめています。
個別リソースの詳細説明などはしていないので細かい部分が知りたい方は公式ドキュメント等をご参照ください。
今回作成するリソースの構成図
AzureFunctionのVNet統合機能を使って、VNet内にあるCosmosDBのPrivateEndpointにセキュアにアクセスします。
CosmosDBへのリクエストはPrivateEndpoint経由のみで行うことが可能で、パブリックなアクセスは一切拒否します。
Azure FunctionはHttpTriggerのものを使用し、実装はTypescriptで行います。
ネットワークの準備
1. VNetの作成
まずはセキュアに通信を行うための仮想ネットワークであるVNetを作成する必要があります。
使用している環境のリソースグループに紐付けてVNetを作成します。
その他の設定項目はデフォルトにしていますが、要件に合わせて適宜ご設定ください。
2. サブネットの作成
次に、作成したVNet内にサブネットを作成します。
先ほどの構成図を見てみましょう。
AzureFunctionからCosmosDBにセキュアにアクセスするためには、
- AzureFunctionのVNet統合用のサブネット
- CosmosDBのPrivateEndpoint用のサブネット
の2種類が必要です。
これらのサブネットを作成していきます。
これでネットワークの準備ができました。
CosmosDBリソースとprivate endpointの準備
1. CosmosDBアカウントの作成
まずは、CosmosDBアカウントの作成をします。
APIはコア(SQL)を選択し、JapanEastにデプロイするようにします。
次にネットワークの設定をします。
接続方法にはプライベートエンドポイントを選択し、
ファイアフォールの構成としては、AzurePortal上のアクセスを許可しておきます。
プライベートエンドポイントの作成もここで行えます。
先ほど作成したVNet, subnetに紐づけて、
プライベートDNSゾーンと統合する、を選択します。
これがないとプライベートエンドポイントの名前解決が行えず、プライベートな接続が行えなくなってしまいます。
ここでプライベートエンドポイントの作成をしていなくても、
「作成したCosmosDBアカウント」=> 「ネットワーク」 => 「プライベートアクセス」
から設定が可能です。
その他ここで触れていない項目は特に要件がなければデフォルトのままでOKです。
2. データベース、コンテナの作成
アカウントの作成が完了した後はデータベース、コンテナの作成を行います。
「作成したCosmosDBアカウント」=> 「概要」 => 「コンテナの追加」からこれらの作成が行えます。
各種項目は要件に沿うものを設定してください。
ここでは、一番安価な手動プロビジョニング(Manual)の400RUでデータベースを作成します。
Azure Functionの準備
1. Azure Functionリソースの作成
Azure FunctionからVNet内のリソースにアクセスするには、VNet統合という機能を利用する必要があります。
この記事執筆時点では、この機能を使えるのは
PremiumプランまたはDedicatedプラン*です。
試しに使ってみるリソースとしてはPremiumプランは高いので、Dedicatedプランを選択します。
Dedicatedプランを使用するには、「ホスティングオプションとプラン」の部分でApp Serviceプランを選択します。
Appプランを作成していなければここで新規作成することになりますが、既存のものがあればそちらを指定しても問題ありません。
この記事を見ながらお試しで作成されている方は、価格プランを「Basic B1」にしておくことをお忘れなく。
※1枚目と2枚目で違うOSを指定していますが、ここではLinuxを選択しました。
また、本記事ではtypescriptを使用予定なので実行環境にNodeを指定していますが、
使用する言語に合わせて適宜設定してください。
2. Azure Function用テンプレートの作成
Azure Functionリソースの準備が整ったので、今度はCosmosDBに接続するためのサンプルコードをFunctionにデプロイします。
Function用のテンプレートはfunction cliで作成できます。
前述の前提の部分で関連ドキュメントを記載しているので、インストールが済んでいない方はそちらを参照してください。
function cliの準備が揃ったら下記を実行しましょう。
func new --name Test --template "HTTP Trigger" --typescript
今回は、httpsリクエストを送信して、動作するfunctionが使いたいので、"Http Trigger"を指定しています。
ちなみに使用できるテンプレート一覧は下記で一覧できるので、
他のトリガーを使いたい場合は適宜ここから選択してください。
func templates list
テンプレートを作成したら、npm installを忘れずに実行しましょう。
npm install
また、初期テンプレートだとtsconfig内にstrict: false
が指定されてしまうので、
strict: true
に変更することをお勧めします。
3. 動作確認
作成したテンプレートをデプロイして動作確認を行います。
Functionのデプロイはfunction cliから行えます。
ビルドしてデプロイコマンドを毎回打ち込むと手間なので、package.jsonに"deploy"コマンドを追加します。
{
...,
"scripts": {
...,
"deploy": "npm run build && func azure functionapp publish cosmosdb-connection-test"
}
...,
}
cosmosdb-connection-testの部分はデプロイ対象のFunctionリソース名に変更してください。
また、function cliでデプロイを行うためには、
デプロイ対象のFunctionリソースが存在しているサブスクリプションを設定しておく必要があります。
az login # azureへのログイン
az account list # サブスクリプション一覧を表示
az account set --subscription azure_subscript_for_study # サブスクリプションの設定
az account show # サブスクリプションが正しく設定されているか確認
それでは、追加したデプロイコマンドを実行します。
npm run deploy
デプロイが実行できたらAzure Portal上で、対象のFunctionリソースを確認してみます。
すると、無事、Testが作成できるのが確認できます。
※ 時間が経っても反映されない場合は、何度かデプロイし直してみてください。
デプロイしたエンドポイントが正しく動作しているか確認してみましょう。
なお、HTTP Trigger Functionには下記3つの認証レベルがあり、デフォルトではfunctionとなっています。
認証レベル | 説明 |
---|---|
anonymous | 誰でもアクセス可能 |
function | アクセスにAPIキーが必要 |
admin | アクセスにMasterキーが必要 |
functionレベルのアクセスにはAPIキーが必要なので、
「デプロイしたFunction」=> 「関数キー」で確認しましょう。
デフォルトでキーが一つ用意されているので今回はそれを使います。
関数キーの値がコピーできたらcurlでFunctionのエンドポイントを叩きます。
curl -H 'x-functions-key: xxxxxx' https://cosmosdb-connection-test.azurewebsites.net/api/Test
※ urlはhttp://<APP_NAME>.azurewebsites.net/api/<FUNCTION_NAME>で指定する
x-functions-keyには先ほどコピーしたAPIキーを指定してください。
うまくいけば下記のレスポンスが返ってくきます。
This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.
これでデプロイしたFunctionが無事、動作していることが確認できました。
4. CosmosDBへの参照/書き込みを追加する
次に、このFunctionからCosmosDBの参照/書き込みが行えるようにします。
cosmos dbに書き込みを行うため**@azure/cosmos**というライブラリをインストールします。
npm i @azure/cosmos
Test/index.ts
を下記のように修正します。
import { AzureFunction, Context, HttpRequest } from "@azure/functions"
import { CosmosClient } from "@azure/cosmos"
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
try {
context.log('HTTP trigger function processed a request.');
const key = process.env.COSMOS_KEY
const endpoint = process.env.COSMOS_ENDPOINT
const databaseId = process.env.COSMOS_DB_ID
const containerId = "test_container"
if (key == null || endpoint == null || databaseId == null) {
throw Error("CosmosDBへの接続情報が正しく設定されていません。")
}
const client = new CosmosClient({ endpoint, key })
const container = client.database(databaseId).container(containerId)
if (req.method === "POST") {
const { id, name } = req.body
if (id == null || name == null) {
context.res = {
status: 400,
body: "idまたはnameの値が正しく入力されていません。"
}
}
const { resource } = await container.items.create({ id, name })
context.res = {
resource
}
} else if (req.method === "GET") {
const { resources } = await container.items.query("SELECT * FROM c").fetchAll()
context.res = {
body: resources
}
} else {
context.res = {
status: 405,
body: "無効なHTTPメソッドが指定されました。"
}
}
} catch (e) {
context.res = {
status: 500,
body: e
}
}
};
export default httpTrigger;
色々書き足していますが、重要なのは
- POSTリクエストが来たら、idとnameを受け取ってCosmosDBへ書き込む
- GETリクエストが来たら、container内のitem一覧を取得する
という部分です。
また、少し雑ですが、debugしやすいように本文中に何かエラーが起きたら、catchして500レスポンスとエラー内容を返すようにしています。
修正ができたら、変更をデプロイし直します。
npm run deploy
また、上記コードを見れば分かる通り、今回CosmosDBとの接続用に下記の環境変数を追加しています。
環境変数名 | 説明 |
---|---|
COSMOS_KEY | cosmosdbアカウントのPrimaryKey |
COSMOS_ENDPOINT | cosmosdbアカウントのurl。PrivateLinkへのDNS解決は自動で行われるので、https://<cosmosdbアカウント>.documents.azure.com:443のようなパブリックurlの指定で良い。 |
COSMOS_DB_ID | 作成したcosmosdbのID。今回でいう"test" |
これらを「対象Function」 => 「構成」から追加していきます。
設定し終わったら保存するのを忘れないようにしましょう。
再度、先ほどのGETリクエストを送ってみます。
curl -H 'x-functions-key: xxxxxx' https://cosmosdb-connection-test.azurewebsites.net/api/Test
すると下記のエラーメッセージを含むレスポンスが返ってきます。
Request originated from IP [FunctionのパブリックIP] through public internet
パブリックなネットワークからCosmosDBにアクセスしようとしているよ、と怒られているわけです。
5. VNet統合を設定
前述の通りプライベートネットワークからCosmosDBのprivate endpointにアクセスするためには、VNet統合をする必要があります。
「対象のFunctionリソース」=>「ネットワーク」からVNet統合の設定を行いましょう。
VNetやsubnetはすでに作成済みのものを選択します。
次に下記の環境変数を設定します。
環境変数名 | 設定する値 | 説明 | 参考資料 |
---|---|---|---|
WEBSITE_DNS_SERVER | 168.63.129.16 | アプリケーションによって名前解決時に使用されるDNSサーバーの設定。 AzureのDNS serverのアドレスを明示することでプライベートなリソースの名前解決を行えるようにする。 |
https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-app-settings#website_dns_server |
vnetrouteallenabled | 1 | 全てのトラフィックをVNet経由でルーティングするための設定。 従来まではWEBSITE_VNET_ROUTE_ALLという名前だったがこちらは非推奨になった。 |
https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-app-settings#vnetrouteallenabled |
最終の動作確認
これで、全ての設定が完了したので最終動作確認に入りましょう。
まず、何度か出てきたcurlリクエストを送信します。
curl -H 'x-functions-key: xxxxxx' https://cosmosdb-connection-test.azurewebsites.net/api/Test
すると、空の配列が返ってきます。
[]
うまく、cosmosdbにアクセスできていそうですね。
今度はPOSTリクエストを投げて、リソースを作成してみます。
curl -X POST https://cosmosdb-connection-test.azurewebsites.net/api/Test \
-H 'x-functions-key: xxxxxx' \
-H 'Content-Type: application/json' \
-d '{"id": "1", "name": "test_item_1"}'
リソースが正しく作成されたか確認するため、再度GETリクエストを投げてみます。
curl -H 'x-functions-key: xxxxxx' https://cosmosdb-connection-test.azurewebsites.net/api/Test
すると、下記のように正しく作成されたリソースの情報が返ってくるはずです。
[
{
"id": "1",
"name": "test_item_1",
"_rid": "JIctAJCzAHYBAAAAAAAAAA==",
"_self": "dbs/JIctAA==/colls/JIctAJCzAHY=/docs/JIctAJCzAHYBAAAAAAAAAA==/",
"_etag": "\"08004721-0000-2300-0000-64f45a930000\"",
"_attachments": "attachments/",
"_ts": 1693735571
}
]
これで、Azure Functionからプライベートなネットワークを通じてセキュアにCosmosDBへの接続を行うことができました。
Discussion