マニフェストの oauthScopes に openid を追加し ScriptApp.getIdentityToken() を呼ぶとユーザーの ID トークンが返る。これを用いることでアクセスキーを渡すことなく AWS の API を呼び出すことができる。
OpenID ConnectのIDトークンの内容と検証 - sambaiz-net
const oidcToken = ScriptApp.getIdentityToken();
const payload = JSON.parse(
Utilities.newBlob(
Utilities.base64DecodeWebSafe(oidcToken.split('.')[1])
).getDataAsString()
);
console.log(payload);
/*
{ iss: 'https://accounts.google.com',
sub: '*****',
aud: '*****.apps.googleusercontent.com',
at_hash: '*****',
iat: 1738927412,
exp: 1738931012 }
*/
この aud で Assume できる Role を作成し、
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "accounts.google.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"accounts.google.com:aud": "*****.apps.googleusercontent.com"
}
}
}
]
}
sts の API を呼ぶと一時的な認証情報を取得できる。
const roleArn = "arn:aws:iam::*****"
const res = UrlFetchApp.fetch("https://sts.amazonaws.com/", {
'method': 'post',
"payload": `Action=AssumeRoleWithWebIdentity&RoleSessionName=GASTest&RoleArn=${roleArn}&WebIdentityToken=${token}&Version=2011-06-15`
});
const xml = res.getContentText();
const elem = XmlService.parse(xml).getRootElement();
const namespace = elem.getNamespace()
const credentials = elem.getChild('AssumeRoleWithWebIdentityResult', namespace).getChild('Credentials', namespace)
const accessKeyId = credentials.getChild('AccessKeyId', namespace).getText();
const secretAccessKey = credentials.getChild('SecretAccessKey', namespace).getText();
const sessionToken = credentials.getChild('SessionToken', namespace).getText();
S3 API の呼び出しには S3-for-Google-Apps-Script を使った。ドキュメントに記載のあるスクリプトIDで読み込めるライブラリには現状 sessionToken の実装が含まれていなかったのでリポジトリのコードをコピーし region も書き換えた。
const s3 = getInstance(accessKeyId, secretAccessKey);
const blob = UrlFetchApp.fetch("http://www.google.com").getBlob();
s3.putObject("(bucket_name)", "(key)", blob, {sessionToken: sessionToken});
const fromS3 = s3.getObject("(bucket_name)", "(key)", {sessionToken: sessionToken});
console.log(fromS3.getDataAsString())