Trust is earned, not given

A different perspective

2023-04-25 · Projects

DevOps on Google Cloud, part 4: VPC, service accounts, and Secret Manager — the security skeleton

Part 4from the DevOps on Google Cloud series · 6 parts in all

Part 4: the security skeleton. GCP's two standout pieces here are the global VPC (subnets are regional, but the VPC and its firewall rules are global objects) and service accounts as first-class identities for workloads — plus the least- promoted feature in cloud security: IAM Conditions.

VPC and firewall rules: global by default

A GCP VPC spans all regions; a subnet is regional. Firewall rules are VPC-level, applied by tags and service accounts rather than subnet membership — which makes the classic three-tier chain cleaner to express:

# The chain: db <- app <- lb. Tags target workloads, not subnets.
gcloud compute networks create maplecart-vnet --subnet-mode=custom
gcloud compute networks subnets create app-useast1 \
  --network=maplecart-vnet --region=us-east1 --range=10.0.2.0/24

gcloud compute firewall-rules create allow-lb-to-app \
  --network=maplecart-vnet --direction=INGRESS \
  --allow=tcp:8080 --source-ranges=130.211.0.0/22,35.191.0.0/16 \  # Google LB health ranges
  --target-tags=web                                                # the web-tier tag

gcloud compute firewall-rules create allow-app-to-db \
  --network=maplecart-vnet --direction=INGRESS \
  --allow=tcp:5432 \
  --source-service-accounts=maplecart-web@maplecart.iam.gserviceaccount.com \  # identity, not IP!
  --target-tags=db

That last rule is the one to notice: the database allows connections from a service account, not a CIDR. Renaming an instance or scaling the app fleet doesn't touch firewall rules — the identity is the anchor.

Service accounts and Workload Identity: the keyless default

Every workload runs as a service account; Cloud Run, GKE Workload Identity, and Cloud Functions attach it at deploy time. The rule mirrors the other series: no downloaded JSON keys — keys exist only for third parties that can't do federation:

# The app's identity and its least-privilege grants
gcloud iam service-accounts create maplecart-web
gcloud iam service-accounts add-iam-policy-binding [email protected] \
  --member=serviceAccount:[email protected] --role=roles/run.invoker
gcloud projects add-iam-policy-binding maplecart \
  --member=serviceAccount:maplecart-web@... --role=roles/secretmanager.secretAccessor \
  --condition='expression=resource.name.startsWith("projects/123/secrets/maplecart-"),title=db-only'
#        ^ IAM Conditions: the SA can read ONLY secrets prefixed "maplecart-" - least privilege
#          with conditions, not with dozens of bespoke roles.

Secret Manager: the audit-friendly vault

gcloud secrets create maplecart-db-password --replication-policy=user-managed \
  --locations=us-east1
echo -n "$PASSWORD" | gcloud secrets versions add maplecart-db-password --data-file=-

# Access from the app: the SA above + one line of SDK
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient()
name = f"projects/maplecart/secrets/maplecart-db-password/versions/latest"
password = client.access_secret_version(name=name).payload.data.decode()

Versioning is built in (rotate = add version; rollback = point at previous), every access is logged, and the IAM Conditions from above can scope which secrets a service may read. With network-by-identity, keyless workloads, and conditioned secret access, this is the tightest default skeleton of the three clouds. Next: delivery and observability.