LAC WATCH

セキュリティとITの最新情報

RSS

株式会社ラック

メールマガジン

サイバーセキュリティや
ラックに関する情報をお届けします。

サービス・製品 | 

Vaultの動的シークレットでTerraformのセキュリティレベルを向上させるには

国内でHashiCorp社が提供する「Terraform」や「Vault」に関連する情報はあまり多くありません。そこでエンジニアの皆さんに役立てていただくため、海外で発信している技術情報をお知らせしたいと思います。

TerraformとVaultで実現する動的シークレット

TerraformやAnsibleなどのツールを使ってInfrastructure as Code(IaC)を実現する際には、クラウドにログインするためのクレデンシャル(ログインIDとパスワードなど)の管理が課題になります。クラウドにログインするためのクレデンシャルをstateファイルなどの定義ファイルにハードコーディングするのは、セキュリティの観点からは絶対に避けなければなりません。

Vaultには必要な時に必要な期間だけシークレットを生成する仕組みがあります。この仕組みを「動的シークレット」と呼んでいます。この記事では、Vaultの動的シークレットを用いてTerraformを構成する方法について解説しています。HashiCorpのソリューションエンジニアであるPatrick Schulz氏の記事を、HashiCorp社の許可を得て、翻訳してお届けします。


この記事では、Vaultの動的シークレット機能を使うことで、Terraformのstateファイルに静的シークレット(クラウドにログインするためのクレデンシャルなど)を記述するのを避ける方法について解説します。

動的シークレット生成のワークフロー
図1 動的シークレット生成のワークフロー

Terraform(やAnsibleのようなツール)を使ってクラウドにリソースをプロビジョニングする際の課題の1つは、クラウドにログインするためのクレデンシャルの管理です。このようなクレデンシャルを、stateファイルなどのコードに記述しておきたくはありません。特にコードをバージョン管理システム(VCS)で管理している場合は、クレデンシャルをコードに記述するのを避けなければなりません。

Terraform Enterprise(あるいはTerraform Cloud)のセキュア変数ストアを使ってTerraformにクレデンシャルを安全に提供する場合でも、Terraformはプロバイダー構成情報で使用するクレデンシャルをTerraform stateファイル(tfstate)に書き込みます。Terraform Enterprise(あるいはTerraform Cloud)では、stateファイルは組み込みの暗号化メカニズムであるRBAC(ロール ベース アクセス制御)により保護され、個々人のPCに保管されるのではなく集中的に管理されます。しかし一部のお客様ではより高度なセキュリティが求められるでしょう。

理想的には、クレデンシャルの有効期限はなるべく短くあるべきです。誰かが何らかの方法でstateファイルにアクセスできてしまったとしても、クレデンシャルの有効期限が過ぎていれば悪さをすることはできないからです。

Vaultの「動的シークレット」という機能をTerraformと組み合わせて使うことで、この課題を解決することができます。動的シークレットとは、短期間だけ有効なクレデンシャルを提供するVaultのセキュリティソリューションです。

Vaultの動的シークレットを機能させるには、Vaultがユーザーの代わりに動的シークレットを作成できるように設定しておく必要があります。この記事では、例としてAzureを使う場合について解説しますが、他のクラウドでも同様に利用することができます。また、Vaultの動的シークレットは、クラウド以外のシステム(たとえば、データベースやActive Directoryなど)でも利用することが可能です。この記事ではVaultにAzure用シークレットエンジンを構成する方法については触れませんので、詳しくは以下の記事を参照してください。

重要なのは、動的シークレットには短いTTLを設定することです。TTLに設定する値と、インフラのプロビジョニングにかかる時間とを調整し、デプロイ作業中にシークレットが期限切れを起こさないように注意しましょう。

ここから先は、Azure用のシークレットエンジンについては設定が完了し、コマンドラインなどからテストが成功しているものと仮定して解説を続けます。

