KMS 暗号化が有効な SSM セッションマネージャー接続を MFA + スイッチロール + AWS CLI で実行してエラーが発生した時の回避方法

AWS CLI での AssumeRole を「プロファイル指定で行うか」「手動で行うか」で、MFA コードの受け渡し可否に差がある場合がある……?というケースです

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

今回の事象が発生した構成は以下の通りです。

  • AWSアカウント
    • スイッチ元:000000000000
    • スイッチ先:999999999999
  • スイッチ先アカウントの EC2 インスタンスに対して AWS CLI でセッションマネージャー接続を行う
    • セッションマネージャー接続は KMS による暗号化が有効になっている
    • スイッチ時には MFA による認証コードの入力を行う
    • AWS CLI によるスイッチロールはプロファイルを指定して実行する

IAM_KMS_SSM

実行を試みると以下のエラーが発生しました。

% aws ssm start-session --target  i-0938992d87f175f68 --profile chiba
Enter MFA code for arn:aws:iam::000000000000:mfa/cm-chiba.yukihiro:

Starting session with SessionId: botocore-session-1616934847-07ebf73805201d7cf


SessionId: botocore-session-1616934847-07ebf73805201d7cf :
----------ERROR-------
Encountered error while initiating handshake. KMSEncryption failed on client with status 2 error: Failed to process action KMSEncryption: error while creating new KMS service, Error creating new aws sdk session AssumeRoleTokenProviderNotSetError: assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.

特に気になるのは以下の部分です。

Error creating new aws sdk session AssumeRoleTokenProviderNotSetError: assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.

マネジメントコンソールからの接続や MFA なしでの AWS CLI 実行は問題なく行えたのですが、 MFA が絡んだときにうまく行かなくなる、という状況でした。

回避策とあわせてご紹介します。

回避策の概要

以下の 3 パターンを取り上げます。

  • KMS 暗号化を無効化する
  • AssumeRole を事前に実行する
  • 外部ツールを使用する

事象について改めて整理

セッションマネージャーの KMS 暗号化

セッションマネージャー接続設定のオプションとして、KMS による暗号化を有効化できます。これにより「接続先の EC2 インスタンスと接続元のローカル端末間のセッションデータ」が KMS カスタマーマスターキーを使用して暗号化されます。(有効化前のデフォルト状態では TLS 1.2 暗号化のみがなされています。)

コンソールでは以下箇所から状態が確認できます。当該画面には映っていませんが、編集画面では特定の KMS キーを指定することになります。

SSMKMS

こういったセッションマネージャーに関する設定内容は SSM ドキュメント SSM-SessionManagerRunShell に反映されます。セッションマネージャー実行時にはこのドキュメントが呼び出されます。

パラメータとしてkmsKeyIdという項目が含まれていますね。

SSMDoc

KMS による暗号化を有効化すると、以下のエンティティで KMS キーに関するアクセス許可が必要となります。

  • セッションマネージャーを開始する IAM エンティティ(今回はスイッチ後のロール)
    • kms:GenerateDataKey
  • 接続先 EC2 インスタンスにアタッチされている IAM ロール
    • kms:Decrypt

Enable AWS KMS key encryption of session data (console) - AWS Systems Manager

IAM_KMS_SSM_Action

各リソースのポリシー設定

冒頭のエラーが発生した際の権限設定は以下の通りでした。スイッチ元のユーザーの権限は特に関係しないので省略します。

  • スイッチ先のロール
    • AWS 管理ポリシーReadOnlyAccess
    • カスタムポリシー(後述)
  • 接続先インスタンスのロール
    • AWS 管理ポリシーAmazonSSM ManagedInstanceCore
  • KMS キーポリシー
    • 上記二つの IAM ロールをキーユーザーとして指定(後述)

IAM_KMS_SSM_Policy

スイッチ先ロールのカスタムポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ssm:ResumeSession",
                "ssm:TerminateSession",
                "ssm:StartSession"
            ],
            "Resource": [
                "arn:aws:ec2:*:999999999999:instance/*",
                "arn:aws:ssm:*:999999999999:session/*",
                "arn:aws:ssm:*:999999999999:document/*"
            ],
            "Effect": "Allow"
        }
    ]
}

KMS キーポリシー

