VPC Service Controls Explained: How Google Cloud Perimeters Actually Work
Key Takeaways
- VPC Service Controls governs where data can go rather than who's allowed to touch it. IAM decides identity and role. VPC Service Controls adds an independent perimeter around a set of projects, and that perimeter holds even for a principal with valid IAM permissions on the resource inside it.
- A perimeter only restricts the services explicitly listed on it. Everything not on that restricted services list moves in and out of the perimeter exactly as it would without one, and that's the most common reason a perimeter doesn't block something a team assumed it would.
- Dry run exists because enforced mode breaks things you don't expect. Cloud Build's worker VMs run outside the perimeter by default, and Cloud Composer environments lose access to public PyPI the moment they're inside one. Both are documented limitations, and dry-run logs surface them before enforcement takes a pipeline down.
- Bridges and ingress/egress rules solve different cross-perimeter problems. A bridge opens broad, bidirectional access between two perimeters in the same organization. An ingress or egress rule grants one narrow, named exception without merging either perimeter's policy.
What Are VPC Service Controls
VPC Service Controls is a Google Cloud security capability that creates a service perimeter around a set of projects, or a folder, to protect against data exfiltration, both from external attackers and from insiders acting entirely within their own IAM permissions. Google frames it as a defense against exactly that combination: accidental or targeted misuse from outside the organization and from inside it, which is a more specific claim than a generic promise of extra security. It's built for the scenario where a principal has entirely legitimate IAM access to a BigQuery dataset or a Cloud Storage bucket and could still copy that data somewhere it shouldn't go, because IAM evaluates identity and role but never the destination.
A service perimeter closes that gap by adding a second, independent check. Google's own framing is direct about the division of labor: IAM handles granular, identity-based access control, and VPC Service Controls handles broader, context-based perimeter security, including control over egress. The two are meant to run together rather than as alternatives, closer in spirit to the layered guardrails native to AWS, Azure, and GCP than to a bolt-on scanning tool. A request to a restricted service is evaluated against the perimeter first. If it doesn't satisfy an allowed access level or an explicit ingress or egress rule, it's denied before the resource's own IAM bindings are even consulted.
Where VPC Service Controls Sits Relative to IAM and VPC Firewalls
Security architects working across Google Cloud tend to have all three of these mechanisms live at once, and conflating them is where the gaps start.
| Layer | Answers | Scope |
|---|---|---|
| IAM | Who can act, with what role | Every resource, every API call |
| VPC firewall rules | Which network traffic is allowed | VM-to-VM and VM-to-internet traffic on a VPC network |
| VPC Service Controls | Which perimeter a request can cross | Google-managed services on the restricted services list |
VPC firewall rules don't touch calls to a managed service like Cloud Storage or BigQuery at all. Those services aren't VMs sitting on a VPC network waiting for a firewall rule to evaluate their traffic; they're API endpoints reachable from anywhere a caller has network access and IAM authorization. That's precisely the gap VPC Service Controls exists to close: it's the layer that decides whether a request to one of those managed services is allowed to originate from, or terminate at, a location outside a defined boundary, independent of what the firewall or IAM would otherwise permit.
Also separate VPC Service Controls from GCP Organization Policy, since both get described loosely as "guardrails" and practitioners moving between them sometimes assume more overlap than exists. Organization Policy governs which configurations are allowed to exist (whether a bucket can be given a public IP for instance), regardless of who's requesting it. VPC Service Controls doesn't evaluate configuration at all; it evaluates whether a request to a restricted service is allowed to cross a perimeter boundary. A resource can be perfectly compliant under Organization Policy and still be denied by VPC Service Controls, because the two are answering different questions.
How a Service Perimeter Is Built
A service perimeter is defined by three things working together: the resources it protects, the services it restricts, and the access levels that determine what's allowed to cross it.
Access Context Manager is the service that defines access levels: named, reusable conditions built from attributes like IP address ranges, device policy, and identity that determine whether a given request is permitted to cross a perimeter boundary. A basic access level combines conditions like an IP subnet range or a required device policy using a combining function of AND or OR. A custom access level uses the Common Expression Language (CEL) to express logic a basic level can't: time-of-day windows, device security posture beyond simple ownership, and compliance signals pulled from third-party device vendors.
The restricted services list is the perimeter's actual scope. Only services explicitly added to this list are governed by the perimeter at all; everything else is unaffected regardless of how the perimeter is otherwise configured. This is the single detail most responsible for a perimeter not doing what someone assumed it did.
| Service | API name | Why teams commonly restrict it |
|---|---|---|
| Cloud Storage | storage.googleapis.com | Highest-volume path for exfiltrating structured and unstructured data via bucket copies |
| BigQuery | bigquery.googleapis.com | Protects datasets from being exported or queried into an external project |
| Vertex AI / Gemini Enterprise | aiplatform.googleapis.com | Keeps training data and model outputs from leaving the environment through an agent, pipeline, or notebook |
| Cloud KMS | cloudkms.googleapis.com | Prevents key material from being requested by a principal or service outside the perimeter |
| Pub/Sub | pubsub.googleapis.com | Blocks a subscription from being created in an external project against a protected topic |
| Artifact Registry | artifactregistry.googleapis.com | Protects container images and packages from being pulled outside the perimeter |
| Compute Engine | compute.googleapis.com | Covers metadata and disk-level operations that could otherwise stage data for exfiltration |
This table is illustrative and far from exhaustive. The full catalog spans a much longer and growing list, with each entry marked GA, Preview, or Deprecated, and Google recommends pulling the current list directly with gcloud alpha access-context-manager supported-services list rather than trusting a static list printed in an article. That distinction matters most for AI workloads: a team that adopted Vertex AI or an agent platform after the perimeter was built has to confirm the service was added to the restricted list rather than assume it inherited protection automatically. That gap is also where the same architectural question shows up in Native's AI-specific solution outcomes: a perimeter that isn't tracking new services as they're adopted stops being a perimeter for the parts of the environment that matter most.
Two different things get called "not protected" here, and mixing them up causes real incidents. A service that's on Google's supported list but not added to your perimeter's restricted services configuration is untouched by that perimeter: traffic to it crosses the boundary exactly like it would without a perimeter at all. A service that isn't supported by VPC Service Controls yet is a different problem: Google's own documentation warns that unexpected issues can occur when a request touches a supported service through infrastructure shared with an unsupported one. Treat the supported-services list as the source of truth for what a perimeter can promise. Don't assume coverage based on how central a service feels to the environment.
Real VPC Service Controls Examples
Defining an access level. A basic access level restricting requests to a corporate IP range:
gcloud access-context-manager levels create corp_network \
--title="Corporate Network" \
--basic-level-spec=level-spec.yaml \
--policy=1234567890
- ipSubnetworks:
- 203.0.113.0/24
- 2001:db8::/32Creating a perimeter in dry run. Every perimeter should start here, before anything is blocked:
gcloud access-context-manager perimeters dry-run create data_perimeter \
--perimeter-title="Production Data Perimeter (Dry Run)" \
--perimeter-resources=projects/111111111111,projects/222222222222 \
--perimeter-restricted-services=storage.googleapis.com,bigquery.googleapis.com,aiplatform.googleapis.com \
--policy=1234567890The --perimeter- prefixed flags apply because this perimeter doesn't exist yet. Adding a dry-run configuration to a perimeter that's already created and enforced uses the bare --resources and --restricted-services flags instead.
Once the dry-run violation logs are clean, promote the same configuration to enforced:
gcloud access-context-manager perimeters dry-run enforce data_perimeter \
--policy=1234567890Adding a narrow ingress exception. Letting service accounts that satisfy the corp network access level read Cloud Storage objects in one protected project, without opening a bridge:
- ingressFrom:
identityType: ANY_SERVICE_ACCOUNT
sources:
- accessLevel: accessPolicies/1234567890/accessLevels/corp_network
ingressTo:
operations:
- serviceName: storage.googleapis.com
methodSelectors:
- method: "google.storage.objects.get"
resources:
- projects/111111111111
title: Allow corp-network service accounts to read objects
gcloud access-context-manager perimeters update data_perimeter \
--set-ingress-policies=ingress-rules.yaml \
--policy=1234567890Perimeter Bridges vs. Ingress and Egress Rules
Both mechanisms let traffic cross a perimeter boundary that would otherwise block it, but they solve different problems and aren't interchangeable.
A perimeter bridge connects two perimeters directly, with perimeterType: PERIMETER_TYPE_BRIDGE set on the connecting resource. Bridges are bidirectional by default: both perimeters get equal access within the bridge's scope, and the projects on each side have to belong to perimeters in the same organization. A single project can have multiple bridges connecting it to other projects. What a bridge doesn't do is override either project's own configuration: the access levels and restricted-services list that govern a project are still controlled solely by the perimeter that project belongs to. A bridge opens the door between two rooms; it doesn't change what's allowed to happen inside either one.
Ingress and egress rules are narrower and attach directly to a single perimeter's policy. An ingress rule permits a specific identity, or a request matching a specific access level, to reach a named service and method inside the perimeter from outside it. An egress rule permits a specific identity or resource inside the perimeter to reach a named service, role, or external resource outside it. Neither creates a standing, bidirectional relationship between two perimeters. Each rule is scoped to exactly the identity, service, and method it names. That makes it the better fit for a single, auditable exception, like one CI/CD service account that needs to write to one bucket in a different perimeter, rather than for two teams that need broad, ongoing collaboration across many services.
VPC Service Controls Limits and Quotas
The exact numbers here are the kind of thing Google adjusts as the service matures, so check the current quotas documentation before designing an architecture that depends on being close to a limit. As of this writing:
| Limit | Applies to | Current value |
|---|---|---|
| Service perimeters per access policy | Includes bridge perimeters | 10,000 |
| Protected resources (projects) per access policy | Cumulative across all perimeters | 40,000 |
| Attributes per service perimeter | Ingress and egress rule entries, counted separately for enforced and dry-run configurations | 6,000 |
| Organization-level access policies | Per organization | 1 |
| Folder- or project-scoped access policies | Per organization | 50 |
| VPC networks per access policy | Cumulative across all perimeters | 500 |
Common Mistakes
Assuming the restricted services list covers everything running in the perimeter. It covers exactly the services named on it, and nothing implied by proximity or by how sensitive a workload feels. A newly adopted service, especially an AI platform or a managed database added after the perimeter was designed, isn't restricted until someone adds it explicitly.
Leaving hybrid or on-prem traffic pointed at private.googleapis.com. That virtual IP allows access to Google APIs beyond what VPC Service Controls supports, which is useful when a workload depends on an unsupported API, but it also means hybrid traffic isn't bound by the same restricted-services enforcement as everything else in the perimeter. restricted.googleapis.com denies access to anything not supported by VPC Service Controls, and it's the right default for on-prem or hybrid connectivity into a perimeter-protected environment unless there's a specific reason to use the broader path.
Skipping dry run and enforcing on day one. Cloud Build's worker VMs sit outside the perimeter even when the triggering project is inside it, so builds lose access to protected resources until you explicitly add the Cloud Build service account to an access level or ingress rule. Cloud Composer environments lose access to public PyPI repositories the moment they're inside a perimeter. Both are documented, known limitations, and they're exactly the kind of infrastructure dependency dry-run mode exists to surface before enforcement breaks a running pipeline.
Reaching for a perimeter bridge when an ingress or egress rule would do. A bridge opens broad, bidirectional access across every restricted service in scope for both perimeters. A named exception scoped to one identity and one method is smaller, easier to audit, and doesn't leave a standing relationship in place after the specific need that justified it has passed.
Treating a bridge as something that can loosen a project's own access levels. It can't. A project's access levels and restricted-services configuration are controlled solely by the perimeter it belongs to, and a bridge doesn't change that even for traffic crossing through it.
Best Practices
Start every perimeter in dry run and review the violation logs in Cloud Logging before enforcing anything. Design perimeters around data sensitivity tiers or environments (production separated from non-production), rather than one perimeter spanning the whole organization, which becomes unmanageable and starts generating exceptions faster than anyone can track them. Prefer ingress and egress rules for narrow, specific exceptions, and reserve bridges for cases where two perimeters need ongoing, broad collaboration inside the same organization. Route perimeter-protected VPC traffic through restricted.googleapis.com for hybrid and on-prem access by default, and treat a move to private.googleapis.com as a decision that needs a documented reason instead of a default fallback. Re-check the supported services list whenever a new Google Cloud service enters the environment, particularly AI and ML services, since a perimeter doesn't retroactively cover something it was never told to restrict.
Why Native
Native maps which projects sit inside which VPC Service Controls perimeter, which services are actually restricted versus only assumed to be, and where a bridge or ingress rule has quietly widened a boundary the security team believed was closed. See how Native enforces data perimeters across Google Cloud.
FAQ
What's the difference between VPC Service Controls and IAM?
IAM controls which principals can perform which actions on which resources. VPC Service Controls adds an independent perimeter around a set of projects that restricts specific Google-managed services from being reached across that boundary, and it holds even for a principal with valid IAM permissions on the resource inside.
Does VPC Service Controls block access from a principal with valid IAM permissions?
Yes, if the request crosses a perimeter boundary for a restricted service and doesn't satisfy an allowed access level or an explicit ingress rule. VPC Service Controls evaluates the perimeter before IAM's resource-level permissions come into play for that request.
What happens if a service isn't on the restricted services list?
Traffic to that service crosses the perimeter boundary exactly as it would without a perimeter in place. Only services explicitly added to a perimeter's restricted services configuration are governed by it.
What's the difference between a perimeter bridge and an ingress or egress rule?
A bridge creates broad, bidirectional access between two perimeters in the same organization, without changing either project's own access levels or restricted services. An ingress or egress rule is a narrower, one-directional exception scoped to a specific identity and a specific service and method, attached directly to one perimeter's own policy.
Should on-premises traffic use private.googleapis.com or restricted.googleapis.com?
Use restricted.googleapis.com by default for hybrid or on-prem access into a perimeter-protected environment, since it denies access to anything not supported by VPC Service Controls. private.googleapis.com allows a broader range of Google APIs, including ones VPC Service Controls doesn't cover, which is sometimes necessary but should be a deliberate exception rather than the default.
Do I need to enforce a perimeter immediately, or can I test it first?
Test it first. VPC Service Controls supports a dry-run configuration that logs what the perimeter would have blocked without actually blocking it, which is how most unexpected breakages, particularly in CI/CD and cross-project service calls, get caught before they affect production traffic.