AWS CloudShell からクロスアカウントのスイッチロールをしてみた
AWS CloudShell の裏で ECS が動いてる
コンバンハ、千葉(幸)です。
先日、かじわら(ゆたか)が書いた以下のブログが目に止まりました。裏で ECS が動いているターミナル上で、AWS CLI コンフィグにソースプロファイルを指定してスイッチロール(AssumeRole)する、というものです。
「裏で ECS が動いている」……そう言えば AWS CloudShell もそうじゃなかったっけ?となったので試してみました。
まとめ
- AWS CloudShell の実行環境は ECS コンテナによりホストされている
~/.aws/credentials
に以下を記載することでスムーズに AssumeRole できる
[ProfileName] role_arn = {引き受けるIAMロールのARN} credential_source = EcsContainer
今回スイッチロールをやってみる構成
以下のイメージで行います。
000000000000
のアカウントから555555555555
へのスイッチロールはマネジメントコンソールの通常の機能を用いて行います。
555555555555
のロールにスイッチした状態で AWS CloudShell に接続し、そこから999999999999
のアカウントのロールにスイッチします。そのスイッチは、AWS CLI のコンフィグファイルにプロファイルを指定することで行います。
ちなみに AWS Cloud Shell のアーキテクチャってこういう感じらしいですよ
以下のセッションレポートでは、AWS CloudShell のアーキテクチャが取り上げられています。
セッションレポートの文章を引用します。
CloudShellがどのように動作するかの概要は次のとおりです。
まず、マネジメントコンソールにログインし、CloudShellセッションを開始します。 Web UI はCloudShellへのAPIコールを行います。 CloudShellが新しいCloudShell用のコンピュート環境をプロビジョニングします。
コンピュート環境はセッションごとにオンデマンドで作成されます。 これは Lambda Function のようなものと考えることができますが、CloudShellセッションが生きている限りコンピュート環境は存在し続けます。 コンピュート環境はVPC上で稼働しています。アウトバウンドのネットワークは許可されていますが、インバウンドのネットワークは許可されていません。
コンピュート環境が用意されると、 Systems Manager を利用して、Webブラウザ上のWeb UIとコンピュート環境との間に双方向の通信チャネルが作成されます。 Web UIにコマンドを入力すると、SSMのWebSocket接続を使ってバックエンドのコンピュート環境に通信し、コンピュート環境でコマンドが実行されます。
AWS CloudShell のコンピュート環境が VPC 上で動いており、利用者は Systems Manager 経由でそれにアクセスできる、ということですね。このコンピュート環境というのは ECS で動いていそうです。
せっかくなので環境変数を確認してみるとこんな感じ。ところどころ ECS を感じますね。
[cloudshell-user@ip-10-0-108-140 ~]$ env WSEP_TTY=true HOSTNAME= __MDE__ENV_TIMEOUT=43800 TERM=linux HISTSIZE=1000 AWS_CONTAINER_CREDENTIALS_FULL_URI=http://localhost:1338/latest/meta-data/container/security-credentials serverSocket=/aws/mde/.controller/mde.sock LC_ALL=en_US.UTF-8 USER=cloudshell-user LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36: AWS_EXECUTION_ENV=CloudShell MAIL=/var/spool/mail/cloudshell-user PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/node_modules/aws-cdk/bin:/home/cloudshell-user/.local/bin:/home/cloudshell-user/bin AWS_DEFAULT_REGION=us-east-1 PWD=/home/cloudshell-user ECS_AGENT_URI=http://169.254.170.2/api/45213fa6042048eb8ebd9c8b78e6a432-614825015 NODE_PATH=/usr/lib/node_modules AWS_REGION=us-east-1 HISTCONTROL=ignoredups HOME=/home/cloudshell-user SHLVL=1 LOGNAME=cloudshell-user MDE_UPDATE_SCRIPT=env | grep -m 1 AWS_REGION | grep -Eo '[a-z0-9-]*' | sudo tee /etc/yum/vars/awsregion && sudo yum -y update --security LESSOPEN=||/usr/bin/lesspipe.sh %s AWS_CONTAINER_AUTHORIZATION_TOKEN=7CApVHB9略 _=/usr/bin/env
環境変数から確認できるトークンや URL の情報を用いてアクセスすると、各種クレデンシャルが取得できます。
[cloudshell-user@ip-10-0-108-140 ~]$ curl -H "Authorization: $AWS_CONTAINER_AUTHORIZATION_TOKEN" \ > localhost:1338/latest/meta-data/container/security-credentials { "Type": "", "AccessKeyId": "ASIAQ3BIIH73U7LZRQPJ", "SecretAccessKey": "uDFeA略", "Token": "IQoJb3JpZ2luX2VjEBY略=", "Expiration": "2022-11-27T13:50:44Z", "Code": "Success" }
ちなにみにこれらのクレデンシャル情報は AWS CloudShell に接続する IAM エンティティ(平たく言えばマネジメントコンソールを操作している IAM ユーザーや IAM ロール)のものが使用されています。
cloudshell:PutCredentials
というアクションが許可されていないとコンピュート環境にクレデンシャル情報を転送できないようになっています。
AWS CloudShell でのスイッチロールやってみた
今回は各アカウントに存在する IAM エンティティが以下の通りであるとします。
arn:aws:iam::000000000000:user/cm-chiba.yukihiro
arn:aws:iam::555555555555:role/cm-chiba.yukihiro
arn:aws:iam::999999999999:role/dest-switch-role
最終的なスイッチ先のロールの準備
999999999999
のスイッチ先のロールでは以下のような信頼ポリシーを設定しています。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::555555555555:role/cm-chiba.yukihiro" }, "Action": "sts:AssumeRole", "Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } } } ] }
特定の IAM ロールからだけ AssumeRole を許可している状態です。
AWS CloudShell の起動まで
000000000000
のアカウントに IAM ユーザーでサインインし、マネジメントコンソール上の操作で555555555555
の IAM ロールcm-chiba.yukihiro
にスイッチします。
その後、AWS CloudShell を起動し接続します。プリンシパルを確認すると、マネジメントコンソールを操作しているセッションプリンシパルであることがわかります。
[cloudshell-user@ip-10-0-108-140 ~]$ aws sts get-caller-identity { "UserId": "AROAQ3BIIH732QEGJXBGU:cm-chiba.yukihiro", "Account": "555555555555", "Arn": "arn:aws:sts::555555555555:assumed-role/cm-chiba.yukihiro/cm-chiba.yukihiro" }
AWS CloudShell 上でのスイッチロールの実行
AWS CloudShell 上で~/.aws/credentials
に以下の設定をします。プロファイル名はもちろん任意のものを設定できます。
[assumeTarget] role_arn = arn:aws:iam::999999999999:role/dest-switch-role credential_source = EcsContainer
環境変数を設定してプロファイルを切り替えます。
[cloudshell-user@ip-10-0-108-140 ~]$ export AWS_PROFILE=assumeTarget
再度プリンシパルを確認するときちんとスイッチロールできていることが確認できます。
[cloudshell-user@ip-10-0-108-140 ~]$ aws sts get-caller-identity { "UserId": "AROARVFQHJ6WCVW5YRQ4F:botocore-session-1669641927", "Account": "999999999999", "Arn": "arn:aws:sts::999999999999:assumed-role/cm-switch-role/botocore-session-1669641927" }
--debug
オプションを付与して確認すると、自動的に localhost からクレデンシャル情報を取得してセットしていることがわかります。
... 2022-11-28 13:28:48,876 - MainThread - botocore.credentials - DEBUG - Looking for credentials via: env 2022-11-28 13:28:48,876 - MainThread - botocore.credentials - DEBUG - Looking for credentials via: assume-role 2022-11-28 13:28:48,877 - MainThread - urllib3.connectionpool - DEBUG - Starting new HTTP connection (1): localhost:1338 2022-11-28 13:28:48,878 - MainThread - urllib3.connectionpool - DEBUG - http://localhost:1338 "GET /latest/meta-data/container/security-credentials HTTP/1.1" 200 1196 2022-11-28 13:28:48,880 - MainThread - botocore.loaders - DEBUG - Loading JSON file: /usr/local/aws-cli/v2/2.8.12/dist/awscli/botocore/data/endpoints.json 2022-11-28 13:28:48,892 - MainThread - botocore.hooks - DEBUG - Event choose-service-name: calling handler <function handle_service_name_alias at 0x7fb7313205e0> 2022-11-28 13:28:48,892 - MainThread - botocore.hooks - DEBUG - Event creating-client-class.sts: calling handler <function add_generate_presigned_url at 0x7fb73138a310> 2022-11-28 13:28:48,895 - MainThread - botocore.endpoint - DEBUG - Setting sts timeout as (60, 60) 2022-11-28 13:28:48,895 - MainThread - botocore.hooks - DEBUG - Event provide-client-params.sts.GetCallerIdentity: calling handler <function base64_decode_input_blobs at 0x7fb72ecd5af0> 2022-11-28 13:28:48,896 - MainThread - botocore.hooks - DEBUG - Event before-parameter-build.sts.GetCallerIdentity: calling handler <function generate_idempotent_uuid at 0x7fb7310b7700> 2022-11-28 13:28:48,896 - MainThread - botocore.hooks - DEBUG - Event before-call.sts.GetCallerIdentity: calling handler <function inject_api_version_header_if_needed at 0x7fb7310bcf70> 2022-11-28 13:28:48,896 - MainThread - botocore.endpoint - DEBUG - Making request for OperationModel(name=GetCallerIdentity) with params: {'url_path': '/', 'query_string': '', 'method': 'POST', 'headers': {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'User-Agent': 'aws-cli/2.8.12 Python/3.9.11 Linux/4.14.294-220.533.amzn2.x86_64 exec-env/CloudShell exe/x86_64.amzn.2 prompt/off command/sts.get-caller-identity'}, 'body': {'Action': 'GetCallerIdentity', 'Version': '2011-06-15'}, 'url': 'https://sts.ap-northeast-1.amazonaws.com/', 'context': {'client_region': 'ap-northeast-1', 'client_config': <botocore.config.Config object at 0x7fb72e1cc580>, 'has_streaming_input': False, 'auth_type': None}} 2022-11-28 13:28:48,896 - MainThread - botocore.hooks - DEBUG - Event request-created.sts.GetCallerIdentity: calling handler <bound method RequestSigner.handler of <botocore.signers.RequestSigner object at 0x7fb72e1cc550>> 2022-11-28 13:28:48,896 - MainThread - botocore.hooks - DEBUG - Event choose-signer.sts.GetCallerIdentity: calling handler <function set_operation_specific_signer at 0x7fb7310b75e0> 2022-11-28 13:28:48,897 - MainThread - botocore.credentials - DEBUG - Credentials for role retrieved from cache. 2022-11-28 13:28:48,897 - MainThread - botocore.credentials - DEBUG - Retrieved credentials will expire at: 2022-11-28 14:25:27+00:00 2022-11-28 13:28:48,897 - MainThread - botocore.auth - DEBUG - Calculating signature using v4 auth. 2022-11-28 13:28:48,897 - MainThread - botocore.auth - DEBUG - CanonicalRequest: ...
おまけ
環境変数を設定してプロファイルを指定しなくても、都度--profile
オプションを付与することで切り替えはできます。
[cloudshell-user@ip-10-0-108-140 ~]$ aws sts get-caller-identity --profile assumeTarget { "UserId": "AROARVFQHJ6WCVW5YRQ4F:botocore-session-1669641927", "Account": "999999999999", "Arn": "arn:aws:sts::999999999999:assumed-role/cm-switch-role/botocore-session-1669641927" }
また、デフォルトではセッション名が自動生成されたものになります。以下のようにプロファイルにセッション名を指定することで固定のものを使用できます。
[assumeTarget] role_arn = arn:aws:iam::999999999999:role/dest-switch-role credential_source = EcsContainer role_session_name = test-session
[cloudshell-user@ip-10-0-108-140 ~]$ aws sts get-caller-identity --profile assumeTarget { "UserId": "AROARVFQHJ6WCVW5YRQ4F:test-session", "Account": "999999999999", "Arn": "arn:aws:sts::999999999999:assumed-role/cm-switch-role/test-session" }
終わりに
AWS CloudShell 上でスイッチロールしてみました。CloudShell での API 操作に必要なクレデンシャルは ECS コンテナから提供されているので、credential_source = EcsContainer
という書き方ができるのが面白いですね。
ホームディレクトリに設定したデータは 120 日間アクセス(起動)がないと削除されてしまいますので、その点は気をつけてもらえればと思います。
ローカルで作業できない・したくない、という場合に今回の方式がマッチすれば幸いです。
以上、 チバユキ (@batchicchi) がお送りしました。
参考
- AWS CLI での IAM ロールの使用 - AWS Command Line Interface
- AWS CLI のコンフィグファイルと環境変数とコマンドラインオプションで指定できる内容をまとめて確認してみた | DevelopersIO
- CloudShellを最小権限で起動してみた | DevelopersIO
- 【AWS】CloudshellでAssumeRoleできるCredential設定を書く - Qiita
- CloudShellの権限を外部にHTTPで提供する方法(認証付き)
- AWS CLIがAssumeRoleする際のセッション名を指定する | DevelopersIO
- Service quotas and restrictions for AWS CloudShell - AWS CloudShell