[仕様変更] IAM ロール信頼ポリシーの挙動が変更になり IAM ロールの「暗黙的な自己信頼」がなくなりました

自分を引き受けたいなら明示的に自分を信頼してあげる必要があります。人生と同じですね。

コンバンハ、IAM ロール大好きおじさん(幸)です。

IAM ロールの信頼ポリシーの挙動がアップデートされた、というアナウンスがありました。

これまで存在していた「暗黙的な自己信頼」が無くなり、自身を Assume Role する際には明示的に自身を信頼してあげる必要がある、という内容です。

これを見て「何のこっちゃ」「自身を Assume Role するって何?」となったかもしれませんが、ほとんどの人には関係ないのでそこまで気にしなくても大丈夫です。

上記のブログによれば、あまねく IAM ロールのうちおよそ 0.0001% が今回の変更の影響を受けるそうです。 *1

先にまとめ

  • 以下条件で Assume Role が成功する要因をここでは「暗黙的な自己信頼」と呼ぶ
    • IAM ロール A の IAM ポリシーで自身を Assume Role する許可がある
    • IAM ロール A の信頼ポリシーで自身からの Assume Role を許可していない
    • IAM ロール A が IAM ロール A を Assume Role する
  • 仕様変更により「暗黙的な自己信頼」は無くなったが、以下に合致するロールは例外となる
    • 2022年6月30日以降に「暗黙的な自己信頼」を使用したことがある
      • その場合 2023年2月15 日まで「暗黙的な自己信頼」は延長され、その後廃止される
  • そもそも自分自身を Assume Role するのが妥当なケースは考えづらいので該当する場合は見直しを

おさらい:Assume Role そして信頼ポリシー

更新された内容を理解するためにまずは基本的に部分を押さえておきましょう。

IAM ロールには以下 2 種類のポリシーをアタッチできます。 *2

  • 信頼ポリシー
    • 誰がその IAM ロールを引き受けられるかを定義
  • IAM ポリシー(アイデンティティベースポリシー)
    • その IAM ロールを引き受けたセッションが何をできるかを定義

IAM ロールを引き受ける(Assume Role)ことで、一定時間そのロールが持つ権限を利用できます。具体的には以下のステップからなります。

  1. IAM ユーザーなどのプリンシパルが、引き受けたい IAM ロールを指定してsts:AssumeRoleを実行する
  2. sts:AssumeRoleにより払い出された一時的なクレデンシャルをセットする *3

ステップ 1 が成功するためには、実行元のプリンシパルが IAM ロールの信頼ポリシーで許可されている必要があります。例では IAM ロールを引き受けるプリンシパルとして IAM ユーザーを挙げましたが、他にもプリンシパルは存在します。

そして「IAM ロールを引き受けたセッション」がプリンシパルになることも可能です。

図では以下のように 2種類の IAM ロールが登場しました。

  1. IAM ユーザーが IAM ロール A を引き受ける
  2. IAM ロール A を引き受けたセッションが IAM ロール B を引き受ける

ステップ 2 で IAM ロール A を指定することもできます。それが冒頭で述べた「自身を Assume Role する」に該当します。

何が変わったのか

おさらいが終わったところで本題に入ります。

以下の条件の IAM ロール A を考えます。

  • IAM ポリシーで「IAM ロール A を Assume Role できる」許可が与えられている
  • 信頼ポリシーで「IAM ロール A が Assume Role できる」許可を与えていない
    • 以下の両方を指す
      • IAM ロール A が所属する AWS アカウント全体の許可
      • IAM ロール A を指定しての許可

ここで IAM ロール A が自身を Assume Role するケースを考えた場合、以下のように挙動が変更になりました。

  • 変更前
    • ②の「自身を Assume Role」が成功する
    • 信頼ポリシーで明示的に許可が与えられていないものの、「暗黙的な自己信頼」により実現していた
  • 変更後
    • ②の「自身を Assume Role」は失敗する
    • 「暗黙的な自己信頼」が無くなり、信頼ポリシーでの明示的な許可がないため失敗する

例え自分自身であっても Assume Role を許可するには信頼ポリシーで明示的に許可が必要になった、ということですね。

