SuperFetch is a proxy for UrlFetchApp with additional features – see SuperFetch – a proxy enhancement to Apps Script UrlFetch for how it works and what it does. In this article I’ll cover how to use the SuperFetch api plugin to access IAM Service Account Credentials to authenticate with Cloud Run.

Page Content hide
1 Motivation

Motivation

In a later article I’ll show how to use the SuperFetch gtb plugin for Gotenberg (converts pretty much anything to PDF files) running on Google Cloud Run

First though, let’s look at in general, how Cloud Run authentication works.

Background

This was quite tricky to figure out, but thanks to fellow GDEs Spencer Easton and Ivan Kutil and this post by guillaume blaquiere I was able to add the technique to my SuperFetch library

Cloud Run and Cloud Function Authentication with IAM

Both of these need a JWT Id token – not the usual access token that we can easily get from ScriptApp.getOAuthToken() to access most APIs, but the kind of token returned by ScriptApp.getIdentityToken() to identify the current Apps Script user.

This will work for Cloud Functions, but Cloud Run is looking for the identity token of a Service Account. So we need to find a way of generating one of those.

Service Account key files

One way is to use a service account key file (there are plenty of examples of using Service Account with my cGoa library on this site, for example Using a service account) and also creating JWT tokens from Apps Script (for example JWT tokens)

However all of that is a lot of work. Guillaume’s post pointed the way to using Service Accounts without the need for a key file.

Generating an idToken on behald of a Service account with IAM

It turns out that we can use the IAM service account credential API to do exactly what’s needed.

Preparation

In preparation we need to

  • Create or reuse a cloud project and assign it to your Apps Script project in place of the default one that Apps Script creates (You can’t change the settings on that Apps Script managed cloud project to do what we need)
  • Give the service account  an IAM role that authorizes it to do the work (in this case “Cloud Run Invoker”)
  • Make the user account (the email address running the Apps Script)  a principal for the service account
  • Give the user account the IAM role (Service Account Token Creator) that authorizes it to generate tokens on behalf of someone else
  • Enable the IAM API
  • Give your Apps Script project “https://www.googleapis.com/auth/cloud-platform” and “https://www.googleapis.com/auth/script.external_request” scopes
When that’s all done, the IAM section of the cloud console should have an entry like something like this.
 
service account
The email address of my service account is
cloud-run-invoker@MYPROJECT.iam.gserviceaccount.com
 

Add the user as a principal

Edit the service account and assign the Service Account creator role
service account creator roe
 

Now you should have this on the permissions screen for your cloud-run-invoker service account

assigned

SuperFetch  and iam plugin

You’ll need the SuperFetch library – details at the end of the article.

The preparation was the hard part. Now we can use the SuperFetch iam plugin to get a drop in replacement for the regular tokenService.

    // get an id token for this end point using this service account
const gtbEndpoint = 'https://xxxxxx.run.app'

// your service account
const gtbServiceAccountEmail = 'cloud-run-invoker@xxxxxxxx.iam.gserviceaccount.com'

// imports
const { Plugins, SuperFetch } = bmSuperFetch

// create a new SuperFetch instance
const superFetch = new SuperFetch({
fetcherApp: UrlFetchApp,
tokenService: ScriptApp.getOAuthToken
})

// create an iam instance
const iam = new Plugins.Iam({ superFetch })


// now we can set up how to get an id token
const idTokens = iam.tokens({
serviceAccountEmail: gtbServiceAccountEmail
}).id({
audience: gtbEndpoint
})


// this returns a function that can generate a token
console.log(idTokens.service())

setting up idtoken framework

Using the idToken service

Later on in this article,  I’ll breifly introduce the gotenberg SuperFetch plugin, but for now you can just access your cloud run instance as normal like this.


headers.authorization = 'Bearer ' idTokens.service()
using idToken

Unit testing for Iam plugin

As usual I’m using Simple but powerful Apps Script Unit Test library for testing. Details at the end of the article. Here’s a complete set of tests for the Iam.tokens plugin trying out all the methods



