今のはスイッチロールではない…AssumeRole からのフェデレーションサインインだ…をやってみた

IAM ロールの認証情報でマネジメントコンソールにアクセスする、を実現できるのはスイッチロールだけではありません。一時的な認証情報を使用してサインイントークンを取得し、フェデレーテッドユーザーとしてサインインすることもできます。

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

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

…今のはスイッチロールでは無い… AssumeRole からのフェデレーションサインインだ…

そんなセリフを言いたくなったことはありませんか?私はありません。「AssumeRole からのフェデレーションサインイン」なんて言い回しはきっとこの地球上に存在しないので、ありません。

ここでのスイッチロールとは「① IAM ユーザーのユーザー名とパスワードを利用してマネジメントコンソールにサインインする」「② IAM ロールに切り替える」の流れを指しています。

IAM ロールの認証情報を利用してマネジメントコンソールで操作したい場合には、このスイッチロールが圧倒的にポピュラーな手法だと思います。しかしこれが唯一の手法ではありません。

IAM ロールを引き受けたセッションをフェデレーテッドユーザーとしてサインインすることもできます。ユーザー名やパスワードを入力することなくマネジメントコンソール上で操作ができてしまうのです。

そんな手法を最近知ったので、実際に試してみます。

フェデレーテッドユーザーによるコンソールアクセス

今回やりたいことの概要は以下です。

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

細部はかなり端折っていますが、以下ページにある内容が該当します。

そして上記ページの内容を実践しているのが以下エントリです。ここでは最初の認証情報の取得を AssumeRole ではなく GetFederationToken によって行っています。

上記のエントリを丸パ……存分に参考にしながら、AssumeRole を用いるパターンでやってみるのが今回の内容です。

フェデレーテッドユーザー用 URL を生成する シェルスクリプト

早速ですが AssumeRole 向けにカスタマイズしたスクリプトが以下です。ハイライト部がオリジナルから改修した部分です。

#!/bin/bash
set -e

# urlencode用のfunction定義
urlencode() {
  local length="${#1}"
  for (( i = 0; i < length; i++ )); do
    local c="${1:i:1}"
    case $c in
      [a-zA-Z0-9.~_-]) printf "$c" ;;
      *) printf '%%%02X' "'$c"
    esac
  done
}

# 引き受けるIAMロールとセッション名と有効期限を指定
ROLE_ARN=arn:aws:iam::000000000000:role/test
ROLE_SESSION_NAME=test-user
DURATION_SECONDS=3600

# FederationToken取得(有効期間を調節したい場合には、--durationオプションを変更)
FEDERATION_TOKEN=$(aws sts assume-role --role-arn ${ROLE_ARN} --role-session-name ${ROLE_SESSION_NAME} --duration-seconds ${DURATION_SECONDS})

# セッション文字列生成
SESSION_ID=$(echo ${FEDERATION_TOKEN} | jp.py "Credentials.AccessKeyId" | tr -d "\"")
SESSION_KEY=$(echo ${FEDERATION_TOKEN} | jp.py "Credentials.SecretAccessKey" | tr -d "\"")
SESSION_TOKEN=$(echo ${FEDERATION_TOKEN} | jp.py "Credentials.SessionToken" | tr -d "\"")

# SigninToken取得
JSON_FORMED_SESSION=$(echo "{\"sessionId\":\"${SESSION_ID}\",\"sessionKey\":\"${SESSION_KEY}\",\"sessionToken\":\"${SESSION_TOKEN}\"}")
SIGNIN_URL="https://signin.aws.amazon.com/federation"
GET_SIGNIN_TOKEN_URL="${SIGNIN_URL}?Action=getSigninToken&SessionType=json&Session=$(urlencode ${JSON_FORMED_SESSION})"
SIGNIN_TOKEN=$(curl -s "${GET_SIGNIN_TOKEN_URL}" | jp.py "SigninToken" | tr -d "\"")

# LOGIN URLの出力
CONSOLE_URL="https://console.aws.amazon.com/"
LOGIN_URL="${SIGNIN_URL}?Action=login&Destination=$(urlencode ${CONSOLE_URL})&SigninToken=$(urlencode ${SIGNIN_TOKEN})" && echo ${LOGIN_URL}

また、オリジナルでは存在していたISSUER_URL(最終的な URL のパラメータの一部)を動作に必須でないため削っています。

