[小ネタ] credentials に書かない AWS IAM ロール切り替えシェル

[小ネタ] credentials に書かない AWS IAM ロール切り替えシェル

2026.05.02

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/configsource_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
  1. AssumeRole で得た一時 credential を ~/.aws/credentialsmyproj_temp という profile セクションとして書き込む (aws_session_token 含む短命 credential が平文で保存される)
  2. shell に「いまどの profile を使うか」を示す独自 env (例: MY_AWS_PROFILE=myproj_temp / MY_AWS_REGION=ap-northeast-1) を export
  3. shell 配下で起動したツールがこの独自 env を AWS_PROFILE として参照する設定になっていれば、自動で myproj_temp の credential が使われる

短い有効期限の credential を _temp profile としてファイルに書き、
env で profile 名をブロードキャストする設計です。

実用としては問題なく動きますが、使っていくと以下のような問題があります。

  • ~/.aws/credentials_temp profile が増える (cleanup面倒)
  • 一時 credential が 平文で残る
  • 独自 env を読まないツール (素の aws CLI / 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/configcredential_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/configcredential_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/configsource_profile / role_arn / mfa_serial
native に解釈できるので、スクリプトを介さず aws-vault exec myproj -- aws ... だけで
AssumeRole + MFA まで完結させることも可能です。
本記事の「AWS 標準 env を shell に export する」ゴールとは別路線になりますが、
これが一番シンプルな方法かもしれません。

Summary

以前の方式は「ファイルに書く / 独自 env を使う」設計で、
人間が一人で複数ターミナルから使う運用には合っていました。

新しい方式は「ファイルに書かない / AWS 標準 env だけ使う」ので、
平文が残る問題と独自 env 依存を解消します。
インターフェースは以前と同じなので置き換えコストは小さく、複数ターミナルで
profile を使い分ける従来運用も同じ呼び出しで成立します。

References

この記事をシェアする

関連記事