const testIam = () => {

// control which tests to skip
const skipTest = {
tokens: false,
caching: false
}
// get a testing instabce
const unit = new bmUnitTester.Unit({ showErrorsOnly: true })

// get an id token for this end point using this service account
const gtbEndpoint = SETTINGS.gtb.endPoint
const gtbServiceAccountEmail = SETTINGS.gtb.serviceAccountEmail

// import required modules
const { Plugins, SuperFetch } = bmSuperFetch

// test all methods
unit.section(() => {
// create a new SuperFetch instance - we don't need caching
const superFetch = new SuperFetch({
fetcherApp: UrlFetchApp,
tokenService: ScriptApp.getOAuthToken
})
// create an iam instance
const iam = new Plugins.Iam({ superFetch })

// produce id tokens
const idTokens = iam.tokens({
serviceAccountEmail: gtbServiceAccountEmail
}).id({
audience: gtbEndpoint
})

unit.is('object', typeof idTokens, { description: 'idTokens namespace is an object' })
unit.is('function', typeof idTokens.service, { description: 'idTokens.service is a function' })
unit.is('string', typeof idTokens.token, { description: 'idTokens.token is a string' })
unit.is(null, idTokens.fullService().throw().error, {
description: 'full service returns a packResponse'
})

}, {
description: 'iam tokens',
skip: skipTest.tokens
})

// caching should never happen no matter the cache settings
unit.section(() => {

// create a new SuperFetch instance
const superFetch = new SuperFetch({
fetcherApp: UrlFetchApp,
tokenService: ScriptApp.getOAuthToken,
cacheService: CacheService.getUserCache()
})
// create an iam instance
const iam = new Plugins.Iam({ superFetch, noCache: false })


// now we can set up how to get an id token
const idTokens = iam.tokens({
serviceAccountEmail: gtbServiceAccountEmail
}).id({
audience: gtbEndpoint
})

unit.is('object', typeof idTokens.fullService().throw().data, {
description: 'full service returns an object'
})
unit.is('object', typeof idTokens, { description: 'idTokens namespace is an object' })
unit.is('function', typeof idTokens.service, { description: 'idTokens.service is a function' })
unit.is('string', typeof idTokens.token, { description: 'idTokens.token is a string' })
unit.not(true, idTokens.fullService().throw().cached, {
description: 'token still didnt come from cache as its a POST'
})

}, {
description: 'iam credentials with caching is not possible',
skip: skipTest.caching
})
}
iam.tokens test

SuperFetch idTokenService plugin

There’s a handy plugin in the SuperFetch library to simplify all that. Here’s how to use it. Just supply your serviceAccount and cloud run endpoint and you’ll get back a function that can generate an id token.

  const { Plugins, SuperFetch } = bmSuperFetch
const { idTokenService } = Plugins


// use this basic setup so we can get an idTokenservice
const superFetch = new SuperFetch({
fetcherApp: UrlFetchApp,
tokenService: ScriptApp.getOAuthToken
})

// use this for a special token service
const gtbTokenService = idTokenService({
superFetch,
serviceAccountEmail: gtbServiceAccountEmail,
audience: gtbEndpoint
})
SuperFetch.Plugins.idTokenService

You can use this token to authenticate to any cloud run service that requires authentication. My service looks like this.

cloud run service

Gtb, Cnv and Drv SuperFetch plugins

I’ll cover these plugins in detail in separate articles, but SuperFetch also has plugins for the Drive REST Api, a conversion Api for PDF’s and the Gotenberg API running on cloud run. Here’s how to apply everything I’ve covered in this article to getting pretty much any kind of Office/image type file from drive, converting it to a PDF and writing the converted file back to Drive

// get an id token for this end point using this service account
const gtbEndpoint = SETTINGS.gtb.endPoint
const gtbServiceAccountEmail = SETTINGS.gtb.serviceAccountEmail

