The One and the Many

Best practices for Google service accounts

When you first start using Google APIs, figuring out how to authenticate can be daunting. There's lots of documentation and many concepts to understand. The best way to do things often comes from trial and error.

Having spent some time with the APIs, I've some thoughts about how to work with service accounts.

Service accounts are a way for a program to identify itself and authenticate with a Google API. Service accounts are appropriate if your program's task isn't tied to a user.

Summary:

  1. Use service accounts with keys managed by Google
  2. Use application default credentials for credential discovery
  3. Use service accounts if your task is appropriate for them

Google managed private keys

Each service account has a private key that it uses to prove its identity.

You can provide the key in a file. However if you're running your program on GCP, you can let Google manage it for you. This means your program won't have access to the key itself. This is good for security since there's less opportunity for the key to be leaked. It also simplifies things: You don't have to deploy or rotate the key, and you can benefit from the Google libraries and tools finding the key automatically.

If you're running your code on GCP, you can likely take advantage of this. GCE VMs have a service account associated with them, so you can use that when running your programs on VMs. Likewise things like Cloud Functions and App Engine have associated service accounts.

For more on using service accounts associated with VMs, see here.

Side benefits of managed keys

An interesting result of using the service account tied to a resource such as a VM is you begin to think about the VM's level of access as a whole. Your thinking goes from "what does the service account for this program have access to" to "what does this VM have access to".

If everything you're running on your VM is using the VM's service account, then it's clear what your VM has access to by reviewing the account's access. If you were managing the keys yourself, the situation is more opaque since you have to know what keys get deployed to the VM.

As well, if you see you're granting a huge amount of access to one VM, that's an indicator that the VM is doing too much and you might benefit from splitting its work up.

Finally, it stops what might otherwise be a confusing proliferation of service accounts. Following an idea of least privilege, you might give a program its own service account. Perhaps you later create another program and give it another service account. Soon enough you are managing and deploying several service account keys, which gets confusing. If you instead look at it from a VM perspective, it can be simpler.

Managed private keys and tools

The story is similar for tools such as gsutil. gsutil will use the VM's service account if run on a GCE VM. Alternatively you can authenticate and manage its configuration yourself. That is more painful, especially if you're using it for tasks suited for a service account.

Application default credentials

Google libraries and tools find the managed key automatically, so setting up an API client is easy. Here's how to create a GCS client in Go that uses the VM's service account:

client, err := storage.NewClient(context.Background())

If you want to use a key from a file, you can pass it in like so:

client, err := storage.NewClient(
  context.Background(),
  option.WithCredentialsFile("/path/to/service-account-key.json"),
)

Maybe your program runs in both environments, so you need to make it conditional:

if keyFile != "" {
  client, err := storage.NewClient(
    context.Background(),
    option.WithCredentialsFile(keyFile),
  )
} else {
  client, err := storage.NewClient(context.Background())
}

However it's better to lean on application default credentials. If not specified, Google libraries have defined steps they take to find credentials.

In situations where you need the key from a file, you can set the environment variable GOOGLE_APPLICATION_CREDENTIALS to its path:

GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json ./my-program

This way your code can look like this and work with both types of keys:

client, err := storage.NewClient(context.Background())

Your code doesn't have to pass in which file contains the key, and it will work with both the Google managed service account as well as a key from the file without code changes. If you're running somewhere with a managed service account, simply omit the environment variable. Your code can be blissfully ignorant!

Use a service account if your task is appropriate for it

In some situations you might have the option of using 3 legged OAuth, where your program requests authorization to perform actions on behalf of a user, instead of a service account. This is what you should do if your program needs to act as a user, but if that isn't required then you should avoid it since it is more complicated (the program must request access and manage tokens).

An example of this is when you want to interact with G Suite resources, such as Drive or Sheets. Consider the case of a Sheet that you want your program to manage, yet it doesn't need to be accessed by the owner.

If you weren't using a service account, this is how that would look: Log in as an account for your program to run as, run your program and have it request access to the Sheet, and then authorize it as the account. To be cautious, you might have a separate G Suite account for the program.

If you were using a service account on the other hand, you could use the Sheet's Share screen to invite the service account (via its email address) to have edit access. And that's it.

The situation becomes more complex if your service account needs to create new files because a service account can't transfer ownership (and you don't want your service account to own files since isn't intended for this). The way to solve this is to share a folder in a Team Drive with the service account, and have the service account create files there (or move them in). Files in Team Drives are owned by the team.

The end

Following these tips makes life easier and improves security. You'll have less complex code, no keys to manage, and authorization will be simpler. There are plenty of ways to deal with these problems, but embracing the platform and its tools is the way to go.

Comments