KMS CMKのデフォルトキーポリシーを変更!セキュアになると思いきや、思わぬ落とし穴が...

Hirokazu Yoshida
Hirokazu Yoshida

シニアセキュリティスペシャリスト / サイバーセキュリティスペシャリスト

こんにちは、ひろかずです。

この記事は、とあるイベントのとあるセッション(開催後にお知らせします)のアンサーブログです。AWSに携わる人にとっても勉強になる記事だと思いますので、最後まで読んでいただけると幸いです。

忙しい人向けの要約

  • デフォルトキーポリシーの Principal: root は「アカウント内の全員への許可」ではなく、「IAMポリシーで認可を管理する仕組み(アカウント)への委任」を意味します。誰かに権限を開放しているわけではありません。
  • このPrincipalを特定ロールに書き換えると、委任が取り消され、委任に依存していたIAMポリシーのAllowが効かなくなります。本記事のように他のキーポリシー許可文も既存のKMSグラントもないキーでは、既存の利用者もAdministratorAccess保有者もそのキーにアクセスできなくなります。KMSはキーポリシーが許可しない限りIAMポリシー単独では許可できないという、厳格なデフォルト拒否だからです。
  • キーを使えるプリンシパルを絞りたいなら、キーポリシーのroot文を安易に削除せず残したまま、IAMポリシー側を最小権限にするのが基本です(root文の維持はAWS KMSの必須要件ではなく、必要な管理者・利用者を直接許可するカスタムキーポリシーも構成できますが、PutKeyPolicy を継続できる管理経路の確保が前提です)。ロックアウト保護が警告を出したら、それは立ち止まるべきサインです。

事のはじまり

AWS KMSのカスタマー管理キー(CMK)を作ると、必ずキーポリシーが付いてきます。そのデフォルトには、こんな一文が入っています。

json
{
  "Sid": "Enable IAM User Permissions",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
  "Action": "kms:*",
  "Resource": "*"
}

セキュリティ意識の高いAさんは、AWSドキュメントでこの文の説明を調べました。

This default key policy statement allows the account to use IAM policies to delegate permission for all actions (kms:*) on the KMS key.
(この文は、KMSキー上のすべてのアクション(kms:*)の許可を、IAMポリシーで委任することをアカウントに許可する。)

これを読んだAさんは、こう考えます。

「rootアカウントを取られたら、このCMKは使い放題ってことだよな? 内部犯がデータを持ち出すことも考えられる…。そうだ! 特定のロールでしかIAMポリシーに委任できないようにすれば、セキュアになるぞ!」

Aさんは Principal をキー管理用ロールのARNに差し替えることにしました。変更はこの1点だけ。シンプルで、いかにも堅牢になりそうです。しかしこの時点でAさんは、「委任」の意味を決定的に誤解しています。

この記事は、その思いつきがどんな落とし穴につながったか、実際に何がどのタイミングで起きるかを追いながら、「アカウントプリンシパルに委任」というわかりづらい概念を学ぶ話です。

前提: 変更前は何が動いていたか

このCMKはAPI(CLI)からキーポリシーを指定せずに作成されたもので、キーポリシーには冒頭のデフォルト文(root文)だけが存在します。キー管理者文・キーユーザー文といった他の許可文や、既存のKMSグラントはありません。

この前提のもと、アプリ担当のユーザーに以下のIAMポリシーをアタッチして運用されていました。

json
{
  "Effect": "Allow",
  "Action": ["kms:Encrypt", "kms:Decrypt"],
  "Resource": "arn:aws:kms:ap-northeast-1:111122223333:key/<KEY_ID>"
}

このユーザーは問題なく暗号化・復号できています。Aさん自身にも、IAMポリシーで kms:PutKeyPolicy が許可されており、変更前のキーポリシーのroot文がこのIAM委任を有効にしていたため、キーポリシーを変更できる状態です。

異変:変更コマンドがエラーで拒否される

Aさんは早速、Principalをロールに差し替えたキーポリシーを適用します。

bash
aws kms put-key-policy --key-id $KEY_ID --policy-name default \
  --policy file://new-key-policy.json

すると、いきなりエラーが返ってきました。

MalformedPolicyDocumentException: The new key policy will not allow
you to update the key policy in the future.

「新しいキーポリシーでは、今後あなたはキーポリシーを更新できなくなります」。

これはKMSのポリシーロックアウト保護です。PutKeyPolicyは、適用しようとするキーポリシーが「実行者自身による以後の PutKeyPolicy」を許可していない場合、リクエストを拒否します。

ここでAさんは立ち止まるべきでした。KMSが「あなた自身が締め出されますよ」と警告しているのですから。しかしAさんはこう考えます。

