[小ネタ] credentials に書かない AWS IAM ロール切り替えシェル
Introduction
以前書いた記事 で、AssumeRole で得た一時認証情報を
~/.aws/credentials に <profile>_temp という profile として書き出し、
独自の env で profile 名を渡すシェルを紹介しました。
その後しばらく運用していくうちに、
「ファイルに書かない / AWS 標準 env だけで済ませる」方が
取り回しが良く安全だとおもったので、少しやり方をかえてみました。
本記事では、作り直したAWSの一時認証シェルを紹介します。
Prerequisites
動作確認した環境は以下です。
- macOS / Linux
- bash 5.x (macOS は標準の
/bin/bashが 3.2 系のため、brew install bashで 5.x を入れ、その bash からsourceしてください) - AWS CLI : 2.34.26
- jq : 1.8.1
AWS 設定は ~/.aws/config に source_profile + role_arn
(+ mfa_serial) が書かれた profile を対象にしています。
例えば myproj であれば以下のような設定を想定。
# ~/.aws/credentials
[default]
aws_access_key_id = AKIA...
aws_secret_access_key = ...
# ~/.aws/config
[profile myproj]
role_arn = arn:aws:iam::123456789012:role/myproj-deploy-role
source_profile = default
mfa_serial = arn:aws:iam::<source-account-id>:mfa/<your-iam-user>
region = ap-northeast-1
source_profile が指す profile (上記では default) に
長期 IAM User の credential が入っており、
role_arn がスイッチ先の IAM Role、mfa_serial が書かれていれば
スクリプトが MFA プロンプトを出します。
About Shell
以前のスクリプトは以下のように動作します。
% source aws-switch.sh myproj
- AssumeRole で得た一時 credential を
~/.aws/credentialsにmyproj_tempという profile セクションとして書き込む (aws_session_token含む短命 credential が平文で保存される) - shell に「いまどの profile を使うか」を示す独自 env (例:
MY_AWS_PROFILE=myproj_temp/MY_AWS_REGION=ap-northeast-1) を export - shell 配下で起動したツールがこの独自 env を
AWS_PROFILEとして参照する設定になっていれば、自動でmyproj_tempの credential が使われる
短い有効期限の credential を _temp profile としてファイルに書き、
env で profile 名をブロードキャストする設計です。
実用としては問題なく動きますが、使っていくと以下のような問題があります。
~/.aws/credentialsに_tempprofile が増える (cleanup面倒)- 一時 credential が 平文で残る
- 独自 env を読まないツール (素の
awsCLI / Terraform / CDK 等) からは profile 名が伝わらないので、--profile myproj_tempを毎回指定する必要がある
面倒な部分もありますし、セキュリティ的にもなんとかしたい問題です。
なので、新しいスクリプトでは以下の変更をしました。
| 変更点 | 旧スクリプト | 新スクリプト |
|---|---|---|
~/.aws/credentials への書き込み |
<profile>_temp を書く |
書かない |
| 主に export する env | 独自 env | AWS 標準 (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEYなど) + AWS_SWITCH_* |
aws コマンドの使い方 |
毎回 --profile 指定 |
--profile なしで OK |
| 後始末 | _temp ファイル汚染を手動でcleanup |
shell 終了で自動消失 |
profile 名を経由せず、credential 自体を env に流すようにしました。
使い方
以下のように使えます。
% source aws-switch-session.sh myproj
MFAトークンコード: 123456
✓ 環境変数を設定しました
Profile: myproj
Source : default
Account: 123456789012
Region : ap-northeast-1
Expires: 2026-04-27T03:13:58.000Z
その shell でそのまま使えます。
% aws sts get-caller-identity
{
"UserId": "AROA...:cli-session-...",
"Account": "123456789012",
"Arn": "arn:aws:sts::123456789012:assumed-role/myproj-deploy-role/cli-session-..."
}
AWS SDK / CLI の credential provider chain は env を最優先で見るので、
shell から子プロセスとして起動するツール (aws CLI / boto3 / aws-sdk-js / CDK 等) は
すべて追加設定なしで同じ credential を継承します。
コード
IAM切り替えスクリプト(aws-switch-session.sh) はこんな感じです。
とてもシンプル。
#!/usr/bin/env bash
# Usage: source aws-switch-session.sh <profile> [duration] [region]
profile="$1"
duration="${2:-3600}"
region="${3:-$(aws configure get region --profile "$profile")}"
role_arn=$(aws configure get role_arn --profile "$profile")
source_profile=$(aws configure get source_profile --profile "$profile")
mfa_serial=$(aws configure get mfa_serial --profile "$profile" 2>/dev/null)
account=$(echo "$role_arn" | cut -d: -f5)
mfa_args=()
if [[ -n "$mfa_serial" ]]; then
printf "MFAトークンコード: " >&2
read -r mfa_code
mfa_args=(--serial-number "$mfa_serial" --token-code "$mfa_code")
fi
# 既存の AWS_* env が source_profile より優先されると、再実行時に意図しない
# credential で AssumeRole してしまうので env -u で明示的に外しておく
creds=$(env -u AWS_ACCESS_KEY_ID -u AWS_SECRET_ACCESS_KEY -u AWS_SESSION_TOKEN -u AWS_PROFILE \
aws sts assume-role \
--profile "$source_profile" \
--role-arn "$role_arn" \
--role-session-name "cli-session-$(date +%s)" \
--duration-seconds "$duration" \
--output json "${mfa_args[@]}") || return 1
export AWS_ACCESS_KEY_ID=$(echo "$creds" | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo "$creds" | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo "$creds" | jq -r .Credentials.SessionToken)
export AWS_REGION="$region"
export AWS_SWITCH_PROFILE="$profile"
export AWS_SWITCH_EXPIRES=$(echo "$creds" | jq -r .Credentials.Expiration)
cat <<EOF
✓ 環境変数を設定しました
Profile: $profile
Source : $source_profile
Account: $account
Region : $region
Expires: $AWS_SWITCH_EXPIRES
EOF
~/.aws/config を直接 INI parse せず、 aws configure get で読みます。
AssumeRole の input 取得は aws sts assume-role --profile "$source_profile" に任せているので、
source profile が
~/.aws/credentials平文~/.aws/configにcredential_processを書いた aws-vault
どちらでも同じスクリプトが動作します。
※Keychain に保存しただけで credential_process 未設定の場合は別途対応が必要
また、出力は AWS 標準 env (AWS_ACCESS_KEY_ID など) のみ。
その他Tips
1. echo "MFA" | source ... だと環境変数が消える
source で env を export するスクリプトは、パイプの右側に置くと subshell が作られて
env が parent shell まで届きません。
# NG:env が subshell 内に閉じ込められる
echo "123456" | source aws-switch-session.sh myproj
# OK: here-string なら parent shell に env が届く
source aws-switch-session.sh myproj <<< "123456"
非対話で MFA を流したいケース (CI / 自動化スクリプトなど) は注意。
here-string や echo 経由で MFA コードを渡すとshell historyやログに残りやすいためです。
MFA コードはワンタイム値なので長期 credential ほど深刻ではありませんが、
再利用可能な時間枠が残るため、注意してください。
2. すでに起動済みのプロセスには反映されない
環境変数は shell プロセスの環境なので、source した shell から後で起動した子プロセス
(aws CLI / Terraform / CDK / SDK 利用アプリなど) にしか伝播しません。
source aws-switch-session.sh myproj <<< "123456"
aws sts get-caller-identity # ← この shell から起動したコマンドが env を継承
すでに別ターミナルで起動済みの長寿命プロセスがある場合、後から source しても反映されません。
その場合、該当プロセスを起動し直しましょう。
3. source_profile を aws-vault で Keychain 管理している場合
aws-vault で長期 credential を Keychain に保管している場合、
状況によって対応が分かれます。
(a) ~/.aws/config に credential_process を設定済みのケース
[default]
credential_process = aws-vault exec default --no-session --json
このスクリプトはそのまま動きます。
(assume-role の中で CLI が credential_process 経由で Keychain から credential を取得)
(b) Keychain に保存しただけで credential_process 未設定
AWS CLI から Keychain は見えないので、--profile default に失敗します。
最も簡単な対処は (a) の credential_process を ~/.aws/config に書く方法です。
スクリプト側で対応したい場合、サブシェル内で source credential を一時 env として使い、
AssumeRole の出力 JSON だけを親 shell に持ち出す形式にします。
creds=$(
src_json=$(aws-vault exec "$source_profile" --no-session --json)
env -u AWS_SESSION_TOKEN -u AWS_PROFILE \
AWS_ACCESS_KEY_ID=$(echo "$src_json" | jq -r .AccessKeyId) \
AWS_SECRET_ACCESS_KEY=$(echo "$src_json" | jq -r .SecretAccessKey) \
aws sts assume-role \
--role-arn "$role_arn" \
--role-session-name "cli-session-$(date +%s)" \
--duration-seconds "$duration" \
--output json "${mfa_args[@]}"
)
# ↑ 親 shell の $creds に AssumeRole 結果だけが入る
# AWS_ACCESS_KEY_ID / SECRET_ACCESS_KEY は src_json で上書きしているが、
# AWS_SESSION_TOKEN / AWS_PROFILE は env -u で外しておかないと
# 親 shell の古い値が aws CLI に渡って AssumeRole に失敗するので注意
長期 credential を export ではなく $(...) のサブシェル内のコマンド単位 env として渡すことで、
サブシェルが終了した時点で長期 credential は消えるため、
AssumeRole が失敗しても親 shell にAPIキー情報は残りません。
--no-session を付けると aws-vault は STS GetSessionToken を経由せず
長期 credential を直接返します。
組織の trust policy / IAM 条件によっては短命 credential 経由の
AssumeRole が拒否されることがあるため、
長期 credential を渡したほうが確実なケースが多いです。
(c) aws-vault に AssumeRole まで任せる
aws-vault は ~/.aws/config の source_profile / role_arn / mfa_serial を
native に解釈できるので、スクリプトを介さず aws-vault exec myproj -- aws ... だけで
AssumeRole + MFA まで完結させることも可能です。
本記事の「AWS 標準 env を shell に export する」ゴールとは別路線になりますが、
これが一番シンプルな方法かもしれません。
Summary
以前の方式は「ファイルに書く / 独自 env を使う」設計で、
人間が一人で複数ターミナルから使う運用には合っていました。
新しい方式は「ファイルに書かない / AWS 標準 env だけ使う」ので、
平文が残る問題と独自 env 依存を解消します。
インターフェースは以前と同じなので置き換えコストは小さく、複数ターミナルで
profile を使い分ける従来運用も同じ呼び出しで成立します。







