Azure Container AppsのSecret管理とIaC
こんにちは、sugar-catです。
この記事は、AzureのContainer AppsをTerraformで構築しようとした際に起きた問題とその解決策についてまとめたものです。
※内容自体は以下のスライドの内容を書き起こし、一部加筆したものになっています。
このスライドを作成した当時、TerraformのAzureRM Provider
単体ではAzure Container AppsのSecret管理をセキュアに行うのが困難でした。
しかし現在ではAzure Key Vault Reference
でSecretを取得できるようになりました。
参考:
AzureとTerraform
Azure用のTerraform Providerは主に以下の5つがあります。
- AzureRM: 仮想マシン、ストレージアカウント等の管理
- AzureAD: グループ、ユーザー、サービスプリンシパルの管理
- AzureStack: 仮想マシン、DNS等のStack Hub上のリソースを管理
- AzAPI: Azure Resource Managerを使用し、直接リソースを管理
- AzureDevOps: エージェント、リポジトリ等のリソース管理
HashiCorp製: AzureRM, AzureAD, AzureStack
Microsoft製: AzAPI, AzureDevOps
基本的なリソース管理にはAzureRM
を使用します。可能な限りHashiCorp公式のProviderだけで構築すると、管理が容易になりおすすめです。
AzureRMとAzure Container Apps
Azure Container Appsは2022年5月にGAしたフルマネージドサービスです。
裏はAKSですが、ノードプールの管理やバージョンアップなどは全てマネージドで行われアプリケーションの開発に集中できるような環境を提供しています。
Roadmap:
TerraformでAzure Container Appsを構築する際のコード例は以下の通りです。
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=3.0.0"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_container_app" "example" {
name = "example-app"
container_app_environment_id = azurerm_container_app_environment.example.id
resource_group_name = azurerm_resource_group.example.name
revision_mode = "Single"
template {
container {
name = "examplecontainerapp"
image = "mcr.microsoft.com/k8se/quickstart:latest"
cpu = 0.25
memory = "0.5Gi"
}
}
}
このコードをApplyすると、TerraformはAzureRM Providerを使用して内部でAzure Resource Manager APIを呼び出し、Container Appsを作成します。
Container AppsにおけるSecret管理
アプリケーションの開発が進むと、コンテナ内で環境変数を扱いたい場面が出てきますその際、環境変数を直接入力するか、Secretsを経由して取得するかを選択することになります。
Secretsを扱う場合、Container Apps Secret
またはKey Vault Reference
を使用します。
Terraformでは、基本的にKey Vault Reference
を使用するのが推奨されます。azurerm_container_app
のsecretブロックに直接設定することもできますが、平文でStateに残るため、セキュリティ上の問題があり非推奨です。
Azure Container AppsでのSecret管理パターン
Azure Key Vault Referenceの利用
現状、セキュアでかつ運用が容易なのはKey Vault Referenceを
使用する方法です。使用方法はsecretブロックにManaged Identity
とsecretのID
を指定するだけです。
resource "azurerm_container_app" "test" {
template {
container {
env {
name = "key-vault-secret"
secret_name = "key-vault-secret"
}
}
}
secret {
name = "key-vault-secret"
identity = azurerm_user_assigned_identity.test.id
key_vault_secret_id = azurerm_key_vault_secret.test.id
}
}
ただしこの簡単な設定に至るまでには、以下の手順が必要です。
- Key Vaultの作成
- Key VaultにSecretの登録
- Managed IdentityにKey Vaultへのアクセス権限の付与
- Container AppsにSecretの登録
公式ドキュメントではLegacyなAccess Policyを使用していますが、現在はRBACベースの制御が推奨されているため、RBACで設定します。
- Key Vaultの作成
enable_rbac_authorization = true
を設定することで、Managed Identityによる認証が可能になります。
resource "azurerm_key_vault" "example_kv" {
name = "example"
location = var.example_rg.location
resource_group_name = var.example_rg.name
sku_name = var.key_vault.sku
tenant_id = data.azurerm_client_config.current.tenant_id
enable_rbac_authorization = true
provisioner "local-exec" {
command = "sleep 60"
}
}
- Key VaultにSecretの登録
toSet
を使用して順序性を排除し、lifecycle
ブロックでvalueの変更を無視します。
プロビジョニング後にコンソールやCLIから実際の値を登録します。
resource "azurerm_key_vault_secret" "secrets" {
for_each = toset([for s in var.container_apps.secrets : s.secret_name])
name = each.value
value = "dummy"
key_vault_id = azurerm_key_vault.example_kv.id
depends_on = [
azurerm_role_assignment.example_ca_rbac,
azurerm_role_assignment.example_kv_rbac_current_user
]
lifecycle {
ignore_changes = [
value
]
}
}
- Managed IdentityにKey Vaultへのアクセス権限の付与
最小権限の原則に従い、Key Vault Secrets User
を付与します。
resource "azurerm_role_assignment" "example_kv_rbac_ca_uai" {
principal_id = azurerm_user_assigned_identity.example_ca_uai.principal_id
role_definition_name = "Key Vault Secrets User"
scope = azurerm_key_vault.example_kv.id
}
- Container AppsにSecretの登録
Dynamic Blockを活用して複数のSecretを扱いやすくします。
resource "azurerm_container_app" "example_ca" {
dynamic "secret" {
for_each = azurerm_key_vault_secret.secrets
content {
name = secret.value.name
identity = azurerm_user_assigned_identity.example_ca_uai.id
key_vault_secret_id = azurerm_key_vault_secret.secrets[secret.value.name].id
}
}
}
このようにAzure Key Vault Referenceを利用することで、Secret管理をセキュアに行うことができます。
詳細な実装方法は、Providerのテストコードを見ると理解が深まります。
AzAPIの利用
現在ではSecret管理には不要ですが、AzAPIを併用する方法もあります。azapi_update_resource
を利用して、Resource Manager APIを直接操作します。
resource "azapi_update_resource" "containerapp_secret" {
type = "Microsoft.App/containerApps@2023-05-01"
resource_id = azurerm_container_app.app.id
body = jsonencode({
properties = {
configuration = {
secrets = [
{
name = "some-secret"
keyVaultUrl = "${azurerm_key_vault.some_vault.vault_uri}secrets/some-secret"
identity = azurerm_user_assigned_identity.some_identity.id
}
]
}
}
})
}
Azure Container Appsでは一度登録したSecretを削除できない(!?)ため、十分な検証をせずにSecretを登録するのは注意が必要です。
AzAPIの利用時には注意が必要ですが、既にAzureのProviderとして一級市民扱いされているため、併用しても問題ありません。
参考:
Dapr Componentの利用
Daprのサイドカーを利用してKey VaultからSecretを取得する方法もあります。この方法では、Secretを登録せずにサイドカー経由で動的にSecretを取得します。
resource "azurerm_container_app_environment_dapr_component" "dapr_component_secretstore" {
name = "secretstore"
container_app_environment_id = azurerm_container_app_environment.example.id
component_type = "secretstores.azure.keyvault"
version = "v1"
metadata {
name = "vaultName"
value = azurerm_key_vault.some_vault.name
}
metadata {
name = "spnClientId"
value = azurerm_user_assigned_identity.some_identity.client_id
}
}
app.get('/show-secret', async (req, res) => {
const port = process.env.DAPR_HTTP_PORT ?? daprPortDefault;
const url = `http://${daprHost}:${port}/v1.0/secrets/secretstore/simple-js-secret`;
try {
const secret_response = await fetch(url);
const secret = await secret_response.json();
return res.status(200).send(secret);
} catch (error) {
console.log(error);
}
});
まとめ
Azure Container AppsにおけるSecret管理について、Azure Key Vault Reference、AzAPI、Dapr Componentなどの利用方法を紹介しました。各方法の特徴を理解し、セキュアで効率的なSecret管理を実現しましょう。
参考資料
Discussion