Skip to main content

Encryption

TSC mapping: CC6.1 (Logical access security — data at rest), CC6.7 (Data transmission and removal controls)

Auditors require evidence that customer data is encrypted at rest and in transit, that keys are managed with proper access controls and rotation, and that secrets are not stored in plaintext.


1. Cloud KMS — Key Management

Cloud KMS provides software and hardware (Cloud HSM) key management. Use Customer-Managed Encryption Keys (CMEK) for any data requiring auditable key control.

Create a key ring and CMEK

# Enable the Cloud KMS API
gcloud services enable cloudkms.googleapis.com \
--project=<project-id>

# Create a key ring in the same region as your data
gcloud kms keyrings create soc2-keyring \
--location=us-central1 \
--project=<project-id>

# Create a symmetric encryption key (for CMEK use)
gcloud kms keys create cmek-prod \
--keyring=soc2-keyring \
--location=us-central1 \
--purpose=encryption \
--rotation-period=7776000s \
--next-rotation-time=$(date -u -d "+90 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v+90d +%Y-%m-%dT%H:%M:%SZ) \
--project=<project-id>

# Verify rotation schedule
gcloud kms keys describe cmek-prod \
--keyring=soc2-keyring \
--location=us-central1 \
--project=<project-id> \
--format="table(name,rotationPeriod,nextRotationTime)"

Restrict KMS key access — least privilege

# Grant only the required service account permission to use the key
gcloud kms keys add-iam-policy-binding cmek-prod \
--keyring=soc2-keyring \
--location=us-central1 \
--member="serviceAccount:storage-sa@<project-id>.iam.gserviceaccount.com" \
--role="roles/cloudkms.cryptoKeyEncrypterDecrypter" \
--project=<project-id>

# Deny key destruction to all except the Key Admin role
gcloud kms keys get-iam-policy cmek-prod \
--keyring=soc2-keyring \
--location=us-central1 \
--project=<project-id>

Reference: Cloud KMS documentation → · Cloud KMS key rotation →


2. Cloud Storage — Encryption at Rest

Cloud Storage always encrypts data at rest with AES-256 (Google-managed key by default). For SOC 2, use CMEK for auditable key control.

Enable CMEK on a Cloud Storage bucket

# Grant the Cloud Storage service account permission to use the key
STORAGE_SA=$(gcloud storage service-agent --project=<project-id>)

gcloud kms keys add-iam-policy-binding cmek-prod \
--keyring=soc2-keyring \
--location=us-central1 \
--member="serviceAccount:${STORAGE_SA}" \
--role="roles/cloudkms.cryptoKeyEncrypterDecrypter" \
--project=<project-id>

# Create a bucket with CMEK
gcloud storage buckets create gs://prod-sensitive-data \
--project=<project-id> \
--location=us-central1 \
--uniform-bucket-level-access \
--default-encryption-key=projects/<project-id>/locations/us-central1/keyRings/soc2-keyring/cryptoKeys/cmek-prod

# Update an existing bucket to use CMEK
gcloud storage buckets update gs://existing-bucket \
--default-encryption-key=projects/<project-id>/locations/us-central1/keyRings/soc2-keyring/cryptoKeys/cmek-prod

Block public access on all buckets

# Disable public access on a specific bucket
gcloud storage buckets update gs://prod-sensitive-data \
--no-public-access-prevention=false

# Enforce public access prevention org-wide via Organization Policy
gcloud resource-manager org-policies set-policy \
--organization=<org-id> \
- <<'EOF'
name: organizations/<org-id>/policies/storage.publicAccessPrevention
spec:
rules:
- enforce: true
EOF

# Verify bucket encryption and public access settings
gcloud storage buckets describe gs://prod-sensitive-data \
--format="table(defaultKmsKeyName,iamConfiguration.publicAccessPrevention,iamConfiguration.uniformBucketLevelAccess.enabled)"

Reference: Cloud Storage encryption → · Customer-managed encryption keys for GCS →


3. Persistent Disk — Encryption at Rest

All Compute Engine Persistent Disks are encrypted by default (Google-managed). Use CMEK for auditable control.

# Grant Compute Engine service account access to the KMS key
CE_SA="service-<project-number>@compute-system.iam.gserviceaccount.com"

gcloud kms keys add-iam-policy-binding cmek-prod \
--keyring=soc2-keyring \
--location=us-central1 \
--member="serviceAccount:${CE_SA}" \
--role="roles/cloudkms.cryptoKeyEncrypterDecrypter" \
--project=<project-id>

# Create a VM with CMEK-encrypted boot disk
gcloud compute instances create vm-prod \
--project=<project-id> \
--zone=us-central1-a \
--machine-type=n2-standard-2 \
--image-family=debian-12 \
--image-project=debian-cloud \
--disk="boot=yes,device-name=boot,kms-key=projects/<project-id>/locations/us-central1/keyRings/soc2-keyring/cryptoKeys/cmek-prod" \
--no-address

# Create a standalone encrypted disk
gcloud compute disks create data-disk-prod \
--project=<project-id> \
--zone=us-central1-a \
--size=100GB \
--type=pd-ssd \
--kms-key=projects/<project-id>/locations/us-central1/keyRings/soc2-keyring/cryptoKeys/cmek-prod

# Audit disks without CMEK
gcloud compute disks list \
--project=<project-id> \
--format="table(name,zone,diskEncryptionKey.kmsKeyName)" | \
grep -v "kmsKeyName" # Disks with no KMS key are using Google-managed keys

Reference: Persistent Disk encryption →


4. Cloud SQL — Encryption at Rest

Cloud SQL instances are encrypted by default. Enable CMEK for auditable key management and to satisfy CC6.1 requirements.

# Grant Cloud SQL service account access to the KMS key
SQL_SA=$(gcloud sql instances describe <instance-name> \
--project=<project-id> \
--format="value(serviceAccountEmailAddress)" 2>/dev/null || \
echo "service-<project-number>@gcp-sa-cloud-sql.iam.gserviceaccount.com")

gcloud kms keys add-iam-policy-binding cmek-prod \
--keyring=soc2-keyring \
--location=us-central1 \
--member="serviceAccount:${SQL_SA}" \
--role="roles/cloudkms.cryptoKeyEncrypterDecrypter" \
--project=<project-id>

# Create a Cloud SQL PostgreSQL instance with CMEK
gcloud sql instances create prod-pg \
--project=<project-id> \
--database-version=POSTGRES_15 \
--region=us-central1 \
--tier=db-n1-standard-2 \
--disk-encryption-key=projects/<project-id>/locations/us-central1/keyRings/soc2-keyring/cryptoKeys/cmek-prod \
--no-assign-ip \
--network=projects/<project-id>/global/networks/prod-vpc \
--backup-start-time=03:00 \
--enable-bin-log

# Verify encryption key assignment
gcloud sql instances describe prod-pg \
--project=<project-id> \
--format="value(diskEncryptionConfiguration.kmsKeyName)"

Enable Cloud SQL audit logs:

gcloud sql instances patch prod-pg \
--project=<project-id> \
--database-flags=log_connections=on,log_disconnections=on,log_duration=on,log_min_duration_statement=1000

Reference: Cloud SQL CMEK → · Cloud SQL best practices →


5. Secret Manager — Secret Storage and Rotation

Store all credentials, API keys, and connection strings in Secret Manager. Never store them in environment variables, source code, or startup scripts.

# Enable Secret Manager API
gcloud services enable secretmanager.googleapis.com \
--project=<project-id>

# Create a secret (stored encrypted with CMEK or Google-managed key)
gcloud secrets create prod-db-password \
--project=<project-id> \
--replication-policy=user-managed \
--locations=us-central1

# Add the secret value
echo -n "my-strong-password" | \
gcloud secrets versions add prod-db-password \
--data-file=- \
--project=<project-id>

# Grant a service account access to read a specific secret
gcloud secrets add-iam-policy-binding prod-db-password \
--project=<project-id> \
--member="serviceAccount:app-sa@<project-id>.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"

# Access a secret from application code (using the default credential chain)
gcloud secrets versions access latest \
--secret=prod-db-password \
--project=<project-id>

Automatic rotation using Cloud Functions or Cloud Run:

# Create a Pub/Sub topic for rotation notifications
gcloud pubsub topics create secret-rotation \
--project=<project-id>

# Configure rotation on the secret
gcloud secrets update prod-db-password \
--project=<project-id> \
--add-topics=projects/<project-id>/topics/secret-rotation \
--next-rotation-time=$(date -u -d "+30 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v+30d +%Y-%m-%dT%H:%M:%SZ) \
--rotation-period=2592000s # 30 days

# List all secrets and their rotation status
gcloud secrets list \
--project=<project-id> \
--format="table(name,rotation.nextRotationTime,replication.userManaged.replicas[0].location)"

Reference: Secret Manager documentation → · Secret rotation →


6. Other Services — Encryption Checklist

ServiceControlHow to verify
BigQueryCMEK on datasetsbq show --format=prettyjson <project>:<dataset> | jq .defaultEncryptionConfiguration
Pub/SubCMEK on topicsgcloud pubsub topics describe <topic> --format="value(kmsKeyName)"
Cloud SpannerCMEK on instancegcloud spanner instances describe <instance> --format="value(encryptionConfig.kmsKeyName)"
Memorystore (Redis)Transit encryptiongcloud redis instances describe <name> --region=<region> --format="value(transitEncryptionMode)"
Cloud ComposerCMEK for DAGs and logsgcloud composer environments describe <name> --format="value(config.encryptionConfig.kmsKeyName)"
Artifact RegistryCMEK for container imagesgcloud artifacts repositories describe <repo> --location=<loc> --format="value(kmsKeyName)"

SOC 2 Evidence for Encryption

Evidence itemHow to collect
KMS key list with rotation schedulegcloud kms keys list --keyring=soc2-keyring --location=us-central1
Cloud Storage bucket encryption configgcloud storage buckets describe gs://<bucket> --format="value(defaultKmsKeyName)"
Persistent Disk CMEK statusgcloud compute disks list --format="table(name,diskEncryptionKey.kmsKeyName)"
Cloud SQL CMEK statusgcloud sql instances describe <name> --format="value(diskEncryptionConfiguration.kmsKeyName)"
Secret Manager rotation policygcloud secrets describe <secret> --format="value(rotation)"
SCC encryption findingsGCP Console → Security Command Center → Findings → Category: CRYPTOGRAPHIC_KEY
Organization Policy — storage.publicAccessPreventiongcloud resource-manager org-policies describe storage.publicAccessPrevention --organization=<org-id>