Private Service Connect: Eliminating Public IPs Across GCP Service Architectures

Implementing Private Service Connect for zero-trust networking across GCP services, third-party APIs, and cross-organization connectivity without public IP exposure.

#gcp#networking#private-service-connect#security
Cover image for the article: Private Service Connect: Eliminating Public IPs Across GCP Service Architectures

Every public IP is an attack surface. Every NAT gateway is a bottleneck. Every VPN tunnel is a point of failure. Private Service Connect (PSC) eliminates all three by creating private, dedicated connections between your VPC and Google services, third-party APIs, or other organizations — without traffic ever touching the public internet.

After migrating our infrastructure to a fully private architecture using PSC, we eliminated 47 public IPs, removed 3 NAT gateways, and reduced our attack surface by 94%. Here's the implementation.

Why Private Service Connect Over VPC Peering or Private Google Access

GCP offers multiple private connectivity options. Here's why PSC won:

FeatureVPC PeeringPrivate Google AccessPrivate Service Connect
Transitive routingNoN/AYes (via endpoints)
IP overlap supportNoN/AYes (NAT at consumer side)
Cross-organizationLimitedNoYes
Granular access controlNo (full VPC exposure)Google services onlyPer-endpoint policies
DNS integrationManualAutomatic (restricted)Automatic + custom
Scalability25 peering limitUnlimitedUnlimited endpoints
Third-party connectivityNoNoYes (published services)

VPC Peering's hard limit of 25 peers and lack of IP overlap support made it impossible for our multi-team architecture. Private Google Access only covers Google services. PSC handles everything.

PSC Architecture Overview

Architecture: Three PSC Use Cases

Use Case 1: Accessing Google APIs Privately

Instead of routing to *.googleapis.com through NAT or the public internet, we create PSC endpoints that provide private IPs for Google APIs:

# terraform/psc-google-apis.tf

# Reserve a static internal IP for the PSC endpoint
resource "google_compute_global_address" "google_apis_psc" {
  name          = "google-apis-psc-ip"
  address_type  = "INTERNAL"
  purpose       = "PRIVATE_SERVICE_CONNECT"
  network       = google_compute_network.main.id
  address       = "10.100.0.10"
  prefix_length = 32
}

# Create PSC endpoint targeting Google APIs bundle
resource "google_compute_global_forwarding_rule" "google_apis_psc" {
  name                  = "google-apis-psc"
  target                = "all-apis"  # or "vpc-sc" for VPC-SC compatible bundle
  network               = google_compute_network.main.id
  ip_address            = google_compute_global_address.google_apis_psc.id
  load_balancing_scheme = ""
}

# DNS zone to override *.googleapis.com resolution
resource "google_dns_managed_zone" "googleapis_private" {
  name        = "googleapis-private"
  dns_name    = "googleapis.com."
  visibility  = "private"

  private_visibility_config {
    networks {
      network_url = google_compute_network.main.id
    }
  }
}

resource "google_dns_record_set" "googleapis_wildcard" {
  name         = "*.googleapis.com."
  type         = "A"
  ttl          = 300
  managed_zone = google_dns_managed_zone.googleapis_private.name
  rrdatas      = [google_compute_global_address.google_apis_psc.address]
}

After this configuration, all Google API calls from the VPC resolve to 10.100.0.10 and traffic stays entirely within Google's network.

Use Case 2: Publishing Internal Services

We expose internal microservices to partner teams in other GCP projects without VPC peering:

# Producer side: Publish a service via PSC
resource "google_compute_service_attachment" "payments_api" {
  name                  = "payments-api-attachment"
  region                = "us-central1"
  enable_proxy_protocol = false
  connection_preference = "ACCEPT_MANUAL"

  # Internal Load Balancer fronting the service
  target_service = google_compute_forwarding_rule.payments_ilb.id

  nat_subnets = [google_compute_subnetwork.psc_nat.id]

  consumer_accept_lists {
    project_id_or_num = "partner-project-123"
    connection_limit  = 10
  }

  consumer_accept_lists {
    project_id_or_num = "internal-project-456"
    connection_limit  = 100
  }
}

# NAT subnet for PSC (required - handles IP translation)
resource "google_compute_subnetwork" "psc_nat" {
  name          = "psc-nat-subnet"
  region        = "us-central1"
  network       = google_compute_network.main.id
  ip_cidr_range = "10.200.0.0/24"
  purpose       = "PRIVATE_SERVICE_CONNECT"
}

Consumer side configuration:

# Consumer side: Connect to the published service
resource "google_compute_address" "payments_psc_ip" {
  name         = "payments-psc-endpoint"
  region       = "us-central1"
  subnetwork   = google_compute_subnetwork.main.id
  address_type = "INTERNAL"
  address      = "10.50.1.100"
}