どんな影響があるのか

基本的にほとんどの IAM ロールは上記の「変更後」の挙動を示すのですが、ごく一部の IAM ロールだけは例外となります。

# IAMロール 暗黙的な自己信頼
1 2022年9月21日以降に作成されたロール 無い(変更後の挙動が適用)
2 2022年6月30日以降に「暗黙的な自己信頼」を使用したことがないロール 無い(変更後の挙動が適用)
3 2022年6月30日以降に「暗黙的な自己信頼」を使用したことがあるロール 2023年2月15 日まで維持される

#3に該当するロールが存在する AWS アカウントには、 AWS ヘルスダッシュボードとメールで通知が行われています。確認するとよいでしょう。

また、#2に該当するロールでも、「数ヶ月に一回しか動かない *4バッチ処理などに使用している」かつ「暗黙的な自己信頼を使用している」場合には次回実行時に影響を受けることになります。そういった IAM ロールが無いか棚卸しをすると良さそうです。

どのように対応すればよいのか

過去に「暗黙的な自己信頼」を使用した実績がある場合には見直しをかけ、対応を検討しましょう。

ワークアラウンドとしては信頼ポリシーを修正し明示的に自身を許可することで仕様変更の影響を回避できます。一方で、「そもそも自身を Assume Role する必要があるのか?」を改めて考えてみるのもよいでしょう。

明示的な信頼を追加する

IAMロールRoleAが自身を Assume Role するケースで考えると、以下のように変更を加えることになります。

変更前の信頼ポリシーは以下。

IAMロールRoleAの信頼ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:role/RoleB"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

変更後の信頼ポリシーは以下です。

IAMロールRoleAの信頼ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::123456789012:role/RoleB",
                    "arn:aws:iam::123456789012:role/RoleA"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

以下のようにアカウント単位で許可することでも「暗黙的な自己信頼」は回避できます。

IAMロールRoleAの信頼ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                    "AWS": "arn:aws:iam::123456789012:root"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

そもそも自身を Assume Role する必要があるのかを見直す

今回の仕様変更に即して AWS 側で調査を行った結果、以下のサービスでの不要な「再引き受け」が多く見られたとのことです。

  • Amazon EKS
    • aws eks get-tokenを使用する際に自身を Assume Role
  • AWS Lambda
    • Lambda 関数上で自身を Assume Role
  • Amazon ECS
    • ECS タスク上で自身(ECS タスク用ロール)を Assume Role
  • Amazon EC2
    • EC2 インスタンス上で自身(EC2 インスタンスにアタッチされたロール *5)を Assume Role

最後の EC2 を例に取ります。EC2 インスタンスに IAM ロールをアタッチし、その権限を利用して AWS CLI を実行したいとします。EC2 インスタンスに IAM ロールをアタッチして起動すると自動的にロールセッションが生成され、そのクレデンシャルの更新も自動的に行われます。

AWS CLI は EC2 が取得したクレデンシャルを使用するため、ユーザーは特に追加の手順なく AWS CLI を実行できます。ところがそのような場合にも一度aws sts assume-roleを実行してロールを引き受けてから操作を実行する、というケースが多々あるようです。

先述の各種コンピューティングサービスでアプリケーションを実行する際、不要な「再引き受け」をすることで CPU、メモリ、ネットワークリソースの無駄遣いになります。該当する処理を行なっていないか見直しましょう。

間違いとは言えないけれど

冒頭で引用した AWS ブログでは、間違いではない「自身を Assume Role する」ケースとして以下が挙げられています。

  • スコープダウンポリシーを用いた再引き受け
  • 開発期間中のコンピュート用ロールの引き受け

前者は正直読んでも内容が理解できませんでした。(ここで指しているスコープダウンポリシーがなんなのかわからなかった。)

後者は、例えば以下のようなケースが想定されています。

  • 将来的に Lambda 関数上で動かすアプリケーションをローカル端末で開発している
  • 端末上でのテスト実行の際に Lambda 関数用ロールを引き受けたいためコードの冒頭で Lambda 関数用ロールを Assume Role する処理を書く
  • 上記のコードをそのまま Lambda 関数上にデプロイする

