SREチームの長田です。 KAYAC Advent Calendar 2022の11日目の記事です。
アプリケーションから何かしらの外部サービスを利用するとき、そのサービスを利用するためのAPI Keyなり秘密鍵なりの秘密情報を保持することになります。 暗号化したものをファイルとしてアプリケーションに持たせたり、 Amazon Web Services(AWS)ならAWS Secrets Managerや AWS Systems ManagerのParameter Store(SSM Paramater Store)に保存したものを実行時に読み込んだりするでしょう。
これらの秘密情報、どこから来たのかわかりますか?
どこから来た秘密情報なのか
秘密情報を使って出どころを調べられるのであれば問題はないでしょう。
# 例えばAWSのIAM User Credenntialsとか $ AWS_ACCESS_KEY_ID=*** AWS_SECRET_ACCESS_KEY=*** aws sts get-caller-identity { "UserId": "***", "Account": "***", "Arn": "arn:aws:sts::***:***" }
ですが、世の中にはそんな親切な秘密情報ばかりではありません。
「このサービスのAPI Keyなんだけど、どのアカウントで発行したのかわからない」
あるいは、
「秘密鍵が流出しちゃったんだけど差し替えのために再発行する方法がわからない」
なんて辛い上にさらに辛い場合もあるでしょう。
そういう場合もたいてい調べればわかるんです。 ですが調べるのにもコストがかかるんです。 その秘密情報を発行した人がいるならすぐ判明することもありますが、退職してしまって誰も情報を持っていないなんてこともあるんです。
秘密情報を保存する際に、それがどこから来たのかをセットでメモしておくだけでこの手間はなくなります。 どのサービスの、どのアカウントの、どの設定なのか。 管理画面があるならそのURLを、管理担当や部門があるならその情報を、書いておくだけで調査のコストは非常に小さくなります。
秘密情報には出どころをセットで書いておきましょう。 お願いします。
出どころをメモしておく例
お願いだけで終わるのも物足りない気がしたので、管理方法の例も紹介しておきましょう。
秘密情報管理サービスのdescriptionに書く
例えばAWS Sercets Manager/AWS SSM Parameter Storeにはちゃんと description
というその秘密情報の説明を書く項目が用意されています。
ここに出どころを書いておくだけです。簡単!
AWS Sercets Manager/AWS SSM Parameter Store以外の秘密情報管理サービスでも、似たような説明記入欄は用意されていることが多いです。 めんどうくさがらずに書きましょう。
説明用の項目が無い場合(例えばGoogle Cloud(GCloud)のSecret Managerなど)は、次に紹介するような秘密情報管理ファイルの導入を検討すると良いでしょう。
秘密情報管理ファイルを運用する
暗号化したファイルで秘密情報とその説明を管理する方法です。
今回はsopsを使って暗号化したyamlファイルをGitHubのリポジトリで管理する例を紹介します。 GitHubでファイルを管理することで変更履歴とその理由が記録されますし、コードレビューを受けることで出どころの記入をチェックすることができます。
※暗号化されているからといって管理用のファイルをむやみに公開してはいけません。 すべての暗号は無限の計算リソースを費やせば鍵がなくとも復号できてしまいます。
yamlファイルの例を示します。
MACKEREL_APIKEY: value: ENC[AES256_GCM,data:***,iv:***,tag:***,type:str] description: https://mackerel.io/orgs/***?tab=apikeys の "stg" sops: kms: - arn: arn:aws:kms:ap-northeast-1:***:key/*** created_at: "2021-10-29T19:04:11Z" enc: *** aws_profile: "" gcp_kms: [] azure_kv: [] hc_vault: [] age: [] lastmodified: "2022-09-15T07:55:10Z" mac: ENC[AES256_GCM,data:***,iv:***,tag:***,type:str] pgp: [] unencrypted_suffix: description version: 3.7.3
*.value
には秘密情報そのものを。 *.description
にはその説明を記入するようにしています。
.sops
はsopsが暗号化する際に追記するメタデータです。暗号化に使用した鍵情報などが記録されています。
.sops.kms
に情報があるので、AWS Key Management Serviceを使って暗号化していることが分かります。
.sops.gcp_kms
や .sops.azure_kv
などの項目からわかるように、GCloudのCloud Key ManagementやAzure Key Vaultなどにも対応しています。
.sops.unencrypted_suffix
には description
を指定し、 description
というキー名の値は暗号化しないようにしています。
この指定をすることでコードレビューをする際の手間を軽減することが出来ます。
これ元にTerraform経由でSecrets Manager/SSM Parameter Storeのリソースを生成しています。 sopsで暗号化したファイルを直接扱えるsops Providerを使用しています。
resource定義を統一することで環境プレフィックス指定などの命名規則を強制することが出来ますし、
yamlファイルに description
が定義されていない場合は terraform plan/apply 時にエラーとなるのでその時点で気づくことが出来ます。
AWS Secrets Managerのsecretを定義するtfファイルの例を示します。
terraform { ... sops = { source = "carlpett/sops" version = "0.7.1" } } } data "sops_file" "secretsmanager_secrets" { source_file = "secretsmanager_secrets.encrypted.yml" } locals { secretsmanager_secrets = nonsensitive( distinct([ for key in keys(data.sops_file.secretsmanager_secrets.data) : split(".", key)[0] ]) ) } resource "aws_secretsmanager_secret" "secret" { for_each = toset(local.secretsmanager_secrets) name = "${local.env}/${each.value}" description = nonsensitive(data.sops_file.secretsmanager_secrets.data["${each.value}.description"]) } resource "aws_secretsmanager_secret_version" "secret" { for_each = toset(local.secretsmanager_secrets) secret_id = aws_secretsmanager_secret.secret[each.value].id secret_string = data.sops_file.secretsmanager_secrets.data["${each.value}.value"] }
sops Providerが生成するdataには少々癖があり((MACKEREL_APIKEY.value
のような連想配列のキーが .
で連結されたフォーマットになります))、かつすべての値がsensitive指定されているので、Secrets Manager上で name
として使用する部分だけを取り出して nonsensitive
指定しています。
※sops Providerの注意書きにもある通り、秘密情報をTerraformで扱う場合はtfstateを安全に管理する必要があります。 tfstateには管理対象リソースの情報がすべて平文で書かれているからです。 例えばbackendにAmazon S3を使用している場合はバケットの暗号化を有効にするなどしてtfstateへのアクセスを制限しましょう。
おわり
繰り返しになりますが、秘密情報にはその出どころをセットで書いておきましょう。 少しの手間で救われるサービス運用考古学者がいます。