VPC Service Controls: Data Exfiltration Prevention on GCP

Stolen credentials can be used to exfiltrate data even if your network is secure. VPC Service Controls create a security perimeter around GCP APIs so that even valid credentials can't move data outside your organization. This guide configures service perimeters, access policies, and ingress/egress rules.

A cloud security breach often follows a predictable pattern: an attacker steals credentials (from a misconfigured CI/CD pipeline, a phishing attack, or a leaked environment variable), uses those credentials to authenticate to GCP APIs, and exfiltrates data to an external storage location they control.

The traditional response is to invest in credential protection: MFA, short-lived tokens, regular rotation. These are necessary but insufficient. If credentials are stolen and used before they're rotated, the data is gone.

VPC Service Controls (VPC-SC) adds a second layer of defense that operates independently of credentials. It creates a security perimeter around your GCP APIs — BigQuery, Cloud Storage, Secret Manager, and others — that restricts which GCP identities can call those APIs and from where. Even if an attacker has valid credentials, they can't use them to move data outside your perimeter.

How VPC Service Controls Work

VPC Service Controls use two concepts: access policies (organization-level configuration containers) and service perimeters (the actual security boundaries).

When you place projects inside a service perimeter, API requests to protected services must come from inside the perimeter. An API call to bigquery.googleapis.com from a GCE VM inside the perimeter succeeds. The same API call from an external IP address — even using valid credentials — gets a 403 error with the body "VPC Service Controls: Request is prohibited by organization's policy".

This works because VPC-SC checks request context (project membership, IP address, access level) in addition to credentials. A stolen API key used from outside the perimeter doesn't satisfy the context check.

Setting Up Your First Perimeter

# Step 1: Enable the Access Context Manager API
gcloud services enable accesscontextmanager.googleapis.com

# Step 2: Create an access policy for your organization
gcloud access-context-manager policies create   --organization=ORGANIZATION_ID   --title="Production Security Policy"

# Note the policy name — you'll use it in subsequent commands
POLICY_ID=$(gcloud access-context-manager policies list   --organization=ORGANIZATION_ID   --format="value(name)" | head -1 | awk -F/ '{print $NF}')

# Step 3: Create an access level for trusted networks/identities
cat > access_level.yaml << 'EOF'
- name: accessPolicies/POLICY_ID/accessLevels/trusted_network
  title: Trusted Network
  basic:
    conditions:
    - ipSubnetworks:
      - "203.0.113.0/24"   # Corporate office
      - "10.0.0.0/8"        # VPC private ranges
      members:
      - serviceAccount:dataflow-sa@my-project.iam.gserviceaccount.com
      - serviceAccount:pipeline-sa@my-project.iam.gserviceaccount.com
EOF

gcloud access-context-manager levels create-file   --policy=$POLICY_ID   --file=access_level.yaml
# Step 4: Start in DRY-RUN mode — monitor before enforcing
gcloud access-context-manager perimeters dry-run create production-perimeter   --policy=$POLICY_ID   --title="Production Data Perimeter"   --resources=projects/my-project   --restricted-services=bigquery.googleapis.com,storage.googleapis.com,secretmanager.googleapis.com,cloudfunctions.googleapis.com

# Monitor dry-run violations for 1-2 weeks before enforcing
gcloud logging read   'resource.type="audited_resource" AND protoPayload.metadata.violationReason!=""'   --freshness=24h

Why Dry-Run Mode Is Critical

Enforcing VPC-SC without testing first will break things. Common legitimate flows that need explicit allowlisting:

  • External CI/CD systems (GitHub Actions, Jenkins) that write to Cloud Storage
  • Third-party analytics tools that read from BigQuery
  • Data transfer from partner organizations
  • Customer-facing APIs that read from Firestore or Cloud Storage

The dry-run mode logs violations without blocking them. Spend 1-2 weeks reviewing the logs, identify legitimate external access, and add access levels or ingress rules to allow them.

Enforcing the Perimeter

After dry-run validation:

# Enforce the perimeter
gcloud access-context-manager perimeters dry-run enforce production-perimeter   --policy=$POLICY_ID

# Verify enforcement
gcloud access-context-manager perimeters describe production-perimeter   --policy=$POLICY_ID   --format="value(status.restrictedServices)"

Configuring Ingress and Egress Rules

Ingress rules allow specific external identities or networks to access resources inside the perimeter. Egress rules control what resources inside the perimeter can communicate with outside it.

# Ingress rule: Allow GitHub Actions to write to Cloud Storage
cat > ingress_rule.yaml << 'EOF'
- ingressFrom:
    sources:
    - accessLevel: accessPolicies/POLICY_ID/accessLevels/github_actions
    identities:
    - serviceAccount:github-actions-sa@my-project.iam.gserviceaccount.com
  ingressTo:
    operations:
    - serviceName: storage.googleapis.com
      methodSelectors:
      - method: google.storage.objects.create
      - method: google.storage.objects.update
    resources:
    - "projects/my-project"
EOF