いずれのケースでも、自身を Assume Role するのではなく 2個目のロールを用意する(その上で ロールがロールを引き受けられるのを片方向にする)ことが推奨されています。

もろもろ鑑みると、「自身を Assume Role する」ことの妥当性があるケースはほぼないのでは、と考えています。今回の仕様変更をきっかけに見直して見るのが良さそうです。

ところでこういう場合は Assume Role できるのだっけ

ふと気になったのでまとめてみました。

以下のプリンシパルが IAM ロール A を引き受けるパターンを考えます。

  • IAM ユーザー 0(同一アカウント)
  • IAM ユーザー 9 (クロスアカウント)
  • IAM ロール A (自己引き受け)

IAM ユーザー 0 と 9 は IAM ロール 0 と 9 に置き換えても考え方は同じですが、IAM ロールがたくさんあるとややこしいのでユーザーにしました。

以下の場合分けで考えます。

  • プリンシパルの IAM ポリシー
    • IAM ロール A を Assume Role できる許可あり
    • IAM ロール A を Assume Role できる許可なし
  • IAM ロール A の信頼ポリシー
    • ユーザーもしくはロール単位で許可
    • アカウント単位で許可
    • 許可なし *6

まとめたのが以下表です。#15に該当するのが「暗黙的な自己信頼」のパターンです。

# プリンシパル プリンシパル側の許可 信頼ポリシー 結果
1 IAM ユーザー 0 あり IAM ユーザー 0 を許可 許可
2 IAM ユーザー 0 あり 000000000000 を許可 許可
3 IAM ユーザー 0 あり 許可なし 暗黙的な拒否
4 IAM ユーザー 0 なし IAM ユーザー 0 を許可 許可
5 IAM ユーザー 0 なし 000000000000 を許可 暗黙的な拒否
6 IAM ユーザー 0 なし 許可なし 暗黙的な拒否
7 IAM ユーザー 9 あり IAM ユーザー 9 を許可 許可
8 IAM ユーザー 9 あり 999999999999 を許可 許可
9 IAM ユーザー 9 あり 許可なし 暗黙的な拒否
10 IAM ユーザー 9 なし IAM ユーザー 9 を許可 暗黙的な拒否
11 IAM ユーザー 9 なし 999999999999 を許可 暗黙的な拒否
12 IAM ユーザー 9 なし 許可なし 暗黙的な拒否
13 IAM ロール A あり IAM ロール A を許可 許可
14 IAM ロール A あり 000000000000 を許可 許可
15 IAM ロール A あり 許可なし 暗黙的な拒否(※)
16 IAM ロール A なし IAM ロール A を許可 許可
17 IAM ロール A なし 000000000000 を許可 暗黙的な拒否
18 IAM ロール A なし 許可なし 暗黙的な拒否

(※)2022年6月30以降に「暗黙的な自己信頼」の使用実績があるロールでは、2023年2月15日まで「許可」

個人的にちょっと勘違いしていたのは#4,#16のケースで、「同一アカウント」かつ「ユーザー/ロール単位で信頼している」場合にはプリンシパル側の IAM ポリシーでの許可はいらない、という点でした。

ちょっと試してみた

上記のパターンからいくつかピックアップして試してみます。

同一アカウントで信頼ポリシーだけで許可されているパターン

#4,#5に該当するパターンです。

IAMUser0 に IAM ポリシーがアタッチされていない状態で、IAMRoleA を Assume Role します。

IAM_Management_Console_IAMUser0

