このエントリは2022/04/01現在の情報に基づいています。将来の機能追加や変更に伴い、記載内容との乖離が発生する可能性があります。
以前(かなり前)、以下のようなエントリを記載した。
このエントリでは、アプリケーションをAzure ADに登録してService Principalを作成し、Client IDとClient Secretを使ってKey Vaultにアクセスしていたが、Azure環境で閉じる場合、Managed Identityを使った認証ならパスワードレスなので管理がしやすくかつ安全である。
今回は、
- Function appでメッセージを受け取り、そのデータを暗号化
- 結果を永続領域に格納
- その後別のFunction appで復号して利用
というシナリオを想定して、以下のような構成を考えてみた。

シーケンス
- HTTP triggerのFunction (Func-A) にデータを投入
- Func-Aは受け取ったデータをKey Vaultのキーを使い暗号化
- Func-AからService BusのTopicに暗号化したデータを投入
- Func-AはHTTP 202を返す
- Service Bus triggerのFunction (Func-B) がService Bus TopicのSubscriptionからデータを取得
- Func-Bは受け取ったデータをKey Vaultのキーを使い復号化
- Func-BはService Busのデータ確認用Queueに投入
認証・アクセス管理
Function appのSystem Managed Identityを使って、Service BusとKey Vaultに対するRBACを構成
利用する暗号鍵
暗号鍵はRSAを利用
準備するもの
以下のインスタンスを準備しておく。なお、実装に使う言語は例によってJavaである。なお、この例ではVNetを作成しない。
コンポーネント | 価格レベル | 備考 |
---|---|---|
Key Vault | Standard | HSMを付けても付けなくてもよい 今回はローカルRBACではなく、Azure RBACを使う |
Azure Functions | Consumption | OS : Linux ランタイム : Java 11 |
Service Bus | Standard | 今回はTopicを使うのでStandard以上が必要 Queue、Topicは以下のように構成 kvt1 (Topic、サブスクリプションはs1、m1、後続Function appはs1をサブスクライブする。m1はデータ確認用) kvq1 (Queue、データ確認用) |
ユーザーやManaged Identityに対するRBACの構成
1. Key Vault 管理プレーン
Key Vault管理プレーンではAzure RBACで権限管理する。
2. Key Vaultデータプレーン
暗号化・復号化のために暗号鍵にアクセスする必要があるため、FunctionsのSystem Managed Identityを有効化した上で、System managed identityを使いKey Vaultに対してRBACを構成する。
Key Vaultでは従来のローカルRBACでももちろん可能ではあるが、今回はAzure RBACを利用する。今回必要な権限は、暗号・復号ができればよいので、少なくとも以下の2個のデータプレーン・ロールに対して設定が必要である。
やりたいこと | 必要なロール | 対象 |
---|---|---|
キーの作成・管理 | キー コンテナー管理者 (Key Vault Administrator) または キー コンテナー暗号化責任者 (Key Vault Crypto Officer) | 管理者ユーザー |
キーを使ったデータの暗号・復号 | キー コンテナー暗号化ユーザー (Key Vault Crypto User) | Func-A Func-B |
Azure RBACを使う場合に必要なロールは以下にまとまっている。
Key Vault データ プレーン操作のための Azure の組み込みロール / Azure built-in roles for Key Vault data plane operations
https://docs.microsoft.com/azure/key-vault/general/rbac-guide?tabs=azure-cli#azure-built-in-roles-for-key-vault-data-plane-operations
3. Service Bus
Service Busに暗号化したデータを投入したり、暗号化されたデータを取り出したりするために、FunctionsのSystem Managed Identityを使ってService Busに対するRBACを構成する。サービスとそのロールは以下の通り。RBACはQueueやTopicのSubscriptionに対してでも、Service Bus名前空間全体に対してでもよいが、権限付与スコープを最小にするならQueueやTopicのSubscriptionに付与することを推奨する。
サービス | ロール | 対象 |
---|---|---|
Topic | Azure Service Bus のデータ受信者 | Func-B (Topic) |
TopicとQueue | Azure Service Bus のデータ送信者 | Func-A (Topic) Func-B (Queue) |
なお、以下のドキュメントに記載の通り、Azure Portalからは現時点ではSubscriptionに対するRBACを構成できないので、Azure CLIを使うなどする必要がある。
キュー、トピック、またはサブスクリプション:ロールの割り当ては、特定の Service Bus エンティティに適用されます。 現在、Azure portal では、サブスクリプション レベルでの Service Bus Azure ロールへのユーザー、グループ、マネージド ID の割り当てはサポートされていません。
Queue, topic, or subscription: Role assignment applies to the specific Service Bus entity. Currently, the Azure portal doesn’t support assigning users/groups/managed identities to Service Bus Azure roles at the subscription level.
リソースのスコープ / Resource scope
https://docs.microsoft.com/azure/service-bus-messaging/service-bus-managed-service-identity#resource-scope
Azure CLIを使う場合、以下のようなコマンドを使う。この例では読み取りFunctionのSystem Managed Identityに対して、Topic subscriptionへのData Receiverロールを付与している。
# Data Owner: 090c5cfd-751d-490a-894a-3ce6f1109419
# Data Sender: 69a216fc-b8fb-44d8-bc22-1f3c2cd27a39
# Data Receiver: 4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0
# Receiver role is granted to function app
service_bus_role=4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0
assignee_id={function appのApplication ID}
$ az role assignment create \
--role $service_bus_role \
--assignee $assignee_id \
--scope /subscriptions/$subscription_id/resourceGroups/$resource_group/providers/Microsoft.ServiceBus/namespaces/$service_bus_namespace/topics/$service_bus_topic/subscriptions/$service_bus_subscription
また、ローカル認証(これまでの接続文字列を使うしくみ)は無効にするとIDベース認証のみになるため、セキュリティが向上するのは確かではあるが、旧来のService Bus ExplorerではIDベース認証に対応していないため、これを引き続き使っている(使う予定がある)場合、ローカル認証は有効にしておかざるを得ない。現在PreviewのAzure PortalのService Bus Explorerは両方の認証に対応している。
Function appの実装
Maven依存関係は前回のエントリと同じ(以下は2022/04/01現在の最新)。
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-security-keyvault-keys</artifactId>
<version>4.4.0-beta.7</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-messaging-servicebus</artifactId>
<version>7.7.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.5.0-beta.2</version>
</dependency>
方針は以下。
書き込み (Func-A) | 読み取り (Func-B) |
---|---|
– HTTP Triggerで動作 – Key Vaultにアクセスし、HTTP bodyを暗号化 – Service Bus Topic (kvt1) に出力 | – Service Bus Trigger (Topic) で動作 – Key Vaultにアクセスし、Service BusのTopicから取り出した値を復号 – データ確認のため、Service Bus Queue (kvq1) に出力 |
Service Busの接続構成はTrigger/OutputBindingの注釈で設定できる。具体的には以下。
@FunctionName("http2sb")
public HttpResponseMessage run(
@HttpTrigger(
name = "req",
methods = {HttpMethod.POST},
authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<String>> request,
@ServiceBusTopicOutput(
name="res",
topicName = "kvt1",
connection = "sbConnection",
subscriptionName = "s1"
) OutputBinding<Payload> message,
final ExecutionContext context) {...}
@FunctionName("topic2Queue")
public void run(
@ServiceBusTopicTrigger(
name = "req",
topicName = "kvt1",
subscriptionName = "s1",
connection = "sbConnection") Payload payload,
@ServiceBusQueueOutput(
name = "res",
queueName = "kvq1",
connection = "sbConnection") OutputBinding<String> output,
final ExecutionContext context) {...}
connection(上記の例ではsbConnectionが値として設定されている)は、アプリケーション設定でService BusのFQDNを取得するための接頭辞として使われる、とドキュメントに記載がある。
ID ベースの接続 / Identity-based connections
https://docs.microsoft.com/azure/azure-functions/functions-bindings-service-bus-trigger?tabs=in-process%2Cextensionv5&pivots=programming-language-java#identity-based-connections
具体的には、sbConnection__fullyQualifiedNamespace
のsbConnection
が上記connection
の値として設定されているので、これを使い取り扱うService Bus名前空間を識別している。

Key VaultへのアクセスではBindingやTriggerを使わないので、明示的にSDKを使ってアクセスする必要がある。今回はManaged Identityを使うため、credentialはDefaultAzureCredentialBuilder().build()
を使えばよく、ユーザー・パスワードといった資格情報を指定する必要はない。
暗号化・復号化のためのKEY_IDだけは、アプリケーション設定で指定する必要があるが、これは通常の環境変数から取得するお作法と同じでよい。暗号化・復号化のメソッド呼び出しの引数として、暗号化対象の文字列などを指定するが、文字列としてではなく、byte配列として渡す必要がある点に注意する必要がある。
なお、今回は同期クライアントを使っているが、もちろん非同期クライアント (CryptographyAsyncClient
) を使ってもかまわない。
// keyId stands for "key identifier", like https://{key_container}.vault.azure.net/keys/{key_name}/{version}
CryptographyClient cryptoClient = new CryptographyClientBuilder()
.credential(new DefaultAzureCredentialBuilder().build())
.keyIdentifier(keyId)
.buildClient();
byte[] rawBytes = body.getBytes(StandardCharsets.UTF_8);
EncryptResult encryptResult = cryptoClient.encrypt(EncryptionAlgorithm.RSA_OAEP, rawBytes);
byte[] cipherText = encryptResult.getCipherText();
DecryptResult decryptResult = cryptoClient.decrypt(EncryptionAlgorithm.RSA_OAEP, cipherText);
rawBytes = decryptResult.getPlainText();
今回のコードは以下のリポジトリのKeyVaultにある。