AWS CloudShell 上でフェデレーションサインイン用 URL を生成してみた

そう言えばそんなこともできたっけ、ということをどこかで思い出してください。

ちょっと環境を覗いてもらうために一時的な URL を払い出す

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

「ちょっと環境を覗いてもらうために AWS マネジメントコンソールへの接続情報を提供したい」

「かといってわざわざ専用の IAM ユーザーやロールを払い出すのは避けたい」

ということがあるかもしれません。

AWS CloudShell のドキュメントを眺めていると、そんなケースにマッチしそうなチュートリアルを見つけました。

フェデレーションサインイン用の URL を生成するスクリプトを載せてくれているので、これが活用できそうです。

ここでは特定の S3 バケットの特定のプレフィックス配下のオブジェクトに対する操作のみ許可するというシチュエーションが想定されていますが、ある程度柔軟にカスタマイズできるのでそれ以外のケースでも転用できます。

何をするのか

やっていることは以下のエントリとほぼ同じです。ここではローカルの端末で bash スクリプトを用いて実現していましたが、AWS CloudShell 上で Python で実行する、というだけの違いです。

AWS CloudShell では実行に必要な環境が揃っていますので、ローカルを汚さずにささっと実行できるのがちょっと嬉しいところです。

上記のエントリと同じ図を用いて説明すると、全体の流れは以下です。

img

  • AssumeRole による一時的な認証情報(アクセスキー/シークレットアクセスキー/セッショントークン)を取得する
  • 認証情報を含んだリクエストを AWS フェデレーションエンドポイントに実行し、サインイントークンを取得する
  • サインイントークンを含む URL を生成し、コンソールアクセスを行う

ここでは IAM ユーザーが AssumeRole を実行していますが、今回は AWS CloudShell に接続している IAM ユーザー/IAM ロールセッションプリンシパルが実行者になります。

やってみた

やっていきます。

AssumeRole する先のロールを準備

最初のステップで AssumeRole する先のロールを準備しておきます。

最終的にフェデレーションサインインするフェデレーテッドユーザーは、このロールを引き受けたセッションプリンシパルとして扱われます。必要な権限のみを割り当てておくようにしましょう。

AWS CloudShell を操作する IAM エンティティからの AssumeRole を信頼ポリシーで許可しておく必要があります。

今回はcm-chiba.yukihiroという IAM ロールにスイッチロールして AWS CloudShell に接続するので、AssumeRole 先のロールの信頼ポリシーは以下内容にしました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::000000000000:role/cm-chiba.yukihiro"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        }
    ]
}

ロールの名称はTest-RoleAにしておきます。

AWS CloudShell への接続

cm-chiba.yukihiro で AWS CloudShell に接続します。プリンシパルを確認するとこのような感じ。

$ aws sts get-caller-identity
{
    "UserId": "AROAQ3BIIH732QEGJXBGU:cm-chiba.yukihiro",
    "Account": "000000000000",
    "Arn": "arn:aws:sts::000000000000:assumed-role/cm-chiba.yukihiro/cm-chiba.yukihiro"
}

せっかくなので Python 関連の各種情報を確認しておきます。

バージョン。

$ python3 --version
Python 3.7.10

ライブラリの一覧。今回のスクリプトで必要となるものは揃っています。

$ pip3 list
Package               Version
--------------------- ---------
arrow                 1.2.3
attrs                 22.1.0
aws-lambda-builders   1.19.0
aws-sam-cli           1.59.0
aws-sam-translator    1.52.0
awscli                1.19.93
awsebcli              3.20.3
backports.zoneinfo    0.2.1
bcrypt                4.0.1
binaryornot           0.4.4
blessed               1.19.1
boto3                 1.24.89
botocore              1.20.93
cached-property       1.5.2
cement                2.8.2
certifi               2022.9.24
cffi                  1.15.1
chardet               4.0.0
chevron               0.14.0
click                 7.1.2
colorama              0.4.3
cookiecutter          2.1.1
cryptography          38.0.1
dateparser            1.1.1
docker                4.2.2
docker-compose        1.25.5
dockerpty             0.4.1
docopt                0.6.2
Flask                 1.1.4
future                0.16.0
git-remote-codecommit 1.16
idna                  2.10
importlib-metadata    5.0.0
itsdangerous          1.1.0
Jinja2                2.11.3
jinja2-time           0.2.0
jmespath              0.10.0
jsonschema            3.2.0
MarkupSafe            2.0.1
paramiko              2.11.0
pathspec              0.9.0
pip                   20.2.2
pyasn1                0.4.8
pycparser             2.21
PyNaCl                1.5.0
pyrsistent            0.18.1
python-dateutil       2.8.2
python-slugify        6.1.2
pytz                  2022.4
PyYAML                5.4.1
regex                 2021.9.30
requests              2.25.1
rsa                   4.7.2
s3transfer            0.4.2
semantic-version      2.8.5
serverlessrepo        0.1.10
setuptools            49.1.3
six                   1.14.0
termcolor             1.1.0
text-unidecode        1.3
texttable             1.6.4
tomlkit               0.7.2
typing-extensions     3.10.0.0
tzlocal               3.0
urllib3               1.26.12
watchdog              2.1.2
wcwidth               0.1.9
websocket-client      0.59.0
Werkzeug              1.0.1
wheel                 0.37.1
zipp                  3.9.0

ついでなので気になるライブラリの詳細を確認。

