🛡

sops × Terrgrunt で機密リソースを安全に IaC 管理する

2023/01/30に公開

こんにちは。エンジニアチームの山岸 (@yamagishihrd) です。

今回は、Secrets Manager シークレットや SSM パラメータのような機密情報を含むリソースを、sops を使って安全に Terraform (Terragrunt) 管理する方法について紹介します。

Terragrunt については別エントリでも書いているので、併せてご覧頂ければと思います。

https://zenn.dev/simpleform/articles/20221111-01-terraform-with-terragrunt

背景

  • シンプルフォームでは、クラウドリソースの IaC ツールとして Terraform (+Terragrunt) を利用しています。
  • 一方、DB アクセス情報などアプリケーションが利用するシークレット情報を Secrets Manager で管理していますが、シークレットに含まれる値はもちろん機密情報を含むため平文で Git 管理できず、シークレット値については Terraform 化が出来ていませんでした。
  • 機密情報を暗号化して安全に管理するための sops というツールがあり、Terragrunt との相性も良さそうだったので、導入・利用の手順についてまとめてみました。

sops について

本題に入る前に、sops について簡単にご紹介します。

  • sops (Secrets OPerationS) とは、Firefox 開発元でもある Mozilla が公開している暗号化ツールです。
  • 暗号鍵として PGP 鍵の他、AWS KMS, Google Cloud KMS, Azure Key Vault のようなクラウド管理型の暗号鍵にも対応しています。
  • ファイル形式として、YAML, JSON, INI, バイナリ形式をサポートしています。

https://github.com/mozilla/sops

デモ内容

sops 暗号鍵として AWS KMS キーを使用し、Secrets Manager シークレットを Terraform 管理するケースを想定します。シークレットの値は YAML 形式で記述し、暗号化した状態で保存します。

サンプルのシークレットとして、以下を登録してみたいと思います。