次に、TerraformにVaultプロバイダーを設定することで、(Azureサービスプリンシパルのケースでは)Terraform実行中(plan, apply)にVaultからシークレットを取得し、クラウドにリソースをデプロイできるようにします。TerraformのVaultプロバイダーは、バージョン2.4.0以降で認証方式としてトークンの代わりにAppRoleも利用できるようになりました。AppRolesの設定については以下のドキュメントを参照ください。

ここでは、とても単純なVaultポリシーを作成し、AppRoleにアタッチします。この単純なポリシーでは、Azureのシークレットエンジンに対する読み取りと、「子」トークンの作成を許可しています。認証方法がAppRoleでも、Terraformは指定されたトークンの「子」となる新しいトークンを生成することができます。そして生成した「子」トークンには短いTTLを設定してシークレットの漏洩を防ぎます。

path "azure/creds/azure-temp-creds" {
capabilities = ["read"]
}
path "auth/token/create" {
capabilities = ["update"]
}

ここで、Vaultプロバイダーの構成を見てみましょう。ログインに使用するパスとクレデンシャルを記述しているだけなので、とても簡単なものになっています。

provider "vault" {
address = var.vault_addr
auth_login {
  path = "auth/approle/login"
    parameters = {
      role_id = var.login_approle_role_id
      secret_id = var.login_approle_secret_id
    }
  }
}

Terraformは、Vaultの認証に使うシークレットをstateファイルには出力しません。Terraform Enterprise(あるいはTerraform Cloud)では、Terraformのセキュア変数ストアを使ってAppRoleのrole_idとsecret_idを簡単に提供することができます。これにより、AppRoleのシークレットは安全に保管されるので、実行中にシークレットが漏洩することはありません。

Terraform Enterpriseの変数ストア
図2 Terraform Enterpriseの変数ストア

このような構成をとることで、それぞれのワークスペースに合わせた専用アカウントが必要な場合には、Terraformのワークスペース毎に異なるAppRoleと動的シークレットを提供することができます。あるいは、Terraformのワークスペース内にプロビジョニングされるインフラ要件に合わせてカスタマイズしたロールを提供することもできます。

Terraformはプロビジョニングの際に、指定されたAppRoleを用いてVaultにログインし、「vault_generic_secret」データソースを使用して新しい(フレッシュな)動的シークレットをその場で生成します。

data "vault_generic_secret" "azure" {
  path = "azure/creds/azure-temp-creds"
}

最後のステップは、クラウドプロバイダーの定義です。次に、TerraformのAzureプロバイダーの定義と、AzureプロバイダーがVaultからシークレットを取得するところを見てみましょう。

動的シークレットを使用する際の重要なポイントの1つとして、プロビジョニングのプロセスにおいて依存関係を実装する必要があることを挙げておきます。依存関係を実装することで、IAM(Identity and Access Management、アイデンティティとアクセス管理)の一貫性を確保することができます。一般的に、すべてのクラウドのエンドポイントでシークレットがアクティブになるには少し時間がかかります。依存関係を実装しておかないと、クラウドのクレデンシャルがまだ利用できない場合にデプロイ(plan, apply)が失敗する可能性が高くなります。

次に示すスクリプトはサブスクリプションIDと待機時間を引数とし、tfファイルから呼び出されます。そして引数で指定された時間だけスリープします。

#!/usr/bin/env bash
subscription_id=$1
sleep $2
echo "{ \"subscription_id\": \"$subscription_id\" }"

delay-vault-azure.sh(tfファイルと一緒に保存し、chmod + xで実行可能にしておきます)

data "external" "subscription_id" {
  program = ["./delay-vault-azure.sh", var.subscription_id, "120"]
}