「自分はAdministratorAccess相当の権限を持っている。IAMポリシーでどうにでもなるはずだ」

そして、ドキュメントに書かれていた回避フラグを付けて再実行しました。

bash
aws kms put-key-policy --key-id $KEY_ID --policy-name default \
  --policy file://new-key-policy.json \
  --bypass-policy-lockout-safety-check

今度は成功。Aさんは満足しました。この時点では。

落とし穴その1: アプリのユーザーが突然、復号できなくなる

キーポリシー変更の直後から、アプリ担当ユーザーの復号がすべて失敗し始めます。

AccessDeniedException: User: arn:aws:iam::111122223333:user/app-user
is not authorized to perform: kms:Decrypt ... because no
resource-based policy allows the kms:Decrypt action

IAMポリシーは何も変えていません。kms:Encrypt / kms:Decrypt を許可するポリシーは、今もユーザーにアタッチされたままです。それでも拒否されます。

落とし穴その2: Aさん自身も締め出される

異変に気づいたAさんは、キーポリシーを確認しようとします。

AccessDeniedException: User: arn:aws:iam::111122223333:user/a-san-admin
is not authorized to perform: kms:GetKeyPolicy ...

自分も見られない。 AdministratorAccessを持っていても、です。ロックアウト保護の警告どおりのことが起きました。このキーに触れるのは、キーポリシーに書いた特定ロールだけになったのです。

なぜこうなったのか: 「アカウントプリンシパルに委任」の意味

種明かしをします。答えは、冒頭でAさんが読んだドキュメントの続きに書かれていました。

「委任」の相手は人ではなく、IAMポリシーという仕組み

arn:aws:iam::111122223333:root という書き方はアカウントプリンシパルと呼ばれ、アカウント内の全ユーザー・全ロールに直接 kms:* を許可するものではありませんAWSドキュメントは、Aさんが読み飛ばした箇所で明確に述べています。

When the principal in a key policy statement is the account principal, the policy statement doesn't give any IAM principal permission to use the KMS key. Instead, it allows the account to use IAM policies to delegate the permissions specified in the policy statement.
(キーポリシー文のプリンシパルがアカウントプリンシパルである場合、その文はどのIAMプリンシパルにもKMSキーを使う許可を与えない。代わりに、その文で指定された許可をIAMポリシーで委任することをアカウントに許可する。)