db_username: postgres
db_password: Rh5]dq9x?tN{Z!2c

Terraform, Terragrunt についてはインストール済みの想定で進めます。筆者環境で利用するバージョンは以下の通りです。

  • Terraform ... 1.3.1
  • Terragrunt ... 0.39.2

事前準備

sops のインストール

Mac であれば Homebrew でインストールできます。詳細は 公式ページ をご確認ください。

% brew install sops
% sops -v
sops 3.7.3 (latest)

KMS キーの作成

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

resource "aws_kms_key" "default" {
  description              = "KMS key for sops"
  key_usage                = "ENCRYPT_DECRYPT"
  customer_master_key_spec = "SYMMETRIC_DEFAULT"
  is_enabled               = true
  enable_key_rotation      = true
  multi_region             = false
}

resource "aws_kms_alias" "default" {
  name          = "alias/sops-demo-kms-key"
  target_key_id = aws_kms_key.default.key_id
}

# outputs.tf
output "kms_key_arn" {
  value = aws_kms_key.default.arn
}

環境変数の設定

作成した KMS キーの ARN を環境変数として設定し、sops から利用できるようにします。

% export SOPS_KMS_ARN=arn:aws:kms:ap-northeast-1:123456789012:key/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

以上で事前準備は完了です。

実施手順

暗号化ファイルの作成・編集

暗号化されたシークレットファイル(以下、"暗号化ファイル")を新規に作成するには、ファイル名を引数に sops コマンドを実行します。(暗号化保存したファイルを再度編集する場合も同様)

% sops secret.yml

vi エディタが起動するので、シークレット情報を平文で編集します。

vi Editor
db_username: postgres
db_password: Rh5]dq9x?tN{Z!2c

編集を抜けると、引数に指定したファイル名で暗号化ファイルが保存されています。暗号鍵にアクセスできない限り復号化できないため、これで Git 管理可能な状態になりました。

secret.yml
db_username: ENC[AES256_GCM,data:WnvCfY/RpX8=,iv:u3BilWG59OI/flukI0zecL3jnvzZsCAwL+xh5wV3hTc=,tag:w1vuscrUeJqoF9CdCVXjEA==,type:str]
db_password: ENC[AES256_GCM,data:lg/QfPNn0cFpOe5hVvcWvQ==,iv:4G0Vof3xFEpDg/PsBtzD7rubKwtwttVr4+PTjqtvAeM=,tag:LM/O1ZET++iqVN9R6VbmuQ==,type:str]
sops:
    kms:
        - arn: arn:aws:kms:ap-northeast-1:123456789012:key/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
          created_at: "2023-01-21T06:49:13Z"
          enc: AQICAHhIlrm5Zlw2fGFM2pmLIJeZiPGO9TzojB/GpRpXImnV3wFWMfaiskiEetbYhPDTY4KVAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMU50BwxILtyVilRfRAgEQgDsJl5DbCdl1d4pppvHDqHSIPtPtzReOvJ2w+8lQt63D8dc6PnrGoc5qF0EgNl7ppuIro7TN0TZVBOhlng==
          aws_profile: ""
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age: []
    lastmodified: "2023-01-21T06:49:57Z"
    mac: ENC[AES256_GCM,data:76MElmFanG/sRW1fzW/OtIKzebXeJOGffv/yvaavgLW3xsT193iUopgS339cpXZH6Ujv8+CpKmAudcR4it2NYmnaqAtD083Ezrr4doYLLegsmKx0C63MVPIWbHxdOcGTpt4bQdEPOWT+q7Tf7Il+vnkmnxjGCW/41WVyN2kn/hE=,iv:7pMUV8KTm7sGr0kQW2/+Bti5AXypacJ2XwpSnjmmjW8=,tag:MsKLT9XZ8iER6zElemjncQ==,type:str]
    pgp: []
    unencrypted_suffix: _unencrypted
    version: 3.7.3

暗号化ファイルを復号化して値を確認するには、-d オプションをつけます。

% sops -d secret.yml 
db_username: postgres
db_password: Rh5]dq9x?tN{Z!2c

Terragrunt からの読み込み

子 terragrunt.hcl から暗号化ファイルを読み取るには、sops_decrypt_file() という Terragrunt の Build-in 関数を使用します。読み取ったシークレット情報を Secrets Manager シークレットのモジュールへの入力として渡します。

terragrunt.hcl
include "root" {
  path = find_in_parent_folders()
}

locals {
  secret = try(yamldecode(sops_decrypt_file("secret.yml")), {})
}

terraform {
  source = "${dirname(find_in_parent_folders())}//modules/secretsmanager/sample_secret/"
}

inputs = {
  secret_string = local.secret
}

Secrets Manager シークレットの作成

続いてモジュール側の定義です。シークレット自体は aws_secretsmanager_secret で定義しますが、値を保持するのは aws_secretsmanager_secret_version になります。

入力から受け取った secret_string を JSON オブジェクトにした上で attribute に設定します。シークレット名や ARN を出力に定義しておくことで、他リソースからの参照が容易になります。

# variales.tf ------------------------------------------------
variable "secret_string" {
  type = map(string)
}

# main.tf ----------------------------------------------------
resource "aws_secretsmanager_secret" "default" {
  name = "sample-secret"
}

resource "aws_secretsmanager_secret_version" "default" {
  secret_id     = aws_secretsmanager_secret.default.id
  secret_string = jsonencode(var.secret_string)
}

# outputs.tf -------------------------------------------------
output "secret_arn" {
  value = aws_secretsmanager_secret.default.arn
}

output "secret_name" {
  value = aws_secretsmanager_secret.default.name
}

確認

terragrunt apply 後にリソースを確認してみると、きちんと設定した値を保持していることが確認できます。

手順は以上です。

まとめ

今回は Secrets Manager シークレットや SSM パラメータのような機密リソースを、sops 暗号化によって安全に Terragrunt 管理する方法についてご紹介しました。読者様の Terraform 運用の参考になれば幸いです。

脚注
  1. https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/key-policy-default.html ↩︎

Discussion