IAM ロールの信頼ポリシーで設定する外部 ID(sts:ExternalId) について

IAM ロールの信頼ポリシーに設定する外部 ID(sts:ExternalI) について勉強しました。
2020.03.23

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

こんにちは、岩城です。

3rd Party 製の SaaS と AWS アカウントを連携する際、専用の IAM ロールの作成と作成したロールに以下のような信頼ポリシーを定義して、特定の AWS アカウントから AssumeRole を許可することがあります。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::xxxxxxxxxxxx:iamuser"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "xxxxxxxxxxxx"
        }
      }
    }
  ]
}

Condition には sts:ExternalId とありますが、外部 ID が一致しないと AssumeRole できないのは想像に難くないと思います。

では、外部 ID とは何でしょうか。

私自身外部 ID について調べたことがなく、ふわっとした理解だったので、以下の公式ドキュメントをベースに勉強した内容を本エントリにまとめておきます。

AWS リソースへのアクセス権を第三者に付与するときに外部 ID を使用する方法

外部 ID とは

自分のアカウントの IAM ロールを他のアカウントから AssumeRole する際に指定できる条件キーのひとつです。 外部 ID に指定できる値は、2~1224 文字、空白のない英数字である必要があります。 +\,.@:/- といった記号も含められます。

重要なポイントとして、この外部 ID について、AWS は機密情報として扱っていないということです。

AWS は、外部 ID を機密情報として扱いません。アクセスキーペアやパスワードなどの機密情報を AWS で作成した場合、それらを再び表示することはできません。ロールの外部 ID は、ロールを表示する権限を持つすべてのユーザーが表示できます。

確かにコンソール上で設定した外部 ID を確認することができます。

余程の理由がない限り、パスフレーズやアカウント ID を指定することはせず、ランダムな文字列の方が良いと思います。

なぜ外部 ID が必要となるのか

なぜ外部 ID が必要となるのか考えます。ここで登場するのが 混乱した代理問題 です。

混乱した代理問題

例えば、私の AWS アカウント上のリソースをモニタリングしてくれるサービスを Example Corp が提供しているとします。

このサービスを利用するには以下のような手順を踏みます。  

私の AWS アカウント上に Example Corp 向けの IAM ロールを作成し、IAM ロールの ARN を Example Corp に伝えます。この IAM ロールは、信頼されたエンティティに Example Corp の AWS アカウントを指定しているので、 Example Corp 以外が AssumeRole することはできません。Example Corp は、この ARN を使用して AssumeRole し、私の AWS アカウント上のリソースを参照することができるようになりました。

ここでもし、この IAM ロールの ARN と Example Corp のサービスを利用していることが外部に漏れ、悪意あるユーザーがその ARN をあたかも自分のものだと偽って Example Corp に伝えた場合はどうなるでしょうか。

Example Corp は、悪意あるユーザーからのリクエストに応じ、私の AWS アカウント上のリソースを参照します。悪意あるユーザーは、Example Corp のサービスを介して、間接的に私の AWS アカウントのリソース情報を不正に参照できてしまうことになります。

これを 混乱した代理問題 と呼びます。 

混乱した代理問題を防止する外部 ID

それでは外部 ID は 混乱した代理問題 をどのように防止するのでしょうか。

Example Corp は、悪意あるユーザーからのリクエストに応じるわけにはいきません。
そこで Example Corp はユーザーに一意の ID を発行します。ここでは仮に 12345 で発行したとします。これを外部 ID として利用し、ユーザーに IAM ロールの信頼ポリシーの条件キーに設定してもらいます。これにより、外部 ID に 12345 が含まれていなければ AssumeRole することができなくなりました。

今回も不運なことに IAM ロールの ARN と Example Corp のサービスを利用していることが漏れてしまったとします。
悪意あるユーザーからのリクエストに対し、Example Corp はまた別の一意の ID として abcde を発行します。Example Corp は、新しく発行した外部 ID で AssumeRole を試みますが、IAM ロール側の条件キーでは外部 ID が 12345 で設定されているため、失敗します。

