MFA トークンを利用して別アカウントのIAM ロールをAssumeRole する際のGetSessionToken とAssumeRole の違い

以下のような前提で考えます

  • 対象アカウントには、Organization のSCP でMFA必須のポリシーが設定されいている
  • 開発者は認証アカウントのアクセスキーを利用して、対象アカウントにAssumeRole でアクセスする
  • AssumeRole を呼び出す際は必ずMFA が必須とする

図示すると以下のような場合です。

MFA 必須SCPとは具体的には以下のようなポリシーです。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllDenyWithoutMFA",
"Effect": "Deny",
"Action": [
"*"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllDenyWithoutMFA", "Effect": "Deny", "Action": [ "*" ], "Resource": "*", "Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" } } } ] }
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllDenyWithoutMFA",
            "Effect": "Deny",
            "Action": [
                "*"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        }
    ]
}

対象アカウントのIAM ロールの信頼ポリシーには、MFA 必須の設定がされています。

以下、信頼ポリシー

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<auth_account_id>:user/<iam_username>"
},
"Action": "sts:AssumeRole",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::<auth_account_id>:user/<iam_username>" }, "Action": "sts:AssumeRole", "Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } } } ]
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<auth_account_id>:user/<iam_username>"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]

また、IAM ロールのアクセス権限には AdministratorAccess ポリシーを設定していて、AssumeRole したら何でもできるといった前提とします。

以上がシナリオの前提です。

AssumeRole にMFA トークンを指定する場合

上記のシチュエーションでは、以下のようにAssumeRole を呼び出すことができ一時的認証情報を得ることができます。

aws configure set aws_access_key_id <auth_account_access_key>
aws configure set aws_secret_access_key <auth_account_secret>

aws sts assume-role --role-arn <target_account_role_arn> \
    --serial-number <your_mfa_device_arn> \
    --role-session-name <any_name> \
    --token-code <your_token_code>  \

一時的認証情報の例

{
    "Credentials": {
        "AccessKeyId": "xxxx",
        "SecretAccessKey": "xxxx",
        "SessionToken": "xxxxxx",
        "Expiration": "2020-04-04T02:54:37+00:00"
    },
    ・・・略・・・
}

では、今回の前提で上記一時的認証情報を使ってAWS API を実行するとどうなるでしょか?

例えば以下のようなコマンドを実行します。(一時的認証情報を dev プロファイルに設定しているとします。)

$ aws s3 ls --profile dev

An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied

今回の前提ではIAM ロールのアクセス権限に AdministratorAccess ポリシーを設定しているので呼び出しは成功しても良いはずですが、Access Denied になってしまいました。

GetSessionToken + AssumeRole をする場合

以下のようなスクリプトを組みます。これはまずGetSessionToken を実行してMFA 認証済みの一時的認証情報を入手してから、その認証情報を利用してAssumeRole をするという流れです。

ファイル名: assume-role.sh

#!/bin/bash -eu
# Auth アカウントのIAM ユーザ アクセスキーにより、get-session-token を実行して、MFA 認証済みの一時的認証情報を入手する
credentials=$(aws sts get-session-token \
--serial-number arn:aws:iam::<auth_account_id>:mfa/<iam_username> \
--token-code $1 \
--query "Credentials.[AccessKeyId, SecretAccessKey,SessionToken]" \
--output text)
# 一旦環境変数にMFA 認証済み一時的認証情報をセット
export AWS_ACCESS_KEY_ID=$(echo $credentials | cut -d ' ' -f 1)
export AWS_SECRET_ACCESS_KEY=$(echo $credentials | cut -d ' ' -f 2)
export AWS_SESSION_TOKEN=$(echo $credentials | cut -d ' ' -f 3)
# 対象アカウントのIAM ロールをassume-role する
credentials=$(aws sts assume-role --role-arn arn:aws:iam::<target-accountid>:role/<role_name> \
--role-session-name <session_name(任意)> \
--query "Credentials.[AccessKeyId, SecretAccessKey,SessionToken]" \
--output text)
# 取得した対象アカウントの一時的認証情報をaws cli のdev プロファイルにセットする
access_key_id=$(echo $credentials | cut -d ' ' -f 1)
secret_access_key=$(echo $credentials | cut -d ' ' -f 2)
session_token=$(echo $credentials | cut -d ' ' -f 3)
aws configure set profile.dev.aws_access_key_id "$access_key_id"
aws configure set profile.dev.aws_secret_access_key "$secret_access_key"
aws configure set profile.dev.aws_session_token "$session_token"
# 環境変数削除
unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY
unset AWS_SESSION_TOKEN
view raw assume-role.sh hosted with ❤ by GitHub

以下のように実行します。

$ aws configure set aws_access_key_id <auth_account_access_key>
$ aws configure set aws_secret_access_key <auth_account_secret>

$ assume-role.sh <your_token_code>

$ aws s3 ls --profile dev

… 実行がうまくいく …

この場合は、今回の前提においても成功します。

2つの挙動の違い

以下のドキュメントに記載があります。

GetSessionToken によって返される一時的な認証情報には MFA 情報が含まれているので、認証情報によって実行される各 API オペレーションで MFA を確認できます。

AssumeRole によって返される一時的な認証情報のコンテキストには MFA 情報が含まれていないので、MFA に対する個別の API オペレーションを確認できません。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_mfa_configure-api-require.html

AssumeRole にMFA トークンを指定して呼び出すだけの場合、取得できる一時的認証情報には、MFA 情報が含まれません。CloudTrail のログを見るとリクエストのuserIdentity の情報としては以下のようになっています。

"userIdentity": {
    "type": "AssumedRole",
    "principalId": "xxx:xxx",
    "arn": "arn:aws:sts::xxxxx:assumed-role/xxxx/xxxxx",
    "accountId": "xxxxx",
    "accessKeyId": "xxxxxxx",
    "sessionContext": {
        "attributes": {
            "mfaAuthenticated": "false",
            "creationDate": "2020-04-04T01:58:27Z"
        },
        "sessionIssuer": {
            "type": "Role",
            "principalId": "xxxxxx",
            "arn": "arn:aws:iam::xxxxxx:role/xxxxxx",
            "accountId": "xxxxxx",
            "userName": "xxxxxxx"
        }
    }
}

“mfaAuthenticated” : “false” となっています。

次に、GetSessionToken -> AssumeRole の場合の、CloudTrail のログを見てみます。同じように”userIdentity” の内容です。

"userIdentity": {
    "type": "AssumedRole",
    "principalId": "xxxxx",
    "arn": "arn:aws:sts::xxxxx:assumed-role/xxxx/xxxxx",
    "accountId": "xxxxx",
    "accessKeyId": "xxxxxx",
    "sessionContext": {
        "sessionIssuer": {
            "type": "Role",
            "principalId": "xxxxx",
            "arn": "arn:aws:iam::xxxxxxx:role/xxxxx",
            "accountId": "xxxxxxx",
            "userName": "xxxxxx"
        },
        "webIdFederationData": {},
        "attributes": {
            "mfaAuthenticated": "true",
            "creationDate": "2020-04-04T00:53:53Z"
        }
    }

この場合、”mfaAuthenticated” : “true” となっており、MFA 認証情報が含まれていることがわかります。

今回の前提では、SCP にて”mfaAuthenticated” : “true” 以外のリクエストはすべてDeny する設定になっているため、GetSessionToke + AssumeRole の場合だけ、正常に実行できたということになります。

以上です。