午前2時のセキュリティアラート
数年前のある朝、電話がアラートで鳴り響いたのを今でも覚えています。開発者が誤って本番環境のデータベース認証情報を含む .env ファイルを公開済みの GitHub リポジトリ にプッシュしてしまったのです。わずか60秒以内に、自動ボットがキーをスクレイピングし、不正な EC2 インスタンス を立ち上げようとしました。私たちはその後の6時間を、すべての認証情報のローテーションと、侵入の兆候を確認するためのアクセスログの監査に費やしました。
これは珍しい怪談ではありません。2023年だけでも、研究者によって1,000万件以上のシークレットが公開リポジトリで公開されているのが発見されました。多くのチームは、シークレットをハードコーディングしたり、CI/CD の環境変数にプレーンテキストとして保存したりすることから始めます。これはクイックな MVP には有効ですが、重大な セキュリティ上の脆弱性 を生み出します。「スクリプター」からプロの DevOps エンジニアへとステップアップするには、一元化されたシークレット管理をマスターする必要があります。
なぜシークレットの分散(Secret Sprawl)が発生するのか
シークレットの分散(Secret Sprawl)は、API キー、データベースのパスワード、SSH キーなどの機密データが GitHub、Jira、Slack、および開発者のローカルマシンに散らばってしまうことで発生します。これは怠慢から起こることは稀です。通常、「公式」なシークレット取得方法が遅すぎることが原因です。開発者がステージング環境のキーを取得するためにチケットの承認を3日間待たなければならない場合、チームメイトからコピー&ペーストしてしまうでしょう。
標準の Kubernetes Secrets はこの問題を解決しません。デフォルトでは、Kubernetes はシークレットを Base64 エンコードされた文字列として etcd に保存します。はっきりさせておきましょう。Base64 は難読化であり、暗号化ではありません。名前空間で基本的な get secret 権限を持つ人なら誰でも、数秒でデコードできます。
# K8sシークレットのデコードは非常に簡単です
echo "S3ViamVjdFBhc3N3b3Jk" | base64 --decode
これらのネイティブシークレットをデバッグする際、私はよく ToolCraft の Base64 エンコーダー/デコーダー を使用して値を素早く確認します。これはブラウザ内で 100% 動作します。これにより、機密性の高い文字列がローカル環境から外に出ることがなくなります。これは小さいながらも不可欠なセキュリティ習慣です。
HashiCorp Vault vs. AWS Secrets Manager
プレーンテキストファイルの使用をやめる決心をしたら、プラットフォームに依存しない HashiCorp Vault か、AWS Secrets Manager のようなクラウドネイティブなサービスのどちらかを選択することになるでしょう。
HashiCorp Vault
Vault はシークレット管理の業界標準です。シークレットを保存、アクセス、保護するための一元化された方法を提供します。私のお気に入りの機能は Dynamic Secrets です。アプリケーションに静的で長期間有効な PostgreSQL のパスワードを渡す代わりに、Vault はわずか15分間だけ存在する一時的なユーザーを生成します。もしアプリケーションが侵害されても、盗まれた認証情報はほぼ即座に無効になります。
- メリット: マルチクラウド対応、非常にきめ細かなポリシー、サービスとしての高性能な暗号化。
- デメリット: 学習曲線が急です。オープンソース版をセルフホストするには、かなりの運用コストがかかります。
AWS Secrets Manager
スタック全体が AWS 上にある場合、これが最もスムーズな選択肢です。IAM(Identity and Access Management)とネイティブに統合されており、RDS データベースのパスワードローテーションなどの重労働を自動的に処理してくれます。
- メリット: メンテナンスが不要で、AWS サービスとネイティブに統合されています。
- デメリット: コストです。シークレット1つにつき月額0.40ドルかかるため、何百ものシークレットを持つマイクロサービスアーキテクチャでは、すぐに費用がかさみます。
Kubernetes へのシークレットの注入
Vault にシークレットを保存するのはパズルの一部に過ぎません。本当の課題は、アプリケーションが Vault の存在を意識することなく、それらのシークレットを Pod に届けることです。一般的に、Vault Agent Sidecar Injector または External Secrets Operator (ESO) の2つのパターンをお勧めします。
サイドカーインジェクターパターン
Kubernetes の Deployment に特定の注釈(アノテーション)を追加します。すると Vault がサイドカーコンテナを注入し、シークレットを取得して /vault/secrets の共有メモリボリュームに書き込みます。アプリケーションは、ローカルファイルであるかのようにシークレットを読み取るだけです。これは、API との統合が容易ではないレガシーアプリケーションに最適です。
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "payment-role"
vault.hashicorp.com/agent-inject-secret-config: "secrets/data/payment/config"
spec:
template:
spec:
containers:
- name: app
image: payment-service:v2.1.0
External Secrets Operator (ESO)
標準の Kubernetes Secret オブジェクトを使い続けたいが、外部ソースから同期させたい場合には ESO を好んで使います。AWS Secrets Manager で値を更新すると、ESO はその変更を自動的に K8s クラスターに反映します。「K8s ネイティブ」であり続けたいチームにとっては、よりクリーンなアプローチです。
セキュアな CI/CD ワークフロー
新しいパイプラインのために YAML を1行書く前に、私は高エントロピーな認証情報を生成します。ToolCraft の パスワードジェネレーター を使用して、ブルートフォース攻撃に耐性のある32文字の文字列を作成します。Vault の API ペイロードをテストする際は、デプロイ前に設定が有効であることを確認するために、彼らの YAML to JSON コンバーター も使用します。
1. OIDC による認証
長期間有効な IAM キーを GitHub Secrets に保存しないでください。代わりに OIDC (OpenID Connect) を使用します。これにより、GitHub Actions は信頼関係に基づいて AWS から一時的で短期間有効なトークンをリクエストできるようになります。キーは保存されないため、ハッカーが CI 設定から盗み出すものは何もありません。
2. 実行時の取得(Runtime Fetching)
ビルド環境に実際のデータベースパスワードを注入する代わりに、SECRET_ID を保存します。パイプラインはその ID を使用して、デプロイステップでどうしても必要になった時だけ、シークレットマネージャーから本物の値を取得します。
# OIDC を使用した GitHub Action の例
- name: 本番環境のシークレットを取得
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: |
DB_PASSWORD, prod/billing/db-pass
3. すべてを監査する
実装は最初の一歩に過ぎません。すべてのシークレットアクセスに対してロギングを有効にする必要があります。エンジニアが本番環境の認証情報にアクセスした場合、不変の追跡記録が残るべきです。Vault と AWS はどちらも、誰がいつ何にアクセスしたかを正確に示す詳細なログを提供します。
最後に
正式なシークレット管理システムへの移行は、最初は多くのオーバーヘッドがあるように感じられます。しかし、それが提供するセキュリティとスケーラビリティは、初期の摩擦に見合う価値があります。偶発的な漏洩を心配するのをやめ、機能のリリースに集中できるようになります。まずは、最も機密性の高いデータベースのパスワードを1つ選び、今日中にマネージャーに移行することから始めてみてください。小さな一歩が、堅牢なインフラへとつながります。

