🔑

Azure Container AppsのSecret管理とIaC

2024/08/16に公開

こんにちは、sugar-catです。

この記事は、AzureのContainer AppsをTerraformで構築しようとした際に起きた問題とその解決策についてまとめたものです。
※内容自体は以下のスライドの内容を書き起こし、一部加筆したものになっています。

https://speakerdeck.com/sugarcat7/azure-container-apps-secretguan-li-toiac

このスライドを作成した当時、TerraformのAzureRM Provider単体ではAzure Container AppsのSecret管理をセキュアに行うのが困難でした。
しかし現在ではAzure Key Vault ReferenceでSecretを取得できるようになりました。

参考:
https://github.com/hashicorp/terraform-provider-azurerm/issues/23668

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したフルマネージドサービスです。
https://azure.microsoft.com/ja-jp/products/container-apps

裏はAKSですが、ノードプールの管理やバージョンアップなどは全てマネージドで行われアプリケーションの開発に集中できるような環境を提供しています。
Roadmap:
https://github.com/orgs/microsoft/projects/540

TerraformでAzure Container Appsを構築する際のコード例は以下の通りです。

sample.tf
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を作成します。
alt text

Container AppsにおけるSecret管理

アプリケーションの開発が進むと、コンテナ内で環境変数を扱いたい場面が出てきますその際、環境変数を直接入力するか、Secretsを経由して取得するかを選択することになります。
alt text

Secretsを扱う場合、Container Apps SecretまたはKey Vault Referenceを使用します。
Terraformでは、基本的にKey Vault Referenceを使用するのが推奨されます。azurerm_container_appのsecretブロックに直接設定することもできますが、平文でStateに残るため、セキュリティ上の問題があり非推奨です。

alt text

Azure Container AppsでのSecret管理パターン

Azure Key Vault Referenceの利用

現状、セキュアでかつ運用が容易なのはKey Vault Referenceを使用する方法です。使用方法はsecretブロックにManaged IdentitysecretのIDを指定するだけです。

sample.tf
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
  }
}

ただしこの簡単な設定に至るまでには、以下の手順が必要です。

  1. Key Vaultの作成
  2. Key VaultにSecretの登録
  3. Managed IdentityにKey Vaultへのアクセス権限の付与
  4. Container AppsにSecretの登録

公式ドキュメントではLegacyなAccess Policyを使用していますが、現在はRBACベースの制御が推奨されているため、RBACで設定します。
https://learn.microsoft.com/ja-jp/azure/container-apps/manage-secrets?tabs=azure-portal#reference-secret-from-key-vault

  1. Key Vaultの作成

enable_rbac_authorization = trueを設定することで、Managed Identityによる認証が可能になります。

sample.tf
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"
  }
}
  1. Key VaultにSecretの登録

toSetを使用して順序性を排除し、lifecycleブロックでvalueの変更を無視します。
プロビジョニング後にコンソールやCLIから実際の値を登録します。

sample.tf
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
    ]
  }
}
  1. Managed IdentityにKey Vaultへのアクセス権限の付与

最小権限の原則に従い、Key Vault Secrets Userを付与します。
https://learn.microsoft.com/ja-jp/azure/key-vault/general/rbac-guide?tabs=azure-cli#azure-built-in-roles-for-key-vault-data-plane-operations

sample.tf
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
}
  1. Container AppsにSecretの登録

Dynamic Blockを活用して複数のSecretを扱いやすくします。

sample.tf
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のテストコードを見ると理解が深まります。

https://github.com/hashicorp/terraform-provider-azurerm/blob/1b6e07fc03daf3671216720cac603e4edb2c5d91/internal/services/containerapps/container_app_resource_test.go#L745

AzAPIの利用

現在ではSecret管理には不要ですが、AzAPIを併用する方法もあります。azapi_update_resourceを利用して、Resource Manager APIを直接操作します。

sample.tf
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として一級市民扱いされているため、併用しても問題ありません。

参考:
https://speakerdeck.com/torumakabe/an-toazuretoterraform-2024xin-chun-baziyon?slide=6

Dapr Componentの利用

Daprのサイドカーを利用してKey VaultからSecretを取得する方法もあります。この方法では、Secretを登録せずにサイドカー経由で動的にSecretを取得します。

dapr_component.tf
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.js
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管理を実現しましょう。

参考資料

https://speakerdeck.com/torumakabe/an-toazuretoterraform-2024xin-chun-baziyon
https://www.docswell.com/s/ussvgr/ZEN271-love-aca#p1
https://www.docswell.com/s/ussvgr/5VDXX5-aca-dapr_secrets-api_managed-id

AI Shift Tech Blog

Discussion