Most engineers think…
Most engineers think Google Cloud IAM is the whole story for data security — "if I lock down the roles tightly, nobody can take my data." So they treat a leaked key as just a permissions problem.
Wrong — and it's the gap that causes real breaches. IAM answers WHO can call an API, but a valid credential used by the wrong person from the wrong place still passes that check. VPC Service Controls answers a different question — WHERE the data may go — by drawing a service perimeter around managed services like BigQuery and Cloud Storage. Inside the wall data moves freely; crossing the wall is denied by default, regardless of IAM. So a leaked key that could read the dataset still cannot copy it out. IAM and VPC-SC are two different walls, and you need both.
① The exfiltration gap IAM cannot close
Picture Sneha, an L2 cloud-security engineer at Flipkart. A data analyst on her team has roles/bigquery.dataViewer on a dataset called customer_pii. The analyst downloads a service-account key to test a script, and the key file ends up committed to a public GitHub repo. Within hours a scanner bot has it. The attacker now holds a credential that, as far as IAM is concerned, is allowed to read that dataset.
Here is the uncomfortable part. IAM's job is to answer one question: can this principal perform this action on this resource? The stolen key is that principal, and the principal does have bigquery.dataViewer. So when the attacker runs bq query and then exports the results to a BigQuery dataset in their own personal project, IAM raises no objection — the role check passes. The data walks out the front door with a valid badge. IAM never asked the one question that matters here: should this data be allowed to leave the company at all?
This is the data exfiltration gap, and it is not theoretical. Google's own product page is blunt that VPC Service Controls exists to minimise data exfiltration risks from services like Cloud Storage and BigQuery — precisely because tight IAM alone does not stop a valid credential from copying data outward. The same gap opens whether the threat is an external attacker with a stolen key or a careless insider with legitimate access who simply copies a dataset to the wrong place.
The fix is a second, independent wall. VPC Service Controls (VPC-SC) lets you draw a service perimeter around a set of projects and the restricted services they use. Inside the perimeter, data flows freely. Crossing the perimeter — reading data out to a project that isn't inside, or copying a dataset to one — is denied by default, and crucially this happens independently of IAM. The leaked key still has the role; the perimeter still says no.
Your locker key is IAM — it proves you may open locker number 42. But the locker room of a bank has a second rule that has nothing to do with your key: no locker leaves the building. Even a thief who steals your key can open the locker, but he cannot carry the locker out the front door — the room itself won't allow it. VPC Service Controls is that room. IAM decides who can open the box; the perimeter decides that the box's contents can't be walked out of the building.
The gap, in one tap each
Tap each card — this is the exact reasoning an interviewer wants when they ask "why isn't IAM enough?"
IAM checks if a principal has the role. A stolen key is still that principal with that role. So: the role check passes.
With dataViewer you can query AND export results to another project. So: the data can leave with a valid badge.
A leaked key works from any IP on the planet — laptop, VPS, attacker box. So: location is invisible to IAM.
The perimeter asks if data may cross the boundary, not who you are. So: the export is blocked regardless of IAM.
Rahul at TCS argues: "We give every analyst exactly the minimum IAM role, so we can't be breached by a leaked key." Where does that reasoning fail?
Pause & Predict
Predict: if VPC-SC blocks data from crossing the perimeter "regardless of IAM", does that mean you can now be sloppy with IAM roles? Type your guess.
② How a perimeter works — ingress, egress & access levels
A service perimeter is just two lists plus a default rule. The first list is the projects (and optionally VPC networks) that sit inside the wall. The second is the restricted services — the Google-managed APIs you're protecting, named by their API hostname like bigquery.googleapis.com or storage.googleapis.com. The default rule is the whole point: requests that cross the boundary are denied, while requests that stay inside flow freely. Everything else in VPC-SC is about carving precise exceptions to that default deny.
Two words name the two directions of crossing. Ingress is a call coming in from outside the perimeter to a protected resource (an on-prem CI runner calling your bucket). Egress is the reverse — something inside reaching out to a project outside the perimeter (copying a dataset to a personal project). Both are blocked by default. To allow a specific, legitimate crossing you write an ingress rule or an egress rule that names exactly which identity may do which operation on which resource.
On top of ingress sits the context-aware layer: access levels. An access level is a named condition built in Access Context Manager — for example "the request must come from the corporate IP range 203.0.113.0/24, from a managed device, in India." You then attach that access level to an ingress rule, so even a valid identity is only let in when the context matches. IP range, geolocation, device posture and identity all become conditions on the wall.
▶ Watch a leaked-key export hit the wall
The attacker holds the leaked key and runs a BigQuery copy of customer_pii to their personal project. Follow the request as the perimeter evaluates it. Press Play for the healthy path, then Break it to see the failure.
# conditions.yaml - ipSubnetworks: - 203.0.113.0/24 gcloud access-context-manager levels create corp_in \ --title="Corp India network" \ --basic-level-spec=conditions.yaml \ --policy=accessPolicies/418972419283
Create request issued for: [corp_in] Waiting for operation [...] to complete...done. Created level [accessPolicies/418972419283/accessLevels/corp_in].
Symptom: a CI runner at Infosys has the right IAM role on the bucket, but every call returns Request is prohibited by organization's policy. Cause: the runner is outside the perimeter, so this is an ingress crossing — and IAM permissions alone don't satisfy a perimeter. Fix: add an ingress rule (or an access level for the runner's IP) that permits that identity to call the restricted service from outside. The perimeter is a separate gate; granting the role doesn't open it.
Aditya at Wipro sees a Cloud Function INSIDE the perimeter failing to write to a Pub/Sub topic in a DIFFERENT project that is OUTSIDE the perimeter. What kind of crossing is this, and what fixes it cleanly?
Pause & Predict
Predict: you attach an access level requiring 'corporate IP 203.0.113.0/24' to your ingress rule. An admin connects from a café on public Wi-Fi with a perfectly valid identity and full IAM rights. What happens? Type your guess.
③ Building it safely — dry-run first, then rules & private connectivity
Here is the rule that separates a calm rollout from a Sev-1 outage: never enforce a perimeter first. A new perimeter denies by default, so flipping straight to enforced mode will silently break every legitimate workflow you forgot about — backups, CI/CD, analytics jobs, partner integrations. Instead you start in dry-run mode, which logs exactly what would be denied while letting everything through.
The official ladder is four rungs. (1) Create the perimeter in dry-run with your restricted services but no ingress/egress rules. (2) Let traffic run for a couple of weeks and collect the violation logs — each one is a real workflow that crosses the boundary. (3) For every legitimate violation, add a precise ingress or egress rule (or an access level) until the dry-run logs go quiet. (4) Only when you see zero unexpected violations do you enforce. Google's guidance is to plan on roughly 2–4 weeks of dry-run analysis before flipping the switch.
gcloud access-context-manager perimeters dry-run create accProd \ --perimeter-title="accProd-data-perimeter" \ --perimeter-type=regular \ --perimeter-resources=projects/839271046512,projects/771203998145 \ --perimeter-restricted-services=bigquery.googleapis.com,storage.googleapis.com \ --policy=418972419283
Create dry-run spec request issued for: [accProd] Waiting for operation [accessPolicies/418972419283/...] to complete...done. Created dry-run spec for Service Perimeter [accProd].
A perimeter is only as good as the path traffic takes. If a VM inside the perimeter reaches BigQuery over a normal public Google API endpoint, that traffic technically rides the internet — which weakens the boundary. The fix is Private Google Access pointed at the restricted VIP. You route *.googleapis.com to restricted.googleapis.com, which resolves to 199.36.153.4/30 — a range that is not announced to the internet — so calls to your restricted services stay on Google's internal network and honour the perimeter.
One more building block for bigger orgs: the perimeter bridge. When two separate perimeters genuinely need to share data — say a central shared-data perimeter and a analytics perimeter — a bridge lets their projects talk both ways. Google's own guidance, though, is to prefer ingress/egress rules over bridges wherever you can, because rules are far more granular: a bridge opens broad bidirectional access, while a rule names one identity, one resource and one operation.
Don't assume — test it. From a VM or identity outside the perimeter, try a read or copy of a protected resource and confirm you get Request is prohibited by organization's policy. Then from inside the perimeter confirm the same call succeeds. If the outside call succeeds when it shouldn't, your perimeter is in dry-run (logging only) — check the Enforcement mode, because dry-run never actually denies.
Priya at PhonePe is asked to roll out VPC-SC across the prod org with zero downtime. Which rollout order is correct?
Pause & Predict
Predict: a team enables a perimeter in dry-run, sees zero violations for two days, and enforces. The next morning the nightly Dataflow export to a partner bucket fails. Why did dry-run not warn them? Type your guess.
④ Operating VPC-SC — violation logs, breakages & a worked block
Once the perimeter is live, most of your day-to-day is reading violations. When a request is blocked, the caller sees a short, deliberately vague error — Request is prohibited by organization's policy — plus a vpcServiceControlsUniqueIdentifier. That identifier is your key. You take it to Logs Explorer and find the matching audit entry, which carries the full story: the caller, the resource, the service, and the all-important violationReason.
The violation reasons are a small, learnable set. NO_MATCHING_ACCESS_LEVEL means the caller's IP, device or identity matched no ingress rule or access level (a client from outside the perimeter). RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER means one request touched resources in two different perimeters (the classic cross-project copy/export). NETWORK_NOT_IN_SAME_SERVICE_PERIMETER means the calling VPC network isn't inside the perimeter, and SERVICE_NOT_ALLOWED_FROM_VPC means the VPC config disallowed that service. Learn these four and you can triage almost any VPC-SC denial.
gcloud logging read ' protoPayload.metadata."@type"="type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata" AND protoPayload.metadata.violationReason="RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER" ' --project=prod-data-9f2a --limit=3 --format="value(protoPayload.metadata.violationReason, protoPayload.requestMetadata.callerIp, resource.labels.service)"
RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER 203.0.113.45 bigquery.googleapis.com RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER 203.0.113.45 bigquery.googleapis.com NETWORK_NOT_IN_SAME_SERVICE_PERIMETER 10.20.4.7 storage.googleapis.com
Three breakages cause most of the tickets, and none of them are bugs — they're the perimeter doing its job. CI/CD pipelines (Terraform, Cloud Build) run from outside the perimeter, so they hit ingress denials until you add a rule for the build identity — engineers on the Google issue trackers routinely report Terraform plans dying with vpcServiceControlsUniqueIdentifier errors mid-deploy. Cross-project analytics that read a dataset in another perimeter throw RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER. And public datasets — a query joining bigquery-public-data — can break because that public project sits outside your perimeter, needing an egress rule.
Karthik at ICICI faces this
Karthik, an L2 analyst, gets paged: a data scientist's nightly job that copies the curated 'customer_360' BigQuery dataset to a sandbox project for ML training started failing at 2 a.m. with 'Request is prohibited by organization's policy'. Nothing in IAM changed.
The sandbox project is OUTSIDE the prod perimeter that was enforced yesterday. The copy is therefore an egress crossing (inside → outside), and there's no egress rule permitting the data scientist's service account to copy to that project — so the default deny blocks it. IAM is fine; the perimeter is new.
Karthik takes the vpcServiceControlsUniqueIdentifier from the error, opens Logs Explorer, and filters on the VpcServiceControlAuditMetadata type. The entry shows violationReason RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER and the sandbox project as the destination — confirming it's an egress block, not an IAM issue.
Cloud Console → Logging → Logs Explorer → filter protoPayload.metadata."@type"="...VpcServiceControlAuditMetadata" → read violationReasonDecide the policy: either bring the sandbox project INTO the perimeter (if it should be trusted), or add a tightly-scoped egress rule (egressFrom = that one service account; egressTo = the sandbox project, bigquery.googleapis.com, the copy operation). Karthik chose the egress rule so only that identity, that destination and that operation are allowed.
Re-run the nightly copy → it succeeds; re-pull the audit log → no new RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER violation for that identity, and an unrelated outside caller is still denied.
- egressFrom:
identities:
- serviceAccount:ml-trainer@prod-data-9f2a.iam.gserviceaccount.com
egressTo:
resources:
- projects/552038174490 # the sandbox project
operations:
- serviceName: bigquery.googleapis.com
methodSelectors:
- method: "google.cloud.bigquery.v2.TableService.InsertTable"
# apply it to the perimeter's dry-run config first, then enforce
# gcloud access-context-manager perimeters dry-run update accProd \
# --set-egress-policies=egress-policy.yaml --policy=418972419283Update dry-run spec request issued for: [accProd] Waiting for operation [accessPolicies/418972419283/...] to complete...done. Updated dry-run spec for Service Perimeter [accProd].
Symptom: the ticket is fixed, but a reviewer flags that your egress rule used identityType: ANY_IDENTITY and resources: '*' to make the error go away fast. That re-opens the exact exfiltration path you built the perimeter to close — now any identity can egress to any project. Fix: scope egress (and ingress) rules to the single identity, the single destination resource, and the specific method. A perimeter with wildcard rules is a perimeter in name only.
An interviewer asks Meera: "A query joining your protected dataset with bigquery-public-data suddenly fails after you enforce a perimeter. Why, and what's the right fix?"
Step back to the picture you should be able to draw cold. IAM gates WHO can call an API; VPC-SC gates WHERE the data may go. The perimeter wraps your projects + restricted services with a default-deny; ingress/egress rules and access levels are the narrow, named exceptions; Private Google Access + the restricted VIP keep the traffic on Google's network; and the dry-run ladder (watch → add rules → enforce) is how you roll it out without an outage. Read the violationReason, scope every rule tightly, and you have anti-exfiltration that holds even when a credential leaks.
A housing-society gate has two checks. The guard's resident list is IAM — it confirms you live in flat B-402. But the gate-pass register for moving furniture out is VPC-SC — even a genuine resident can't carry a sofa out the gate without a signed move-out pass naming exactly what's leaving. The dry-run phase is the society first logging every item that goes out for a month (without stopping anyone) so they learn the normal pattern, then switching the rule on. IAM = do you belong here; VPC-SC = may this thing leave.
Pause & Predict
Predict: for the Professional Cloud Security Engineer exam, in ONE sentence, what is VPC Service Controls' role — and what is it NOT? Type your guess.
🤖 Ask the AI Tutor
Tap any question — instant, scoped to this lesson. No login, no waiting.
Pre-curated from GCP docs + community Q&A, scoped to this lesson. For a live prod issue, paste your export into chat.techclick.in.
📝 Wrap-up assessment — six more
You've answered 4 inline. Six left. 70% (7 of 10) marks the lesson complete on your profile. Tap Submit all answers at the end.
🧠 In your own words
Type one line: In one line, why can VPC Service Controls block a leaked service-account key that IAM happily lets through? Then compare to the expert version.
🗣 Teach a friend
Best way to lock it in — explain it in one line to a teammate. Tap to generate a paste-ready summary.
📖 Glossary
- VPC Service Controls (VPC-SC)
- A GCP control that draws a perimeter around managed services so data cannot leave it, independent of IAM.
- Service perimeter
- A security boundary around a set of projects and restricted services; free comms inside, deny across by default.
- Restricted services
- The Google-managed APIs you protect inside a perimeter, named by hostname (e.g. bigquery.googleapis.com).
- Ingress
- An API call from OUTSIDE the perimeter to a resource INSIDE it; denied unless an ingress rule or access level allows it.
- Egress
- A client/resource INSIDE the perimeter reaching a resource OUTSIDE it; denied unless an egress rule allows it.
- Access level
- A named condition in Access Context Manager (IP range, region, device, identity) attached to ingress rules.
- Access Context Manager
- The GCP service that defines access levels and holds the access policy under which perimeters live.
- Dry-run mode
- A perimeter config that logs violations as if enforced but does not deny — used to find breakages before enforcing.
- Restricted VIP
- restricted.googleapis.com → 199.36.153.4/30, a not-internet-announced range for VPC-SC-aware API access.
- Private Google Access
- Lets VMs without external IPs reach Google APIs over Google's internal network, paired with the restricted VIP.
- Perimeter bridge
- Lets projects in two perimeters communicate bidirectionally; Google prefers granular ingress/egress rules instead.
- violationReason
- The audit-log field naming why a request was denied — e.g. RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER, NO_MATCHING_ACCESS_LEVEL.
📚 Sources
- Google Cloud — "Overview of VPC Service Controls" (service perimeter definition: a security boundary that blocks cross-perimeter communication by default; restricted services; access levels; perimeter bridge; data-exfiltration mitigation for Cloud Storage and BigQuery). cloud.google.com/vpc-service-controls/docs/overview
- Google Cloud — "Dry run mode for service perimeters" + "Manage dry run configurations" (dry-run logs violations without denying; gcloud access-context-manager perimeters dry-run create / enforce; plan dry-run analysis before enforcing). cloud.google.com/vpc-service-controls/docs/dry-run-mode · cloud.google.com/vpc-service-controls/docs/manage-dry-run-configurations
- Google Cloud — "Ingress and egress rules" + "Context-aware access with ingress rules" (ingress = outside→in, egress = inside→outside; egressFrom/egressTo with identities, resources, operations, serviceName, methodSelectors; access levels as conditions). cloud.google.com/vpc-service-controls/docs/ingress-egress-rules · cloud.google.com/vpc-service-controls/docs/context-aware-access
- Google Cloud — "VPC Service Controls for BigQuery" + "Private Google Access with VPC Service Controls" (BigQuery copy/export across a perimeter is blocked independent of IAM; restricted.googleapis.com resolves to 199.36.153.4/30, a range not announced to the internet). cloud.google.com/bigquery/docs/vpc-sc · cloud.google.com/vpc-service-controls/docs/private-connectivity
- Google Cloud — "Troubleshoot common issues" / VPC-SC troubleshooter (error "Request is prohibited by organization's policy" + vpcServiceControlsUniqueIdentifier; violationReason codes NO_MATCHING_ACCESS_LEVEL, RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER, NETWORK_NOT_IN_SAME_SERVICE_PERIMETER, SERVICE_NOT_ALLOWED_FROM_VPC; audit type google.cloud.audit.VpcServiceControlAuditMetadata). cloud.google.com/vpc-service-controls/docs/troubleshooting
- GitHub issue — GoogleCloudPlatform/terraform-google-cloud-functions #109, "Investigate vpcServiceControlsUniqueIdentifier policy violation" (real CI/CD breakage: Terraform operations blocked by a perimeter mid-deploy). github.com/GoogleCloudPlatform/terraform-google-cloud-functions/issues/109
- Google Cloud certification — Professional Cloud Security Engineer exam guide (configuring network security / VPC Service Controls perimeters; IAM-vs-perimeter for data-exfiltration scenarios). cloud.google.com/learn/certification/cloud-security-engineer
What's next?
You've sealed the perimeter so data can't leak — but how do you SEE the leaked key, the public bucket and the risky egress rule across the whole org in one place? That's the next lesson.