$ pip3 show boto3 botocore requests
Name: boto3
Version: 1.24.89
Summary: The AWS SDK for Python
Home-page: https://github.com/boto/boto3
Author: Amazon Web Services
Author-email: None
License: Apache License 2.0
Location: /usr/local/lib/python3.7/site-packages
Requires: botocore, jmespath, s3transfer
Required-by: serverlessrepo, aws-sam-translator, aws-sam-cli
---
Name: botocore
Version: 1.20.93
Summary: Low-level, data-driven core of boto 3.
Home-page: https://github.com/boto/botocore
Author: Amazon Web Services
Author-email: None
License: Apache License 2.0
Location: /home/cloudshell-user/.local/lib/python3.7/site-packages
Requires: jmespath, python-dateutil, urllib3
Required-by: s3transfer, awscli, git-remote-codecommit, boto3, awsebcli
---
Name: requests
Version: 2.25.1
Summary: Python HTTP for Humans.
Home-page: https://requests.readthedocs.io
Author: Kenneth Reitz
Author-email: me@kennethreitz.org
License: Apache 2.0
Location: /usr/local/lib/python3.7/site-packages
Requires: idna, urllib3, chardet, certifi
Required-by: docker, docker-compose, cookiecutter, awsebcli, aws-sam-cli

Python スクリプトの実行

準備が揃ったところでスクリプトを実行していきます。今回は以下内容のスクリプトをshare.pyという名称で作成しました。

share.py

import urllib, json, sys
import requests
import boto3

def main():
  sts_client = boto3.client('sts')
  assume_role_response = sts_client.assume_role(
      RoleArn="arn:aws:iam::000000000000:role/Test-RoleA",
      RoleSessionName="Test-Session"
  )
  credentials = assume_role_response['Credentials']
  url_credentials = {}
  url_credentials['sessionId'] = credentials.get('AccessKeyId')
  url_credentials['sessionKey'] = credentials.get('SecretAccessKey')
  url_credentials['sessionToken'] = credentials.get('SessionToken')
  json_string_with_temp_credentials = json.dumps(url_credentials)
  print(f"json string {json_string_with_temp_credentials}")

  request_parameters = f"?Action=getSigninToken&Session={urllib.parse.quote(json_string_with_temp_credentials)}"
  request_url = "https://signin.aws.amazon.com/federation" + request_parameters
  r = requests.get(request_url)
  signin_token = json.loads(r.text)
  request_parameters = "?Action=login" 
  request_parameters += "&Issuer=Example.org" 
  request_parameters += "&Destination=" + urllib.parse.quote("https://us-west-2.console.aws.amazon.com/cloudshell")
  request_parameters += "&SigninToken=" + signin_token["SigninToken"]
  request_url = "https://signin.aws.amazon.com/federation" + request_parameters

  # Send final URL to stdout
  print (request_url)

if __name__ == "__main__":
  main()

基本的にはドキュメントに記載のものをそのまま踏襲していますが、引き受けるロールを指定する箇所はベタ書きに変えています。(ドキュメントの例では環境変数に設定していた。)

また、25 行目でフェデレーションサインした後の遷移先の URL をコントロールできます。今だとオレゴンリージョンの CloudShell に接続されるようになっています。コンソールのホームに遷移させたければ`https://console.aws.amazon.com/`に変更しましょう。

スクリプトを実行するとまずは AssumeRole により取得した一時クレデンシャルの情報が表示され、その後にフェデレーションサイン用 URL が生成されます。

URL は以下のような内容になっています。

https://signin.aws.amazon.com/federation?Action=login&Issuer=Example.org&Destination=https%3A//us-west-2.console.aws.amazon.com/cloudshell&SigninToken=IjEOpc03C6kb5d417s以下略

フェデレーションサインインの実施

生成された URL をシークレットウインドウで開いてみます。

特にサインインのプロセス(パスワードを入力するなど)を経ず、オレゴンリージョンの AWS CloudShell のコンソールに遷移しました。

AWS_CloudShell_federation_signin_cloudShell

そのまま AWS CloudShell 上でプリンシパルを確認すると AssumeRoled セッションであることがわかります。

$ aws sts get-caller-identity
{
    "UserId": "AROAQ3BIIH73W5JO32XAA:Test-Session",
    "Account": "000000000000",
    "Arn": "arn:aws:sts::000000000000:assumed-role/Test-RoleA/Test-Session"
}

画面右上を確認するとフェデレーテッドユーザーとしてサインインしていることがわかります。ユーザー名はロール名+セッション名ですね。

AWS_CloudShell_federated_user

もちろんここからマネジメントコンソールのホームに遷移したりリージョンを変更したりと、通常の IAM ユーザーと同じ感覚でコンソールを操作できます。

どこまで操作できるかは、AssumeRole したロール(今回で言えばTest-RoleA)に付与された権限次第です。

この URL はどのくらいの時間有効なの?

今回のケースではこのセッションが有効なのは 1 時間のみです。これを延ばしたい場合、スクリプトで AssumeRole する際にDurationSecondsで希望する秒数を指定してください。

あわせて、IAM ロールの最大セッション時間を延ばしておくこともお忘れなく。

AWS_CloudShell_IAM_Management_Console

終わりに

AWS CloudShell 上でフェデレーションサインイン用の URL を生成してみました。

AWS CloudShell 特有の要素は特にないのですが、スクリプトさえ用意すれば Python や各種ライブラリがセットアップ済みなのでサッと実行できる、というところが嬉しいポイントです。

ユースケースは限られるかもしれませんが、わざわざ IAM ユーザーや IAM ロールを新規作成するほどの手間はかけたくない……!というときに方式の一つとして思い出してもらえればと思います。

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

参考