【AWS IAM】Condition の条件キーやポリシー変数は可用性を意識しよう!という話

2021.07.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

突然ですが問題です

あなたはAWS環境の管理者です。 IAMロールに付与したタグベース で、 EC2インスタンス開始/停止を制御しようと試みています。

img

現状 利用者ロールに割り当てている権限は PowerUserAccess 相当です。 以下のポリシーを追加で付与して制御を実現しました。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": ["ec2:StartInstances", "ec2:StopInstances"],
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalTag/Project": "${aws:ResourceTag/Project}"
}}}]}

これの適用後、実環境で 2つのミス をしてしまいました。 1つは「あるロールへのタグの付け忘れ (ケース#1) 」、 もう1つは「あるインスタンスへのタグの付け忘れ (ケース#2) 」です。

img

ケース#1, ケース#2 の状態で Start/StopInstances されたときに、 追加したポリシーが働くのか(=Denyされるか)どうか、 以下選択肢から正しい組み合わせを選んでください(10点)

  • A. ケース#1は Deny される、ケース#2は Deny される
  • B. ケース#1は Deny される、ケース#2は Deny されない
  • C. ケース#1は Deny されない、ケース#2は Deny される
  • D. ケース#1は Deny されない、ケース#2は Deny されない

(Thinking time…)

はい、正解は B. でした。正解できたでしょうか?

以降で なぜ B. になったのか説明していきます。

はじめにまとめ

  • 【リクエストコンテキスト】の キーの可用性を意識しよう
  • 【条件キー】に指定したキーが無いとき、 それは "不一致" (false) として扱われる

    • 符号反転系の【条件演算子】の場合、 true になる
  • 【ポリシー変数】に指定したキーが無いとき そのステートメントは無効になる

【】でくくった用語を次の "前提知識" で説明します。 下線部分の解説もその後に行います。

前提知識

リクエストコンテキスト

この単語は IAMポリシー周りのドキュメントによく見られます。

「プリンシパル」が AWSに「リクエスト」を送る際に、 リクエストに関する情報 をまとめます。 この リクエストに関する情報リクエストコンテキスト と言います。

ここでいう「プリンシパル」は、IAMユーザーやIAMロール等を指します。 そして「リクエスト」は、マネジメントコンソールや AWS API, AWS CLIの使用を指します。

このリクエストコンテキストを使って、リクエストの評価、及び 許可/拒否 の判断(ポリシーを適用するか)を行います。

リクエストコンテキストのキー(条件キー)

リクエストコンテキストの 特定要素を指すためのキー です。 IAMポリシーの Condition (後述) で主に使用するため、 条件キー(or 条件コンテキストキー) とも言います。

大きく分けて サービスに依存しない グローバル条件キー各サービス固有の条件キー が存在します。

  • 例えば aws:username はグローバル条件キーです。 リクエスト時の プリンシパルのユーザー名 を指します。
  • 例えば ec2:Vpc はサービス固有の条件キーです。 EC2サービスで使用します。操作対象リソースの 所属するVPC ARN を指します。

使用できる条件キーは以下ドキュメントで調べられます。

ポリシー変数

ポリシー変数を使うことで、リクエストコンテキストのキーを IAMポリシーの値として活用 できます。 これは IAMポリシーの Resource および Condition で使用できます。

以下 ポリシー例を見ると使い方がよく分かると思います。 ${} で括ります。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": ["s3:ListBucket"],
      "Effect": "Allow",
      "Resource": ["arn:aws:s3:::mybucket"],
      "Condition": {"StringLike": {"s3:prefix": ["${aws:username}/*"]}}
    },
    {
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Effect": "Allow",
      "Resource": ["arn:aws:s3:::mybucket/${aws:username}/*"]
    }
  ]
}

– 引用: IAM ポリシーの要素: 変数とタグ - AWS Identity and Access Management

※Tipsその1 ポリシー変数変数を利用するには IAMポリシーに "Version": "2012-10-17", の記載が必要です。

※Tipsその2 デフォルト値を指定できます。 例えば ${aws:userid, 'anonymous'}aws:userid (プリンシパル識別子)を指しますが、 このキーが含まれない(=匿名リクエスト) 場合は、 anonymous 文字列を返します。

IAMポリシーの Condition

IAMポリシーの Condition要素 でポリシーが適用されるかどうかの判断を行うことができます。

一番シンプルな Conditionは以下のような構文です。

"Condition" : {
  "(条件演算子)" : {
    "(条件キー)" : "(条件キーの値)"
  }
}

条件演算子 を使用して「条件キーの値」と「実際のリクエストコンテキストの値」との比較を行います。 代表的な条件演算子として 文字列の完全一致比較に使う StringEquals や 数値の大小比較に使う NumericLessThan/NumericGreaterThan があります。

「条件キー」には先程の リクエストコンテキストのキー が入ります。

「条件キーの値」には特定文字列/数値、もしくは先程の ポリシー変数 が入ります。

ここで冒頭の問題に示したポリシーを改めて見てみましょう。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": ["ec2:StartInstances", "ec2:StopInstances"],
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalTag/Project": "${aws:ResourceTag/Project}"
}}}]}

