IDベースの認証でAzure Key VaultとService Busを使う

このエントリは2022/04/01現在の情報に基づいています。将来の機能追加や変更に伴い、記載内容との乖離が発生する可能性があります。

以前(かなり前)、以下のようなエントリを記載した。

このエントリでは、アプリケーションをAzure ADに登録してService Principalを作成し、Client IDとClient Secretを使ってKey Vaultにアクセスしていたが、Azure環境で閉じる場合、Managed Identityを使った認証ならパスワードレスなので管理がしやすくかつ安全である。

今回は、

  • Function appでメッセージを受け取り、そのデータを暗号化
  • 結果を永続領域に格納
  • その後別のFunction appで復号して利用

というシナリオを想定して、以下のような構成を考えてみた。

シーケンス

  1. HTTP triggerのFunction (Func-A) にデータを投入
  2. Func-Aは受け取ったデータをKey Vaultのキーを使い暗号化
  3. Func-AからService BusのTopicに暗号化したデータを投入
    • Func-AはHTTP 202を返す
  4. Service Bus triggerのFunction (Func-B) がService Bus TopicのSubscriptionからデータを取得
  5. Func-Bは受け取ったデータをKey Vaultのキーを使い復号化
  6. Func-BはService Busのデータ確認用Queueに投入

認証・アクセス管理

Function appのSystem Managed Identityを使って、Service BusとKey Vaultに対するRBACを構成

利用する暗号鍵

暗号鍵はRSAを利用

準備するもの

以下のインスタンスを準備しておく。なお、実装に使う言語は例によってJavaである。なお、この例ではVNetを作成しない。

コンポーネント価格レベル備考
Key VaultStandardHSMを付けても付けなくてもよい
今回はローカルRBACではなく、Azure RBACを使う
Azure FunctionsConsumptionOS : Linux
ランタイム : Java 11
Service BusStandard今回は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に付与することを推奨する。

サービスロール対象
TopicAzure Service Bus のデータ受信者Func-B (Topic)
TopicとQueueAzure Service Bus のデータ送信者Func-A (Topic)
Func-B (Queue)

なお、以下のドキュメントに記載の通り、Azure Portalからは現時点ではSubscriptionに対するRBACを構成できないので、Azure CLIを使うなどする必要がある。

キュートピック、またはサブスクリプション:ロールの割り当ては、特定の Service Bus エンティティに適用されます。 現在、Azure portal では、サブスクリプション レベルでの Service Bus Azure ロールへのユーザー、グループ、マネージド ID の割り当てはサポートされていません。

Queuetopic, 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__fullyQualifiedNamespacesbConnectionが上記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にある。

https://github.com/anishi1222/ID-based-connection

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中