LGTM Devlog 32: Secrets Management to avoid storing API keys in services

meseta

Yuan Gao

Posted on February 3, 2021

LGTM Devlog 32: Secrets Management to avoid storing API keys in services

It's now time to get LGTM to post issues and comments as one of the "characters". To do this, LGTM's game core loop will use the GitHub API to interact with github, and to do that, it needs to be able to authenticate as a user account, which means generating a token.

Eventually, I may have several tokens being used, one for each character account. These need to be kept secure to avoid accidental exposure, and since this is an open-source project, I can't just leave them in a file (which you shouldn't do anyway).

Fortunately there are solutions to this - Secrets Management services, which are basically like password managers for code. They can be used to programatically store and retrieve secrets, and in some cases might be so automated that a human never has to see the password nor have access to it!

In the production systems I use at work, our infrastructure usually does this - when a database gets set up, a password is automatically randomly generated, and stored in a secrets management system. No human is involved in that process, so the password is never seen by human eyes. In fact it's entirely possible to configure that system to lock humans out!

Reading secrets from GCP Secrets Manager

Google Cloud has a simple secrets manager that suits our purposes. It's accessible via API/SDK and it uses the same IAM and authentication that already protects our other stuff, so it'll take very minimal setup. The Python SDK for it is google-cloud-secret-manager and it's extremely easy to use, something like:

from google.cloud import secretmanager
secret_client = secretmanager.SecretManagerServiceClient()

def fetch_secret(secret_name: str) -> str:
    secret_path = f"projects/{GCP_PROJECT_ID}/secrets/{secret_name}/versions/latest"
    secret = secret_client.access_secret_version(request=dict(name=secret_path))
    return secret.payload.data.decode()
Enter fullscreen mode Exit fullscreen mode

Where GCP_PROJECT_ID is the project's ID, which I'm providing from ENV already. Like most Google SDKs, this will read the service account credential from ENV, which I have set up already for firebase access.

I just need to add an additional IAM permission to the service accounts I'm using: Secret Manager Secret Accessor

Adding IAM access

Writing secrets

While it's common to programatically create these secrets, in my case, I will be creating them by hand, because unless I programatically register accounts on GitHub (something you can't do), I have to grant a script somewhere access to a GitHub account, and if I have to do that, I might as well go generate the token and save the secret manually as well.

To do that, I go into GitHub's personal access tokens page, and generate a new token with the scopes that I want:

Generating a new personal access token

I selected repo:status as I need to read commits; public_repo as these accounts will own their own forks of the repo in order to make PRs from; repo:invite in case in the future I need characters to invite people to their repos; and notifications which may become a key in how to hit GitHub's API less in the future (using the notifications to detect when there are replies from the player rather than polling every Issue for replies every few minutes)

Now I can go over to Secret Manager settings and register a new secret:

Creating a new secret


Now the code, whenever it is running in an environment where a valid GCP credential is stored (and I have this set up already in my local machine, as well as on Github CI, and of course in production, it can fetch these secrets, without me having to maintain a big set of env vars to do it

💖 💪 🙅 🚩
meseta
Yuan Gao

Posted on February 3, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related