シークレット管理をSOPS + ageで一元化する実践パターン
株式会社Bizmarq

私たちがプロジェクトを進めるうえで、地味に効いてくるのがシークレット管理です。APIキーやトークンは開発が進むほど増えていき、ローカルとCI/CDで二重管理になりがちです。片方だけ更新し忘れる、という事故は誰もが一度は経験しているのではないでしょうか。私たちはこの問題を SOPS + age の組み合わせで一元化しています。本記事ではその設計と運用の勘所を共有します。
何が課題だったのか
自動化パイプラインやAPI連携を組んでいると、1つのプロジェクトで扱うシークレットは簡単に5種類、6種類と増えていきます。APIキーだけでなく、チャンネルIDや各種設定値など「リポジトリに直接書きたくない値」はいくらでも出てきます。
ローカルでは .env に書けば済みますが、CI/CDで定期実行するとなると話が変わります。すべてをCIのSecretsに個別登録する手もありますが、数が増えるとワークフロー側の環境変数定義が膨れ上がり、どのシークレットがどこで使われているのか把握しきれなくなります。
そこで方針として、暗号化した設定ファイルをgitにコミットし、必要なときだけ復号する形に切り替えました。
SOPSとageを選んだ理由
SOPS はYAMLやJSONの「値だけ」を暗号化するツールです。キー名はそのまま残るため、暗号化した状態でも構造が読め、diffも追えます。何がいつ変わったかの履歴が残るのは運用上の大きな利点です。
暗号化のバックエンドにはAWS KMSやGCP KMSなども選べますが、小規模な構成でクラウドKMSは大げさですし、料金も発生します。そこで age を採用しました。ageはPGPの代替として作られた軽量な暗号化ツールで、鍵の生成はコマンド1つ、外部サービスへの依存がありません。SOPSがageをバックエンドとしてサポートしているため、この2つの組み合わせなら外部KMSなしでファイル暗号化が完結します。
仕組みの全体像
やっていることはシンプルです。
- すべてのシークレットを1つのYAMLに集約する
- ageの公開鍵で暗号化し、暗号化ファイルをgitにコミットする
- 使うときに秘密鍵で復号して読み込む
ローカルでは秘密鍵ファイルを環境変数で指定し、CIでは秘密鍵をSecretsに1つだけ登録して復号します。gitに預けるシークレットは秘密鍵1つだけで、残りはすべて暗号化ファイルの中にある、という状態が作れます。
セットアップの流れ
まずageの鍵ペアを生成します。
age-keygen -o keys.txt
keys.txt に秘密鍵と公開鍵の両方が出力されます。次にSOPSの設定ファイルを置き、どのファイルをどの公開鍵で暗号化するかを定義します。
# .sops.yaml
creation_rules:
- path_regex: config/secrets\.yaml$
age: age1...(公開鍵)
.sops.yaml はgitにコミットして構いません。公開鍵は暗号化にしか使えないためです。秘密鍵の keys.txt は必ず .gitignore に入れる —ここだけは絶対に守ります。
暗号化と復号、そして直接編集は次のように行います。
# 暗号化
sops -e config/secrets.yaml > config/secrets.enc.yaml
# 復号
SOPS_AGE_KEY_FILE=keys.txt sops -d config/secrets.enc.yaml
# 直接編集(復号→エディタ→再暗号化を自動化)
sops config/secrets.enc.yaml
特に便利なのが直接編集です。sops に暗号化ファイルを渡すと、復号された状態でエディタが開き、保存すると自動で再暗号化されます。キーの差し替えがワンコマンドで済みます。
CI/CDとの統合
ここが最も効果を感じる部分です。CI側では、SOPSをインストールして暗号化ファイルを復号するだけで済みます。
steps:
- name: Decrypt secrets
env:
SOPS_AGE_KEY: ${{ secrets.AGE_SECRET_KEY }}
run: sops -d config/secrets.enc.yaml > config/secrets.yaml
CIのSecretsに登録するのは秘密鍵1つだけ。復号されたファイルはランナー上にしか存在せず、ジョブが終われば消えます。新しいシークレットが増えてもワークフロー側を触る必要がないため、パイプラインの追加も復号ステップをコピーするだけで完結します。
なお、SOPSは SOPS_AGE_KEY_FILE(ファイル指定)と SOPS_AGE_KEY(値を直接指定)の両方に対応しており、ローカルとCIで実行環境に応じて自然に切り替わります。アプリケーション側のコードを変える必要はありません。
運用上の注意点
便利な一方で、押さえておくべき点もあります。
- 鍵の集約はリスクの集約でもある。秘密鍵が漏れれば暗号化ファイルはすべて復号されます。1つに集約する分、その1つの管理は厳重にする必要があります。
- ageの鍵に有効期限はない。定期的な鍵ローテーションの仕組みは自前で用意する必要があります。チームで使うなら、運用開始前にローテーション方針を決めておくのが安全です。
- エラーメッセージに値を出さない。読み込み時のバリデーションでは、不足しているキー名やファイルパスだけを出し、シークレットの値そのものがログに漏れないよう配慮します。
どんな構成に向いているか
外部KMSに依存したくない、特定のクラウドに縛られたくない、しかし平文のキーをリポジトリに置きたくもない。そうした要件を持つプロジェクトにとって、SOPS + ageはちょうどよい落としどころです。セットアップ自体は短時間で済み、一度整えてしまえばローカルとCIが同じファイルを参照するため、「片方だけ古い」という事故から解放されます。
私たちは小さな仕組みほど後から効いてくると考えています。シークレット管理はその典型で、早い段階で型を決めておくことが、後々の運用コストを大きく下げてくれます。