Back to blog
Best PracticesCloud SecurityDatabasesGCPOperations & Compliance

Cloud SQL Not Using CMEK: Why Customer-Managed Keys Matter and How to Enforce Them

Learn why GCP Cloud SQL instances should use customer-managed KMS keys (CMEK), the risks of default encryption, and step-by-step CLI and Terraform fixes.

TL;DR

This check flags Cloud SQL instances that rely on Google's default encryption instead of a customer-managed encryption key (CMEK). Without CMEK you cannot control the key lifecycle, rotation, or revocation. Fix it by creating a Cloud KMS key, granting the Cloud SQL service account access, and provisioning a new instance with that key.

Every Cloud SQL instance is encrypted at rest by default. That is a good baseline, but the keys are owned and rotated entirely by Google. For workloads under PCI DSS, HIPAA, or internal data-governance rules, you often need to prove you control the encryption key, not just trust that one exists. The sql_nocmk check looks for any Cloud SQL instance that has not been bound to a customer-managed key in Cloud KMS.


What this check detects

Lensix inspects each Cloud SQL instance's diskEncryptionConfiguration field. When an instance is created with CMEK, this field references a KMS key resource name. If the field is empty, the instance is using Google-managed default encryption, and the check is marked as failing.

In practice the check fires on:

  • Instances created without specifying a --disk-encryption-key
  • Older instances provisioned before your team adopted CMEK
  • Read replicas or clones that inherited the default encryption of a non-CMEK source

Note: CMEK does not replace Google's encryption. The data is still encrypted with a Google-managed Data Encryption Key (DEK), but that DEK is itself wrapped by your Cloud KMS key (the Key Encryption Key). Revoke or disable your KMS key and the DEK can no longer be unwrapped, which renders the data inaccessible.


Why it matters

Default encryption protects you against one specific threat: someone walking out of a Google data center with a physical disk. It does nothing for the threats most teams actually worry about.

You cannot revoke access in an incident

Imagine credentials to your project are compromised and an attacker is exfiltrating data from a production database. With Google-managed keys, you have no kill switch at the encryption layer. With CMEK, disabling the KMS key version immediately cuts off the instance's ability to decrypt data, buying you time to contain the breach.

Compliance frameworks expect key ownership

Auditors for PCI DSS, HIPAA, FedRAMP, and many financial regulations increasingly ask a direct question: who controls the encryption keys for cardholder or patient data? "Google does" is rarely an acceptable answer. CMEK lets you point at a key you created, with a rotation schedule you defined and an audit trail of every decrypt operation in Cloud Audit Logs.

Centralized key lifecycle and rotation

With CMEK you set rotation periods, you decide when to disable old key versions, and you can store keys in a specific region to satisfy data-residency requirements. None of that is possible with the default scheme.

Warning: You cannot add CMEK to an existing Cloud SQL instance in place. Encryption configuration is set at creation time. Converting an existing instance means creating a new CMEK-enabled instance and migrating data, so plan for a maintenance window.


How to fix it

The fix has three stages: create a KMS key, give the Cloud SQL service agent permission to use it, then provision the instance with the key.

1. Create a key ring and key

Keep the key in the same region as the Cloud SQL instance. Cross-region CMEK is not supported for Cloud SQL.

gcloud kms keyrings create sql-keyring \
  --location=us-central1

gcloud kms keys create sql-cmek \
  --location=us-central1 \
  --keyring=sql-keyring \
  --purpose=encryption \
  --rotation-period=90d \
  --next-rotation-time=$(date -u -d '+90 days' +%Y-%m-%dT%H:%M:%SZ)

2. Grant the Cloud SQL service account access to the key

Cloud SQL uses a per-project service agent to perform encryption operations. First find or create it, then grant it the encrypter/decrypter role on the key.

# Get the Cloud SQL service account for your project
SA_EMAIL=$(gcloud sql instances create-instance --quiet 2>/dev/null; \
  gcloud beta services identity create \
  --service=sqladmin.googleapis.com \
  --project=YOUR_PROJECT_ID \
  --format='value(email)')

# Grant it permission to use the key
gcloud kms keys add-iam-policy-binding sql-cmek \
  --location=us-central1 \
  --keyring=sql-keyring \
  --member="serviceAccount:${SA_EMAIL}" \
  --role=roles/cloudkms.cryptoKeyEncrypterDecrypter

Note: The service agent email follows the pattern [email protected]. If gcloud beta services identity create returns nothing, that agent may not exist yet, which is why the command above triggers its creation.

3. Create the instance with the key

gcloud sql instances create prod-db \
  --database-version=POSTGRES_15 \
  --region=us-central1 \
  --tier=db-custom-2-7680 \
  --disk-encryption-key=projects/YOUR_PROJECT_ID/locations/us-central1/keyRings/sql-keyring/cryptoKeys/sql-cmek

Migrating an existing instance