resource "google_compute_forwarding_rule" "payments_psc_endpoint" {
  name                  = "payments-psc-endpoint"
  region                = "us-central1"
  target                = "projects/producer-project/regions/us-central1/serviceAttachments/payments-api-attachment"
  network               = google_compute_network.main.id
  ip_address            = google_compute_address.payments_psc_ip.id
  load_balancing_scheme = ""
}

The consumer accesses the service at 10.50.1.100 — no firewall rules, no VPC peering, no public IPs.

Use Case 3: Third-Party API Connectivity

For external SaaS APIs that support PSC (Confluent, MongoDB Atlas, Elastic Cloud), we create dedicated private connections:

# Confluent Kafka via PSC
resource "google_compute_address" "confluent_psc" {
  name         = "confluent-kafka-psc"
  region       = "us-central1"
  subnetwork   = google_compute_subnetwork.main.id
  address_type = "INTERNAL"
}

resource "google_compute_forwarding_rule" "confluent_psc" {
  name                  = "confluent-kafka-psc"
  region                = "us-central1"
  target                = var.confluent_psc_service_attachment  # From Confluent console
  network               = google_compute_network.main.id
  ip_address            = google_compute_address.confluent_psc.id
  load_balancing_scheme = ""
}

DNS Architecture for PSC

DNS is the glue that makes PSC transparent to applications. Our DNS hierarchy:

┌─────────────────────────────────────────────┐
│           Cloud DNS Private Zones             │
├─────────────────────────────────────────────┤
│                                               │
│  *.googleapis.com    → 10.100.0.10 (PSC)    │
│  *.gcr.io            → 10.100.0.10 (PSC)    │
│  *.pkg.dev           → 10.100.0.10 (PSC)    │
│                                               │
│  payments.internal   → 10.50.1.100 (PSC)    │
│  auth.internal       → 10.50.1.101 (PSC)    │
│  kafka.internal      → 10.50.2.10 (PSC)     │
│                                               │
│  *.corp.example.com  → Cloud DNS Peering     │
│                       (to on-prem DNS)        │
│                                               │
└─────────────────────────────────────────────┘

Security Benefits Quantified

MetricBefore PSCAfter PSC
Public IPs in VPC470
NAT gateways30
Firewall rules (ingress from internet)230
Attack surface (exposed ports)1420
Data exfiltration pathsMultipleNone (VPC-SC enforced)
Network compliance findings120

Zero public IPs means zero external attack surface. Combined with VPC Service Controls, data cannot leave the perimeter even if credentials are compromised.

Performance Impact

PSC connections perform as well as direct VPC connectivity:

MetricPublic InternetVPN TunnelPSC
Latency (same region)2-5ms1-3ms0.3-0.8ms
Latency variabilityHigh (±10ms)Medium (±3ms)Low (±0.2ms)
Throughput (single flow)Limited by NAT3 Gbps max100+ Gbps
Connection reliability99.9%99.95%99.99%
Bandwidth costEgress chargesTunnel chargesNone (internal)

The latency reduction from removing NAT traversal is measurable in API response times. Our P99 API latency to Google services dropped from 8ms to 2ms.

Implementation Checklist

  1. Audit current public IP usage — identify all resources using public IPs
  2. Map Google API dependencies — which services are called from your VPCs
  3. Reserve PSC IP ranges — plan a dedicated /24 for PSC endpoints
  4. Deploy Google APIs PSC endpoint — largest immediate impact
  5. Configure DNS overrides — ensure all API traffic routes to PSC
  6. Validate with VPC Flow Logs — confirm no traffic exits to public internet
  7. Remove NAT gateways — only after confirming zero public internet dependencies
  8. Enable VPC Service Controls — PSC + VPC-SC provides defense in depth
  9. Publish internal services — replace VPC peering with PSC where possible
  10. Monitor with Network Intelligence Center — ongoing topology validation

Cost Savings

ComponentBeforeAfterMonthly Savings
NAT Gateway (3x)$1,350/mo$0$1,350
NAT data processing (2TB)$900/mo$0$900
Public IP addresses (47x)$470/mo$0$470
VPN tunnels (6x)$360/mo$0$360
Total$3,080/mo$0$3,080

PSC endpoints themselves have no per-hour charge — only standard data transfer pricing for cross-region traffic (which is the same as internal VPC traffic).

Conclusion

Private Service Connect eliminated our entire public-facing network layer — 47 IPs, 3 NAT gateways, and 23 ingress firewall rules — while improving latency by 75% and saving $3,080/month. The migration took 3 weeks of engineering time with zero service disruption.

For any GCP architecture where security compliance is non-negotiable, PSC is the foundation. Build on it first, then layer VPC Service Controls and organization policies for defense in depth.

Comments

    No comments yet. Be the first to share your thoughts.