AWS CloudShell からクロスアカウントのスイッチロールをしてみた

クレデンシャルのソースを ECS コンテナにすれば AWS CLI のプロファイル指定でスイッチロールできます

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

AWS CloudShell の裏で ECS が動いてる

コンバンハ、千葉(幸)です。

先日、かじわら(ゆたか)が書いた以下のブログが目に止まりました。裏で ECS が動いているターミナル上で、AWS CLI コンフィグにソースプロファイルを指定してスイッチロール(AssumeRole)する、というものです。

「裏で ECS が動いている」……そう言えば AWS CloudShell もそうじゃなかったっけ?となったので試してみました。

まとめ

  • AWS CloudShell の実行環境は ECS コンテナによりホストされている
  • ~/.aws/credentialsに以下を記載することでスムーズに AssumeRole できる

~/.aws/credentials

[ProfileName]
role_arn = {引き受けるIAMロールのARN}
credential_source = EcsContainer

今回スイッチロールをやってみる構成

以下のイメージで行います。

000000000000のアカウントから555555555555へのスイッチロールはマネジメントコンソールの通常の機能を用いて行います。

555555555555のロールにスイッチした状態で AWS CloudShell に接続し、そこから999999999999のアカウントのロールにスイッチします。そのスイッチは、AWS CLI のコンフィグファイルにプロファイルを指定することで行います。

ちなみに AWS Cloud Shell のアーキテクチャってこういう感じらしいですよ

以下のセッションレポートでは、AWS CloudShell のアーキテクチャが取り上げられています。

img

セッションレポートの文章を引用します。

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に以下の設定をします。プロファイル名はもちろん任意のものを設定できます。

~/.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"
}

また、デフォルトではセッション名が自動生成されたものになります。以下のようにプロファイルにセッション名を指定することで固定のものを使用できます。

~/.aws/credentials

[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) がお送りしました。

参考