Since you cannot retrofit CMEK, the path for an existing database is to create a CMEK-enabled instance and move the data into it.

  1. Create the new CMEK instance using the steps above.
  2. Export the existing database to a Cloud Storage bucket, or use Database Migration Service for minimal downtime.
  3. Import into the new instance and verify row counts and application connectivity.
  4. Cut over your connection strings, then decommission the old instance.
# Export from the old instance
gcloud sql export sql old-db gs://my-export-bucket/dump.sql \
  --database=appdb

# Import into the new CMEK instance
gcloud sql import sql prod-db gs://my-export-bucket/dump.sql \
  --database=appdb

Danger: Do not delete the source instance until you have confirmed the new instance is serving production traffic correctly and you have a verified backup. Deleting a Cloud SQL instance also removes its automated backups unless you have exported them elsewhere.

Terraform example

If you manage infrastructure as code, bind the key at the resource level so every new instance is CMEK by default.

resource "google_kms_key_ring" "sql" {
  name     = "sql-keyring"
  location = "us-central1"
}

resource "google_kms_crypto_key" "sql" {
  name            = "sql-cmek"
  key_ring        = google_kms_key_ring.sql.id
  rotation_period = "7776000s" # 90 days
}

resource "google_project_service_identity" "sql_sa" {
  provider = google-beta
  service  = "sqladmin.googleapis.com"
}

resource "google_kms_crypto_key_iam_member" "sql" {
  crypto_key_id = google_kms_crypto_key.sql.id
  role          = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
  member        = "serviceAccount:${google_project_service_identity.sql_sa.email}"
}

resource "google_sql_database_instance" "prod" {
  name             = "prod-db"
  database_version = "POSTGRES_15"
  region           = "us-central1"

  encryption_key_name = google_kms_crypto_key.sql.id

  settings {
    tier = "db-custom-2-7680"
  }

  depends_on = [google_kms_crypto_key_iam_member.sql]
}

Tip: Wrap the key ring, key, and IAM binding into a reusable Terraform module. Then a single encryption_key_name input on your database module makes CMEK the path of least resistance for every team, rather than something engineers have to remember to add.


How to prevent it from happening again

Manual fixes drift. The instances you remediate today will be joined by new ones next quarter unless you enforce CMEK at provisioning time.

Organization policy constraint

GCP ships a built-in constraint that blocks creation of resources without CMEK. Apply it at the org or folder level and Cloud SQL will reject any instance that does not specify a key.

gcloud resource-manager org-policies enable-enforce \
  constraints/gcp.restrictNonCmekServices \
  --organization=YOUR_ORG_ID

# Then list the services that must use CMEK
gcloud resource-manager org-policies set-policy cmek-policy.yaml \
  --organization=YOUR_ORG_ID

Where cmek-policy.yaml includes sqladmin.googleapis.com in the allowed values list. You can also restrict which key projects are permitted with constraints/gcp.restrictCmekCryptoKeyProjects.

Policy-as-code in CI/CD

Catch the problem before it ever reaches GCP by scanning Terraform plans. A simple Conftest/OPA rule rejects any Cloud SQL resource missing an encryption key:

package main

deny[msg] {
  resource := input.resource.google_sql_database_instance[name]
  not resource.encryption_key_name
  msg := sprintf("Cloud SQL instance '%s' must set encryption_key_name (CMEK)", [name])
}

Run it as a required check on every pull request so a missing key fails the build, not the audit.

Tip: Pair the org policy with continuous monitoring in Lensix. The org policy stops new violations, while the sql_nocmk check surfaces legacy instances and any resources created through paths the policy does not cover, such as imported state or manual console actions during an incident.


Best practices

  • Rotate keys automatically. Set a rotation period of 90 days or less. Cloud SQL re-wraps the DEK with the new key version on the next maintenance event without downtime.
  • Separate duties with a dedicated key project. Store KMS keys in a project that only your security team can administer, so application teams can use keys but not delete or disable them.
  • Never grant broad KMS admin roles to service accounts. The Cloud SQL agent needs only cryptoKeyEncrypterDecrypter, nothing more.
  • Monitor key usage. Enable Cloud Audit Logs for Cloud KMS and alert on unexpected Decrypt volume or any attempt to destroy a key version.
  • Protect against accidental key destruction. Cloud KMS enforces a 24-hour delay before a key version is destroyed. Treat that window as a safety net, not a plan, and keep verified database exports.
  • Match key region to instance region. Cross-region CMEK is unsupported and will cause instance creation to fail.

CMEK is one of the cheapest controls you can add to a database. A KMS key costs a fraction of a cent per month plus a small fee per operation, and in return you get a revocation switch, a rotation schedule, and a clean answer for every auditor who asks who holds the keys.

Adopt CMEK as the default for new Cloud SQL instances, enforce it with org policy and CI gates, and let Lensix watch for the stragglers. That combination turns key ownership from a recurring audit headache into a property of your platform.