🌏

【sops + AWS KMS】Terraformで安全に機密情報を扱う方法

2022/09/08に公開

はじめに

私が Terraform で AWS リソースの開発を進めるなかで DB の認証情報等の機密情報をどうやって取り扱うかという問題にぶつかりました。
tf ファイルに平文で記述してしまうと Git で tf ファイルを管理することができません。
あらかじめ Terraform 外で Secrets Manager や Parameter Store を作成しておいて、Data Source として扱う方法もありますが、そうすると SecretsManager や ParameterStore は Terraform での管理対象外になってしまいます。
なんとかして機密情報を直接 Terraform で扱う方法がないかと調べた結果、sops + AWS KMS がとても便利だったので紹介します。

前提

sops において AWS KMS で作成したキーを使用するにあたり、AWS Credential の設定が必要です。
これは Terraform を使用する上でも前提となります。
まだ設定していない方は先に設定してください。
AWS Credential 管理の方法はいくつかありますが、個人的なおすすめは aws-vault を使用する方法です。
aws-vault を使用する場合の利点や設定方法はぜひ以下の記事を参考にしてください。
*記事:【aws-vault】AWS マルチアカウント環境での Credential 管理

sops とは

sops は AWS KMS、GCP KMS、Azure Key Vault、age、PGP 等を使用してファイルを暗号化できるエディターです。YAML、JSON、ENV、INI、BINARY 形式をサポートしています。

https://github.com/mozilla/sops

嬉しい点は Terraform の provider として sops が使用できる点です。
これによって sops で暗号化したファイルの内容を Terraform の実行時に自動で復号化して使用することができます。

sops インストール

Mac の環境では以下の通り、Homebrew を使用してインストールします。

$ brew install sops

Windows でのインストール方法はのこちらの記事で紹介されています。
※Windows は実際にインストールを検証したわけではないので内容は保証しません。

KMS キー作成・設定

AWS KMS を使用して暗号化に使用するキーを作成します。

*AWS コンソール:KMS > Customer managed keys > [Create key]

基本的にはデフォルトの設定で作成してください。
< Define key administrative permissions >の項目ではこのキーの作成時に使用している IAM ユーザ・ロールを選択しておけば OK です。

< Define key usage permissions >では Terraform の実行に使用している IAM ユーザ・ロールを選択してください。
※KMS キーと同じアカウントの IAM リソースしか選択できません。他のアカウントからこの KMS キーを使用する場合の設定は後述します。

他の設定はデフォルトのままで作成してください。

作成したらキーの ARN をコピーしておきます。
ローカル PC のターミナルを開き、環境変数”SOPS_KMS_ARN”に KMS キーの ARN を設定します。

$ export SOPS_KMS_ARN='arn:aws:kms:ap-northeast-1:999999999999:key/XXXXXXXX-XXXX-XXXX-bd50-ac0ec6d03d63'

キー共有設定 ※AWS マルチアカウント環境のみ

もし AWS マルチアカウント環境で Terraform を使用している場合、それぞれのアカウントでキーを作成してアカウントごとに使い分けるのよりも共通の 1 つのキーで暗号化できた方が便利です。
作成したキーのキーポリシーを編集し、現状のキーポリシーの最後に以下のようなステートメントを追加して他のアカウントの IAM リソースからもキーを使えるようにしておきましょう。
※”arn:aws:iam::xxxxxxxxxxxx:role/Terraform-Role”の部分を各アカウントで使用する IAM ユーザ・ロールの ARN に変更してください。

{
	"Sid": "Allow an external account to use this KMS key",
	"Effect": "Allow",
	"Principal": {
		"AWS": [
			"arn:aws:iam::xxxxxxxxxxxx:role/Terraform-Role",
			"arn:aws:iam::yyyyyyyyyyyy:role/Terraform-Role",
			"arn:aws:iam::zzzzzzzzzzzz:role/Terraform-Role"
		]
	},
	"Action": [
		"kms:Encrypt",
		"kms:Decrypt",
		"kms:ReEncrypt*",
		"kms:GenerateDataKey*",
		"kms:DescribeKey"
	],
	"Resource": "*"
}

以上で KMS キーの作成・設定は完了です。

機密情報ファイル暗号化

準備ができたら sops を使用して暗号化されたファイルを作成します。
キーを使用するにあたり、AWS の Credential が必要です。
aws-vault を使用している場合は aws-vault exec コマンドで任意の Credential へ切り替えてください。
Credential を使用できる状態で、以下のコマンドを入力します。(filename は任意のファイル名です)

$ sops filename.json

するとサンプルとして以下の内容でファイルが作成されます。
一度作成したファイルを再度編集する際も上記と同じコマンドを使用します。

{
	"hello": "Welcome to SOPS! Edit this file as you please!",
	"example_key": "example_value",
	"example_array": ["example_value1", "example_value2"],
	"example_number": 1234.56789,
	"example_booleans": [true, false]
}

サンプルの内容は削除し、任意の内容に変更して大丈夫です。
私の場合は Terraform で使用する機密情報を以下のような”secrets.enc.json”という 1 つのファイルにまとめています。

secrets.enc.json
{
        "django_secret_key": "xxxxxxxxxxxxxxxxxxxxx",
        "db_username": "admin",
        "db_password": "password"
}

