Authenticating to GCP with a Heroku app: a cautionary tale

2022-01-15 00:00:00 +0000 UTC

The libraries that wrap Google Cloud service APIs expect a specific authentication scheme. Your environment should include GOOGLE_APPLICATION_CREDENTIALS="KEY PATH" where KEY_PATH is the path to a JSON file generated by the Google Cloud platform.

This is just fine on a standard VM or something like an Amazon EC2 instance or Digital Ocean droplet–you have persistant disk. However my application runs on Heroku–dynos are automatically restarted frequently and rebuilt from source. And I don’t want to include my credentials in my git repo!

Based on a Stack Overflow answer I decided to set an additional Heroku config var GOOGLE_CREDENTIALS with the contents of my JSON key file. Since my app uses the Heroku docker build service, I would then write a creds-to-file.sh script and edit my heroku.yml file to invoke that script during the release phase. Like this:

# creds-to-file.sh
echo "$GOOGLE_CREDENTIAL" > /app/gcp-credentials.json

and

# heroku.yml
#...
release:
	web: ./creds-to-file.sh

and

# ...
COPY creds-to-file.sh /creds-to-file.sh
# ...

Simple enough! But it didn’t work. “Duh!” I thought, “My web image is based on the distroless container image, so it does not have bash!” Dutifully I swapped out the base image for the deploy stage to something with more meat. Again: git push heroku main. And again the app failed to release.

Ok–there must be something I don’t quite get about the interaction between the Docker build, the heroku.yml build and release specification, or something like that. So I undo the changes to my Dockerfile, heroku.yml, and git rm my creds-to-file.sh script. I’ll just write this into my Go code, where I know it will be executed and I know what context it will be executed in:

// main.go
//...
_, prs = os.LookupEnv("HEROKU")
if prs {
	// we need to get our creds from the environment and write them to the disk so it works
	creds := os.Getenv("GOOGLE_CREDENTIALS")
	f, err := os.Create("/app/gcp-credentials.json")
	if err != nil {
		panic(err)
	}
	_, err = f.Write([]byte(creds))
	if err != nil {
		panic(err)
	}
	f.Close()
}
//...

Another git push heroku main later and the app deploys! But I get instead a Heroku error screen instead of my app. Here we go: heroku logs --tail and everything looks right. But after re-reading the error message a few times I notice something: file gcp_credentials.json does not exist. Back to the Heroku config vars and…GOOGLE_APPLICATION_CREDENTIALS=/app/gcp_credentials.json. You’ll see in my shell script and Go code above a different file name: gcp-credentials.json

A long story short: authenticating to Google Cloud services on Heroku is easy. You just need to find a way to write the value of a Heroku config var to disk when the dyno is started and you’re off to the races. Just make sure you look very closely at your filenames!

(Additionally, you can provide credentials in-memory: details here)

Tags: heroku google cloud devops