まずは IAMRoleA の信頼ポリシーが以下の場合です。(#4

IAMRoleAの信頼ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::000000000000:user/IAMUser0"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

この組み合わせで Assume Role すると、問題なく成功します。

# プリンシパルの確認
% aws sts get-caller-identity
{
    "UserId": "AIDAQ3BIIH73Y4Z5VSZHK",
    "Account": "000000000000",
    "Arn": "arn:aws:iam::000000000000:user/IAMUser0"
}

# Assume Role の実行
% aws sts assume-role\
  --role-arn arn:aws:iam::000000000000:role/IAMRoleA\
  --role-session-name hoge
{
    "Credentials": {
        "AccessKeyId": "ASIAQ3BIIHXXXXXXXXXX",
        "SecretAccessKey": "OSIS以下略"
        "SessionToken": "IQoJb3JpZ2以下略",
        "Expiration": "2022-09-29T13:40:00+00:00"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "AROAQ3BIIH736BYCRY6IV:hoge",
        "Arn": "arn:aws:sts::000000000000:assumed-role/IAMRoleA/hoge"
    }
}

続いて IAMRoleA の信頼ポリシーを以下のように変更します。ユーザー単位でなく、少し広めにアカウント単位の許可を与えています。

IAMRoleAの信頼ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::000000000000:root"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

この状態だと Assume Role は失敗します。「プリンシパルをどの単位で指定するか」によって評価論理が変わるのが楽しいところです。

# プリンシパルの確認
% aws sts get-caller-identity
{
    "UserId": "AIDAQ3BIIH73Y4Z5VSZHK",
    "Account": "000000000000",
    "Arn": "arn:aws:iam::000000000000:user/IAMUser0"
}

# Assume Role の実行
% aws sts assume-role\
  --role-arn arn:aws:iam::000000000000:role/IAMRoleA\
  --role-session-name hoge

An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::000000000000:user/IAMUser0 is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::000000000000:role/IAMRoleA

自身を引き受けるパターン

#15,#14のパターンです。

IAMRoleA に以下の IAM ポリシーをアタッチします。

AllowAssumeRole

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::000000000000:role/IAMRoleA"
        }
    ]
}

IAMRoleAAssumeRole

この状態で IAMRoleA を自身で引き受けます。

