AssumeRoleの一時クレデンシャルが有効期限切れなら再認証させるシェルスクリプトを作ってみた(zsh)

2022.01.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部 IoT事業部の若槻です。

作業環境などでAWSコマンドの認証をAssumeRoleで行いたい時には、aws sts assume-roleで取得した一時クレデンシャルの情報を環境変数に格納して使用することが多いのではないでしょうか。

しかしこの取得した一時クレデンシャルが有効期限内であるかどうかを知るためには、実際にAWS CLIコマンドを試してエラーなく実行できるかを確認する必要があり、ひと手間掛かるのが不便です。

今回は、AssumeRoleの一時クレデンシャルが期限切れなら再認証させるシェルスクリプトを作ってみました。

前提

使用するシェルはzshです。Macだとデフォルトのシェルですね。

$ echo $SHELL
/bin/zsh
$ zsh --version
zsh 5.8 (x86_64-apple-darwin20.0)

またAssumeRoleに使用するIAMユーザーにはMFAが設定されている前提とします。

シェルスクリプト

次のようなシェルスクリプトを作ってみました。

update-aws-credential.sh

#!/bin/zsh
set -e

## 一時クレデンシャルのセッション有効期限を指定
SESSION_DURATION_SECONDS=$((60*60*12))

## 現在より SESSION_DURATION_SECONDS 前の日時(Unixtime)を取得
session_duration_unixtime=$(date -v -${SESSION_DURATION_SECONDS}S +%s)

## 最終認証日時(Unixtime)を環境変数から取得
last_assumed_unixtime=${LAST_ASSUMED_UNIXTIME:-0}

## セッション有効期限切れなら再認証する
if [[ ${last_assumed_unixtime} -lt ${session_duration_unixtime} ]]; then
  echo "再認証します。MFAコードを入力してください。"

  ## MFAコードをキーボード入力で受付け
  read mfa_code

  ## 認証
  AWS_STS_CREDENTIALS=`aws sts assume-role \
    --profile default \
    --role-arn $(aws configure get ${target_profile}.role_arn) \
    --role-session-name ${target_profile}-session \
    --serial-number $(aws configure get ${target_profile}.mfa_serial) \
    --duration-seconds ${SESSION_DURATION_SECONDS} \
    --token-code ${mfa_code}`
  export AWS_ACCESS_KEY_ID=`echo "${AWS_STS_CREDENTIALS}" | jq -r '.Credentials.AccessKeyId'`
  export AWS_SECRET_ACCESS_KEY=`echo "${AWS_STS_CREDENTIALS}" | jq -r '.Credentials.SecretAccessKey'`
  export AWS_SESSION_TOKEN=`echo "${AWS_STS_CREDENTIALS}" | jq -r '.Credentials.SessionToken'`

  ## 最終認証日時(Unixtime)を環境変数に設定
  export LAST_ASSUMED_UNIXTIME=$(date +%s)

else
  echo "セッションが有効期限内のため、再認証不要です。"

fi

このスクリプト内では次のようなことをしています。

  1. 一時クレデンシャルの任意のセッション有効期限を指定
  2. 現在より有効期限の期間以前の日時(Unixtime)を取得
  3. 最終認証日時(Unixtime)を環境変数から取得(未定義なら0となる)
  4. セッション有効期限切れ(2の値より3の値が大きい)であるか判定(以下、有効期限切れの場合)
  5. MFAコードをキーボード入力で受付け
  6. AssumeRoleによる認証実施
  7. 最終認証日時(Unixtime)を環境変数に設定

つまりスクリプト事項で認証を実施したらLAST_ASSUMED_UNIXTIMEに現在日時を設定し、次回スクリプト実行時にLAST_ASSUMED_UNIXTIMEの値を取得して再認証が必要か判断し、必要なら行うという内容です。

使ってみる

(初回のみ)スクリプトファイルの権限を設定して実行可能にします。

$ chmod 744 update-aws-credential.sh

使用したいプロファイル名(~/.aws/credentials内で指定している名前)を環境変数target_profileに指定しておきます。

$ export target_profile=some-profile-name

未認証(LAST_ASSUMED_UNIXTIME未定義)の場合

まず、スクリプトによる認証を一度も行っていない状態の場合です。この時点ではLAST_ASSUMED_UNIXTIMEが未定義となっています。

$ echo $LAST_ASSUMED_UNIXTIME

そしてスクリプトを実行するのですが、この時sourceを必ず使用します。これによりシェルスクリプト内で環境変数に設定した一時クレデンシャルの情報が呼び出し元のシェルにも反映され使えるようになります。

実行するとMFAコードの入力を求められるので指定します。

$ source ./update-aws-credential.sh
再認証します。MFAコードを入力してください。

認証が成功しました!

認証が成功し、一時クレデンシャルが設定されたのでAssumeRole後のセッションでAWS CLIが実行可能となっています。

$ aws sts get-caller-identity
{
    "UserId": "AROAUL6WVPY2E4JAUAR6D:some-profile-name-session",
    "Account": "XXXXXXXXXXXX",
    "Arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/cm-wakatsuki.ryuta/some-profile-name-session"
}

認証済み(LAST_ASSUMED_UNIXTIMEが有効期限内)の場合

次に、スクリプトによる認証がセッション有効期限内に実施されている場合です。この時点ではLAST_ASSUMED_UNIXTIMEに有効期限内の時間が設定されています。

$ echo $LAST_ASSUMED_UNIXTIME
1643126192

再認証不要のため認証は求められませんでした!

$ source ./update-aws-credential.sh
セッションが有効期限内のため、再認証不要です。

認証期限切れ(LAST_ASSUMED_UNIXTIMEが有効期限以前)の場合

次に、スクリプトによる認証がセッション有効期限以前に実施されている場合です。

明らかに有効期間外のUnixtimeをLAST_ASSUMED_UNIXTIMEに設定してみます。

$ export LAST_ASSUMED_UNIXTIME=12345

MFAコードが求められ、指定すると認証が行えました!

$  source ./update-aws-credential.sh
再認証します。MFAコードを入力してください。
824093

ハマった箇所

zshとbashの文法の違い

zshとbashでは文法が微妙に違うのですが、ネットの情報にはbashが多いため、zshの文法を確認するのに苦労しました。

例えば、if文のConditionの指定は、bashが[]なのに対し、zshは[[]]となります。

## bash
if [a -lt b]; then
  echo "bashの場合"
fi

## zsh
if [[a -lt b]]; then
  echo "zshの場合"
fi

他には、dateコマンドで過去の日時を取得する場合、指定の仕方が違ってきます。

## bash
date -d '1 days' +%Y%m%d

## zsh
date -v '+1d' +%Y%m%d

環境変数未定義を許容

シェルスクリプト冒頭でset -euとすると、環境変数が未定義の場合に実行が停止してしまいます。よって-uオプションを使わずset -eと記述する必要がありました。

おわりに

AssumeRoleの一時クレデンシャルが期限切れなら再認証させるシェルスクリプトを作ってみました。

このシェルスクリプトを単独で使うもよし、他のAWSコマンドなどを実行するスクリプトに組み込むも良し、です。

私は下記のスクリプトに組み込んでみようと思います。

参考

以上