Make encrypted storage the default for all your GCP services
Encrypting cloud resources is a priority for security teams, but it oftentimes goes overlooked. Encryption should be the default for many use cases, and as easy as clicking a button.
In this tutorial, we’ll walk through how you can get started with encrypting common GCP cloud resources at rest. Then we’ll complete a complex encryption use case: encrypting data in transit via https. We’ll do all of this with Resourcely Blueprintsand Guardrails.
Resourcely generates configuration and enforces policies as part of your CI. While you can use Resourcely standalone, to make the most out of it you will want to integrate it with your version control, CI, and Terraform runner.
To set up your environment, follow the Quickstart.
Creating crypto keys
Encryption on GCP hinges on crypto keys: tools that can be used to encrypt and decrypt cloud resources. Keys are managed with GCP KMS, which allows users to easily create and manage keys with features like automatic rotation, permissions, grouping, and more.
Here's a simple example of creating a crypto key with Terraform:
resource "google_kms_key_ring" "keyring" { name ="keyring-example" location ="global"}resource "google_kms_crypto_key" "example-key" { name ="crypto-key-example" key_ring = google_kms_key_ring.keyring.id purpose ="ENCRYPT_DECRYPT" rotation_period ="7776000s"}
While this example is straightforward, there are drawbacks. A developer may...
not know what any of these parameters mean
may enter values that are dangerous or incorrect
omit values that are important to their organization
We can turn this into a Resourcely Blueprint by adding tags. This will create a guided UI a developer can use to confidently deploy infrastructure. Here is a Resourcely Blueprint for a crypto key, and the associated UI that is generated:
This Blueprint has two primary sections: Frontmatter (where variables are defined and augmented with guidance), and the primary resources section (Terraform, but with variables added to it).
---
constants:
__name: "{{ name }}_{{ __guid }}"
variables:
name:
desc: |
The name of the resource being created, used for both the Key Ring and the Crypto Key.
- Must be unique within the GCP project
- Avoid using sensitive or identifiable information
required: true
suggest: "my-kms-key"
group: General
key_ring_id:
desc: |
The **ID of the Key Ring** to hold the encryption keys.
- Must be unique within the specified location
- For more information, see the [Terraform docs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/kms_key_ring)
suggest: "my-key-ring"
required: true
group: KMS Configuration
location:
desc: |
The **GCP location** for the Key Ring and Crypto Key.
- Default: `us-central1`
- Possible values: `us-central1`, `us-east1`, `europe-west1`, `asia-east1`, ...
- For best practices, see [GCP Regions and Zones](https://cloud.google.com/about/locations)
suggest: "us-central1"
required: true
group: KMS Configuration
rotation_period:
desc: |
The **rotation period** for the encryption key.
- Default: `30d` (30 days)
- Specify in the format `Xd`, e.g., `90d` for 90 days
- Learn more about key rotation [here](https://cloud.google.com/kms/docs/key-rotation)
suggest: "30d"
required: true
group: KMS Configuration
---
resource "google_kms_key_ring" "{{ __name }}" {
name = "{{ name }}"
location = "{{ location }}"
}
resource "google_kms_crypto_key" "{{ __name }}" {
name = "{{ name }}"
key_ring = google_kms_key_ring.{{ __name }}.id
purpose = "ENCRYPT_DECRYPT"
rotation_period = "{{ rotation_period }}"
}
Navigate to Foundry in Resourcely, paste the above code, and navigate to the Developer Experience tab to see the form that is created.
Attaching crypto keys to cloud resources
Now that we have given users the ability to create crypto keys, let's make it easy for them to use keys and encrypt their services.
In Terraform, you might use code that looks like this to encrypt your resources:
# Create a Crypto Key
resource "google_kms_crypto_key" "sql_crypto_key" {
name = "sql-crypto-key"
key_ring = google_kms_key_ring.sql_key_ring.id
rotation_period = "100000s" # Optional: Set key rotation period
version_template {
algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION"
}
}
# Provision a Cloud SQL Database Instance with encryption
resource "google_sql_database_instance" "default" {
name = "my-sql-instance"
database_version = "POSTGRES_13"
region = "us-central1"
settings {
tier = "db-f1-micro"
# Attaching a crypto key for encryption
disk_encryption_configuration {
kms_key_name = google_kms_crypto_key.sql_crypto_key.id
}
This leaves too many surface areas for encryption failure:
Choosing the wrong alogrithm
Improper naming
Too long of a rotation period
Using the wrong crypto key ID
In our Blueprint code, notice our frontmatter tags:
Description
Default
Suggest
Required
Group
These tags embed context into our form that helps users avoid issues and choose the right configuration.
Let's now create Blueprints for our cloud resources that we want to encrypt:
Compute
With Google Compute Instances, you would normally link to a crypto key by referencing its ID:
What if you want to give users a simple list to choose from of possible crypto keys, with some guidance for what to pick? You can do this with the links_to tag!
Start out by creating this Google Compute Instance Blueprint in Resourcely Foundry:
Compute Instance Blueprint
---
constants:
__name: "{{ name }}_{{ __guid }}"
variables:
name:
desc: |
The **name of the Compute Engine instance**.
- Used as the unique identifier for the instance
- See the [Terraform docs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance) for details
required: true
suggest: "encrypted-instance"
group: General
machine_type:
desc: |
The **machine type** for the Compute Engine instance.
- Default: `e2-medium`
- Possible values: `e2-medium`, `e2-standard-2`, `n1-standard-1`, `e2-highmem-2`, `n2-standard-2`, ...
- Check [GCP machine types](https://cloud.google.com/compute/docs/machine-types) for more options
required: true
suggest: "e2-medium"
group: Compute Configuration
disk_encryption_key:
desc: |
The **KMS key** used to encrypt the disk.
- Must be a valid Crypto Key resource
- [Learn more about encrypted disks](https://cloud.google.com/compute/docs/disks/customer-supplied-encryption)
required: true
links_to: resource.google_kms_crypto_key.id
group: Compute Configuration
---
resource "google_compute_instance" "{{ __name }}" {
name = "{{ name }}"
machine_type = "{{ machine_type }}"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "projects/debian-cloud/global/images/family/debian-11"
}
kms_key_self_link = "{{ disk_encryption_key }}"
}
}
```
Notice the links_to tag usage:
disk_encryption_key:
desc: |
The **KMS key** used to encrypt the disk.
- Must be a valid Crypto Key resource
- [Learn more about encrypted disks](https://cloud.google.com/compute/docs/disks/customer-supplied-encryption)
required: true
links_to: resource.google_kms_crypto_key.id
group: Compute Configuration
This manifests itself in the Blueprint UI with a dropdown box. Publish the Blueprint you just pasted, and create a PR in Resourcely with your crypto key Blueprint and this Compute Instance Blueprints.
When you do so, observe the disk encryption key field. It lists crypto keys that are being created by the user, or any existing crypto keys that you want to make available:
Using links_to makes it simple for developers to link resources, making encryption easy to achieve: no looking up ARNs or IDs.
Requiring encryption
Now that you've created paved roads for creating encrypted resources by default, let's build some more restrictions into our input fields and some checks when CI runs. We'll do this with Resourcely Guardrails.
Navigate to Foundry and click "Author a Guardrail". We'll start with a simple Guardrail that makes sure we have a crypto key on our Cloud SQL databases.
GUARDRAIL "Require encryption on Cloud SQL databases"
WHEN google_sql_database_instance
REQUIRE encryption_key_name EXISTS
Paste the above example into the Foundry IDE and publish it. Then, create a Blueprint in Foundry with for a Cloud SQL database that allows for a crypto key to be attached:
Cloud SQL Blueprint
---
constants:
__name: "{{ name }}_{{ __guid }}"
variables:
name:
desc: |
The **name of the Cloud SQL instance**.
- Must be unique within the project
- See [Cloud SQL Documentation](https://cloud.google.com/sql/docs/introduction) for instance naming rules
required: true
suggest: "encrypted-sql-instance"
group: General
database_version:
desc: |
The **version of the Cloud SQL database**.
- Default: `MYSQL_8_0`
- Possible values: `MYSQL_5_7`, `MYSQL_8_0`, `POSTGRES_13`, `POSTGRES_14`, ...
- See [Supported Database Versions](https://cloud.google.com/sql/docs/mysql/supported-versions)
required: true
suggest: "MYSQL_8_0"
group: SQL Configuration
encryption_key:
desc: |
The **KMS key** used to encrypt the database instance.
- Must link to a valid Crypto Key resource
- Learn more about [Cloud SQL Encryption](https://cloud.google.com/sql/docs/mysql/data-encryption)
required: true
links_to: resource.google_kms_crypto_key.id
group: SQL Configuration
---
resource "google_sql_database_instance" "{{ __name }}" {
name = "{{ name }}"
database_version = "{{ database_version }}"
region = "us-central1"
encryption_key_name = "{{ encryption_key }}"
}
```
Notice that our Guardrail manifests itself on the Cloud SQL Blueprint form (see the Developer Experience tab):
If the field is left empty, this Guardrail will be triggered. The user must unlock the Guardrail with the Proceed button, acknowledging they aren't attaching a crypto key. In this case, the resulting PR will be blocked until the appropriate team can review it.
Guardrails are a great way to require or limit configuration settings that your team cares about (like encryption) without blocking the developers following your guidance.
Encryption in transit
Suppose that you want to make it easy for developers to set up https support for their applications. Creating a certificate and connecting it to a backend service is not a straightforward exercise. There are many tricky parameters:
Host rules and path matching
Certificate setup and lifecycle rules
URL mapping and connecting to the backend service
Resourcely's QoL features for Blueprints are a perfect candidate for streamlining encryption in transit:
Easy, automated linking means that users never have to make their own references
Default values mean developers don't have to bother with fields they shouldn't need to know about
i.e. paths = ["/*"]
Markdown-based description make for robust instructions that keep developers on track
To implement encryption in transit using Resourcely, simply use the following Blueprint:
Encrypted Load Balancer Blueprint
---
constants:
__name: "{{ name }}_{{ __guid }}"
variables:
name:
desc: |
The **base name** for the resources to be created.
- Used for all related resources such as certificates, proxies, and backend services
- Must be unique within the GCP project
required: true
suggest: "secure-data-in-transit"
group: General
certificate_path:
desc: |
The **path to the SSL certificate file** for the self-managed SSL certificate.
- PEM-formatted certificate
required: true
suggest: "/path/to/certificate.crt"
group: SSL Configuration
private_key_path:
desc: |
The **path to the private key file** for the self-managed SSL certificate.
- PEM-formatted private key
required: true
suggest: "/path/to/private.key"
group: SSL Configuration
host_names:
desc: |
A list of hostnames for the URL map's host rules.
- Must be valid DNS hostnames
required: true
suggest: ["domain.com","example.com"]
group: URL Configuration
backend_service_name:
desc: |
The name of the backend service.
- Must be unique within the GCP project
required: true
suggest: "backend-service"
links_to: resource.google_compute_backend_service.id
group: Backend Configuration
health_check_name:
desc: |
The name of the HTTP health check resource.
required: true
suggest: "http-health-check"
links_to: resource.google_compute_http_health_check.id
group: Health Check Configuration
check_interval_sec:
desc: "Interval in seconds for the health check to run"
required: true
suggest: 1
group: Health Check Configuration
timeout_sec:
desc: "Timeout in seconds for the health check to wait for a response"
required: true
suggest: 10
group: Health Check Configuration
---
# SSL Certificate
resource "google_compute_ssl_certificate" "{{ __name }}" {
name_prefix = "{{ name }}-cert-"
private_key = file("{{ private_key_path }}")
certificate = file("{{ certificate_path }}")
lifecycle {
create_before_destroy = true
}
}
# Target HTTPS Proxy
resource "google_compute_target_https_proxy" "{{ __name }}" {
name = "{{ name }}-proxy"
url_map = google_compute_url_map.{{ __name }}.id
ssl_certificates = [google_compute_ssl_certificate.{{ __name }}.id]
}
# URL Map
resource "google_compute_url_map" "{{ __name }}" {
name = "{{ name }}-url-map"
description = "URL map for {{ name }}"
default_service = google_compute_backend_service.{{ backend_service_name }}.id
host_rule {
hosts = {{ host_names }}
path_matcher = "allpaths"
}
path_matcher {
name = "allpaths"
default_service = google_compute_backend_service.{{ backend_service_name }}.id
path_rule {
paths = ["/*"]
service = google_compute_backend_service.{{ backend_service_name }}.id
}
}
}
# Backend Service
resource "google_compute_backend_service" "{{ backend_service_name }}" {
name = "{{ backend_service_name }}"
port_name = "http"
protocol = "HTTP"
timeout_sec = 10
health_checks = [google_compute_http_health_check.{{ health_check_name }}.id]
}
# HTTP Health Check
resource "google_compute_http_health_check" "{{ health_check_name }}" {
name = "{{ health_check_name }}"
request_path = "/"
check_interval_sec = "{{ check_interval_sec }}"
timeout_sec = "{{ timeout_sec }}"
}
Paste it into Foundry, publish, and explore the UI.
Descriptions
See the desc tags in frontmatter, and note how links can be included - giving users the ability to explore documentation if they want.
Host Names
Note the host_names variables and see how they allow for multiple domains to be included in the UI. Resourcely automatically detects the hosts parameter in Terraform expects a list and adjusts accordingly.
Suggestions and defaults
Adding suggestions and defaults helps users know what they should pick, especially if they aren't familiar
Publish your Blueprint and use it as-is to facilitate encrypted in transit web traffic for your applications!
Conclusion
Encryption doesn't need to be an optional nice-to-have. You can make it easy to add encryption to your resources with Blueprints, and enforce encryption with Guardrails.
We have pre-built encryption Blueprints and Guardrails available for:
Crypto keys and key rings
Compute Instances
Blob Storage
Pub/Sub Topics
Cloud SQL
BigQuery
Cloud Functions
You can find them in our GitHub repo of use case examples! Simply copy the code into Foundry, and publish to get started.