A Terraform configuration for deploying a secure Databricks workspace on Google Cloud Platform (GCP) using customer-managed VPC with Customer-Managed Encryption Keys (CMEK) for enhanced data security.
This deployment creates a secure Databricks workspace with encryption featuring:
graph TB
subgraph "GCP Project - Host/Shared VPC"
subgraph "Customer VPC"
SUBNET[Node Subnet<br/>Databricks Clusters]
NAT[Cloud NAT<br/>Internet Access]
end
subgraph "Cloud KMS"
KEYRING[Key Ring<br/>databricks-keyring]
KEY[Crypto Key<br/>databricks-key]
KEY_VER[Key Version<br/>Auto-rotated annually]
end
end
subgraph "GCP Project - Service/Consumer"
subgraph "Databricks Managed - Encrypted"
GKE[GKE Cluster<br/>Encrypted with CMEK]
GCS_DBFS[GCS Bucket - DBFS<br/>Encrypted with CMEK]
GCS_SYS[System Storage<br/>Encrypted with CMEK]
DISK[Persistent Disks<br/>Encrypted with CMEK]
end
end
subgraph "Databricks Control Plane"
CONTROL[Databricks Control Plane<br/>accounts.gcp.databricks.com]
end
subgraph "Users"
USER[Workspace Users<br/>Web Browser]
end
KEYRING --> KEY
KEY --> KEY_VER
KEY -.Encrypts.-> GCS_DBFS
KEY -.Encrypts.-> GCS_SYS
KEY -.Encrypts.-> DISK
KEY -.Encrypts.-> GKE
SUBNET --> CONTROL
SUBNET --> NAT
GKE --> SUBNET
USER --> CONTROL
CONTROL --> GKE
style CONTROL fill:#FF3621
style KEY fill:#FBBC04
style KEYRING fill:#FBBC04
style GCS_DBFS fill:#4285F4
style GKE fill:#4285F4
style SUBNET fill:#34A853
This configuration does NOT include:
For these features, see:
../byovpc-psc-cmek-ws/../end2end/../infra4db/../byovpc-ws/https://accounts.gcp.databricks.comautomation-sa@project.iam.gserviceaccount.com)This configuration requires a pre-existing VPC with appropriate subnets. To create the infrastructure, use ../infra4db/ first.
Required:
/24 CIDR recommended (251 usable IPs)The service account needs these IAM roles on both projects:
On Service/Consumer Project (where workspace will be created):
roles/compute.networkAdminroles/iam.serviceAccountAdminroles/resourcemanager.projectIamAdminroles/storage.adminroles/cloudkms.admin (for KMS key management)roles/cloudkms.cryptoKeyEncrypterDecrypter (for encryption operations)On Host/Shared VPC Project (if using Shared VPC):
roles/compute.networkUserroles/compute.securityAdminFor detailed role requirements, see Databricks CMEK Documentation.
You need two project IDs:
google_project_name): Where Databricks resources will be createdgoogle_shared_vpc_project): Where your VPC network existsNote: If not using Shared VPC, both values should be the same project ID.
gcloud CLI) configureddatabricks_admin_user variableCustomer-Managed Encryption Keys (CMEK) allow you to control the encryption keys used to protect your data in Databricks. With CMEK, you:
The CMEK created in this configuration encrypts:
| Resource | Encrypted Data | Key Usage |
|---|---|---|
| DBFS Storage | User data, notebooks, libraries in GCS | STORAGE |
| System Storage | Workspace configurations, logs | STORAGE |
| GKE Cluster Disks | Persistent disks for cluster nodes | STORAGE |
| Notebook State | Notebook execution state | MANAGED |
resource "google_kms_key_ring" "databricks_key_ring"
Purpose: Logical grouping of encryption keys
Name: databricks-keyring
Location: Same region as workspace
Note: Cannot be deleted, only disabled
resource "google_kms_crypto_key" "databricks_key"
Purpose: Actual encryption key used by Databricks
Name: databricks-key
Key Purpose: ENCRYPT_DECRYPT
Rotation Period: 1 year (31536000 seconds)
Algorithm: Google-managed symmetric encryption
Use Cases:
STORAGE: Encrypts DBFS, logs, GKE disksMANAGED: Encrypts notebook state in control planeresource "databricks_mws_customer_managed_keys" "this"
Links the KMS key to your Databricks account
Attributes:
kms_key_id: Full resource ID of the crypto keyuse_cases: List of [“STORAGE”, “MANAGED”]account_id: Your Databricks account IDKeys automatically rotate annually (365 days):
rotation_period = "31536000s" # 1 year in seconds
What happens during rotation:
Best Practices:
Databricks needs these permissions on your KMS key:
cloudkms.cryptoKeyVersions.useToEncrypt
cloudkms.cryptoKeyVersions.useToDecrypt
cloudkms.cryptoKeys.get
Granted automatically when you register the key with Databricks.
If you already have a KMS key, you can use it instead of creating a new one:
workspace.tf:
# Comment lines 28-56 (key ring and crypto key creation)
# Line 20: variable "cmek_resource_id" {}
workspace.auto.tfvars:
cmek_resource_id = "projects/my-project/locations/us-central1/keyRings/my-keyring/cryptoKeys/my-key"
# Line 65: kms_key_id = var.cmek_resource_id
Key Requirements:
ENCRYPT_DECRYPT# Set the service account to impersonate
gcloud config set auth/impersonate_service_account automation-sa@project.iam.gserviceaccount.com
# Generate access token
export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
# Download service account key
gcloud iam service-accounts keys create ~/sa-key.json \
--iam-account=automation-sa@project.iam.gserviceaccount.com
# Set environment variable
export GOOGLE_APPLICATION_CREDENTIALS=~/sa-key.json
Security Best Practice: Use Option 1 (impersonation) to avoid managing key files.
For detailed authentication guide, see ../sa-impersonation.md.
This deployment uses three Terraform providers:
Manages resources in the service/consumer project.
provider "google" {
project = var.google_project_name
region = var.google_region
}
Manages resources in the host/shared VPC project.
provider "google" {
alias = "vpc_project"
project = var.google_shared_vpc_project
region = var.google_region
}
Creates workspace and account-level configurations.
provider "databricks" {
alias = "accounts"
host = "https://accounts.gcp.databricks.com"
google_service_account = var.google_service_account_email
}
Used for:
Manages workspace-level configurations after workspace creation.
provider "databricks" {
alias = "workspace"
host = databricks_mws_workspaces.databricks_workspace.workspace_url
google_service_account = var.google_service_account_email
}
Used for:
Before deploying the workspace, ensure you have:
google_vpc_id variablegoogle_shared_vpc_projectnode_subnet variable/24 (251 IPs)google_region variableEgress (Outbound) - Required:
*.gcp.databricks.com (control plane)*.googleapis.com (GCP APIs)*.docker.io, *.maven.org, *.pypi.org (package downloads)Ingress (Inbound) - Optional:
../byovpc-psc-cmek-ws/)Minimum required firewall rules (managed separately):
Source: Node subnet CIDR
Target: Node subnet CIDR
Protocols: TCP, UDP, ICMP (all ports)
Source: Node subnet CIDR
Target: 0.0.0.0/0
Protocols: TCP 443, 3306 (HTTPS, external metastore)
Source: Node subnet CIDR
Target: cloudkms.googleapis.com
Protocols: TCP 443
For infrastructure creation including firewall rules, use ../infra4db/.
resource "google_kms_key_ring" "databricks_key_ring"
Creates:
databricks-keyringImportant Notes:
resource "google_kms_crypto_key" "databricks_key"
Creates:
Key Attributes:
name: databricks-keykey_ring: Reference to key ring created aboverotation_period: 31536000s (1 year)resource "databricks_mws_customer_managed_keys" "this"
Registers:
Lifecycle:
ignore_changes = all: Prevents Terraform from modifying key after creationresource "databricks_mws_networks" "databricks_network"
Creates:
Key Attributes:
network_name: Generated with random suffix for uniquenessnetwork_project_id: Host/shared VPC projectvpc_id: Your VPC namesubnet_id: Your node subnet namesubnet_region: Must match workspace regionresource "databricks_mws_workspaces" "databricks_workspace"
Creates:
Key Attributes:
workspace_name: Display name in Databricks consolelocation: GCP region for workspacecloud_resource_container.gcp.project_id: Your service projectnetwork_id: Links to network configurationGCE_config.connectivity_type: PRIVATE_NODE_PUBLIC_MASTER (default)GCE_config.master_ip_range: IP range for GKE master (optional)storage_customer_managed_key_id: Links to CMEK registrationDeployment Time: ~10-15 minutes
resource "databricks_user" "me"
resource "databricks_group_member" "allow_me_to_login"
Creates:
admins groupgraph TD
A[Start] --> B[Authenticate with GCP]
B --> C[Verify Existing VPC & Subnet]
C --> D[Create Random Suffix]
D --> E[Create KMS Key Ring]
E --> F[Create Crypto Key with Rotation]
F --> G[Register CMEK with Databricks]
G --> H[Create Network Configuration]
H --> I[Create Databricks Workspace with CMEK]
I --> J[Wait for Workspace Provisioning]
J --> K[Lookup Admins Group]
K --> L[Create User in Workspace]
L --> M[Add User to Admins Group]
M --> N[Workspace Ready - Encrypted]
style A fill:#4285F4
style N fill:#34A853
style I fill:#FF3621
style G fill:#FBBC04
sequenceDiagram
participant TF as Terraform
participant GCP as Google Cloud
participant KMS as Cloud KMS
participant DB_ACC as Databricks Account
participant DB_WS as Databricks Workspace
Note over TF,GCP: Phase 1: Validation
TF->>GCP: Verify VPC exists
TF->>GCP: Verify Subnet exists
TF->>GCP: Verify Service Account permissions
Note over TF,KMS: Phase 2: KMS Key Creation
TF->>KMS: Create Key Ring
TF->>KMS: Create Crypto Key
TF->>KMS: Configure Key Rotation (365 days)
KMS-->>TF: Key Resource ID
Note over TF,DB_ACC: Phase 3: CMEK Registration
TF->>DB_ACC: Register CMEK
DB_ACC->>KMS: Verify Key Access
KMS-->>DB_ACC: Access Granted
DB_ACC-->>TF: CMEK ID
Note over TF,DB_ACC: Phase 4: Network Configuration
TF->>DB_ACC: Create Network Config
DB_ACC-->>TF: Network ID
Note over TF,DB_ACC: Phase 5: Workspace Creation
TF->>DB_ACC: Create Workspace with CMEK
DB_ACC->>GCP: Deploy GKE Cluster (CMEK encrypted)
DB_ACC->>GCP: Create GCS Bucket (CMEK encrypted)
DB_ACC->>KMS: Encrypt with Customer Key
GCP-->>DB_ACC: Resources Ready (Encrypted)
DB_ACC-->>TF: Workspace URL + ID
Note over TF,DB_WS: Phase 6: User Assignment
TF->>DB_WS: Lookup Admins Group
TF->>DB_WS: Create User
TF->>DB_WS: Add User to Admins Group
DB_WS-->>TF: User Configured
Note over DB_WS: Workspace Ready (Encrypted with CMEK)
Edit providers.auto.tfvars:
# Service Account for Terraform authentication
google_service_account_email = "automation-sa@my-service-project.iam.gserviceaccount.com"
# Service/Consumer Project (where workspace will be created)
google_project_name = "my-service-project"
# Host/Shared VPC Project (where VPC network exists)
# If not using Shared VPC, use the same value as google_project_name
google_shared_vpc_project = "my-host-project"
# GCP Region (must match KMS key location)
google_region = "us-central1"
Edit workspace.auto.tfvars:
# Databricks Account ID (found in Account Console)
databricks_account_id = "12345678-1234-1234-1234-123456789abc"
# Databricks Account Console URL
databricks_account_console_url = "https://accounts.gcp.databricks.com"
# Workspace Name
databricks_workspace_name = "my-encrypted-workspace"
# Admin User Email (must be valid user in your organization)
databricks_admin_user = "admin@mycompany.com"
# Existing VPC Name
google_vpc_id = "my-vpc-network"
# Existing Subnet Name
node_subnet = "databricks-node-subnet"
# Optional: GKE Master IP Range (for private GKE)
# Uncomment if you want to specify a custom range
# GCE_master_ip_range = "10.3.0.0/28"
If you already have a KMS key:
Step 1: Uncomment the variable declaration in workspace.tf:
# Line 20
variable "cmek_resource_id" {}
Step 2: Add key resource ID to workspace.auto.tfvars:
cmek_resource_id = "projects/my-project/locations/us-central1/keyRings/my-keyring/cryptoKeys/my-key"
Step 3: Comment out key creation blocks in workspace.tf:
# Lines 28-56 (key ring and crypto key resources)
Step 4: Update local variable reference in workspace.tf:
# Line 53: Change from
locals {
cmek_resource_id = google_kms_crypto_key.databricks_key.id
}
# To:
locals {
cmek_resource_id = var.cmek_resource_id
}
# Line 65: Change from
kms_key_id = local.cmek_resource_id
# To:
kms_key_id = var.cmek_resource_id
Before deployment, verify:
# Option 1: Service Account Impersonation (Recommended)
gcloud config set auth/impersonate_service_account automation-sa@project.iam.gserviceaccount.com
export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
# Option 2: Service Account Key
export GOOGLE_APPLICATION_CREDENTIALS=~/sa-key.json
cd gcp/gh-repo/gcp/terraform-scripts/byovpc-cmek-ws
terraform init
Expected Output:
Initializing provider plugins...
- Installing databricks/databricks...
- Installing hashicorp/google...
- Installing hashicorp/random...
Terraform has been successfully initialized!
terraform validate
terraform plan
Review the plan carefully:
Expected Resources:
random_string.databricks_suffixgoogle_kms_key_ring.databricks_key_ringgoogle_kms_crypto_key.databricks_keydatabricks_mws_customer_managed_keys.thisdatabricks_mws_networks.databricks_networkdatabricks_mws_workspaces.databricks_workspacedatabricks_user.medatabricks_group_member.allow_me_to_loginterraform apply
Type yes when prompted.
Deployment Time: ~12-18 minutes
Progress:
terraform output
Expected Outputs:
key_self_link = "projects/my-project/locations/us-central1/keyRings/databricks-keyring/cryptoKeys/databricks-key"
workspace_url = "https://12345678901234.1.gcp.databricks.com"
Verify KMS Key Usage:
# Check key versions
gcloud kms keys versions list \
--key=databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project
# View key details
gcloud kms keys describe databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project
After successful deployment, the following outputs are available:
| Output | Description | Example |
|---|---|---|
key_self_link |
Full resource ID of the KMS crypto key | projects/my-project/locations/us-central1/keyRings/databricks-keyring/cryptoKeys/databricks-key |
workspace_url |
URL to access the Databricks workspace | https://1234567890123456.1.gcp.databricks.com |
To view outputs:
terraform output
terraform output key_self_link
terraform output workspace_url
terraform output -json
Error:
Error: google: could not find default credentials
Solution:
# Verify authentication
gcloud auth list
# Re-authenticate
gcloud auth application-default login
# Or set service account impersonation
export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
Error:
Error: googleapi: Error 403: Permission 'cloudkms.cryptoKeys.create' denied
Solution:
Verify service account has KMS admin role:
# Check KMS permissions
gcloud projects get-iam-policy my-service-project \
--flatten="bindings[].members" \
--filter="bindings.members:serviceAccount:automation-sa@my-service-project.iam.gserviceaccount.com"
# Grant KMS admin role
gcloud projects add-iam-policy-binding my-service-project \
--member="serviceAccount:automation-sa@my-service-project.iam.gserviceaccount.com" \
--role="roles/cloudkms.admin"
Error:
Error: cannot register customer-managed key: key not accessible
Causes:
Solution:
gcloud kms keys describe databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project
# In workspace.auto.tfvars
google_region = "us-central1" # Must match key location
Error:
Error: Error creating KeyRing: googleapi: Error 409: KeyRing already exists
Solution:
Key rings cannot be deleted, only disabled. You have options:
Option A: Use existing key ring
# Import existing key ring into Terraform state
terraform import google_kms_key_ring.databricks_key_ring \
projects/my-project/locations/us-central1/keyRings/databricks-keyring
Option B: Use different key ring name
# In workspace.tf, change line 31:
resource "google_kms_key_ring" "databricks_key_ring" {
name = "databricks-keyring-v2" # Different name
location = var.google_region
}
Error:
Error: workspace creation failed: unable to access customer-managed key
Solution:
This usually means Databricks cannot use the KMS key. Check:
terraform state show databricks_mws_customer_managed_keys.this
gcloud kms keys get-iam-policy databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project
gcloud kms keys describe databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project \
--format="value(state)"
# Should output: ENABLED
terraform apply -target=databricks_mws_customer_managed_keys.this
Error: Key hasn’t rotated after expected period
Solution:
gcloud kms keys describe databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project \
--format="value(rotationPeriod)"
gcloud kms keys describe databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project \
--format="value(nextRotationTime)"
gcloud kms keys versions create \
--key=databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--primary \
--project=my-project
Error: Users cannot log in or clusters fail to start after disabling KMS key
Solution:
Critical: Disabling the KMS key will make your workspace inaccessible!
gcloud kms keys versions enable <VERSION> \
--key=databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project
gcloud kms keys versions describe <VERSION> \
--key=databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project
Prevention: Never disable or delete KMS keys used by active workspaces!
To destroy all resources created by this configuration:
terraform destroy
Warning: This will:
force_destroy = true)Before destroying:
Important: The KMS key ring will remain (cannot be deleted). The crypto key will be scheduled for deletion after a retention period (default 30 days).
Manual Cleanup Required:
# List key versions
gcloud kms keys versions list \
--key=databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project
# Destroy key versions (scheduled destruction)
gcloud kms keys versions destroy <VERSION> \
--key=databricks-key \
--keyring=databricks-keyring \
--location=us-central1 \
--project=my-project
After successfully deploying your CMEK-encrypted workspace, consider:
../byovpc-psc-cmek-ws/ for PSC + CMEK enabled workspace../end2end/ for complete workspace with Unity Catalog../uc/ for standalone Unity Catalog setupThis configuration is provided as a reference implementation for deploying Databricks workspaces with CMEK on GCP.