はじめに
RunCommandでインスタンスからS3にファイルをアップロードするスクリプトを使用していたところ、あるインスタンスのみ以下のようなエラーが発生し、S3へのファイルアップロードができませんでした。
upload failed: <filePATH> to s3://<bucketPATH> An error occurred (InvalidAccessKeyId) when calling the PutObject operation:
The AWS Access Key Id you provided does not exist in our records.
正常にアップロードが完了するインスタンスもあり、スクリプト自体の問題ではありませんでした。
結論
原因は認証の呼び出し場所
どうやら、RunCommand実行時の認証の呼び出し場所が違うようです。
エラーが発生しないインスタンス
Run Command 実行時に、EC2にアタッチされた IAM ロールの認証情報(IMDS)
を使用して AWS CLI を実行する。
エラーが発生するインスタンス
Run Command 実行時に、インスタンス上のユーザー(root)の ~/.aws/credentials に保存された認証情報
を使用してAWS CLIを実行する。
スクリプトでは、--profileなどの指定はなく、プレーンにaws s3 cp
を呼び出していました。
aws s3 cp hogehoge.txt s3://hogehoge/hogehoge.txt
~/.aws/credentialsの認証情報にはS3へのPUT権限は付与されていないので、権限がなく実行が失敗していたようです。
OSに起因するものかと思っていたのですが、CentOSと一部のAmazon Linux2でエラーが発生していました。
どこかの設定が原因だとは思うのですが詳しい理由はわからず…
下記の解決策でどちらのOSも解決できました。
解決策
エラーが発生するインスタンスにも 「認証はインスタンスのIAMロールを呼び出して使ってね!」 と明示してあげれば解決できました。
IAMロールの認証を呼び出す流れ
-
curl
でインスタンスに紐づいたIAMロールを取得 - IAMロールに紐づいた認証情報を取得(JSON形式)
- JSONから必要情報を抜き出して各変数に格納する
jqが便利だが使わなくても可能
jq使用
ROLE_NAME=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
CREDS=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME)
export AWS_ACCESS_KEY_ID=$(echo "$CREDS" | jq -r .AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | jq -r .SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo "$CREDS" | jq -r .Token)
1行ずつ分解したもの
- メタデータサービスからインスタンスに付与されたIAMロール名を取得して
ROLE_NAME
に格納
169.254.169.254 は EC2 インスタンス内からのみアクセス可能なIP
ROLE_NAME=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
-
ROLE_NAME
に紐づくJSON形式の認証情報を取得しCREDS
に格納
CREDS=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME)
- jqを使って
CREDS
からAccessKeyIdを抽出してAWS_ACCESS_KEY_ID
に格納
export AWS_ACCESS_KEY_ID=$(echo "$CREDS" | jq -r .AccessKeyId)
- jqを使って
CREDS
からSecretAccessKeyを抽出してAWS_SECRET_ACCESS_KEY
に格納
export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | jq -r .SecretAccessKey)
- jqを使って
CREDS
からToken(セッショントークン)を抽出してAWS_SESSION_TOKEN
に格納
export AWS_SESSION_TOKEN=$(echo "$CREDS" | jq -r .Token)
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN
を環境変数として指定すると、その後しばらくはAWS CLIやSDKがこの認証情報を使って通信をしてくれるので、コマンド自体を書き直す必要はありません。
jq不使用
JQはインスタンスへのインストールが必要です。
「インストールするのはちょっと…」という場合はgrep
とcut
で成形できます。
ROLE_NAME=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
CREDS=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME)
export AWS_ACCESS_KEY_ID=$(echo "$CREDS" | grep AccessKeyId | cut -d'"' -f4)
export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | grep SecretAccessKey | cut -d'"' -f4)
export AWS_SESSION_TOKEN=$(echo "$CREDS" | grep Token | cut -d'"' -f4)
1行ずつ分解したもの
- メタデータサービスからインスタンスに付与されたIAMロール名を取得して
ROLE_NAME
に格納
169.254.169.254 は EC2 インスタンス内からのみアクセス可能なIP
ROLE_NAME=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
-
ROLE_NAME
に紐づくJSON形式の認証情報を取得しCREDS
に格納
CREDS=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME)
-
CREDS
からAccessKeyIdを抽出してAWS_ACCESS_KEY_ID
に格納
export AWS_ACCESS_KEY_ID=$(echo "$CREDS" | grep AccessKeyId | cut -d'"' -f4)
-
CREDS
からSecretAccessKeyを抽出してAWS_SECRET_ACCESS_KEY
に格納
export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | grep SecretAccessKey | cut -d'"' -f4)
-
CREDS
からToken(セッショントークン)を抽出してAWS_SESSION_TOKEN
に格納
export AWS_SESSION_TOKEN=$(echo "$CREDS" | grep Token | cut -d'"' -f4)
最終的なスクリプトはこんな感じ
エラーが発生したスクリプト
schemaVersion: '2.2'
description: "cred test document"
mainSteps:
- action: aws:runShellScript
name: testdocuments
inputs:
runCommand:
- |
# Upload to S3
aws s3 cp hogehoge.txt s3://hogehoge/hogehoge.txt
aws s3 cp mochimochi.txt s3://hogehoge/mochimochi.txt
エラーが解消されたスクリプト
schemaVersion: '2.2'
description: "cred test document"
mainSteps:
- action: aws:runShellScript
name: testdocuments
inputs:
runCommand:
- |
# IAM role credentials (no jq)
ROLE_NAME=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
CREDS=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME)
export AWS_ACCESS_KEY_ID=$(echo "$CREDS" | grep AccessKeyId | cut -d'"' -f4)
export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | grep SecretAccessKey | cut -d'"' -f4)
export AWS_SESSION_TOKEN=$(echo "$CREDS" | grep Token | cut -d'"' -f4)
# Upload to S3
aws s3 cp hogehoge.txt s3://hogehoge/hogehoge.txt
aws s3 cp mochimochi.txt s3://hogehoge/mochimochi.txt
権限が必要な実行部分の前に、インスタンスメタデータ(IMDS)を呼び出して格納しておくことで、認証でエラーが出ることがなくなりました
以下、解決への時系列
なにが原因だかわからず遠回りしたので、どうやってトラブルシューティングをしたかの備忘録です。
1. 最初に確認したこと
エラーが発生するインスタンスで、以下のことを確認しました。
- アップロードができないインスタンスも、正常にアップロードできるインスタンスと同じポリシーを付与している
- SSM Agentのバージョンは3.xx以上であり、フリートマネージャー上は通信が確認できる
- SessionManagerで接続し、
aws configure list
を確認しても、iam-roleのaccess_keyとsecret_keyを使用している
最初に気になったのは3つ目です。
sh-4.2$ aws configure list
Name Value Type Location
---- ----- ---- --------
profile <not set> None None
access_key ****************LWOK iam-role
secret_key ****************n1C3 iam-role
region ap-northeast-1 imds
たしかにIMDSの認証を使っているし、IAM-ROLEのアクセスキーとシークレットアクセスキーです。
同じように、RunCommand実行時にもIAMロールの認証情報が使用されているなら、The AWS Access Key Id you provided does not exist in our records.
は起きないはず。
2. RunCommandで使用されている認証情報を特定する
SessionManagerで接続した場合とRunCommandで実行した場合の認証が違いそうだったので、実際にRunCommand実行時に呼び出される認証情報を確認しました。
テスト用のRunCommandドキュメント
以下のRunCommandドキュメントを新規で作成し、エラーが発生しないインスタンスと、エラーが発生するインスタンスに走らせました。
schemaVersion: '2.2'
description: Run `aws configure list` to check AWS CLI configuration
mainSteps:
- action: aws:runShellScript
name: RunAwsConfigureList
inputs:
runCommand:
- aws configure list
出力結果 :
エラーが発生しないインスタンス
Name Value Type Location
---- ----- ---- --------
profile <not set> None None
access_key ****************WUWI iam-role
secret_key ****************OWU1 iam-role
region ap-northeast-1 imds
出力結果 :
エラーが発生するインスタンス
Name Value Type Location
---- ----- ---- --------
profile <not set> None None
access_key ****************U3QW shared-credentials-file
secret_key ****************Ifje shared-credentials-file
region ap-northeast-1 config-file ~/.aws/config
SessionManagerで接続して確認したときと違って、shared-credentials-fileを使っている!
3. 対処法を生成AIと相談
ここまでで、「SessionManagerで接続するときはIAMロールの認証情報を使うのに、RunCommandではインスタンス上のユーザーの認証情報を使ってしまう」という事情があることがわかりました。
生成AIには、わかっている状況はこまかに伝えたうえで「あなたならなにが原因だと思いますか?」と相談しました
IMDSを呼び出す解決策を提案してくれましたが、なにが原因だったのかまではわからず…。きちんと調べて原因を究明できるようになりたいです。
おわりに
認証コンテキストってなに?
今回のように、同じスクリプトでも実行方法によって使われる認証情報が変わることがままあるようです。
この「どの認証情報
が、どの実行環境
で、どう使われるか
」という背景を指すのが、認証コンテキスト というらしいです。
AWSは認証関係が難しいなと感じることが多いので、このあたりは今後も壁にぶつかりそうな気がします。
感想
エラー文章 The AWS Access Key Id you provided does not exist in our records.
で調べても、RunCommandに関するものは見つからずに困っていましたが、解決できてよかった。
最終的にはAIに頼ってしまったけれど、どこに原因があるかの切り分けは楽しくできました。(解決できたいまだから思うことではある。)