はじめに
terraform でパスワード等の機密情報を扱う場合、state に情報が載ってしまうとセキュリティリスクに繋がるケースがあります。
terraform 1.10 で GA になった Ephemeral values を利用し、HashiCorp Vault 上の機密情報を安全に一元管理した上で環境構築できると嬉しいのですが、現在は vault provider が ephemeral resource に対応しておらず、 state に情報が載らないようにするのは 2025/02/28 時点では不可能な状態です。
一方、terraform 1.11 では Write-only Arguments がリソースブロックで利用可能になりました。
これを使用できるリソースを利用すれば、HCP Vault Secret から同期した Variable を Write-only Arguments をサポートしたリソースに流し込むパターンであれば state に記録されなくなります。
今回は state を記録されないコードの例を示します。
構成例
HCP Vault Secret には、HCP Terraform に Secret を同期する仕組みがあります。
この仕組みで同期し作成された Variable の値を Write-only Arguments に書き込む場合は state に Variable 値が書かれませんので、「Vault で安全に管理された機密情報が state に書き込まれず環境に反映される」という構成をとることができます。
実際に AWS Systems Manager パラメータストアを作成する場合のサンプルコードで確認してみましょう。
サンプルコード
AWS Systems Manager パラメータストアを作成する場合、aws_ssm_parameter
リソースを使用します。
従来は value
属性でパラメータ値を設定する必要があり、この値は state に書かれていました。
これを value_wo
属性でパラメータ値を設定する形にすれば、state に書かれなくなります。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.87.0"
}
}
}
provider "aws" {
region = var.aws_region
}
locals {
token_version = 1
}
resource "aws_ssm_parameter" "wo_token" {
name = "test_param"
type = "SecureString"
value_wo = var.token
value_wo_version = local.token_version
}
variable "aws_region" {
type = string
default = "ap-northeast-1"
}
variable "token" {
type = string
sensitive = true
}
value_wo_version
属性は value_wo
属性のバージョンを示し、この値が変化したら SSM Parameter の変更が実施されます。
state に記録されなくなることで設定した値が変更されたかどうか terraform 的に判断できなくなるので、別の属性で判断するということですね。
Variable には ephemeral = true
を指定したいところですが、HCP Terraform 上で Sensitive な Variable を使って動かすと下記のエラーが出るので sensitive = true
の指定のみとしました。
現時点では ephemeral と sensitive をどちらも指定することが想定されていないようです。
Error: failed to encode aws_ssm_parameter.wo_token in state: .value_wo: cannot serialize value marked as cty.NewValueMarks(marks.Ephemeral, marks.Sensitive) for inclusion in a state snapshot (this is a bug in Terraform)
Error: Changes.Encode: new value .value_wo: can't serialize value marked with cty.NewValueMarks(marks.Ephemeral, marks.Sensitive) (this is a bug in Terraform)
どちらも指定可能になるか、HCP Terraform の Workspace Variable で ephemeral の指定ができるようになることで解消する理解なので、今後のアップデートに期待ですね。
Workspace の作成
上記のコードを動かす Workspace を作成します。
使用する terraform のバージョンは 1.11 以降を指定するのを忘れないようにしてください。
AWS に対する操作が必要なので、aws provider 用に AWS アクセスキー、シークレットアクセスキーの Variable を設定するか、dynamic credentials の設定を実施して plan 実行可能にしておきます。
HCP Vault Secret に Secret を作成
下記画像のように HCP Vault Secret に Secret を作成します。
今回は変数 token
の値を Vault から設定したいので、Name には token
を指定します。
HCP Vault Secret からのシークレット値の同期
あとは token
変数を Vault から指定するようにすれば OK ですね。
下記に記述がある通り設定を進め、HCP Vault Secret に定義した Secret を HCP Terraform の Workspace Variables として同期しておきます。
HCP Terraform で Organization Token または Team Token を作成し、そのトークンを指定することで実現できます。
今回は Free 版の HCP Terraform を使用しており Team が使用できないため、Organization Token を使用して連携してみました。
同期できれば、下記のように Workspace variables の一覧に追加されます。
動作確認
ここまでの構成を実施することで、
- トップレベルの Variable 属性は state に記録されない
- aws_ssm_parameter リソースに指定した機密情報は state に記録されない
となるので、state に機密情報が載らない想定です。
動作を確認していきましょう。
state に記録されないことの確認
Workspace で apply してみましょう。
state には機密情報が見当りません!
{
~ 略 ~
"resources": [
{
"mode": "managed",
"type": "aws_ssm_parameter",
"name": "wo_token",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"allowed_pattern": "",
"arn": "arn:aws:ssm:ap-northeast-1:xxxx:parameter/test_param",
"data_type": "text",
"description": "",
"has_value_wo": true,
"id": "test_param",
"insecure_value": null,
"key_id": "alias/aws/ssm",
"name": "test_param",
"overwrite": null,
"tags": null,
"tags_all": {},
"tier": "Standard",
"type": "SecureString",
"value": "",
"value_wo": null,
"value_wo_version": 1,
"version": 1
},
~ 略 ~
}
しかしパラメータストアを確認すると、HCP Vault Secret に登録したトークン: this_is_sample_token
が表示されます。
値の更新に関する確認
HCP Vault Secret の値を変えて同期しただけだと No changes となります。
value_wo_version の値を更新してみましょう。
~ 略 ~
locals {
token_version = 2
}
~ 略 ~
すると無事 Apply でき、パラメータストアで変更が確認できました。
Secret の変更があった際のバージョンの値は自動的に更新できるか ?
実験として Secret の変更を検知して value_wo_version
の値を自動的に変更する方法として下記を考えました。
value_wo_version
に設定する値を time_static
リソース の結果とし、time_static
の keepers
に機密情報のハッシュ値を指定する方法です。
variable "token" {
type = string
sensitive = true
# ephemeral = true ## ephemeral を指定すると random_integer の keepers に指定できない
}
resource "time_static" "token_update" {
triggers = {
# トークンのハッシュ値を使用してトリガー
token_hash = sha256(var.token)
}
}
resource "aws_ssm_parameter" "wo_token" {
name = "test_param"
type = "SecureString"
value_wo = var.token
value_wo_version = time_static.token_update.unix
}
time_static
リソースの Output: unix
は現在日時の UNIX 時間なので、今回のような使用方法であれば重複するリスクもなく更新できる認識です。
state には time_static
リソースの keepers
属性に指定した機密情報のハッシュ値しか記録されないため、state の安全性もある程度確保されます。
ただ、variable の値を random_integer の keepers に指定できるようにするため variable に ephemeral
を指定できなくなるので、もし variable が意図しない場所で参照されると state に機密情報が記録されてしまうリスク があります。
この実装の採用を検討する場合は十分注意してください。
また、terraform のコード変更ではなく Vault の値の変更がきっかけで修正が実施されるので、 意図しないタイミングで terraform の plan 結果に差分が発生する 可能性があります。
これは CI/CD パイプラインでの自動デプロイ時の予期せぬ変更や、コードレビューの複雑化など、運用上不都合になると思われますので、ローカル変数等でバージョンを持っておく形が良いケースが多いのではないかと思われます。