アクセスキーが漏れる理由は、存在するからだ
一言でいうと
長期のアクセスキーは、作らないのが最善です。ロールを引き受けて受け取った一時的な資格情報は、数時間後に自分で期限切れになるので、漏れても、被害が時間で区切られます。
なぜ必要なのか
GitHubの公開リポジトリにAWSのキーがコミットされる事故は、今も毎日起きています。ボットが数分で見つけ出して、暗号資産のマイニング用のインスタンスを起動します。請求書は数千万ウォン単位で届きます。
ここで本当の問題は、コミットのミスではなく、そのキーが存在していたことです。長期のキーには、次の性質があります。
- 自分で期限切れにならない: 人が取り消すまで、永遠に有効です
- どこで使われているか追跡しにくい: だから、取り消すのが怖いです
- コピーしやすい: ドキュメント、チャット、環境変数、CIの設定に広がります
どう動くのか
ロールを引き受けるということ
ロールは権限のまとまりで、信頼ポリシー(trust policy)が、「誰がこのロールを引き受けられるか」を決めます。
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
このロールは、EC2インスタンスが引き受けられます。インスタンスに付けておくと、その中で動くプログラムがキーなしで資格情報を得ます。メタデータサービスから自動で受け取り、期限切れの前に自動で更新されます。
まとめると、次のとおりです。
| 長期のアクセスキー | ロール(一時的な資格情報) | |
|---|---|---|
| 有効期限 | なし | 通常1–12時間 |
| 配布 | ファイル・環境変数でコピー | 自動で注入 |
| 取り消し | 手動で探して取り消す | 期限切れを待てばよい |
| 追跡 | 誰が使っているか不明 | ロール単位で監査ログ |
サービスアカウントのキーも同じ問題
Kubernetesでも同じです。Podにクラウドのキーを、Secretとして入れる方式は、同じ危険をそのまま抱えます。代わりにOIDCフェデレーションを使います。クラスターが発行したサービスアカウントトークンで、クラウドのロールを引き受けます(AWSのIRSA、GCPのWorkload Identity、AzureのWorkload Identity)。キーがそもそも存在しなくなることが核心です。
CIも同じです。GitHub ActionsやGiteaのランナーに長期のキーを入れる代わりに、OIDCでロールを引き受けさせれば、リポジトリのSecretからクラウドのキーが消えます。
それでも長期のキーが必要なとき
完全になくせない場合があります(外部SaaS連携など)。そのときは、最低限、次のようにします。
- 専用のプリンシパルを作ります。人のアカウントのキーを共有しません。
- 権限をその用途だけに絞ります。Conditionで、IPや時間まで。
- 定期的なローテーション(rotation)を手順にします。カレンダーに入れます。
- 使用の痕跡を見ます。最後に使われた時刻が古ければ、削除します。
4つ目が最も安上がりで、効果が大きいです。使っていないキーを削除するだけで、攻撃面が大きく減ります。
セッションと有効期限
ロールを引き受けるときに、セッション時間を決めます。短いほど安全ですが、短すぎると、長い作業の途中で期限切れになります。実務の基準は次のとおりです。
- 人のコンソール作業: 1時間程度
- CIジョブ: ジョブの最大実行時間より少し長く
- 長いバッチ: 資格情報の更新をコードが処理するようにします(SDKがたいていやってくれます)
現場での姿
- リポジトリでキーが見つかった → すぐ取り消し。ところが、どこで使っているかわからず、取り消しを先送りしました。
- インスタンスにキーファイルを置いて使っている → ロールを付ければ、そのファイルは不要です。
- キー1つをチーム全体で共有している → 監査ログで、誰がやったのかがわかりません。
ロールを引き受けるときに気をつけること
ロールが長期のキーより安全なのは確かですが、信頼ポリシーを緩く書くと、その安全がまるごと消えます。事故が起きる場所は、たいてい決まっています。
誰でも引き受けられるように開けておいたロール。信頼ポリシーのプリンシパルを広く書くと、別のアカウントの誰かが、そのロールを引き受けられます。外部の協力会社に権限を与えようと開けておいたものが代表的で、相手のアカウント番号だけでは足りません。そのアカウントの中の誰でも引き受けられるからです。外部に開けるときは、双方だけが知る識別子を条件として一緒にかけ、第三者がそのアカウントを経由して迂回できないようにする必要があります。
フェデレーションで条件を抜かすこと。CIがOIDCでロールを引き受けるとき、どのリポジトリのどのブランチかを条件にかけないと、そのCIサービスを使うどのリポジトリでも、こちらのロールを引き受けられます。これが最もよく出るミスで、条件1行で防げます。
ロールを渡す権限。リソースを作るときに、そのリソースにロールを付ける操作は、実質的に、「自分にはできないことを代わりにやらせられる」権限です。この操作を与えるときは、どのロールを付けられるかまで絞る必要があり、そうしないと、低い権限のユーザーが、管理者ロールを付けたリソースを作って、権限を引き上げられます。
チェーンの終わりを見ます。ロールが別のロールを引き受け、それがまた別のロールを引き受ける構造ができると、最終的に何ができるのかを、人が追跡しにくくなります。前のモジュールで述べた評価ツールが、ここで特に役立ち、チェーンの深さをルールで制限しておくことも方法です。
最後に、一時的な資格情報も、有効なあいだは取り消されません。ロールの権限を減らしても、すでに発行されたセッションは、そのまま生きています。そのため、セッション時間を短くすることは、単なる衛生ではなく、事故が起きたときの対応速度そのものです。
次に見ること
権限を実際に絞っていく順序。最初から最小権限を当てることは不可能です。