GCP / Guide 5

NAT, Cloud Armor and admin access

Cloud NAT, Cloud Armor, private access to Google APIs, VPC Service Controls, public access prevention, and IAP instead of open SSH and RDP.

Firewall rules filter by address and port. They don't stop someone copying data from a Cloud Storage bucket to their own project, they don't inspect web requests, and they don't make a Cloud SQL instance private. This guide covers the layers around them: controlled outbound, Cloud Armor, private access to Google services, VPC Service Controls, public access prevention for buckets, and getting to VMs without opening SSH or RDP.

What sits where

ControlProtects againstWorks at
Firewall rules and policiesUnwanted connections between addresses and portsVM network interfaces (layer 4; layer 7 with NGFW Enterprise)
Cloud NATVMs needing public IPs to reach the internetRegion and VPC
Cloud ArmorAttacks on websites and APIs; DDoSGoogle Cloud external load balancers
Private Google Access and Private Service ConnectTraffic to Google APIs over public addressesSubnet, VPC
VPC Service ControlsData leaving your projects through Google APIs, even with valid credentialsService perimeter around projects
Public access preventionBuckets made public by mistakeBucket, project, organisation
IAP TCP forwarding and OS LoginOpen SSH and RDP; SSH keys nobody managesAdmin access to VMs

Control outbound traffic

A VM without an external IP can't reach the internet until you give it a way out. The standard way is Cloud NAT: a managed, regional NAT on a Cloud Router, with no VM to run and no inbound exposure. Use manual NAT IP addresses when partners need fixed addresses to allow-list, and turn on NAT logging for errors at least.

gcloud compute routers create rtr-notes-test-us-east1 --network=vpc-notes-test --region=us-east1
gcloud compute routers nats create nat-notes-test-us-east1 --router=rtr-notes-test-us-east1 --region=us-east1 `
    --auto-allocate-nat-external-ips --nat-all-subnet-ip-ranges --enable-logging --log-filter=ERRORS_ONLY

Then stop VMs getting public IPs at all with the compute.vmExternalIpAccess organisation policy (allow-listing the few that need one), and remember that Google charges for external IPv4 addresses. For filtering outbound traffic by domain name, use Cloud NGFW Standard (FQDN objects in firewall policies) or Secure Web Proxy for HTTP(S).

Cloud Armor

Any website or API on the internet should sit behind a Google Cloud external Application Load Balancer with a Cloud Armor security policy. Cloud Armor gives you:

  • Preconfigured WAF rules based on the OWASP Core Rule Set (SQL injection, cross-site scripting, local file inclusion and others), tunable by sensitivity level.
  • Rate limiting and throttling per client, and bot management with reCAPTCHA.
  • IP and geography rules, and Google's threat intelligence lists (Enterprise).
  • DDoS protection for everything behind the load balancer, with Adaptive Protection to spot and suggest rules for layer 7 attacks.

Add new rules in preview mode, check the load balancer logs for what they would have blocked, tune, then enforce. Cloud Armor Enterprise (a subscription) adds threat intelligence, DDoS response support and bill protection for internet-facing services where downtime is expensive. And make sure traffic can't go around the load balancer: backends should only accept Google's front-end ranges (35.191.0.0/16, 130.211.0.0/22), not 0.0.0.0/0.

gcloud compute security-policies create sp-web-notes-test --description="WAF for app.gcp.ravindran.in"
gcloud compute security-policies rules create 1000 --security-policy=sp-web-notes-test `
    --expression="evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})" --action=deny-403 --preview
gcloud compute backend-services update bes-web-notes-test --security-policy=sp-web-notes-test --global

Private access to Google services

  • Private Google Access (a subnet setting) lets VMs without external IPs reach Google APIs such as Cloud Storage and BigQuery. Turn it on for every subnet; it's free.
  • Private Service Connect for Google APIs gives the APIs an internal IP address in your VPC, reachable from on-premises over VPN or Interconnect, and lets you choose which API bundle (all APIs, or only those that support VPC Service Controls) is reachable.
  • Private services access (VPC peering) and Private Service Connect endpoints give managed services such as Cloud SQL, Memorystore and partner services private IPs. With the sql.restrictPublicIp organisation policy, databases never get a public address.

VPC Service Controls

IAM decides who can call an API. VPC Service Controls decide from where and to where: a service perimeter around a set of projects blocks calls to protected services (Cloud Storage, BigQuery, Secret Manager and many more) that come from outside the perimeter, and stops data being copied from inside to a project outside, even by someone with valid credentials. It's the main defence against data exfiltration with stolen keys.

  • Start in dry-run mode. Violations are logged but not blocked. Run it for a few weeks and read the logs: CI/CD pipelines, BI tools and support access are the usual surprises.
  • Allow what's legitimate with access levels (corporate IP ranges, managed devices) and ingress/egress rules for specific identities and projects.
  • One perimeter per data sensitivity (for example production data) is easier to run than many small ones.

Public access prevention for buckets

A bucket is public when its IAM policy grants a role to allUsers or allAuthenticatedUsers. Public access prevention blocks both, whatever the IAM policy says. Enforce it with the storage.publicAccessPrevention organisation policy everywhere, and serve genuinely public content through a load balancer with Cloud CDN, or a bucket in a separate project exempted from the policy. Uniform bucket-level access (enforced by default in new organisations) removes per-object ACLs, so the bucket's IAM policy is the whole story.

# Enforce on one bucket
gcloud storage buckets update gs://prj-notes-test-logs --public-access-prevention --uniform-bucket-level-access

# Who can read a bucket
gcloud storage buckets get-iam-policy gs://prj-notes-test-logs --format=json

Get-GcpBucketPublicAccessReport.ps1 checks every bucket's IAM policy, public access prevention, uniform access, versioning and soft delete across projects.

Admin access without open ports

SSH and RDP should never be open to the internet. Google Cloud has a clean alternative built in:

IAP TCP forwarding tunnels SSH, RDP or any TCP port through Identity-Aware Proxy. The VM needs no external IP, and the only firewall rule is one that allows 22 and 3389 from IAP's range, 35.235.240.0/20. Who can connect is controlled by IAM (roles/iap.tunnelResourceAccessor, optionally with conditions), and every connection is logged.

# Allow IAP to reach SSH and RDP on tagged admin targets (or better, by service account)
gcloud compute firewall-rules create allow-iap-to-admin --network=vpc-notes-test --direction=INGRESS `
    --action=ALLOW --rules=tcp:22,tcp:3389 --source-ranges=35.235.240.0/20 --priority=3000

# SSH (uses IAP automatically when the VM has no external IP)
gcloud compute ssh vm-notes-test-01 --zone=us-east1-b --tunnel-through-iap

# RDP: connect your Remote Desktop client to localhost:13389
gcloud compute start-iap-tunnel vm-notes-test-01 3389 --local-host-port=localhost:13389 --zone=us-east1-b

Pair it with OS Login for Linux: SSH access is granted through IAM roles (roles/compute.osLogin, or osAdminLogin for sudo), keys are tied to the person's Google identity, 2-step verification can be required, and leavers lose access when their account is disabled. Enforce it with the compute.requireOsLogin organisation policy, and block project-wide SSH keys in metadata.