実際のAzureクラウドプロバイダー定義では、AzureのテナントID(tenant_id)を通常の変数として取得します。そしてサブスクリプションID(subscription_id)をスクリプトdelay-vault-azure.shから取得するように依存関係を実装することで、シークレットが利用可能になるまでプロバイダーを待機させます。準備ができたら、動的に生成されたクライアントID(client_id)とクライアントシークレット(client_secret)をvault_generic_secretデータソースから取得します。

provider "azurerm" {
  tenant_id = var.tenant_id
  subscription_id = "${data.external.subscription_id.result["subscription_id"]}"
  client_id = "${data.vault_generic_secret.azure.data["client_id"]}"
  client_secret = "${data.vault_generic_secret.azure.data["client_secret"]}"
}

それでは、プロビジョニングが完了した後のstateファイルをざっと見てみましょう。

"resources": [
{
  "mode": "data",
  "type": "vault_generic_secret",
  "name": "azure",
  "provider": "provider.vault",
  "instances": [
  {
    "schema_version": 0,
      "attributes": {
        "data": {
          "client_id": "a61699ab-6ecd-405c-b815-e38ba34fae0b",
          "client_secret": "b54648a5-77c4-a5ef-d48d-6f3aa6a2b38f"
        },
        "data_json": "{\"client_id\":\"a61699ab-6ecd-405c-b832-e38ba34fae0b\",\"client_secret\":\"b54648a5-77c4-a5ef-d57d-6f3aa6a2b38f\"}",
        "id": "3c4c3f23-34df-79c2-2653-9d5f3af2f0f4",
        "lease_duration": 1200,
        "lease_id": "azure/creds/azure-temp-creds/UvhySVzPDxtTFpeSgbTR6IxO",
...(以下略)

動的シークレットの有効期間は1200秒間としています。VaultはAzureのクレデンシャルをID「UvhySVzPDxtTFpeSgbTR6IxO」で払い出し(Leas)ます。AppRoleのrole_idまたはsecret_idに関する記述はありません。

Vault内のLeasesセクションを確認すれば、Leases IDを確認することができます。インシデントが発生した場合には、払い出しているアクティブなID(リース)を簡単に取り消すことができます。VaultはTTLの有効期限切れ前であっても、Azureサブスクリプション内のシークレットを削除します。

Vault内のLeasesセクションの確認

この投稿を締めくくる前に、GCPやAWSなどのクラウドプラットフォームでも同じことができることを追記しておきます。VaultでGCPおよびAWS用シークレットエンジンを設定する方法は、この記事で私がAzureで行ったこととかなり似ていますが、プラットフォームごとに若干異なる箇所があります。GCPの場合は、ロールセット構成では新しいサービスアカウントを作成します。そしてvault readコマンドを実行すると、アカウントに対して新しいアクセストークンないしサービスアカウントキーを発行します。Azureの場合とは異なり、新しいアカウントプリンシパル(サービスプリンシパル)を作成しません。AWSの場合は、今回のユースケースに該当するネイティブのデータソースとして「vault_aws_secret_backend_role」があります。よって、Terraform側で「vault_generic_secret」を利用する必要はありませんし、Azureの例で示したようなタイマーによる同期を考慮する必要もありません。

まとめ

重要なことは、コード内に静的シークレットを記述しておく必要がなくなる、ということです。オンデマンドで生成される動的シークレットは本質的に短命ですから、TTLの有効期限が切れれば、シークレットにまつわる潜在的なリスクは自動的に排除されます。また、Vaultの認証に使用されるシークレットは、プロビジョニングのプロセス全体を通じて外部に公開されることはありません。VCS(バージョン管理システム)を介してシークレットが偶発的に漏洩してしまう、というような事態を防ぐことができます。同時に、プロビジョニングに使用するクラウドアカウントのパスワードのローテーションを考える必要もなくなります。この記事で説明したように、Vaultの動的シークレットを用いることで、IaCを実装する際のセキュリティレベルを改善することができます。

「Vault」に関するお問い合わせ

この記事は役に立ちましたか?

はい いいえ