This article explains how to leverage the google_kms_secret_ciphertext resource for encrypting data using a symmetric key with Terraform on GCP. This resource exports a ciphertext attribute that you can use to create a file in a bucket or to define a custom metadata key in a VM. Therefore, we will cover three key areas:
- Configure Cloud KMS on GCP: Create and configure the necessary things on GCP for the google_kms_secret_ciphertext resource to work properly. First, we will enable the required API (cloudkms), create the keyRing, and finally, the key. Next, we’ll review the permissions required to work with KMS and service accounts.
- Encrypt the data. We will examine the plaintext argument expected by ciphertext google_kms_secret_ciphertext.
- Decrypt the data. To decrypt the data, we will explore our options, specifically focusing on using the gcloud CLI.
Configuring Cloud KMS on GCP
To use Google KMS, you first need to enable the Cloud Key Management Service (KMS) API. You can enable it using gcloud with the following command:
gcloud services enable cloudkms.googleapis.com
For those interested, there is a Terraform resource called google_project_service that accomplishes the same task. It usually takes a few minutes for cloudkms to become available. Once available, you can proceed to create keyRings and keys. From this point, we will use Terraform to create all necessary resources.
For authentication and to run Terraform commands, I use a service account and a corresponding key. This setup leads us to discuss the permissions required for using KMS effectively.
Roles for Cloud KMS
If you want to use a key that already exists to encrypt and decrypt data using google_kms_secret_ciphertext, you will need the:
If you need to create or delete keyRings and keys, you will need the:
- “Cloud KMS Admin” role.
An important note about Cloud KMS permissions and GCE
To use KMS keys with Google Compute Engine (GCE), it’s necessary to assign the roles/cloudkms.cryptoKeyEncrypterDecrypter role to the Compute Engine Service Agent account. This requirement applies, for example, when using a KMS key to encrypt a disk on a VM. Thus, this specific role must be allocated to both the service account executing Terraform and the Compute Engine service agent.
Creating a keyRing and a key with terraform
The following code Terraform code creates a keyRing first and then a key. It creates the type of key we need for google_kms_secret_ciphertext which is a software key with the following default properties:
- Protection level: software
- Purpose: encrypt/decrypt
Google recommends enabling prevent_destroy for KMS keys, since disabling the key will make impossible to decrypt the things that were encrypted with it.
provider "google" {
project = var.project
region = var.region
zone = var.zone
}
resource "google_kms_key_ring" "key_ring" {
name = var.key_ring_name
location = var.key_ring_location
}
resource "google_kms_crypto_key" "key" {
name = var.key_name
key_ring = google_kms_key_ring.key_ring.id
rotation_period = "7776000s"
lifecycle {
prevent_destroy = true
}
}
Encrypting the data
The google_kms_secret_ciphertext resource requires two attributes: plaintext and crypto_key. The crypto_key refers to the resource’s name, formatted as “projects/<your_project>/locations/<location>/keyRings/<key_ring>/cryptoKeys/<key>”. The plaintext must be textual, meaning base64 encoded data is compatible, allowing for diverse applications with templates and files. According to Google’s documentation, plaintext is limited to 64KB. To accommodate more data, you can utilize the base64gzip Terraform function, which compresses and then encodes the data in base64. This approach requires a decompressing and decoding step before decryption. It’s worth noting that google_kms_secret_ciphertext outputs the encrypted data as a base64 string.
resource "google_kms_secret_ciphertext" "my_encrypted_data" {
crypto_key = google_kms_crypto_key.key.id
plaintext = "my sensitive data"
additional_authenticated_data = "im some additional authenticated data"
}
The additional_authenticated_data is an optional attribute that you can use to encrypt the plaintext, which can make things a bit safer. Just keep in mind that when decrypting, you need to use the exact same additional_authenticated_data because the key alone is not going to work (no spaces or break lines, exactly the same)
The ciphertext looks something like this:
"ciphertext": "CiQA2dqsQDBEYZxwm1v3HzkupsUNJvWXrcu3zsGq9jKcs62klisSOgBFgk53XcMyhAHzYEMu+umK0AVwG4V6dSus/hTaYAEKOON840lPva2f0X+3pam0ujoA+qrvAcd7fcE="
Decrypting the data
There’s no corresponding terraform resource to decrypt the data (as expected), so you will have to use the Google Cloud SDK to do it. In our case we will do it with gcloud:
echo -n 'im some additional authenticated data' > auth-data.txt
echo "CiQA2dqsQDBEYZxwm1v3HzkupsUNJvWXrcu3zsGq9jKcs62klisSOgBFgk53XcMyhAHzYEMu+umK0AVwG4V6dSus/hTaYAEKOON840lPva2f0X+3pam0ujoA+qrvAcd7fcE=" | base64 --decode > encrypted-data.bin
gcloud kms decrypt \
--location global \
--keyring my-key-ring02 \
--key my-key \
--additional-authenticated-data-file auth-data.txt \
--ciphertext-file encrypted-data.bin \
--plaintext-file decrypted-data.txt
We are creating the files for the optional authenticated data and the encrypted data once it is decoded. We should see the decrypted output in the decrypted-data.txt file. The “ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed” most likely means there’s something wrong with your authenticated data or the way your are creating the file for the encrypted data.
The entire code:
provider "google" {
project = var.project
region = var.region
zone = var.zone
}
resource "google_kms_key_ring" "key_ring" {
name = var.key_ring_name
location = var.key_ring_location
}
resource "google_kms_crypto_key" "key" {
name = var.key_name
key_ring = google_kms_key_ring.key_ring.id
rotation_period = "7776000s"
lifecycle {
prevent_destroy = true
}
}
resource "google_kms_secret_ciphertext" "my_encrypted_data" {
crypto_key = google_kms_crypto_key.key.id
plaintext = "my sensitive data"
additional_authenticated_data = "im some additional authenticated data"
}
One practical application of google_kms_secret_ciphertext is encrypting metadata for a VM in Google Compute Engine (GCE). To facilitate decryption from within the machine, attach a service account endowed with the necessary permissions (Cloud KMS CryptoKey Encrypter/Decrypter) and configure the scope to cloudkms. Upon installing the gcloud CLI on the machine, it will automatically recognize the service account, enabling decryption capabilities. I plan to elaborate on this process in an upcoming article. Based on my testing, this method works effectively and can be seamlessly automated.