Manage your Application Secrets when deploying an application to Google Application Engine.

Secrets management using Google KMS

Google Cloud Key Management Service (KMS) is a cloud tool that let’s you manage cryptographic keys. You can use KMS to protect secrets and sensitive data via encryption, and provide decrypted values through different build ecosystems.

Keyrings and Keys

Gcloud KMS consists of two main concepts: Keyrings and Keys.

Keyring: grouping of keys for organizational purposes. A key ring belongs to a GCP Project and resides in a specific location.

Key: named object representing a cryptographic key used for a specific purpose. A key is used to protect some corpus of data. You could encrypt a collection of files with the same key, and people with decrypt permissions on that key would be able to decrypt those files.

Keyrings and Keys

Let’s open our terminal. Before creating a new one, let’s check the key rings available for our project:

$ gcloud kms keyrings list --location=global

Now, let’s create a key ring called cloudbuilder. We’re creating this key ring because we’d like for our application to be configured and run in a CI/CD build environment and want to share the keys with that environment. We’ll use the following command:

$ gcloud kms keyrings create cloudbuilder --location global

We can add a key to the key ring that we just created:

$ gcloud kms keys create service-account --location=global --keyring=cloudbuilder --purpose=encryption

Let’s verify that our keyring has the key that we just created:

$ gcloud kms keys list --location=global --keyring=cloudbuilder

Keys List

Encrypting Secrets

First, we’ll create a password and put it inside a text file:

$ echo "MySecret" > my_secret.txt

Then, we’ll use the KMS encryp function. It’s a large command so I’ll explain it in parts.

  1. We begin telling gcloud that we want to use kms’ encrypt function.

     gcloud kms encrypt
    
  2. Here we specify the file that has our password. We previously created my_secret.txt and we have to use it here.

     --plaintext-file=my_secret.txt
    
  3. Then, we have to specify where we want our encrypted text file to be.

     --ciphertext-file=my_secret.enc.txt
    
  4. The location must be global

     --location=global
    
  5. Here we have to put what keyring we are using. Currently, we use the keyring called cloudbuilder for our project.

     --keyring=cloudbuilder
    

    Remember that if you want to see the list of keyrings availables, you may use this command:

     $ gcloud kms keyrings list --location global
    
  6. The last step is to specify the key that we previously created.

     --key=service-account
    

    Verify the available keys

     $ gcloud kms keys list --location=global --keyring=cloudbuilder
    

The whole command looks like this:

gcloud kms encrypt \
--plaintext-file=my_secret.txt \
--ciphertext-file=my_secret.enc.txt \
--location=global \
--keyring=cloudbuilder \
--key=service-account

We can see the encoded secret using this:

$ cat my_secret.enc.txt

Note that the secret is binary encoded. We need to encode it to a base64 string using this command:

$ base64 my_secret.enc.txt -b 0 > my_secret.enc.64.txt

And review its content:

$ cat my_secret.enc.64.txt

Cloudbuild

Now, let’s review how can we use the encrypted value with Cloudbuild.

In order for cloudbuild to decrypt our value, it must be base64 encoded and denoted as a secret (hence the last part of the previous section).

At the bottom of our cloudbuild.yaml file, we must create a section named secrets. Then, we add a new entry called kmsKeyName where we can specify the key that we previously created with gcloud KMS.

secrets:
    - kmsKeyName: "projects/build-pipeline-1/locations/global/keyRings/cloudbuilder/cryptoKeys/service-account"

Where:

  • build-pipeline-1: The project that we are currently working on.
  • cloudbuilder: The name of our Key ring.
  • service-account: The name of our Key.

Remember that you can get this value with the following command:

$ gcloud kms keys list --location=global --keyring=cloudbuilder

What we put on - kmsKeyName is at the NAME column.

Now it’s time to use our base64 encrypted value. We are going to use that value in our cloudbuild.yaml file, which contains specific instructions to build a Docker image. At kmsKeyName, we must add a new entry called secretEnv; that’s where we are going to specify our secret environment variables.

After that, we are able to add a variable. We are going to name it OPENSSL_PASS and assign it the base64 encrypted value. Remember that we can review that value in the my_secret.enc.64.txt file that we previously created:

$ cat my_secret.enc.64.txt

At our cloudbuild.yaml:

secretEnv:
    OPENSSL_PASS: "CiQAUcsuTcp/g/1wopD3rP2eEuWTmlHUFvgy7sLlWYTHXL93bMMSagAKymd5BG3s3NYJ33oqk0fHb2rTZWEEjj83tZhfXO6wEB4ZFl/fGHXMJv6GYgjANNhG9Iqcpgn+svwSMhhaB/qP2lelzX+uUtRcFopnXuNMcttHOpEyWHngw8h9A6417u0URCGPudRRFqk="

The whole secrets section should look like this now:

secrets:
    - kmsKeyName: "projects/build-pipeline-1/locations/global/keyRings/cloudbuilder/cryptoKeys/service-account"
        secretEnv:
            OPENSSL_PASS: "CiQAUcsuTcp/g/1wopD3rP2eEuWTmlHUFvgy7sLlWYTHXL93bMMSagAKymd5BG3s3NYJ33oqk0fHb2rTZWEEjj83tZhfXO6wEB4ZFl/fGHXMJv6GYgjANNhG9Iqcpgn+svwSMhhaB/qP2lelzX+uUtRcFopnXuNMcttHOpEyWHngw8h9A6417u0URCGPudRRFqk="

We are telling our cloudbuild the secret to be decrypted, the key to use for decryption, and the env variable that we can use to refer to it with.

Now we are able to use our secret environment variable by just adding this:

secretEnv: ["OPENSSL_PASS"]

In this directive we declare the secretEnv attribute along with the expected key of “OPENSSL_PASS”. This tells cloudbuild that we want to pass the decrypted value into this build step rather than the encrypted base64 value.

Now our cloudbuild.yaml file should look like this:

cloudbuild.yaml

- name: "docker/compose:1.23.2"
args: ["run", "build"]
env: 
    - OPENSSL_PASSWORD=$$OPENSSL_PASS
secretEnv: ["OPENSSL_PASS"]

secrets:
    - kmsKeyName: "projects/build-pipeline-1/locations/global/keyRings/cloudbuilder/cryptoKeys/service-account"
        secretEnv:
            OPENSSL_PASS: "CiQAUcsuTcp/g/1wopD3rP2eEuWTmlHUFvgy7sLlWYTHXL93bMMSagAKymd5BG3s3NYJ33oqk0fHb2rTZWEEjj83tZhfXO6wEB4ZFl/fGHXMJv6GYgjANNhG9Iqcpgn+svwSMhhaB/qP2lelzX+uUtRcFopnXuNMcttHOpEyWHngw8h9A6417u0URCGPudRRFqk="

Generating a Certificate

Our application uses a secret to create a key. That secret is passed to the application with OPENSSL_PASS. We create the key with the secret, but we still need to share that secret with the application so it can unencrypt the key.

To reference our variable in the docker-compose.yaml file, we have to do the following:

environment:
  - OPENSSL_PASSWORD=$$OPENSSL_PASS

That applies for every step in our docker-compose.yaml file.

Accesing our variable from Rust

Now that our environment variable is set, we can use it with

let openssl_secret = env::var("OPENSSL_PASSWORD")
    .unwrap_or_else(|_| "OPENSSL_PASSWORD secret not set".to_owned());

Note that we use the .unwrap_or_else() function in case the environment variable is not set.

And that’s how we use and manage secrets with KMS in a manner that works with our CI/CD on GCloud :)