AWS (/SAM/EB) CLI で IAM Role / MFA を使用する

各種 CLI から IAM ロールを使って AWS リソースにアクセスする方法をまとめました。

参考

方法1) プロファイルによるロール切り換え

プロファイルとして使用する IAM ロールを設定しておき、コマンド実行時の環境変数やコマンドライン引数でプロファイルを指定して実行する。

プロファイルを作成する

~/.aws/config を編集してプロファイルを追加する。プロファイル名は IAM ロールに合うような一意のものをつける。

[profile profile-name]
region = ap-northeast-1
output = json
role_arn = (IAM ロールの ARN)
source_profile = default

MFA 設定

IAM ロールで MFA 必須 (aws:MultiFactorAuthPresent = true) と設定されている場合、IAM ユーザーで MFA を有効にし、プロファイルに mfa_serial を設定しておく。使用できる MFA デバイスは TOTP (仮想デバイス) のみで、U2F キーは現状使用できない[^1]。

[profile profile-name]
region = ap-northeast-1
output = json
role_arn = (IAM ロールの ARN)
source_profile = default
mfa_serial = (MFA シリアル)

MFA シリアルは、IAM > ユーザー > 認証情報から確認できる。

AWS CLI の実行プロファイルを指定する

AWS_PROFILE 環境変数に、設定したプロファイル名を指定して実行する。 MFA が必要な場合は MFA コードを聞かれるので、入力する。

AWS_PROFILE=profile-name aws ...

AWS SAM CLI の実行プロファイルを指定する

AWS_PROFILE 環境変数に、設定したプロファイル名を指定して実行する。 MFA が必要な場合は MFA コードを聞かれるので、入力する。

AWS_PROFILE=profile-name sam ...

AWS EB CLI の実行プロファイルを指定する

環境変数を使う場合

AWS_EB_PROFILE 環境変数に、設定したプロファイル名を指定して実行する。(AWS_PROFILE ではないので注意) MFA が必要な場合は MFA コードを聞かれるので、入力する。

AWS_EB_PROFILE=profile-name eb ...

特定のプロジェクトで常に固定のプロファイルを使うよう設定する場合

eb init 時に使用するプロファイルを指定すると、環境変数を設定しない場合にそのプロファイルが使用される。

$ eb init (アプリケーション名) --profile profile-name

これにより、.elasticbeanstalk/config.yml に設定される。eb init 後であればこれを直接書き換えても良い。

global:
  profile: profile-name

方法2) コマンドによるロール切り換え

プロファイル設定によるロール切り換えは aws コマンド実行のたびにおこなわれるため、MFA による認証も毎回発生してしまう(トークンコードを再利用できないため、毎回 1 分待つ必要もある)。連続してコマンドを実行した場合は aws sts コマンドで認証情報を取得して一時的に環境変数に設定しておくと良い。

$ aws sts assume-role \
    --role-arn (ロールARN) \
    --role-session-name (セッション名) \
    --serial-number (MFAシリアル) \
    --token-code (トークンコード)
{
    "Credentials": {
        "AccessKeyId": "...",
        "SecretAccessKey": "...",
        "SessionToken": "...",
        "Expiration": "..."
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "...",
        "Arn": "..."
    }
}
$ export AWS_ACCESS_KEY_ID=(Credentials.AccessKeyId の内容)
$ export AWS_SECRET_ACCESS_KEY=(Credentials.SecretAccessKey の内容)
$ export AWS_SESSION_TOKEN=(Credentials.SessionToken の内容)

※この環境変数は AWS_PROFILE より優先されるので、併用時は注意。

以下のようなコマンドを作成しておくと便利。

#!/bin/bash

usage() {
  echo "usage: \$($(basename $0) <profile_name> [<token_code>])" >&2
}

PROFILE_NAME=$1
TOKEN_CODE=$2

if [ -z "${PROFILE_NAME}" ]; then
  usage
  exit 1
fi

ENV=$(awk "/\[/{f=0} f; /^\[profile ${PROFILE_NAME}\]/{f=1}" ~/.aws/config | grep -E -v '^#')
ROLE_ARN=$(echo "${ENV}" | grep '^role_arn' | sed 's/^role_arn[ \t]*=[ \t]*//')
SOURCE_PROFILE=$(echo "${ENV}" | grep '^source_profile' | sed 's/^source_profile[ \t]*=[ \t]*//')
MFA_SERIAL=$(echo "${ENV}" | grep '^mfa_serial' | sed 's/^mfa_serial[ \t]*=[ \t]*//')

if [ -z "${ROLE_ARN}" ] || [ -z "${SOURCE_PROFILE}" ]; then
  usage
  exit 1
fi
if [ -n "${MFA_SERIAL}" ] && [ -z "${TOKEN_CODE}" ]; then
  echo "ERROR: token_code for mfa required."
  usage
  exit 1
fi

ASSUME_ROLE_OPT="--profile ${SOURCE_PROFILE} --role-arn ${ROLE_ARN} --role-session-name ${PROFILE_NAME}"
if [ -n "${MFA_SERIAL}" ] && [ -n "${TOKEN_CODE}" ]; then
  ASSUME_ROLE_OPT="${ASSUME_ROLE_OPT} --serial-number ${MFA_SERIAL} --token-code ${TOKEN_CODE}"
fi

RESULT=$(aws sts assume-role ${ASSUME_ROLE_OPT})
if [ $? -ne 0 ]; then
  exit 1
fi

echo "export AWS_ACCESS_KEY_ID=$(echo "${RESULT}" | jq -r '.Credentials.AccessKeyId')"
echo "export AWS_SECRET_ACCESS_KEY=$(echo "${RESULT}" | jq -r '.Credentials.SecretAccessKey')"
echo "export AWS_SESSION_TOKEN=$(echo "${RESULT}" | jq -r '.Credentials.SessionToken')"

[^1]: aws/aws-cli#3607。U2F キーは普段非常に便利なので、AWS さんにはせめてデバイスを複数登録できるようにお願いしたい。。