「どのIAMプリンシパルにも許可を与えない(doesn't give any IAM principal permission)」。Aさんの理解と正反対です。委任の相手は「アカウント内の全員」ではなく「アカウントのIAMポリシー評価の仕組み」であり、この一文の正体は、権限の付与ではなく**「このキーへのアクセス可否は、IAMポリシー側で決めてよい」という委任のスイッチ**です。

  • スイッチON(root文あり): IAMポリシーで kms:Decrypt を許可されたユーザーは復号できる
  • スイッチOFF(root文なし): IAMポリシーで何を許可しても、このキーには効かない

Aさんが恐れた2つの脅威は、どこへ行ったのか

こう整理すると、冒頭のAさんの心配にも答えが出ます。

「rootアカウントを取られたら使い放題」

この懸念自体は事実です。アカウントプリンシパルはrootユーザーを含むアカウント全体を指すため、rootユーザーが侵害されれば、このキーは実際に使われます。ただしその対処はrootユーザー自体の保護(MFAの強制、アクセスキーを作らない)であり、キーポリシーのPrincipal書き換えではありません。

「内部犯がデータを持ち出す」

こちらは前提が崩れています。この一文はアカウント内の誰にも許可を与えていないため、キーを使えるのは最初からIAMポリシーで明示的に許可されたプリンシパルだけです。つまり内部犯対策の実体はIAMポリシーの最小権限管理そのものであり、委任スイッチを切ることではないのです。

KMSはデフォルト拒否、IAMポリシー単独では許可できない

KMSのアクセス制御は他のAWSリソースと少し違い、キーポリシーが明示的に許可しない限り、IAMポリシーのAllowは効力を持ちません

Unless the key policy explicitly allows it, you cannot use IAM policies to allow access to a KMS key. Without permission from the key policy, IAM policies that allow permissions have no effect.

Aさんの変更を時系列で整理すると、こうなります。

タイミング起きたことなぜ
変更前アプリユーザーがIAMポリシーで復号できていたroot文が委任スイッチONにしていた
put-key-policy 実行ロックアウト保護で一度拒否された新ポリシーにAさん自身の許可がなかった
バイパスして適用成功(無出力)意図的な保護解除として受理された
直後アプリユーザーが復号不能にPrincipalからアカウントプリンシパルが外れ、委任スイッチOFF。委任に依存していたIAMポリシーのAllowが無効化
直後Aさん(Administrator相当)も操作不能に同上。AdministratorAccessもIAMポリシーにすぎない

Aさんは「委任できる範囲を絞った」つもりでした。しかし実際にやったのは、アカウントプリンシパルへの委任を丸ごと取り消し、その委任に依存していたIAMポリシーによる全員のアクセスを一括で無効化することだったのです。このキーには他の許可文も既存グラントもなかったため、結果としてキーポリシーに書いた特定ロール以外、誰もキーを使えなくなりました。

それでも救いはある

不幸中の幸いが2つあります。

  1. キーポリシーに残した特定ロールは kms:* を持っているため、そのロールを引き受ければキーの操作もキーポリシーの復旧もできます。Aさんのケースではロールの信頼ポリシーが正しく設定されていたため、ロール経由で元のキーポリシーに戻せました。
  2. 仮にそのロールを削除してしまいキーが完全に管理不能になっても、AWSドキュメントによればAWS Supportに連絡してアクセスを回復する道が残っています。ただし、それは最後の手段です。

実はAWSドキュメントは、この一文からアカウントプリンシパルを外すことへの警告を最初から明記していました。

The following default key policy statement is critical.
(以下のデフォルトキーポリシー文は、極めて重要である。)
However, giving access to the account principal helps you prevent the key from becoming unmanageable.
(アカウントプリンシパルにアクセスを与えておくことは、キーが管理不能になることを防ぐのに役立つ。)

さらに、キーポリシーを最小権限に編集すること自体は推奨しつつも、その方法として案内されているのは「アカウントプリンシパル文を残したままIAMポリシー側を絞る」か「キー管理者・キーユーザーの許可文を明示的に追加する」であり、この文を変更する方法は示されていません。

デフォルトキーポリシーがrootプリンシパル(アカウントを削除しない限り消えない唯一のプリンシパル)を指定しているのは、まさにキーが管理不能になるリスクを減らすためです。「緩すぎる設定」に見えた一文は、AWSが「critical」と明記する安全装置でした。

教訓:絞りたいなら、絞る場所が違う

Aさんの志は正しいのです。問題は手段でした。KMSへのアクセスを絞りたい場合の定石は次のとおりです。

root文(Enable IAM User Permissions)は原則として安易に削除しない

これは全開放ではなく委任スイッチです。外すと、委任に依存していたIAMポリシーのAllowが当該CMKに対して効かなくなります

  • 別のキーポリシー許可文や既存グラントによるアクセス経路は残ります

直接許可だけのカスタムキーポリシーも構成可能ですが、PutKeyPolicy を継続できる管理者・復旧経路・適用前テストの確保が前提です。

絞るのはIAMポリシー側で

委任を有効にしたまま、IAMポリシーで対象キー・対象アクションを最小権限に絞るのが基本形です。

キーポリシーで直接制御したい場合は、root文を消すのではなく明示的な許可文を足す

キー管理者文・キーユーザー文を追加するコンソールのデフォルトキーポリシーの構成が参考になります。

  • --bypass-policy-lockout-safety-check は、自分が締め出されることを意図した時だけです。 警告をバイパスで黙らせる前に、なぜ警告されたのかを確認しましょう。

まとめ

  • キーポリシーの root プリンシパルは「アカウント全体へのフルアクセス許可」ではなく、「IAMポリシーへの委任を有効にするスイッチ」
  • Principalを書き換えてこのスイッチを外すと、アカウントプリンシパルへの委任に依存していたIAMポリシーのAllowが効かなくなる(別のキーポリシー許可文や既存のKMSグラントがあれば、それらによるアクセス経路は残る)
  • AdministratorAccessも例外ではない。IAMポリシーである以上、委任が切れれば無力
  • ロックアウト保護の警告は、設計を見直せというサイン

アカウントプリンシパルARNが「全員への直接許可」ではなく「アカウントへの権限委任」を表すこと自体は、IAMの基本概念です。ただし、その委任が実際にどう評価されるか(同一アカウント内でIAMポリシー単独で許可できるか等)は、サービスとポリシー種別によって異なります。

キーポリシーが許可しない限りIAMポリシーのAllowが効かないのはKMSの厳格な仕様です

  • たとえばS3バケットポリシーでは同一アカウント内ならIAMポリシー単独でアクセスを許可できます。

「rootに kms:* は怖い」という直感は、怖さの正体を読み違えていました。本当に怖かったのは、その一文のPrincipalを書き換え、KMSのデフォルト拒否と組み合わさった後でした。

参考資料

この記事をシェア