
はじめに
SOPS (Secrets OPerationS) は YAML / JSON / ENV / INI などに記載されているシークレット値を暗号化するツールで,現在は CNCF Sandbox のプロジェクトになっている.暗号化のバックエンドとしては PGP / age / AWS KMS / Google Cloud KMS / Azure Key Vault / HashiCorp Vault など幅広くサポートしている.シークレット値が暗号化されるため GitHub でそのまま管理することもできる.
さらに terraform-provider-sops(carlpett/sops プロバイダー)を使うと Terraform と SOPS を組み合わせてシークレット値を扱える.ようするに SOPS で暗号化したファイルを Terraform 側で復号して安全にデプロイできる.また terraform-provider-sops は Terraform の Ephemeral resources(エフェメラルリソース)にも対応していてシークレット値を tfstate に保存しないようにもできる❗️
さっそく試していく \( 'ω')/
SOPS のドキュメントに以下のように書いてあって,今回は暗号化のバックエンドとして「age」を使う🔐
age is a simple, modern, and secure tool for encrypting files. It's recommended to use age over PGP, if possible.
環境
- SOPS 3.13.1
- age v1.3.1
- Terraform v1.15.6
- hashicorp/aws プロバイダー v6.50.0
- carlpett/sops プロバイダー v1.4.1
ちなみに Terraform の Ephemeral resources(エフェメラルリソース)は Terraform v1.10 以降で使える.
セットアップ
まずは SOPS と age をセットアップしておく.
$ brew install sops $ sops --version sops 3.13.1 (latest) $ brew install age $ age --version v1.3.1
age で鍵を作る
まずは age-keygen コマンドで鍵を作る.すると公開鍵が出力される.さらに keys.txt には秘密鍵が出力される.誤ってコミットしないように keys.txt は別のディレクトリに退避しておくと良さそう.
$ age-keygen -o keys.txt
Public key: age1udwhv8n287suzrvuhgrqm2efcjfm446ax2uw2zn29d7umt5j539sq8wvyu
さらに環境変数 SOPS_AGE_KEY_FILE に keys.txt のパスを設定しておく.
$ export SOPS_AGE_KEY_FILE="~/sops/xxxxx/keys.txt"
👾 .sops.yaml
今度は .sops.yaml に age で作った公開鍵を指定する.
creation_rules: - age: age1udwhv8n287suzrvuhgrqm2efcjfm446ax2uw2zn29d7umt5j539sq8wvyu
👾 sandbox.enc.yaml
ここで sops コマンドの引数にシークレット値を含めるファイル名を指定して実行すると自動的にエディタが立ち上がる.
$ sops sandbox.enc.yaml
今回は sops コマンドにデフォルトで入っているサンプルのシークレット値をそのまま使う.
example_key: example_value example_array: - example_value1 - example_value2 example_number: 1234.56789 example_booleans: - true - false
エディタを保存すると sandbox.enc.yaml はシークレット値が ENC[AES256_GCM,data:...] というフォーマットで暗号化された状態になっている👌
example_key: ENC[AES256_GCM,data:3JoNQXeN9ArDANGsow==,iv:fXdh6tGbXwZLlW6eYHGFmeKMmck7fUxBZt2Cxbz2hfY=,tag:MdzfSPGLr+8JfV3kes+GYA==,type:str] example_array: - ENC[AES256_GCM,data:eXicRcKAQZZlcfKovEA=,iv:XJQPlqB1sIU2LQao5v51QPJQJYs2FLLOkM416t1bwe4=,tag:5l+HCkVA/cHqRgWcNVJZHQ==,type:str] - ENC[AES256_GCM,data:hi1/Cq2RdUwqMgZy+cg=,iv:J1h2WaiDKlH0u7h5UohmjuEmsp8KXxFtWBH/r6yfkWM=,tag:7s8SW08w6p4invTem+Xe6A==,type:str] example_number: ENC[AES256_GCM,data:ojoifALrZETniA==,iv:1KURyR0b7DGnnj7TdvBnl1FoRdq5sCkyipBz9P4Qpa0=,tag:H1w/Gs+y1o7fvq5BKh6Sig==,type:float] example_booleans: - ENC[AES256_GCM,data:H48Eag==,iv:D3tycWiSPkH8zYUDu94wNVt5d/vbv3hdY3a/LE/GNeM=,tag:KJ/eeaItBuau4e+IXtk/vw==,type:bool] - ENC[AES256_GCM,data:tavixiM=,iv:L0CkLwKUJZfmDP7DewlBBmeMhEYHrNECGbSJ4thBnC4=,tag:dH47EaqUBLd5x/Cxbxq72Q==,type:bool] sops: age: - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKSXlsY1YvWXc4QkcvVDJ6 eTE2TnF0Z1lTaUZuV2hNdGZkdWZxaDR0SGlFCnFrMWl1RWUwdHk4cGZ5ZTc0YU8x dStPWnJ4ZXBwM2N0S3JvSFRVRStza3MKLS0tIDlOem5GZGxma2xFazkzcWlvZUFT eE9FRW8vaml1UUFmOTNTRnJYcTBwTG8KkOzcfjQUawtEzubxwNPcIXzO28pULY89 yv9CHMFLP/WWv2IfR3R0PLQkCjp7hqbbgQoP3Hb/PGVztcsJQlop5Q== -----END AGE ENCRYPTED FILE----- recipient: age1udwhv8n287suzrvuhgrqm2efcjfm446ax2uw2zn29d7umt5j539sq8wvyu lastmodified: "2026-06-16T16:15:46Z" mac: ENC[AES256_GCM,data:RN3oUZ6vR5ebkUhLC0S8km8oEQAGd6AysoG5OiQxk/dHE7ltEl1QOo8Ox9t0wcL6ogOoWRmwLVy3R95f+miJ/KISOPCP20sBXQywJgVlHJOeE8BHml5Empd17jI/z5lqOfi0bOrHT1ZKmx8jWBgYj7Jy1RrbSGh2KJXNb+a2WYo=,iv:Tcd4OAKiMHaUV98SnCl/bq1/jQ0ZhXlsX04cnQY+s3g=,tag:MAFLjewUiVW42AB+FstoHQ==,type:str] unencrypted_suffix: _unencrypted version: 3.13.1
sops decrypt コマンドでシークレット値を復号することもできる.
$ sops decrypt sandbox.enc.yaml
example_key: example_value
example_array:
- example_value1
- example_value2
example_number: 1234.56789
example_booleans:
- true
- false
Terraform と SOPS を組み合わせる
今回は AWS Systems Manager Parameter Store (SecureString) の値に SOPS 経由でシークレット値をデプロイしてみようと思う❗️
👾 terraform.tf
まずは terraform-provider-sops(carlpett/sops プロバイダー)を使えるようにしておく.
terraform { required_version = "~> 1.15.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 6.50.0" } sops = { source = "carlpett/sops" version = "~> 1.4.1" } } }
👾 ssm-data.tf(data source を使うパターン)
sops_file data source で sandbox.enc.yaml ファイルを参照しつつ,aws_ssm_parameter リソースの value にシークレット値(今回は example_key)を登録する.
data "sops_file" "secrets_data" { source_file = "sandbox.enc.yaml" } resource "aws_ssm_parameter" "sandbox_sops_data" { name = "/sandbox-sops-data/example_key" type = "SecureString" value = data.sops_file.secrets_data.data["example_key"] }
terraform plan コマンドを実行すると value は (sensitive value) になる.
$ terraform plan (中略) # aws_ssm_parameter.sandbox_sops_data will be created + resource "aws_ssm_parameter" "sandbox_sops_data" { + arn = (known after apply) + data_type = (known after apply) + has_value_wo = (known after apply) + id = (known after apply) + insecure_value = (known after apply) + key_id = (known after apply) + name = "/sandbox-sops-data/example_key" + region = "ap-northeast-1" + tags_all = (known after apply) + tier = (known after apply) + type = "SecureString" + value = (sensitive value) + value_wo = (write-only attribute) + version = (known after apply) }
terraform apply コマンドを実行すると期待通りに AWS Systems Manager Parameter Store をデプロイできるけど,tfstate(今回は S3 Backend)にはシークレット値が残ってしまう.
$ terraform show -json | jq -r '.values.root_module.resources[] | select(.address=="aws_ssm_parameter.sandbox_sops_data") | .values.value' example_value $ terraform show -json | jq '.values.root_module.resources[] | select(.address=="aws_ssm_parameter.sandbox_sops_data") | .values | {value, value_wo, value_wo_version}' { "value": "example_value", "value_wo": null, "value_wo_version": null }
👾 ssm-ephemeral.tf(Ephemeral resources を使うパターン)
今度は sops_file Ephemeral resources で sandbox.enc.yaml ファイルを参照しつつ,aws_ssm_parameter リソースの value_wo(書き込み専用)にシークレット値(今回は example_key)を登録する.
ephemeral "sops_file" "secrets_ephemeral" { source_file = "sandbox.enc.yaml" } resource "aws_ssm_parameter" "sandbox_sops_ephemeral" { name = "/sandbox-sops-ephemeral/example_key" type = "SecureString" value_wo = ephemeral.sops_file.secrets_ephemeral.data["example_key"] value_wo_version = 1 }
terraform plan コマンドを実行すると value は (sensitive value)・value_wo は (write-only attribute) になる.
$ terraform plan (中略) # aws_ssm_parameter.sandbox_sops_ephemeral will be created + resource "aws_ssm_parameter" "sandbox_sops_ephemeral" { + arn = (known after apply) + data_type = (known after apply) + has_value_wo = (known after apply) + id = (known after apply) + insecure_value = (known after apply) + key_id = (known after apply) + name = "/sandbox-sops-ephemeral/example_key" + region = "ap-northeast-1" + tags_all = (known after apply) + tier = (known after apply) + type = "SecureString" + value = (sensitive value) + value_wo = (write-only attribute) + value_wo_version = 1 + version = (known after apply) }
terraform apply コマンドを実行すると期待通りに AWS Systems Manager Parameter Store をデプロイできて,tfstate(今回は S3 Backend)にもシークレット値が残らなくなった.
$ terraform show -json | jq '.values.root_module.resources[] | select(.address=="aws_ssm_parameter.sandbox_sops_ephemeral") | .values | {value, value_wo, value_wo_version}' { "value": "", "value_wo": null, "value_wo_version": 1 }
まとめ
Terraform と SOPS (Secrets OPerationS) を組み合わせてシークレット値を扱うパターンを試してみた❗️SOPS を使えばシークレット値が暗号化されるため GitHub でそのまま管理することもできるし,Ephemeral resources(エフェメラルリソース)を使えばシークレット値を tfstate に保存しないようにもできる.
Terraform を使うときにシークレット値の扱いは必ず話題に出るため SOPS を使うという選択肢も覚えておこう \( 'ω')/