悪意あるユーザーが IAM ロールの ARN を知っていたとしても外部 ID が変わるため、混乱した代理問題 を防止できるというわけです。

外部 ID は誰が発行するのか

今回のケースでは AssumeRole する側が外部 ID を発行し、混乱した代理問題 に対応しているため、ユーザーが 外部 ID を発行することはありません。

私のこれまでの経験では、この辺サービスによってバラバラだと感じています。中にはサービス側から外部 ID が発行されるものの、ユーザーが希望する外部 ID を設定できるものもあります。

AssumeRole する側に求められるテスト

ユーザーから IAM ロールの ARN を受け取ったら、正しい外部 ID と正しくない外部 ID の両方を使用し、AssumeRole できるかテストしましょう。外部 ID がなくても AssumeRole できる場合は、ユーザー側に IAM ロールの信頼ポリシーの修正を依頼し、正しい外部 ID で AssumeRole できるまでテストしましょう。

以下の 3 つのケースでテストしてみます。

  • 正しい外部 ID を指定して AssumeRole
  • 誤った外部 ID を指定して AssumeRole
  • 信頼ポリシーに外部 ID の設定がない中、外部 ID を指定して AssumeRole

正しい外部 ID を指定して AssumeRole

まずは、正しい外部 ID で AssumeRole が成功するケースです。

$ sts assume-role \
    --role-arn arn:aws:iam::XXXXXXXXXXXX:role/externalid-test \
    --role-session-name test-session \
    --external-id 12345abcde@+-/.,
{
    "Credentials": {
        "AccessKeyId": "XXXXXXXXXXXX",
        "SecretAccessKey": "XXXXXXXXXXXX",
        "SessionToken": "XXXXXXXXXXXX",
        "Expiration": "2020-03-23T03:31:44Z"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "XXXXXXXXXXXX:test-session",
        "Arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/externalid-test/test-session"
    }
}

誤った外部 ID を指定して AssumeRole

つぎに、誤った外部 ID を指定して AssumeRole すると失敗するケースです。

$ sts assume-role \
    --role-arn arn:aws:iam::XXXXXXXXXXXX:role/externalid-test \
    --role-session-name test-session \
    --external-id 12345

An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::XXXXXXXXXXXX:user/XXXXXX is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::XXXXXXXXXXXX:role/externalid-test

信頼ポリシーに外部 ID の設定がない中、外部 ID を指定して AssumeRole

さいごに、信頼ポリシーの Condition から sts:ExternalId を削除たした状態で AssumeRole するケースです。
当然、外部 ID なしで AssumeRole が成功します。

$ sts assume-role \
    --role-arn arn:aws:iam::XXXXXXXXXXXX:role/externalid-test \
    --role-session-name test-session
{
    省略
}

ここで注意すべき点は、IAM ロールの信頼ポリシーで sts:ExternalId が設定されていない状態で外部 ID を指定しても AssumeRole できてしまう点です。

$ sts assume-role \
    --role-arn arn:aws:iam::XXXXXXXXXXXX:role/externalid-test \
    --role-session-name test-session \
    --external-id 12345abcde@+-/.,
{
    省略
}

以上の理由から、外部 ID 利用の際は 必ず正しい外部 ID と誤った外部 ID で AssumeRole をテストしましょう。誤った外部 ID の時に AssumeRole が失敗し、正しい外部 ID の時のみ AssumeRole が成功すれば、IAM ロールの信頼ポリシーが適切に設定されていることの確認になります。

おわりに

ある 3rd Partyの SaaS と AWS アカウントを連携する際、その製品が提示する外部 ID の設定を求められました。いたずら心で私の方で適当な外部 ID を IAM ロールと SaaS に設定しました。外部 ID は一致しているので当然ながら問題なく連携できました。じゃあ何でわざわざ SaaS 側から外部 ID を提示しているのだろう?という疑問から今回の勉強はスタートしました。混乱した代理問題 が成立するケースは稀だと思いますが、何気なく使っていた外部 ID について少し詳しくなれて良かったです。

本エントリがどなたかのお役に立てれば幸いです。