LayerX Fintech事業部(※)で、ガバナンス・コンプラエンジニアリングをしている 鈴木 (@ken5scal )です。 ※三井物産デジタル・アセットマネジメントに出向しています。
今回は、AWS IAMポリシーの条件における「ForAllValues」の仕様を誤って理解していたことから、安全でないアクセス制御を実装していたという内容です。もし同様の勘違いをされている方がいたら参考になれば幸いです。
ユースケース
AWS IAMユーザーを、ロールの trust policy がユーザーのタグで制御するケースで考えます。
具体的には、「Group A あるいは Group B」に所属し、且つ「Admin」権限のあるユーザーのみが行使できる役割「AdminABRole」があるとしましょう。
この場合、Group と Admin のタグが存在し、下記のようなパターン(※)が考えられます。
※完全なカバレッジではありませんが、本ブログの内容から省略します。
| ユーザー名 | Groupタグ | Adminタグ | 想定挙動 |
|---|---|---|---|
| UserA | A | タグキーなし | ✗ |
| AdminA | A | true | ◯ |
| UserB | B | タグキーなし | ✗ |
| AdminB | B | true | ◯ |
| UserC | C | タグキーなし | ✗ |
| UserX | タグキーなし | タグキーなし | ✗ |
| UserY | タグ値なし | タグ値なし | ✗ |
では、早速、自分が犯した間違いから見ていきましょう。
間違ったポリシーの例
# Bad example
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::297323088823:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"ForAllValues:StringEquals": {
"aws:PrincipalTag/Group": [
"A",
"B"
],
"aws:PrincipalTag/Admin": "true"
}
}
}
]
}
改めてですが、上記では意図したアクセス制御になりません。
具体的には、Groupタグキーのない「Admin」保持ユーザー、「Admin」タグキーのないGroup A/B所属のユーザー、あるいはタグキーが全くないユーザー(例: UserX)は評価上「true」になり、AdminABRole に assumeできてしまいます。
適切にアクセス制御(Deny)できる対象は、UserCやUserYのみです。
| ユーザー名 | Groupタグ | Adminタグ | 想定挙動 | 実際の挙動 |
|---|---|---|---|---|
| UserA | A | タグキーなし | ✗ | ◯ |
| AdminA | A | true | ◯ | ◯ |
| UserB | B | タグキーなし | ✗ | ◯ |
| AdminB | B | true | ◯ | ◯ |
| UserC | C | タグキーなし | ✗ | ✗ |
| UserX | タグキーなし | タグキーなし | ✗ | ◯ |
| UserY | タグ値なし | タグ値なし | ✗ | ✗ |
びっくりしちゃいますね。

正しくは次のようにします。先との違いは「ForAllValues」修飾子がありません。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::297323088823:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/Group": [
"A",
"B"
],
"aws:PrincipalTag/Admin": [
"true"
]
}
}
}
]
}
Why?
直接的な原因は、「ForAllValues」の仕様にあります。
下記の通り、「ForAllValues」は評価対象の aws:PrincipalTag/{tag-key} (が含まれるContext Keys)が存在しない場合、あるいは、その値がnullデータセットと判定される場合、trueを返します。ドキュメントでは、Null条件修飾子の利用もあわせて紹介されていますが、本ブログの趣旨ではないので割愛します。
It also returns true if there are no context keys in the request, or if the context key value resolves to a null dataset, such as an empty string.(※)
Single-valued vs. multivalued context keys - AWS Identity and Access Management ※タグ値を空にした場合はしっかりfalse判定されるので、no context keysや null datasetにresolveされるvalueの、正確な意味を図りかねています...
そのように誤解した理由としては、request contextにあるContext Keyの「Single-valued 」と「multivalued context」を間違って理解していたためです。当初は字面そのものを受け止め、複数の値(value)があれば「multivalued context」となり、したがって次のルールが適用されると思っていました。
Multivalued context keys require a condition set operator. Do not use condition set operators ForAllValues or ForAnyValue with single-valued context keys
Single-valued vs. multivalued context keys - AWS Identity and Access Management
しかし、実際は、「Single-valued 」と「multivalued context」は、request contextの種別であり、
aws:PrincipalTag/{tag-key} は、tagのキーごとに評価される「Single-valued 」でした。
なお、各request contextの種別は次から確認できます。
AWS global condition context keys - AWS Identity and Access Management
ちなみに
「Single-valued 」と「multivalued context」の違いについては、めちゃくちゃ警告しています。これらのページに行く前にすでに勘違いしてた....

終わりに
今回は、ForAllValues修飾子の仕様間違いについて取り上げました。 直接的な内容は上記の通りですが、根本的には仕様の誤解でした。 しかし、これは厄介です。 コピペならまだわかる(?)のですが、誤解による構成ミスについては、一定の確率でおこってしまいそうです。 一応、AWSコンソール上であればIAM Access Analyzerが警告してくれますが、これをIaCとしてチェックしたり、或いはシステム監査的にやるにはどうすればいいかは考える必要があります。 それらについては今後のエントリにご期待ください。

絶賛採用中!
Fintech 事業部(MDM)のコーポレートシステム部では、上記のように難しい課題を一個一個解きほぐしていただけるメンバーを募集しています。是非、下記や https://twitter.com/ken5scalのDMでお声がけください!

