Getting ID Token from Google Apps Script to assume AWS role and read/write to S3

gcpaws

You can get the user’s ID token by adding openid to the oauthScopes in the manifest and calling ScriptApp.getIdentityToken(). It enables to call AWS APIs without passing access keys.

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 }
*/

Creating a role that can be assumed with this aud,

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "accounts.google.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "accounts.google.com:aud": "*****.apps.googleusercontent.com"
                }
            }
        }
    ]
}

and calling the sts API, you can obtain the temporary credentials.

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();

I used S3-for-Google-Apps-Script to call S3 API. Since the library that could be loaded with the script ID mentioned in the document didn’t include the sessionToken implementation, so I copied the code from the repository and modified the region as well.

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())

Reference

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