はじめまして、丹(たん)です。
AWSの技術サポート/支払い代行や組織アカウント管理支援、AWS構築運用作業支援、AWSに関わる業務を行っています。
今回は気になった「外部ID(External ID)」の利用用途から、クロスアカウントアクセスについて考えてみました。
外部IDとは?
なぜ、このような記事を書こうと思ったかという経緯から少しお話します。
AWSについて調べる日々の中で「外部ID(External ID)」という言葉を見つけたのですが、この「外部ID」を私は利用したことがなく、どういうものでどのような時に利用するのか分かりませんでした。
そこで、まずは「外部ID(External ID)」とは何かを調べてみたところ、どうやら3rd Partyのシステム等外部のアカウントからクロスアカウントするために利用するIDだということが分かりました。
公式ドキュメントによると、AWSリソースへのアクセス権を第三者に付与したい時に利用する方法であり、AWSリソースへのアクセスに「アカウントID」だけではなく、「外部ID」も指定することでセキュアにクロスアカウントアクセスできるということです。
他アカウントからのAWSリソースへのアクセスを、許可した「アカウントID」のリソースの内「外部ID」を指定したアクセスのみに制限することができます。
アクセス先とアクセス元のアカウントで以下の手順を踏むことにより、外部IDを利用したアクセスを行うことができます。
- [アクセス先:アカウントA] 必要な権限を許可したIAMロールを作成して、信頼関係にアクセス元の「アカウントID」と「外部ID」を指定する
- [アクセス元:アカウントB] アクセス先のIAMロールを利用する際に、「外部ID」を指定してアクセス先IAMロールのARNに対してスイッチを行う(CLIコマンドの場合、aws sts assume-role)
クロスアカウントIAMロールを作成する際に、「外部ID」を指定することができます。
IAMロールの信頼関係に「外部ID」を指定している状態で、実際にスイッチするための一時認証情報を取得してみました。
「外部ID」を指定して一時的セキュリティ認証情報を取得することができました。
$ sts assume-role \ --role-arn arn:aws:iam::XXXXXXXXXXXX:role/ExternalIdTestRole \ --role-session-name test-session \ --external-id 1-9a-Z@+-/., { "Credentials": { "AccessKeyId": "xxxxxxxxxxxx", "SecretAccessKey": "xxxxxxxxxxxx", "SessionToken": "xxxxxxxxxxxx", "Expiration": "2021-05-30T02:33:14+00:00" }, "AssumedRoleUser": { "AssumedRoleId": "xxxxxxxxxxxx:test-session", "Arn": "arn:aws:sts::xxxxxxxxxxxx:assumed-role/ExternalIdTestRole/test-session" } }
異なる「外部ID」を指定すると一時的セキュリティ認証情報を取得することができません。
$ aws sts assume-role --role-arn arn:aws:iam::xxxxxxxxxxxx:role/ExternalIdTestRole --role-session-name test-session --external-id 11122233344455 An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/ReferenceSourceRole/test-user is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::xxxxxxxxxxxx:role/ExternalIdTestRole
外部IDを利用する理由
では、なぜこの外部IDを使うのでしょうか。先程見た公式ドキュメントで記述されている、「混乱した代理」問題について考えていきます。(詳細については公式ドキュメントを参照するとして)「混乱した代理」問題を一言で説明すると、IAMロール名が分かる、または、推測できると意図したアカウントやユーザ以外からもアクセスできてしまうため、(代理アクセスによる)正しいアクセスが分からず混乱が生じる、という問題です。
アカウント上に作成するリソースの用途に従い、マルチアカウントでアカウントを分けて管理する方法が主流となってきています*1。
「アカウントID」を信頼関係に設定することで、特定のアカウントのみにアクセスを絞ることはできますが、様々な理由によりアカウントの分離ができない(できていない)アカウントでは複数の環境が同一アカウント内に構築されているということがあるかと思います。社内で管理しているアカウントであればまだしも、3rd Partyのアカウントとなるとブラックボックスであるため「アカウントID」以外の「外部ID」等の要素を指定してアクセスを制限することが重要となります。(後述しますが、「外部ID」以外にも「IAMロール」を指定する方法もあります)
クロスアカウントでのプログラム実行については、プログラム実行用のIAMユーザを作成してアクセスキーを発行する方法もあります。しかし、昨今アクセスキーの流出によるインシデント事例が多いこともあり、可能な限りアクセスキーは発行しないでクロスアカウントを実現したいところです。
このようにクロスアカウントのアクセスを実現するための方法は他にもあります。
外部IDについては、どういうものでどのような時に利用するのか分かったところで、クロスアカウントアクセスの方法について考えていきましょう。
クロスアカウントアクセスについて考える
クロスアカウントアクセスの例 ※前提:「アカウントID」は指定
- 例1:[アクセス元:アカウントB]で「アクセスキー」を利用したクロスアカウントアクセス
- 例2:[アクセス元:アカウントB]で「外部ID」を利用したクロスアカウントアクセス
- 例3:[アクセス元:アカウントB]の「IAMロール」を指定したクロスアカウントアクセス
今回は、例2「外部ID」と例3「IAMロール」の指定を組み合わせた方法について、実際に検証してみました。別の AWS アカウントからロールを引き受けるように Lambda 関数を設定するために、公式ドキュメントを参照しました。
[アクセス先:アカウントA]のIAMロールには、以下の信頼関係を設定しました。権限はReadOnlyAccessです。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/LambdaFunction-role-xxxxxxxx" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "1-9a-Z@+-/." } } } ] }
[アクセス元:アカウントB]のLambda用IAMロールはLambdaの新規作成過程で新しいIAMロールを作成し、[アクセス先:アカウントA]のIAMロールにスイッチするための権限をアタッチしました。 実際にLambda関数を作成して検証してみましょう。
import boto3 def lambda_handler(context, event): sts_connection = boto3.client('sts') acct_b = sts_connection.assume_role( ExternalId='1-9a-Z@+-/.', RoleArn="arn:aws:iam::xxxxxxxxxxxx:role/TestRole", RoleSessionName="cross_acct_lambda" ) ACCESS_KEY = acct_b['Credentials']['AccessKeyId'] SECRET_KEY = acct_b['Credentials']['SecretAccessKey'] SESSION_TOKEN = acct_b['Credentials']['SessionToken'] # create service client using the assumed role credentials, e.g. S3 client = boto3.client( 's3', aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY, aws_session_token=SESSION_TOKEN ) print("S3 buckets list") s3_buckets_list = client.list_buckets() print(s3_buckets_list) result = boto3.client('sts').get_caller_identity() return result
実行ユーザの情報を取得することができました、スイッチ成功です。 Amazon CloudWatch Logsにも、Amazon S3バケットの一覧が出力されています。
{ "UserId": "xxxxxxxxxxxxxxxxxxxxx:TestFunction", "Account": "xxxxxxxxxxxx", "Arn": "arn:aws:sts::xxxxxxxxxxxx:assumed-role/TestFunction-role-xxxxxxxx/TestFunction", "ResponseMetadata": { "RequestId": "xxxxxxxxxxxxxxxxxxxxx", "HTTPStatusCode": 200, "HTTPHeaders": { "x-amzn-requestid": "xxxxxxxxxxxxxxxxxxxxx", "content-type": "text/xml", "content-length": "xxx", "date": "Sat, 05 Jun 2021 18:35:22 GMT" }, "RetryAttempts": 0 } }
[アクセス先:アカウントA]のIAMロールでPrincipalに、[アクセス元:アカウントB]に存在しないIAMロールを指定しようとしても文法エラーとなり設定できません。また、[アクセス元:アカウントB]のLambdaにアタッチしているIAMロール以外のロールを指定しても以下のようにアクセス拒否エラーとなります。もちろん、異なるExternalIdを指定してもアクセス拒否エラーとなります。
{ "errorMessage": "An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/TestFunction-role-xxxxxxxx/TestFunction is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::xxxxxxxxxxxx:role/TestRole", "errorType": "ClientError", ... }
今回は例2「外部ID」と例3「IAMロール」を指定する方法を検証してみましたが、 まずは、例3「IAMロール」を指定する方法でクロスアカウントアクセスを検討することをお勧めします。IAMロール名が推測されてもアクセス先とアクセス元で対象のIAMロールを双方に指定しているため、「混乱した代理」問題が発生することはありません。 利用するIAMロール名が決まっていないという場合には、「外部ID」を利用し、前述したインシデント事例があることも踏まえ、極力アクセスキーは利用しない方法でクロスアカウントを考えましょう。*2
まとめ
今回は、「外部ID(External ID)」の利用用途からクロスアカウントのアクセス方法について考えてみました。 クロスアカウントを実現する環境や要件によってクロスアカウントアクセスの方法について考える必要があります。 IAMはAWSを利用する上で避けては通れないサービスです。奥が深いサービスですが、基本を押さえて利用することが必要となります。IAMの基本を知るにはまず佐々木さんのIAM本を読んでおくのがよいのではないでしょうか。
*1:マルチアカウント管理については、マルチアカウントの基本を参照
*2:アクセスキーを簡単に通知する方法については、上野さんのIAMユーザーのアクセスキー作成を簡単に通知して敏感になるを参照