NodeJS Secure Environment Variables with Google Key Management Service

Introduction

When developing applications, using environment variables is essential. They help configure values for different environments like development, staging, and production. Some environment variables, such as API keys, database connections, or passwords, are sensitive and need to be kept secure. If you're using Google Cloud, their Key Management Service (KMS) can help you manage keys, and allows you to encrypt and decrypt values using those keys.

Prerequisites

Before we proceed, make sure you have:

Key Management Service

KMS works with key rings, which hold multiple keys. You use these keys to encrypt and decrypt string values or file data.

Be cautious: if a key is deleted, any data encrypted with it can't be decrypted anymore.


To create a key ring, execute the following command:

gcloud kms keyrings create "key-ring-name" \
--location "global"


Then create a key based on the keyring.

gcloud kms keys create "key-name" \
--location "global" \
--keyring "key-ring-name" \
--purpose "encryption"


You can check the keyring and key that were created by using a command or by visiting the Google Cloud Console.

gcloud kms keys list \
--location "global" \
--keyring "key-ring-name"

gcloud kms keys versions list \
--location "global" \
--keyring "key-ring-name" \
--key "key-name"


Deploying with NodeJS

Once you've successfully set up your NodeJS Typescript project, install the following package:

yarn add @google-cloud/kms


Use the following code block to interact with Google KMS.

import {KeyManagementServiceClient} from '@google-cloud/kms'

const projectId = 'project-id'
const location = 'global'
const keyRing = 'key-ring-name' // replace your keyring name
const cryptoKey = 'key-name' // replace your key name

const kmsClient = new KeyManagementServiceClient()
const parent = kmsClient.locationPath(projectId, location)
const kmsPath = kmsClient.cryptoKeyPath(projectId, location, keyRing, cryptoKey)
const [keyRings] = await kmsClient.listKeyRings({parent})

const decrypt = async (encryptValue: string): Promise<string> => {
try {
const request = {
name: kmsPath,
ciphertext: encryptValue,
}
const [decryptedText] = await kmsClient.decrypt(request)
return decryptedText.plaintext.toString().trim()
} catch (e) {
console.error(e)
return null
}
}

if (keyRings.length) {
const keyRingNames = keyRings.map(keyRing => keyRing.name)
console.log('Key rings:', keyRingNames)
}

const [username, password] = await Promise.all([decrypt(process.env.GCP_USERNAME), decrypt(process.env.GCP_PASSWORD)])
console.log('Decrypted value:', username, password)


I use `process.env.GCP_USERNAME` and `process.env.GCP_PASSWORD` defined in the `.env` file like this

GCP_USERNAME=CiQAgHBm4/HepIf4wK9keDAYTenhPTA5lgJOOXEziBNdjiZ14SwSOABot+e3PRhP8Tepqb8QvvmD9yPcOuBNt5Goh0404pHqpcs1/sCagzYhhnEvn71gYY3GDps0CzK2
GCP_PASSWORD=CiQAgHBm74TCT5a66lD0Q0L1k/k0IP4y2zPCmia0VBB76blzxmUSOABot+e2hvUl91xbUvuSvUVYKtrqDlw8+qTM3d+1Mw6j+WpsXH+fZ0YkZdZi2DsLRpDXT4fTPc8g

The values for GCP_USERNAME and GCP_PASSWORD are encrypted in base64 based on the key created above. Therefore, you cannot use the usual method to decrypt these values.


To encrypt a value to base64, use the following command:

echo "password_value" | gcloud kms encrypt \
--location "global" \
--keyring "key-ring-name" \
--key "key-name" \
--plaintext-file - \
--ciphertext-file - | base64


After obtaining the value, replace it in your .env file accordingly.


The result of executing the code will be as follows:


If you have any suggestions or questions about the content of this article, please feel free to leave a comment below!

Comments

Popular posts from this blog

Kubernetes Practice Series

NodeJS Practice Series

Docker Practice Series

React Practice Series

Sitemap

Deploying a NodeJS Server on Google Kubernetes Engine

Setting up Kubernetes Dashboard with Kind

A Handy Guide to Using Dynamic Import in JavaScript

DevOps Practice Series

Create API Gateway with fast-gateway