// import required modules
const { Plugins, SuperFetch } = bmSuperFetch
const { idTokenService, Drv, Gtb, Cnv } = Plugins


// use this basic setup so we can get an idTokenservice
const superFetch = new SuperFetch({
fetcherApp: UrlFetchApp,
tokenService: ScriptApp.getOAuthToken
})

// use this for a special token service
const gtbTokenService = idTokenService({
superFetch,
serviceAccountEmail: gtbServiceAccountEmail,
audience: gtbEndpoint
})

// we'll need a different superFetch to authenticate to cloud run
const gtbSuperFetch = new SuperFetch({
fetcherApp: UrlFetchApp,
tokenService: gtbTokenService,
})


// get instances of the plugins we'll need
const drv = new Drv({ superFetch })
const gtb = new Gtb({
superFetch: gtbSuperFetch,
endPoint: gtbEndpoint
})
const cnv = new Cnv({ gtb })

// get an image to convert
const blob = drv.files.path({ path: 'filmid/mixedartwork/Chrysler.jpeg' }).download().throw().blob

// convert it to a pdf
const pdf = cnv.exec({ blob, convertTo: 'pdf' }).throw().blob

// write it back to drive
drv.files.path({path: 'converted/Chrysler.pdf', blob: pdf}).upload().throw()


full example

Testing Cnv and Gtb plugins

And here’s the tests for the Cnv and gtb plugins

const testCnv = () => {

const skipTest = {
gtb: false
}
// get a testing instabce
const unit = new bmUnitTester.Unit({ showErrorsOnly: true })

// get an id token for this end point using this service account
const gtbEndpoint = SETTINGS.gtb.endPoint
const gtbServiceAccountEmail = SETTINGS.gtb.serviceAccountEmail

// import required modules
const { Plugins, SuperFetch } = bmSuperFetch
const { idTokenService, Drv, Gtb, Cnv } = Plugins


// use this basic setup so we can get an idTokenservice
const superFetch = new SuperFetch({
fetcherApp: UrlFetchApp,
tokenService: ScriptApp.getOAuthToken
})

// use this for a special token service
const gtbTokenService = idTokenService({
superFetch,
serviceAccountEmail: gtbServiceAccountEmail,
audience: gtbEndpoint
})

// we'll need a different superFetch to authenticate to cloud run
const gtbSuperFetch = new SuperFetch({
fetcherApp: UrlFetchApp,
tokenService: gtbTokenService,
})


unit.section(() => {
const drv = new Drv({
superFetch
})
const gtb = new Gtb({
superFetch: gtbSuperFetch,
endPoint: gtbEndpoint
})
const cnv = new Cnv({
gtb
})
unit.is(`up`, gtb.health().throw().data.status, {
description: 'gtb is running'
})
// get an image to convert
const img = unit.not(
null,
drv.files.path({ path: 'filmid/mixedartwork/Chrysler.jpeg' }).download().throw().blob, {
description: 'get an image to convert'
})
// convert it to a pdf
const pdf = unit.not(
null,
cnv.exec({ blob: img.actual, convertTo: 'pdf' }).throw().blob, {
description: 'convert using gtb'
})
unit.is('application/pdf',
drv.files.path({path: 'converted/Chrysler.pdf'}).upload({blob: pdf.actual}).throw().data.mimeType, {
description: 'uploaded the converted file and mimeType is good'
})
}, {
skip: skipTest.gtb,
description: 'Check gtb is all good'
})

}
testing cnv and gtb

Links

bmSuperFetch: 1B2scq2fYEcfoGyt9aXxUdoUPuLy-qbUC2_8lboUEdnNlzpGGWldoVYg2

IDE

GitHub

bmUnitTester: 1zOlHMOpO89vqLPe5XpC-wzA9r5yaBkWt_qFjKqFNsIZtNJ-iUjBYDt-x

IDE

GitHub

file conversion

Convert any file with Apps Script