コンソールから「キーユーザー」として二つのロールを選択した際に自動で生成されたものです。

なお、ロール名はそれぞれ以下の通りです。

  • スイッチ先のロール:Role-for-switch
  • 接続先インスタンスのロール:AmazonSSMRoleForInstancesQuickSetup
{
    "Id": "key-consolepolicy-3",
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::999999999999:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow use of the key",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::999999999999:role/AmazonSSMRoleForInstancesQuickSetup",
                    "arn:aws:iam::999999999999:role/Role-for-switch"
                ]
            },
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow attachment of persistent resources",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::999999999999:role/AmazonSSMRoleForInstancesQuickSetup",
                    "arn:aws:iam::999999999999:role/Role-for-switch"
                ]
            },
            "Action": [
                "kms:CreateGrant",
                "kms:ListGrants",
                "kms:RevokeGrant"
            ],
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "kms:GrantIsForAWSResource": "true"
                }
            }
        }
    ]
}

スイッチ先ロール、インスタンス用ロールのアイデンティティベースポリシーでは KMS に関するアクションが Allow されていませんが、キーポリシー側で許可されているためにアクションが可能、という状態です。

マネジメントコンソールでの接続

上記の権限構成で、マネジメントコンソールから接続を試みます。問題なく接続できます。

MC1

MCSSM2

This session is encrypted using AWS KMS.のメッセージとともに、正常に接続できました。

MCSSM3

AWS CLI (MFA なし)での接続

事前に Session Manager plugin のインストールを済ませています。

また、コンフィグファイルの設定状況は以下の通りです。

.aws/config

[default]
region = ap-northeast-1

[profile chiba]
region = ap-northeast-1
role_arn = arn:aws:iam::999999999999:role/Role-for-switch
source_profile = cm-base
# mfa_serial = arn:aws:iam::000000000000:mfa/cm-chiba.yukihiro

ここではmfa_serialはコメントアウトしています。

.aws/credentials

[default]
aws_access_key_id = AKIAQ3BIIHXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXX0gHnBTNsj5b

[cm-base]
aws_access_key_id = AKIARK998PXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXYqVryTVSURj

プロファイルchibaでは、source_profileとしてcm-baseを指定しています。

aws_config_files

上記の状態で名前つきプロファイルを指定してaws ssm start-sessionを実行すると、問題なく接続が成功します。

% aws ssm start-session --target  i-0938992d87f175f68 --profile chiba

Starting session with SessionId: botocore-session-1616934777-0b94b704a86b058f0
This session is encrypted using AWS KMS.
sh-4.2$

AWS CLI (MFAあり)での接続

先ほどのコンフィグから mfa_serialをアンコメントし、スイッチ時の MFA を有効化します。

.aws/config

[profile chiba]
region = ap-northeast-1
role_arn = arn:aws:iam::999999999999:role/Role-for-switch
source_profile = cm-base
mfa_serial = arn:aws:iam::000000000000:mfa/cm-chiba.yukihiro

再びプロファイルを指定してaws ssm start-sessionを実行し、MFA コードを入力します。冒頭で載せたエラーが発生します。

% aws ssm start-session --target  i-0938992d87f175f68 --profile chiba
Enter MFA code for arn:aws:iam::000000000000:mfa/cm-chiba.yukihiro:

Starting session with SessionId: botocore-session-1616934847-021099c96056c99dd


SessionId: botocore-session-1616934847-021099c96056c99dd :
----------ERROR-------
Encountered error while initiating handshake. KMSEncryption failed on client with status 2 error: Failed to process action KMSEncryption: error while creating new KMS service, Error creating new aws sdk session AssumeRoleTokenProviderNotSetError: assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.

関連する権限構成は同一であっても、AWS CLI で MFA を使用する場合のみエラーが発生する、という事象に直面しました。

ちなみに

この事象は権限設定に起因したものではありません。念のためにスイッチ先ロールと接続先インスタンスのロールにAdministratorAccessをアタッチして試行しても同様の結果となりました。

どのように回避すればいいのか

今回は以下のパターンをご紹介します。

  • KMS 暗号化を無効化する
  • AssumeRole を事前に実行する
  • 外部ツールを使用する

厳密に考えるとパターン 2 と 3 は同じ括りなのですが、アプローチが異なるので別物としました。