このポリシーの「条件演算子」、「条件キー」、「条件キーの値」を整理してみます。

  • 条件演算子 … StringNotEquals (文字列完全一致の符号反転)
  • 条件キー … aws:PrincipalTag/Project (プリンシパルのProjectタグ値)
  • 条件キーの値 … ${aws:ResourceTag/Project} (リソースのProjectタグ値)

つまり 「プリンシパルのProjectタグ値」と「リソースのProjectタグ値」 が「文字列一致しない」場合に、ポリシー適用(Deny)される内容となっています。

各キーには可用性がある

以降が本題です。

リクエストコンテキストのキーには可用性があります。 例えば aws:ResourceTag/tag-key というキーは、 対象リソースにそのタグが付与されている場合に存在します。 当然ですが付与されていない場合は存在しません。

この「キーの可用性」、グローバル条件キーの場合はドキュメントに記載があります。

img

– 画像: AWS グローバル条件コンテキストキー - AWS Identity and Access Management

サービス固有の条件キーの場合は、「アクション × リソース」毎に条件キーが決まります。 ドキュメントで調べることができます。

img

– 画像: Amazon EC2 のアクション、リソース、および条件キー - サービス認証リファレンス

条件キーに指定したキーが存在しないとき、どうなる?

条件キーに指定したキーが存在しないとき、 不一致(=false) として扱われます。

例えば以下のような Condition の場合、 プリンシパルに Projectタグがアタッチされていない場合、 Condition全体としては falseを返します

"Condition": {
  "StringEquals": {
    "aws:PrincipalTag/Project": "XXX"
  }
}

ここで注意したいのが 符号反転系の条件演算子 を使うケースです。 StringNotEqualsNumericNotEquals など、 Not が付与される条件演算子です。

これらを使用した場合、条件キーが無い場合は trueを返します

img

ポリシー変数に指定したキーが存在しないとき、どうなる?

次にポリシー変数に指定したキーが存在しないとき。 こちらは ステートメントが無効になります

無効になる は以下のようなイメージです。

img

前の章で説明した 不一致 のケースでは、 条件演算子次第で true になりました(=ステートメントが評価される)。

しかしポリシー変数に指定したキーが存在しないときは、 無効 です (=ステートメントは評価されない)。もちろん条件演算子に依存しません。

改めて問題の答え合わせ

img

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": ["ec2:StartInstances", "ec2:StopInstances"],
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalTag/Project": "${aws:ResourceTag/Project}"
}}}]}

改めて冒頭の問題を見てみましょう。

まず説明しやすい ケース#2 から見ます。 リソースタグはポリシー変数として利用しています。 つまりリソースタグの付け忘れは ステートメント無効化 に繋がります。 よって追加のポリシーは評価されず、 Denyされません

次に ケース#1 を見てみます。 プリンシパルタグを条件キーとして利用しています。 つまりプリンシパルタグの付け忘れは 不一致(false) として扱われます。 そして使用している条件演算子は StringNotEquals です。 符号反転の条件演算子なので、 Condition 部分は true になります。 よって追加のポリシーは評価され、 Denyされます

…といった理由で正解は 「B. ケース#1は Deny される、ケース#2は Deny されない」 でした。

キーが無いときに備えてどう対処するか?

条件キーの場合

指定したキーが無いときに 「trueとしたいか」、「falseとしたいか」 ポリシー設計段階で決めましょう。

「trueとしたい」 場合は IfExists を活用します。 (Nullを除く) 全ての条件演算子の末尾に付けることができます。 キーが存在しない場合に 条件要素は true として評価されます

「falseとしたい」 場合は Null 条件演算子 を使った条件を 明示的に追加 しましょう。

例えば冒頭問題のポリシーが「プリンシパルタグが無い場合は Denyされないようにしたい」設計だったとします。 その場合は以下のような Null条件を追加します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": ["ec2:StartInstances", "ec2:StopInstances"],
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "Null": {
          "aws:PrincipalTag/Project": false
        },
        "StringNotEquals": {
          "aws:PrincipalTag/Project": "${aws:ResourceTag/Project}"
}}}]}

Null条件を入れることで、 条件演算子に左右されずに 「条件キーが無いときは false」になります。

ポリシー変数の場合

「デフォルト値を設定する」「明示的に Null条件を付与する」 の 2つをポリシー設計段階で検討しましょう。

▼デフォルト値を設定する

前提知識 > ポリシー変数 の Tips に書いたとおり、 ポリシー変数にデフォルト値を設定できます。これを設定することで、 とりあえずステートメントが無効になる事態は回避できます。

▼明示的に Null条件を付与する

Null条件を付与することで 「指定したキーが有る前提のステートメント」 であることを明示的に示せます。 具体的には指定したキーが無いときに false となるように Null条件を追加します。

この Null条件の有無で、 以下のように「指定したキーが無いときの挙動」が異なります。

  • [Null条件が無い場合]
    • ポリシー変数に指定したキーが存在しないため無効
    • 無効のためステートメントは評価されない
  • [Null条件が有る場合]
    • Null 条件の部分で false が返る
    • Condition は 全条件のAND で評価されるためこの時点で Condition 部分が false となる
    • Condition 部分が false のためステートメントは評価されない

おわりに

以上、条件キーとポリシー変数の「キーがないときの挙動」の話でした。 タグベースの制御(ABAC)をする際は、今回話したようなポリシー設計の考慮点多々あると思います。

「キーが無いときにどうするか」までを考慮した設計を進めましょう。

参考