Google Apps Script で IDトークンを取得し AWS の Role を Assume して S3 を読み書きする

gcpaws

マニフェストの 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())

参考

Google Apps ScriptのOIDCトークンを使ったAWSアクセス | 豆蔵デベロッパーサイト