KMS 暗号化を無効化する

暗号化を無効化することで、MFA ありでも問題なく接続できるようになります。「 KMS による暗号化を必須とする」といった社内のセキュリティポリシーが定められていない場合には、このアプローチが最もシンプルに事象を回避できます。

KMS 暗号化を無効化した際の分かりやすい影響としては、「制御できるレイヤーが一つ減る」というものがあります。

再掲となりますが、KMS 暗号化が有効な場合には以下の権限設定が必要となります。

IAM_KMS_SSM_Action

接続を開始する IAM エンティティに KMS に対する権限が足りない場合には以下のエラーが発生します。

AWS_Systems_Manager_-_Session_Manager-6930277

% aws ssm start-session --target  i-0938992d87f175f68 --profile chiba

Starting session with SessionId: botocore-session-1616930143-05560414b3eb7b9ef


SessionId: botocore-session-1616930143-05560414b3eb7b9ef :
----------ERROR-------
Encountered error while initiating handshake. KMSEncryption failed on client with status 2 error: Failed to process action KMSEncryption: Error calling KMS GenerateDataKey API: AccessDeniedException: User: arn:aws:sts::999999999999:assumed-role/Role-for-switch/1616930144774813000 is not authorized to perform: kms:GenerateDataKey on resource: arn:aws:kms:ap-northeast-1:999999999999:key/XXXXXXXX-8d89-4c70-8b93-108ca3283f27
	status code: 400, request id: effaa166-de06-4f2c-a582-b389518142a0

EC2 インスタンス用ロールの権限が足りない場合は以下のメッセージが表示されます。

AWS_Systems_Manager_-_Session_Manager

% aws ssm start-session --target  i-0938992d87f175f68 --profile chiba

Starting session with SessionId: botocore-session-1616930143-05560414b3eb7b9ef


SessionId: botocore-session-1616930143-05560414b3eb7b9ef :
----------ERROR-------
Encountered error while initiating handshake. Fetching data key failed: Unable to retrieve data key, Error when decrypting data key AccessDeniedException: The ciphertext refers to a customer master key that does not exist, does not exist in this region, or you are not allowed to access.
	status code: 400, request id: 1c93bcd2-57ec-4bf4-beee-0e1821d50bf2

セッションマネージャー接続そのものに対する権限とは別に、 KMS に対する権限が必要となります。こういったコントロールが必須ではない場合には、暗号化を無効化するのも一つの手です。

AssumeRole を事前に実行する

エラーが発生した際は、CLI 実行時のオプションとしてプロファイルを指定することで、role_arnで指定したロールへの自動的な AssumeRole を実現していました。

.aws/config

[default]
region = ap-northeast-1

[profile chiba]
region = ap-northeast-1
role_arn = arn:aws:iam::999999999999:role/Role-for-switch
source_profile = cm-base
mfa_serial = arn:aws:iam::000000000000:mfa/cm-chiba.yukihiro

以下のように事前に明示的に AssumeRole を行う方法を取ることで、事象を回避できました。なお、ここでは default のプロファイルを使用しています。

 % TOKENCODE=000000
aws_credentials=$(aws sts assume-role --role-arn arn:aws:iam::999999999999:role/Role-for-switch --role-session-name RoleSession1 --serial-number arn:aws:iam::999999999999:mfa/chiba-cli --token-code $TOKENCODE)
export AWS_ACCESS_KEY_ID=$(echo $aws_credentials|jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $aws_credentials|jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $aws_credentials|jq -r '.Credentials.SessionToken')

( assume-role 時に指定しているオプションは以下の通りです。)

aws sts assume-role\
  --role-arn arn:aws:iam::999999999999:role/Role-for-switch\
  --role-session-name RoleSession1\
  --serial-number arn:aws:iam::999999999999:mfa/chiba-cli\
  --token-code $TOKENCODE

AssumeRole 後に念のため IAM エンティティを確認すると、ロールを引き受けた状態であることが確認できます。

% aws sts get-caller-identity
{
    "UserId": "AROAQ3BIIH736USBVHHYC:RoleSession1",
    "Account": "999999999999",
    "Arn": "arn:aws:sts::999999999999:assumed-role/Role-for-switch/RoleSession1"
}