この状態で試しに普通にファイルの中身を表示してみましょう。

$ cat secrets.enc.json

すると以下のような内容になっており、暗号化されていることがわかります。

{
	"django_secret_key": "ENC[AES256_GCM,data:Xaiw0wKBvd07mgV3p/usXuJ/PXR32yTJanIW1VOz9Mv8KLaYVpLU0tlqHEe+6WRE7lY=,iv:xKf/A0fA3FJ69c9qzv1+Jh4uNzrz+8c2wRvPNuj/vvw=,tag:h3okmXhac474+ZwMn1zWHA==,type:str]",
	"db_username": "ENC[AES256_GCM,data:s0kM8KY=,iv:/FCqTuJbdb187/jHIfaTOStYlpPYICd5FW+2Q+89mfc=,tag:hyZsgqB3TJX+5yR8ioBofA==,type:str]",
	"db_password": "ENC[AES256_GCM,data:K+C4WcVS19A=,iv:ifQIdyrGTkusGZpQZIpfL53+lKlFh5XaKXGdrxlHraY=,tag:vEej+akpBLvWG/ABJJk2AQ==,type:str]",
	"sops": {
		"kms": [
			{
				"arn": "arn:aws:kms:ap-northeast-1:999999999999:key/XXXXXXXX-XXXX-XXXX-bd50-ac0ec6d03d63",
				"created_at": "2022-07-20T16:21:34Z",
				"enc": "AQICAHiBRtZjOIwqyoBVorJAnMZChYZYwOqEAEbke5Z7Xot+8gEzBoRV8G6Js/eI55Q7mlNaAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMJk9PG0M0TWhRBkLGAgEQgDsC1bVk2eJX2oerQNCjg702JmUsoCV3TlqQcMngayoIdFb1cQXX2SmCvMu4gqRLOJjGFnqkmZBrVC3Mpw==",
				"aws_profile": ""
			}
		],
		"gcp_kms": null,
		"azure_kv": null,
		"hc_vault": null,
		"age": null,
		"lastmodified": "2022-08-13T14:37:35Z",
		"pgp": null,
		"unencrypted_suffix": "_unencrypted",
		"version": "3.7.3"
	}
}

なお、暗号化ファイルの初回作成時には環境変数”SOPS_KMS_ARN”の設定が必要ですが、
再度そのファイルを開いて編集する際には環境変数の設定は不要です。
上記のファイルの内容を見ればわかる通り、KMS キーの ARN の情報が sops で作成された暗号化ファイルに含まれているため、その KMS キーを使用する権限がある Credential を持ってさえいればファイルを暗号化・復号化できるわけです。
チームで開発をしている際は、一度他のメンバーが作った機密情報ファイルをいじる際にいちいち 必要な KMS キーの情報を設定する必要がないので便利です。

Terraform 設定

sops を使って機密情報ファイルを暗号化したら Terraform でも sops を使えるように設定します。

まずは provider に sops を追加します。

provider.tf
terraform {
  required_version = "1.2.4"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.27.0"
    }
    sops = {
      source  = "carlpett/sops"
      version = "~> 0.7"
    }
  }
}

次に、Data Resource として sops で作成した暗号化済みのファイルを指定します。
あとは必要な箇所で Data Resource ファイル内の各機密情報の Key を指定することで Value を取り出して使うことができます。

以下は sops で暗号化した機密情報を Parameter Store 作成時に使用している例です。

parameter.tf
data "sops_file" "secrets" {
  source_file = "./templates/secrets/secrets.enc.json"
}

resource "aws_ssm_parameter" "django_secret_key" {
  type  = "SecureString"
  name  = "${local.prefix}-app-django-secret-key"
  value = data.sops_file.secrets.data["django_secret_key"]
}

resource "aws_ssm_parameter" "db_username" {
  type  = "SecureString"
  name  = "${local.prefix}-app-db-username"
  value = data.sops_file.secrets.data["db_username"]
}

resource "aws_ssm_parameter" "db_password" {
  type  = "SecureString"
  name  = "${local.prefix}-app-db-password"
  value = data.sops_file.secrets.data["db_password"]
}

以上で Terraform の設定は完了です。
なお、provider に sops を追加した後の初回 Terraform 実行時には terraform init が必要です。

これで暗号化した KMS キーを操作できる権限を持つ Credential で Terraform を実行すれば自動で復号化して使用してくれます。
ファイルを復号化してから Terraform を実行して再度暗号化しておく、という手順が必要ないので便利ですね。

注意点としては tfstate には平文で出力されてしまうみたいなので、tfstate はきちんと S3 等の安全なリモートバックエンドで管理するようにしましょう。

Warning
To prevent plaintext secrets from being written to disk, you must use a secure remote state backend. See the official docs on Sensitive Data in State for more information.

https://registry.terraform.io/providers/carlpett/sops/latest/docs

まとめ

今回は sops + AWS KMS を使用して機密情報を簡単に暗号化して Terraform で使用する方法をご紹介しました。
これで機密情報をストアしておく AWS リソースもまとめて Terraform で管理できるようになりました。
一度暗号化した機密情報を使用する際の復号化は Terraform が自動でやってくれるのも便利なポイントですね。
どなたかの参考になれば嬉しいです。

参考

https://chroju.dev/blog/terraform_with_sops

Discussion