このスクリプトを実行すると、以下形式の URL が出力されます。

https://signin.aws.amazon.com/federation?Action=login&Destination=https%3A%2F%2Fconsole.aws.amazon.com%2F&SigninToken=aQG2h7cOALssYnyq...

末尾のサインイントークンは実際にはもっと長く、1500 字程度の長さがあります。

フェデレーテッドユーザーとしてのサインイン

上記で生成された URL にブラウザからアクセスしてみます。今回はシークレットウインドウでアクセスします。

以下のようにフェデレーテッドユーザーとしてサインインできました。今回は AssumeRole するロールとしてtest、セッション名としてtest-userを指定しましたが、それがユーザー名に反映されているのが読み取れます。

この状態から、AssumeRole した IAM ロールが持つ権限の範囲内でコンソール上での操作ができます。このあたりはスイッチロールと変わりありません。AssumeRole 時にセッションポリシーを指定できるため、フェデレーテッドユーザー方式の方がより細やかに制御できる、とも言えます。

このフェデレーテッドユーザーが実行したアクションは CloudTrail イベントでは以下のように記録されていました。

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROAQ3BIIH73U2LGAXTZQ:test-user",
        "arn": "arn:aws:sts::000000000000:assumed-role/test/test-user",
        "accountId": "000000000000",
        "accessKeyId": "ASIAQ3BIIH736XGFSDD2",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROAQ3BIIH73U2LGAXTZQ",
                "arn": "arn:aws:iam::000000000000:role/test",
                "accountId": "000000000000",
                "userName": "test"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2021-12-28T11:17:07Z",
                "mfaAuthenticated": "false"
            }
        }
    },
    "eventTime": "2021-12-28T11:17:53Z",
    "eventSource": "ec2.amazonaws.com",
    "eventName": "DescribeInstanceStatus",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "219.xx.xx.xx",
    "userAgent": "EC2ConsoleFrontend, aws-internal/3 aws-sdk-java/1.12.100 Linux/5.4.156-94.273.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.312-b07 java/1.8.0_312 vendor/Oracle_Corporation cfg/retry-mode/standard",
    "requestParameters": {
        "instancesSet": {
            "items": [
                {
                    "instanceId": "i-047875da17caa9cc2"
                },
                {
                    "instanceId": "i-07856803079a58378"
                },
                {
                    "instanceId": "i-0986014d98308ac67"
                }
            ]
        },
        "filterSet": {},
        "includeAllInstances": false
    },
    "responseElements": null,
    "requestID": "bd38f7f6-d4ff-4b29-b59e-6018fe5f41dd",
    "eventID": "eb4f51db-95ac-4eeb-a688-c63f30a424af",
    "readOnly": true,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "000000000000",
    "eventCategory": "Management"
}

Trail イベント上でのユーザー名はtest-userです。ロールを引き受ける際に指定したセッション名がここに現れるようです。スイッチロールパターンでもuserIdentityは同じように表示されるため、イベントだけを見てどちらのパターンでコンソールアクセスをしたかの判別は難しそうです。

おまけ:Duration 指定のエラー

AssumeRole の存続期間は最大で 12 時間のため、--duration-secondsの値として43200を指定したところ以下のエラーが出ました。

An error occurred (ValidationError) when calling the AssumeRole operation: The requested DurationSeconds exceeds the MaxSessionDuration set for this role.

IAM ロールのパラメータ「最大セッション時間」を超える値はここでは指定できないため、まずはそちらを延ばしてあげる必要があります。

終わりに

AssumeRole で取得した一時的な認証情報を使用してフェデレーテッドユーザーとしてコンソールにアクセスしてみました。

この手法であれば IAM ユーザーとしてのコンソールアクセスは不要で、パスワードを管理する必要がありません。AssumeRole ができる権限だけ持たせてアクセスキー/シークレットアクセスキーを払い出しておけば事足ります。

基本的にはアプリケーションでの使用が想定されているため今回のように手動で URL を作成するのは手間が大きいかと思いますが、選択肢のひとつとしてこういった手法もあると覚えておくと捗るかもしれません。外部の人に一時的にコンソールを覗いてもらう、といった場面でも使えそうですね。

後から気づきましたが以下のエントリでもほとんど同じようなことを試していますので、合わせてご参考ください。

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