この状態で aws ssm start-session を実行すると、 KMS で暗号化された状態でのセッションマネージャー接続を開始できました。

% aws ssm start-session --target  i-01456ff79ca9f6247
Starting session with SessionId: RoleSession1-0a171025a0417765b
This session is encrypted using AWS KMS.
sh-4.2$

assume-role 部分をある程度自動化したい、となると以下のようにスクリプトを作成するのもよいでしょう。

名前つきプロファイルについてちょっと勘違い

以下のような .aws/configにした上でプロファイルを指定(--profile chiba)して AssumeRole をしようとすると失敗しました。

.aws/config

[default]
region = ap-northeast-1

[profile chiba]
region = ap-northeast-1
# role_arn = arn:aws:iam::999999999999:role/Role-for-switch
source_profile = cm-base
# mfa_serial = arn:aws:iam::000000000000:mfa/cm-chiba.yukihiro
% TOKENCODE=000000
aws sts assume-role\
  --role-arn arn:aws:iam::999999999999:role/Role-for-switch\
  --role-session-name RoleSession1\
  --serial-number arn:aws:iam::999999999999:mfa/chiba-cli\
  --token-code $TOKENCODE\
  --profile chiba

Unable to locate credentials. You can configure credentials by running "aws configure".

credentials が見つからない、という怒られのようです。source_profileで指定しているのに何故……?と疑問に思いました。

調べ直したところ、source_profilerole_arnとセットで使用するものであり、単独で.aws/credentials内のエントリを指定できるものではありませんでした。

aws_config_files_error

今回使用したいプロファイル名chibaと同名のエントリを.aws/credentialsに追加することで、エラーは回避できました。

.aws/credentials

[default]
aws_access_key_id = AKIAQ3BIIHXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXX0gHnBTNsj5b

#[cm-base]
#aws_access_key_id = AKIARK998PXXXXXXXXXX
#aws_secret_access_key = XXXXXXXXXXYqVryTVSURj

[chiba]
aws_access_key_id = AKIARK998PXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXYqVryTVSURj

外部ツールを使用する

上記のパターンで行った AssumeRole の操作をなるべくラップしたい、という場合には外部ツールを使用するのも一つの手です。

AssumeRole 補助ツールはいくつかありますが、今回は以下エントリで取り上げられている remind101/assume-role を使用してみました。

筆者の環境は mac のため、以下コマンドでインストールを行います。

$ brew install remind101/formulae/assume-role

設定ファイルはパターン 2 と変わらず以下の通りです。

.aws/config

[default]
region = ap-northeast-1

[profile chiba]
region = ap-northeast-1
role_arn = arn:aws:iam::999999999999:role/Role-for-switch
source_profile = cm-base
mfa_serial = arn:aws:iam::000000000000:mfa/cm-chiba.yukihiro

.aws/credentials

[default]
aws_access_key_id = AKIAQ3BIIHXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXX0gHnBTNsj5b

[cm-base]
aws_access_key_id = AKIARK998PXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXYqVryTVSURj

assume-role <プロファイル名>の後に、実行したい aws cli コマンドを続けます。MFA コードの入力が求められたのち、コマンドが実行されます。

% assume-role chiba aws ssm start-session --target  i-01456ff79ca9f6247
MFA code: 000000

Starting session with SessionId: 1617330888779191000-0cabd3eb74fa1fb2f
This session is encrypted using AWS KMS.
sh-4.2$

特に AssumeRole の処理を意識することなく、プロファイルの指定だけで事象を回避できました。

終わりに

KMS による暗号化が有効な SSM セッションマネージャー接続を、 MFA コード入力を伴う AWS CLI (プロファイル指定でのスイッチロールパターン)で実行しようとした際に発生するエラーについてでした。

AWS CLI でのスイッチロールはプロファイルで指定しておくのが便利ですが、組み合わせによっては MFA コードの受け渡し(という表現が正しいのか分かりませんが)がうまく行かないことがあるようです。

Error creating new aws sdk session AssumeRoleTokenProviderNotSetError: assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.で検索すると、以下のような Terraform 関連が多くヒットしました。他にもありそうだなーと思っています。)

環境によっては外部ツールの使用が認められていないこともあるかと思いますので、その場合にはパターン 1 や 2 で回避いただければと思います。

以上、千葉(幸)がお送りしました。