まずは IAMRoleA の信頼ポリシーが以下の場合です。許可しているのは IAMUser0 のみであるため、IAMRoleA に対する許可はありません。(#15

IAMRoleAの信頼ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::000000000000:user/IAMUser0"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

実行結果は以下の通りで、失敗しました。仕様変更前はここで「暗黙的な自己信頼」が働き Assume Role ができたはずですが、失敗するようになりました。

# まずはIAMユーザーであることの確認
% aws sts get-caller-identity
{
    "UserId": "AIDAQ3BIIH73Y4Z5VSZHK",
    "Account": "000000000000",
    "Arn": "arn:aws:iam::000000000000:user/IAMUser0"
}

# Assume Role を実行し環境変数を設定
% OUTPUT=$(aws sts assume-role --role-arn arn:aws:iam::000000000000:role/IAMRoleA --role-session-name hoge)

export AWS_ACCESS_KEY_ID=$(echo $OUTPUT | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $OUTPUT | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $OUTPUT | jq -r .Credentials.SessionToken)

# 改めてプリンシパルの確認
% aws sts get-caller-identity
{
    "UserId": "AROAQ3BIIH736BYCRY6IV:hoge",
    "Account": "000000000000",
    "Arn": "arn:aws:sts::000000000000:assumed-role/IAMRoleA/hoge"
}

# Assume Role の実行
% aws sts assume-role\
  --role-arn arn:aws:iam::000000000000:role/IAMRoleA\
  --role-session-name hoge

An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:sts::000000000000:assumed-role/IAMRoleA/hoge is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::000000000000:role/IAMRoleA

ここで IAMRoleA の信頼ポリシーを以下のように変更します。(#14

IAMRoleAの信頼ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::000000000000:root"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

IAMUserA にもAllowAssumeRoleをアタッチした上で改めて試行します。

# まずはIAMユーザーであることの確認
% aws sts get-caller-identity
{
    "UserId": "AIDAQ3BIIH73Y4Z5VSZHK",
    "Account": "000000000000",
    "Arn": "arn:aws:iam::000000000000:user/IAMUser0"
}

# Assume Role を実行し環境変数を設定
% OUTPUT=$(aws sts assume-role --role-arn arn:aws:iam::000000000000:role/IAMRoleA --role-session-name hoge)

export AWS_ACCESS_KEY_ID=$(echo $OUTPUT | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $OUTPUT | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $OUTPUT | jq -r .Credentials.SessionToken)

# 改めてプリンシパルの確認
% aws sts get-caller-identity
{
    "UserId": "AROAQ3BIIH736BYCRY6IV:hoge",
    "Account": "000000000000",
    "Arn": "arn:aws:sts::000000000000:assumed-role/IAMRoleA/hoge"
}

# Assume Role の実行
% aws sts assume-role\
  --role-arn arn:aws:iam::000000000000:role/IAMRoleA\
  --role-session-name hoge

{
    "Credentials": {
        "AccessKeyId": "ASIAQ3BIIHXXXXXXXXXX",
        "SecretAccessKey": "N1Q+以下略"
        "SessionToken": "IQoJb3JpZ2以下略",
        "Expiration": "2022-09-29T14:17:06+00:00"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "AROAQ3BIIH736BYCRY6IV:hoge",
        "Arn": "arn:aws:sts::000000000000:assumed-role/IAMRoleA/hoge"
    }
}

↑ Assume Role が成功しました。

「暗黙的な自己信頼」の回避は IAM ロール単位でなく AWS アカウント単位での許可でも実現できました。

まとめ(再掲)

  • 以下条件で Assume Role が成功する要因をここでは「暗黙的な自己信頼」と呼ぶ
    • IAM ロール A の IAM ポリシーで自身を Assume Role する許可がある
    • IAM ロール A の信頼ポリシーで自身からの Assume Role を許可していない
    • IAM ロール A が IAM ロール A を Assume Role する
  • 仕様変更により「暗黙的な自己信頼」は無くなったが、以下に合致するロールは例外となる
    • 2022年6月30日以降に「暗黙的な自己信頼」を使用したことがある
      • 2023年2月15 日まで「暗黙的な自己信頼」は延長され、その後廃止される
  • そもそも自分自身を Assume Role するのが妥当なケースは考えづらいので該当する場合は見直しを

終わりに

IAM ロール信頼ポリシーの挙動が変更になり IAM ロールの「暗黙的な自己信頼」が無くなった、という話でした。

IAM ロールは IAM ポリシーもリソースベースポリシー(信頼ポリシー)もアタッチできるという唯一無二の存在ですが、その特異さゆえにいわば抜け穴的に「暗黙的な自己信頼」が成立していました。

例外的な挙動を示すのでなく、引き受け元が他のプリンシパルであった場合と同じ評価論理に従ってもらった方が分かりやすいので、ありがたい変更ですね。

惜しむらくはそんな裏メニュー的な仕様にこれまで気づけなかったことです。IAM ロールを愛するが故に「自身を引き受ける」という発想が浮かばなかったのですが、世の中には不要な自己引き受けを実行しているケースが一定数存在するようです。

今回の仕様変更をきっかけに、IAM ロールの使い方を見直してみてはいかがでしょうか。 *7

以上、 チバユキ (@batchicchi) がお送りしました。

脚注

  1. わたしが所有する IAM ロールはその中に入っていないので少し寂しい気持ちになりました。意味もなく IAM ロールを連鎖させて遊んでいた実績があるのだから、意味もなく自身を Assume Role しておけばよかったです。なんでその発想がなかったのだろう。
  2. 3 種類目として Permissions boundary も考えられますが、ここでは割愛します。
  3. この「セットする」をどう実現するかは、プリンシパルや使用するツールにより様々です。
  4. つまりここでは「2022年6月30日より前に実行された」ことを意図します。
  5. 「EC2 インスタンスに IAM ロールをアタッチする」って正しい表現なのか?直接アタッチしてるのはインスタンスプロファイルじゃないか?と思ったりしてましたが、(少なくとも)最近の公式の表現を見ると正しそうですね。
  6. 厳密に言うと当該プリンシパルを「アカウント単位」「ユーザーもしくはロール単位」「セッションプリンシパル単位」いずれでも許可していない、ということを表します。
  7. 仕様変更に伴う通知の対象となっているのは『2022年6月30日以降に「暗黙的な自己信頼」による自己引き受けをしたことがあるロール』であるため、「通知がないから不要な自己引き受けをしていない」というわけではありません。