s3の署名付きURLが有効期限より前に見れなくなってしまう
CloudWatchメトリクスのグラフ画像をSlackに定期通知するLambdaを作るにて毎朝通知を行うようにしたが、有効期限を指定しているにも関わらず1日立つと画像が見れなくなってしまう現象に遭遇した。
原因
画像URLにアクセスしてみると、有効期限切れではなくトークンの期限切れというエラーが発生していた。
ExpiredToken
The provided token has expired.
最大7日間なのかーという部分しか確認せずにExpiresIn=604800を指定してみましたが、残念ながら有効期限より前に失効する状況は改善しませんでした。
それもそのはずで、よく読めば利用している認証情報で有効期限の最大値が決まり、その最大値で有効期限が失効するということになります。
AWS LambdaだとIAM role を利用しているので、AssumeRole アクションにてAWS STS から一時トークンを取得し、その一時トークンで認証を行なっていることになります。
そのため、上記ドキュメントに記載のSTS 利用時に合致し指定している7日を待たずに失効する形になっていました。
上記記事の方と同じで、IAMユーザではなくIAMロールで[[s3.getSignedUrl]]を実行していたため、有効期限より前にトークンの期限切れが発生してしまっていた。
対策
専用のIAMユーザを作成して、SSMから認証情報を読み込み署名付きURLを作成するように修正した。
- 専用IAMユーザの作成し、指定のS3バケットのみにアクセスできるポリシーを付与
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::your-bucket-name",
"arn:aws:s3:::your-bucket-name/*"
]
}
]
}
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
Policies:
- SSMParameterReadPolicy:
ParameterName: !Ref SsmIamUserAccessKeyName
- SSMParameterReadPolicy:
ParameterName: !Ref SsmIamUserSecretKeyName
Environment:
Variables:
SSM_IAM_USER_ACCESS_KEY_NAME: !Ref SsmIamUserAccessKeyName
SSM_IAM_USER_SECRET_KEY_NAME: !Ref SsmIamUserSecretKeyName
Parameters:
SsmIamUserAccessKeyName:
Type: String
Default: YourSsmIamKey
SsmIamUserSecretKeyName:
Type: String
Default: YourSsmIamSecret
- LambdaコードにてSSMからIAMユーザ情報を取得し、S3 SDKのインスタンス生成時にIAMユーザを指定するように修正
const aws = require("aws-sdk")
const ssm = new aws.SSM()
const iamUserInfo = async () => {
const accessKeyName = process.env.SSM_IAM_USER_ACCESS_KEY_NAME;
const secretKeyName = process.env.SSM_IAM_USER_SECRET_KEY_NAME;
const param = {
Names: [accessKeyName, secretKeyName],
WithDecryption: true
};
const data = await ssm.getParameters(param).promise();
const config = {};
for (const i of data.Parameters) {
if (i.Name === accessKeyName) {
config.accessKeyId = i.Value;
} else if (i.Name === secretKeyName) {
config.secretAccessKey = i.Value;
}
}
return config;
};
exports.lambdaHandler = async (event, context) => {
const iamUser = await iamUserInfo();
const s3 = new aws.S3({ signatureVersion: "v4", ...iamUser });
try {
const downloadUrl = s3.getSignedUrl("getObject", {
Bucket: bucket,
Key: key,
Expires: 60 * 60 * 24 * 7 // max 7days
});
} catch (err) {
console.log(err);
return err;
}
};
これで有効期限指定ができるようになった。
参考
- S3 バケットの署名付き URL が、指定した有効期限より前に失効する
- S3の署名付き URLを利用する際に注意する3つのポイント #aws #s3 #lambda #python #serverless - uchimanajet7のメモ
- AWS SDK、CLI、Explorer の使用 - Amazon Simple Storage Service
- Class: AWS.S3 — AWS SDK for JavaScript
- node.js - Retrieve AWS ssm parameter in bulk - Stack Overflow
- amazon web services - AWS SAM managed policy for SSM get parameter - Stack Overflow
- Class: AWS.SSM — AWS SDK for JavaScript
- パラメータストアを活用して Lambda で機密情報 (SecureString) を扱う with AWS SAM | DevelopersIO