gcloud access-context-manager perimeters update production-perimeter   --policy=$POLICY_ID   --set-ingress-policies=ingress_rule.yaml

# Egress rule: Allow Cloud Dataflow inside the perimeter to read from external sources
cat > egress_rule.yaml << 'EOF'
- egressFrom:
    identities:
    - serviceAccount:dataflow-sa@my-project.iam.gserviceaccount.com
  egressTo:
    operations:
    - serviceName: storage.googleapis.com
      methodSelectors:
      - method: google.storage.objects.get
    resources:
    - "projects/partner-project"  # Partner project outside perimeter
EOF

gcloud access-context-manager perimeters update production-perimeter   --policy=$POLICY_ID   --set-egress-policies=egress_rule.yaml

Protecting Specific Services

Different services warrant different levels of protection:

BigQuery

BigQuery is a primary exfiltration target — it holds analytical data and has an easy-to-use export API. Key controls:

# Restrict BigQuery exports to authorized destinations only
# Add bigquery.googleapis.com to restricted services
gcloud access-context-manager perimeters update production-perimeter   --policy=$POLICY_ID   --add-restricted-services=bigquery.googleapis.com

# Also enable BigQuery's organization policy to restrict export destinations
gcloud org-policies set-policy bq_export_policy.yaml

# bq_export_policy.yaml
cat > bq_export_policy.yaml << 'EOF'
name: organizations/ORGANIZATION_ID/policies/gcp.bigquery.allowDataExportToStorageLocations
spec:
  rules:
  - values:
      allowedValues:
      - "gs://my-authorized-export-bucket/"
EOF

Cloud Storage

# Prevent bucket creation without uniform bucket-level access
gcloud org-policies set-policy storage_policy.yaml

# storage_policy.yaml
cat > storage_policy.yaml << 'EOF'
name: organizations/ORGANIZATION_ID/policies/storage.uniformBucketLevelAccess
spec:
  rules:
  - enforce: true
EOF

# Enforce public access prevention across all buckets
gcloud org-policies set-policy public_access_policy.yaml

# public_access_policy.yaml
cat > public_access_policy.yaml << 'EOF'
name: organizations/ORGANIZATION_ID/policies/storage.publicAccessPrevention
spec:
  rules:
  - enforce: true
EOF

Secret Manager

# Add Secret Manager to VPC-SC perimeter
# This ensures secrets can only be accessed from within the perimeter
gcloud access-context-manager perimeters update production-perimeter   --policy=$POLICY_ID   --add-restricted-services=secretmanager.googleapis.com

Troubleshooting VPC Service Controls Violations

When a legitimate application breaks after VPC-SC enforcement, diagnose with:

# Find recent VPC-SC violations
gcloud logging read   'resource.type="audited_resource" AND protoPayload.status.code=403 AND protoPayload.metadata.violationReason!=""'   --freshness=24h   --format="table(timestamp,protoPayload.authenticationInfo.principalEmail,protoPayload.resourceName,protoPayload.metadata.violationReason)"

The violation log entry includes:

  • principalEmail: Which identity made the request
  • resourceName: Which GCP resource was being accessed
  • violationReason: Why it was blocked (outside perimeter, access level not met, etc.)
  • callerIp: The source IP address

For each violation, determine whether to:

  • Add the identity to the perimeter's service accounts list
  • Create an access level that matches the caller's network
  • Add an ingress rule for the specific API method and source

VPC-SC and Private Google Access

For VMs inside your VPC to access GCP APIs over private IP (not public internet), configure Private Google Access:

# Enable Private Google Access on your subnet
gcloud compute networks subnets update my-subnet   --region=europe-west4   --enable-private-ip-google-access

# Add the restricted.googleapis.com DNS zone to ensure API calls
# route to VPC-SC endpoints (not public Google API endpoints)
gcloud dns managed-zones create restricted-googleapis   --visibility=private   --networks=my-vpc   --dns-name=googleapis.com

gcloud dns record-sets create restricted.googleapis.com   --type=A   --rrdatas="199.36.153.4,199.36.153.5,199.36.153.6,199.36.153.7"   --ttl=300   --zone=restricted-googleapis

Using restricted.googleapis.com (rather than googleapis.com) ensures that API requests from your VMs go through VPC-SC enforcement — requests that don't meet the perimeter conditions are blocked rather than bypassing VPC-SC via the public API endpoint.

Multi-Organization Perimeters

For organizations that share data with partners, VPC-SC supports cross-organization access bridges:

# Create a perimeter bridge allowing partner project access
gcloud access-context-manager perimeters create partner-bridge   --policy=$POLICY_ID   --title="Partner Data Bridge"   --perimeter-type=PERIMETER_TYPE_BRIDGE   --resources=projects/my-project,projects/partner-project

VPC Service Controls, combined with Security Command Center monitoring and BeyondCorp access controls, creates a layered data protection architecture that addresses the most common cloud data breach scenarios.

For the monitoring layer that detects when VPC-SC prevents an attack, see our Security Command Center guide. For the access control layer, see our BeyondCorp Zero Trust guide.