目次
- .envと環境変数の限界:どこから漏れるのか
- .envは「開発の便利機能」であって保管庫ではない
- 環境変数が漏れる4つの経路
- 主要なシークレット管理サービスの使い分け
- AWSならParameter Storeから始める
- Vaultは「動的シークレット」が必要になってから
- SOPS+ageはサーバーレスな現実解
- ローテーション:「漏れたら変える」ではなく「定期的に変える」
- なぜ定期ローテーションが必要か
- 無停止でローテーションする手順
- CIでのシークレット注入と最小権限
- CIにキーを置かず、CIに「権限」を与える
- 最小権限:読める範囲をアプリ単位・環境単位で絞る
- トラブル事例:エラー監視ツールにDBパスワードが送られていた
- 症状
- 原因
- 対処
- まとめ
「本番のAPIキーは.envに書いてサーバーに置いてある」「退職者が知っているDBパスワードを変えていない」「CIのログにトークンが出ていた」――小さなチームのシークレット管理は、動いているうちは問題が表に出ないため、後回しにされがちです。しかし漏洩は一度起きると取り返しがつかず、原因のほとんどは「仕組みがなかった」ことにあります。
この記事では、まず.envと環境変数がどこまで安全で、どこから危ないのか(ログ・ps・子プロセスへの漏れ方)を仕組みから説明します。そのうえで、AWS SSM Parameter Store/Secrets Manager、GCP Secret Manager、HashiCorp Vault、SOPS+ageという主要な選択肢を規模と用途で使い分ける基準、シークレットのローテーション、CIへの安全な注入、最小権限の考え方まで解説します。なお、Gitリポジトリに秘密情報を混入させない仕組み(.gitignoreやpre-commitフック)は秘密情報をGitに入れない仕組みで扱っているので、そちらを参照してください。
.envと環境変数の限界:どこから漏れるのか
.envは「開発の便利機能」であって保管庫ではない
.envファイルは、ローカル開発で環境変数をまとめて読み込むための仕組みです。平文のテキストファイルなので、そのサーバーやPCにファイルを読める人は誰でも中身を見られます。本番サーバーに.envを置く運用は、次の問題を抱えます。
- 誰がいつ値を変えたか記録が残らない(監査ができない)
- サーバーが複数台になると、値の配布と更新漏れが人力になる
- バックアップやAMI(マシンイメージ)に平文で含まれ、意図しない場所に複製される
- アクセス権がOSのファイル権限に依存し、「アプリは読めるが人間は読めない」という分離ができない
環境変数が漏れる4つの経路
「環境変数にしておけば安全」と思われがちですが、環境変数はプロセスに紐づく情報であり、いくつかの経路で外に出ます。
- ログとエラーレポート:例外発生時に環境をダンプするライブラリやAPMツールは珍しくありません。デバッグ用に
console.log(process.env)を残したまま本番に出るケースもあります。 - 子プロセスへの継承:環境変数はデフォルトで子プロセスにすべて引き継がれます。アプリから起動した外部コマンドやプラグインが、意図せずDBパスワードを受け取ります。
/proc/<pid>/environとps:Linuxでは同じユーザー(またはroot)であれば、他プロセスの環境変数をファイルとして読めます。コマンドライン引数で渡した秘密はps auxで全ユーザーに見えます。- コンテナのメタデータ:
docker inspectやECSタスク定義、Kubernetesのマニフェストに平文で書いた環境変数は、そのAPIを読める権限があれば誰でも参照できます。
# 同じユーザーのプロセスなら環境変数は丸見え
cat /proc/$(pgrep -f "node server.js" | head -1)/environ | tr '\0' '\n' | grep -i key
# コマンドライン引数の秘密は全ユーザーから見える
ps aux | grep -- "--password"
だからといって環境変数を使うなという話ではありません。実行時にアプリへ値を渡す手段としては環境変数が最も汎用的で、12-Factor Appでも推奨されています。重要なのは「保管場所」と「渡し方」を分けて考えることです。保管はシークレットマネージャーに任せ、起動時に取り出して環境変数やメモリに渡す、という構成が基本形になります。Linuxの環境変数そのものの仕組みはLinuxの環境変数を完全理解を参照してください。
主要なシークレット管理サービスの使い分け
| サービス | 向いている環境 | 特徴 | コスト感 |
|---|---|---|---|
| AWS SSM Parameter Store | AWS上の小〜中規模 | SecureString型でKMS暗号化。階層パス(/app/prod/db_password)で整理。スタンダード階層は無料 | 低い |
| AWS Secrets Manager | AWS上でローテーションが必要 | RDS等との自動ローテーション連携。バージョン管理。シークレット単位の課金 | 中 |
| GCP Secret Manager | GCP上 | バージョン管理、IAM連携、監査ログ。Cloud Run/GKEから直接マウント可 | 低〜中 |
| HashiCorp Vault | マルチクラウド・オンプレ混在、動的シークレット | DBの一時ユーザーを都度発行する動的シークレット、PKI、暗号化API。自前運用は重い(HCP Vaultのマネージドあり) | 運用コスト大 |
| SOPS + age | GitOps、小規模、クラウド非依存 | YAML/JSONの値だけを暗号化してGitに置ける。鍵はage/KMS/PGP。サーバー不要 | ほぼゼロ |
AWSならParameter Storeから始める
AWSで動いているなら、最初の選択肢はSSM Parameter Storeです。追加費用なしで始められ、IAMでパスごとに読み取り権限を絞れます。ローテーションの自動化やRDS連携が必要になった時点でSecrets Managerに移行するのが一般的な流れです。ECS/Fargateではタスク定義のsecretsに指定するだけで、コンテナ起動時に環境変数として注入され、タスク定義には値そのものが残りません。
# 値の登録(SecureString = KMSで暗号化)
aws ssm put-parameter \
--name "/myapp/prod/STRIPE_SECRET_KEY" \
--type SecureString \
--value "sk_live_xxxxxxxx" \
--overwrite
# ECSタスク定義(抜粋):値ではなくARNを書く
"secrets": [
{
"name": "STRIPE_SECRET_KEY",
"valueFrom": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/myapp/prod/STRIPE_SECRET_KEY"
}
]
ECSのタスク定義全体についてはAWS ECS/Fargateでコンテナ運用で解説しています。
Vaultは「動的シークレット」が必要になってから
Vaultの真価は、DBの認証情報を固定で配るのではなく、「このアプリのこのインスタンスに、1時間だけ有効なDBユーザーを発行する」といった動的シークレットにあります。漏れても短時間で失効し、誰がどの認証情報を使ったか追跡できます。ただしVault自体の高可用構成、アンシール(起動時の鍵解除)運用、ポリシー設計が必要で、専任がいない小さなチームには過剰になりがちです。マルチクラウドやオンプレ混在で、クラウド各社のサービスでは統一できない場合に検討します。
SOPS+ageはサーバーレスな現実解
SOPSは設定ファイル(YAML/JSON/.env)の「値だけ」を暗号化するツールで、キー名は平文のまま残るためGitのdiffが読めます。暗号鍵にはage(軽量な公開鍵暗号ツール)やクラウドのKMSが使えます。Kubernetesのマニフェストや、Ansible/Terraformの変数ファイルを暗号化してリポジトリに置く用途で広く使われています。専用サーバーが不要で、鍵の配布さえ管理できれば運用は非常に軽いのが利点です。
# ageの鍵ペアを作成(秘密鍵は各メンバーが手元で保管)
age-keygen -o ~/.config/sops/age/keys.txt
# 出力される public key: age1... を .sops.yaml に登録
# .sops.yaml(暗号化ルール)
creation_rules:
- path_regex: secrets/.*\.yaml$
age: age1qxyz...,age1abcd... # 復号できるメンバーの公開鍵
# 暗号化してコミット / 復号して利用
sops --encrypt --in-place secrets/prod.yaml
sops --decrypt secrets/prod.yaml | kubectl apply -f -
ローテーション:「漏れたら変える」ではなく「定期的に変える」
なぜ定期ローテーションが必要か
シークレットは、退職・端末紛失・ログ混入・依存ライブラリの脆弱性など、気づかないうちに漏れる可能性が常にあります。定期的に変えておけば、漏れたことに気づけなくても被害の期間を区切れます。また「変えられる」状態を保つこと自体に価値があります。何年も変えていないDBパスワードは、どこで使われているか誰も把握しておらず、いざ変えようとすると怖くて触れない、という状態になりがちです。
無停止でローテーションする手順
APIキーやDBパスワードは、多くのサービスが「同時に2つ有効」にできます。この性質を使って次の順で入れ替えます。
- 新しいキー(またはDBユーザー)を発行し、古いものと並行して有効にする。
- シークレットマネージャーの値を新しいキーに更新する。
- アプリを再起動(またはローリング更新)して新しい値を読み込ませる。
- 古いキーが使われていないことをログやメトリクスで確認してから、古いキーを無効化する。
Secrets ManagerのRDS用ローテーションはこの流れを自動化したもので、Lambdaが新パスワードを発行してDBに反映し、アプリは次回取得時に新しい値を受け取ります。アプリ側は「起動時に1回読んで終わり」ではなく、取得失敗時に再取得する(キャッシュにTTLを持たせる)実装にしておくと、ローテーションを無停止で回せます。
CIでのシークレット注入と最小権限
CIにキーを置かず、CIに「権限」を与える
CI/CDでクラウドにデプロイするために、アクセスキーをCIのシークレットに登録する運用は広く行われていますが、長期間有効な鍵がCIサービスに置かれ続けるリスクがあります。現在はOIDC(OpenID Connect)でCIのジョブに一時的な認証情報を発行する方式が主流です。GitHub ActionsならAWSのIAMロールを信頼関係で紐づけ、ジョブ実行時にだけ有効な一時クレデンシャルを取得します。
# GitHub Actions:長期キーを持たずにAWSへ認証する
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-deploy-role
aws-region: ap-northeast-1
- run: aws ssm get-parameter --name /myapp/prod/DB_PASSWORD --with-decryption
GitHub Actionsは登録済みシークレットの値をログ上でマスクしますが、値を加工(base64化など)してから出力するとマスクが効きません。デバッグ目的でも秘密をechoしない、を徹底してください。設定全体はGitHub Actions CI/CD 設定方法を参照してください。
最小権限:読める範囲をアプリ単位・環境単位で絞る
シークレットマネージャーを導入しても、すべてのアプリが全シークレットを読めるなら、1つのアプリの侵害で全部漏れます。Parameter Storeなら/myapp/prod/*のようにパスで、Secrets Managerならタグやリソース名で、IAMポリシーのResourceを絞ります。本番と開発で別のロール、アプリごとに別のロール、が基本単位です。IAMポリシーの設計はAWS IAMのベストプラクティスで詳しく扱っています。
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"],
"Resource": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/myapp/prod/*"
}, {
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:ap-northeast-1:123456789012:key/your-kms-key-id"
}]
}
トラブル事例:エラー監視ツールにDBパスワードが送られていた
症状
エラー監視SaaSのイベント詳細画面を見ていたメンバーが、「Environment」セクションにDATABASE_URLがパスワード込みで表示されていることに気づきました。数か月分のイベントに同じ情報が含まれており、SaaS側にアクセスできる外部委託先のメンバーも閲覧可能な状態でした。
原因
監視SDKの初期化時に、デバッグを楽にする目的で「環境変数を添付する」オプションを有効にしていました。開発環境で試したときの設定がそのまま本番に持ち込まれ、環境変数の中身が例外レポートに毎回同梱されていたのです。「保管場所」はParameter Storeに移していたものの、「渡した先で環境変数がどう扱われるか」までは確認していませんでした。
対処
- SDKのオプションを無効化し、送信前フック(
beforeSend)でキーやパスワードに該当するフィールドをマスクする処理を追加した。 - 該当するDBパスワード・APIキーをすべてローテーションし、旧値を無効化した。
- SaaS側のイベントデータを削除依頼し、閲覧権限を最小限に見直した。
- 再発防止として、ログ出力とエラー送信のライブラリ設定をコードレビューのチェック項目に加え、環境変数名に
_SECRET/_KEY/PASSWORDを含むものは自動でマスクする共通処理を入れた。
この事例の教訓は、シークレットは「保管」だけでなく「利用中の扱い」まで含めて管理対象だということです。
まとめ
.envはローカル開発の便利機能であり、本番の保管庫ではありません。環境変数はログ・子プロセス・/proc・コンテナ定義から漏れる経路があるため、「保管はシークレットマネージャー、渡し方は起動時の注入」と役割を分けます。AWSならParameter Storeから始めてローテーションが必要になったらSecrets Manager、GCPならSecret Manager、マルチクラウドや動的シークレットが必要ならVault、サーバーを持ちたくないGitOps運用ならSOPS+age、という基準で大きく外れません。
そのうえで、定期ローテーションを無停止で回せる設計、CIにはOIDCで一時的な権限を渡す、アプリごと・環境ごとに読める範囲を絞る、という3点を揃えれば、小さなチームでも十分に堅牢なシークレット管理になります。
シークレット管理の導入や、既存システムの認証情報の棚卸しとローテーション設計でお困りの場合は、Harmonic Societyのシステム開発・インフラ支援にご相談ください。
Harmonic Society
この記事の内容、自社の業務でも活かせそうですか?
ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。
関連記事
Related / 9 articles
Notes & Insights
- プログラミング
DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用
DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。
- プログラミング
WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド
WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。
- プログラミング
セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説
CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。
- プログラミング
クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計
クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。
- プログラミング
秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除
APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。
- プログラミング
開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御
開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。
- プログラミング
ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方
NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。
- プログラミング
ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方
ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。
- プログラミング
localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決
localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。
Harmonic Society
「読んで終わり」にせず、自社の業務で試してみませんか?
AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。
無料・30分・オンラインOK|1営業日以内に返信します