The Drive API offers a whole range of conversions between mimeTypes, but it's a little fiddly to figure out exactly ...
Superfetch plugin

Caching, property stores and pre-caching

I've written many times about various Apps Script caching techniques such as how to deal with size limits and use ...
document AI add-on

State management across CardService, HtmlService and Server side Add-ons

Motivation I've been working on a CardService Add-on lately which also uses HtmlService, and also runs quite a few things ...
Secret Manager

SuperFetch Plugin: Cloud Manager Secrets and Apps Script

Smg is a SuperFetch plugin to access the Google Cloud Secrets API. SuperFetch is a proxy for UrlFetchApp with additional ...
Superfetch plugin

SuperFetch caching: How does it work?

SuperFetch is a proxy for UrlFetchApp with additional features such as built-in caching – see SuperFetch – a proxy enhancement ...
superfetch tank drive

SuperFetch plugins: Tank events and appending

Tank and Drv are SuperFetch plugins to emulate streaming and use the Drive REST API with Apps Script. SuperFetch is ...
superfetch tank drive

SuperFetch Plugins: Apps Script streaming with Tank and Drive

Tank and Drv are SuperFetch plugins to emulate streaming and use the Drive REST API with Apps Script. SuperFetch is ...
superfetch tank apps script streaming

SuperFetch Tank Plugin: Streaming for Apps Script

Tank is a SuperFetch plugin to emulate streaming with Apps Script. SuperFetch is a proxy for UrlFetchApp with additional features ...
superfetch drive plugin logo

SuperFetch plugin – Google Drive client for Apps Script – Part 1

Drv is a SuperFetch plugin to access the Google Drive API. SuperFetch is a proxy for UrlFetchApp with additional features ...
Superfetch plugin twitter

SuperFetch – Twitter plugin for Apps Script – Get Follows, Mutes and blocks

Twt is a SuperFetch plugin to easily access to the Twitter v2 API. SuperFetch is a proxy for UrlFetchApp with ...
Superfetch plugin twitter

SuperFetch plugin – Twitter client for Apps Script – Counts

Twt is a SuperFetch plugin to easily access to the Twitter v2 API. SuperFetch is a proxy for UrlFetchApp with ...
goa twitter oauth2 apps script

OAuth2 and Twitter API – App only flow for Apps Script

I covered how to handle the somewhat more complex OAUTH2 authorization flow for the Twitter v2 API (OAuth 2.0 Authorization ...
Superfetch plugin twitter

SuperFetch plugin – Twitter client for Apps Script – Search and Get

Twt is a SuperFetch plugin to easily access to the Twitter v2 API. SuperFetch is a proxy for UrlFetchApp with ...
Goa Oauth2 for Apps Script

Apps Script Oauth2 library Goa: tips, tricks and hacks

Motivation Goa is a library to support OAuth2 for Apps Script connecting to a variety of services, using a variety ...
goa twitter oauth2 apps script

Apps Script Oauth2 – a Goa Library refresher

It's been a few years since I first created the Goa library. Initially it was mainly to provide OAuth2 authorization ...
SuperFetch

SuperFetch plugin – Firebase client for Apps Script

Frb is a SuperFetch plugin to easily access a Firebase Real time database. SuperFetch is a proxy for UrlFetchApp with ...
SuperFetch

SuperFetch plugin – iam – how to authenticate to Cloud Run from Apps Script

SuperFetch is a proxy for UrlFetchApp with additional features - see SuperFetch - a proxy enhancement to Apps Script UrlFetch ...
SuperFetch

SuperFetch – a proxy enhancement to Apps Script UrlFetch

I've written a few articles about JavaScript proxying on here, and I'm a big fan. I also use a lot ...
SuperFetch

Apps script caching with compression and enhanced size limitations

Motivation Caching is a great way to improve performance, avoid rate limit problems and even save money if you are ...

Simple but powerful Apps Script Unit Test library

Why unit testing? There are many test packages for Node (my favorite is ava) and there are also a few ...