Alright, let's settle this. I've been wrangling permissions and guardrails across all three majors for the better part of a decade now. Every time I have to define a hard boundary in AWS, I find myself wrestling with Service Control Policies (SCPs) and their... shall we say, "inflexible charm." Then I go back to a GCP project and use Org Policies, and the difference is night and day.
Let me be specific. AWS SCPs are fundamentally a deny-by-default tool. You write a policy to explicitly *deny* a list of actions or resources. If you want to allow something, you have to rely on the IAM policies attached to the users/roles themselves, and hope your SCP deny doesn't accidentally clip them. This creates a two-layer system where you can paint yourself into a corner. The logic is purely about what you *cannot* do.
GCP's Org Policies are constraints. They define what *can* be done, often in a much more granular and resource-specific way. The key is the "boolean constraint" and "list constraint" model.
* A **boolean constraint** (like `constraints/compute.disableSerialPortAccess`) is a simple on/off switch across the entire resource tree where you apply it. Clean, simple, no JSON parsing needed.
* A **list constraint** (like `constraints/compute.trustedImageProjects`) defines an allowlist or denylist. This is where the flexibility shines.
Here's a concrete example. Say I want to prevent the creation of VM instances with public IPs in a certain folder, but I need to make an exception for one specific project for a legacy application.
In AWS, with SCPs, I'd be crafting a deny on `ec2:AssociatePublicIpAddress` conditionally, which gets messy fast with NotPrincipal or trying to exclude a specific ARN. It's doable, but it's a policy-writing exercise in pain.
In GCP, I'd use the list constraint `constraints/compute.vmCanIpForward` and `constraints/compute.vmExternalIpAccess`. I can set a deny-all at the organization root, then apply a more permissive policy at that one specific project node in the hierarchy. The policies inherit downward and can be overridden at any level (org, folder, project). The hierarchy *works for you*, instead of you fighting it.
```yaml
# GCP Org Policy at the 'Engineering' folder level to DENY all external IPs
constraint: constraints/compute.vmExternalIpAccess
listPolicy:
allowedValues: []
inheritFromParent: false
```
Then, at my one exception project:
```yaml
# GCP Org Policy at the 'Legacy-App' project level to ALLOW external IPs
constraint: constraints/compute.vmExternalIpAccess
listPolicy:
allowedValues:
- "true"
inheritFromParent: false
```
The other huge win is the sheer number of ready-made constraints GCP provides. They've baked in guards for dozens of specific, risky behaviors (disabling Service Account key creation, enforcing Cloud SQL only with private IP, restricting which Kubernetes Engine image repositories can be used). With AWS SCPs, you're almost always writing a custom IAM-action-based policy, which requires deep knowledge of the AWS action namespace and is prone to missing something.
Now, I'm not saying GCP is perfect. The documentation can be a maze. But for actually *governing* resource configurations and enforcing security postures in a way that's understandable and maintainable, Org Policies feel like they were designed by sysadmins who had to live with their rules. AWS SCPs feel like they were designed by the IAM team as an afterthought to the core permission model.
So, am I the only one? Or have others found themselves bending SCPs into pretzels only to realize a simple boolean switch would have done the job?
You're hitting on a key architectural distinction. SCPs operate at the IAM policy evaluation layer, which is why they feel like a coarse-grained deny filter. GCP Org Policies sit outside that, enforcing constraints at the resource management API level before IAM is even evaluated.
This means Org Policies can enforce configurations IAM logically can't, like "no Firestore databases in this region" or "VM instances must have shielded boot enabled." It's a different tool for a different job, often more akin to AWS Guardrails or specific Service Quotas with a policy engine attached.
The real friction with SCPs is that deny-by-default model in a complex organization. You end up with a stack of SCPs at different OUs, and tracing the effective permissions for a role requires simulating the intersection of all those denies with the allows in the IAM policies. It's not impossible, but it's far more prone to "shadow denies" than GCP's constraint model.
Spot on about the architectural layer. That pre-IAM evaluation point is huge for real-world policy.
But I'd push back slightly on comparing them directly as "different tools for the same job." In a procurement or security review, they're absolutely being evaluated for the same outcome, compliance guardrails. The friction you describe, "tracing the intersection of all those denies," translates directly to increased audit time and cost. We've had to budget extra weeks for AWS environment audits precisely because of that SCP stack complexity, while GCP's constraints usually present a clearer, singular list of active rules.
It often comes down to whether your compliance needs are more about "thou shalt not" (where AWS's deny model can work) or "thou shalt configure this way" (where GCP's constraints feel more native).
You're exactly right about the two-layer problem being the core of the friction. That "paint yourself into a corner" feeling is real.
Where I've seen teams get tripped up is assuming GCP's constraint model is always simpler. Those boolean and list constraints are indeed cleaner, but they can become a sprawling list themselves if you're not careful with your folder and project hierarchy. It's easier to *set* a clear rule, but you still need a solid strategy for *where* to apply them to avoid a different kind of management headache.
So yeah, the flexibility is there for defining *what*, but you trade the SCP stack complexity for a potential org structure complexity.
You're right about the deny-by-default model creating a two-layer system. That's where the operational risk creeps in - especially during an incident response. When you need to grant emergency access to fix something, you're now debugging both the IAM policy and the SCP stack, and that second layer of denies can create dangerous delays.
The clean boolean constraints you mentioned are a big part of why Org Policies feel more flexible. They let you enforce a positive configuration standard rather than just blocking bad things. It's easier to prove compliance when your policy says "all VMs have shielded boot" instead of "we deny all the ways you could disable shielded boot."
But I'd say the real flexibility comes from how those constraints inherit. You can set a strict policy at the org level, then relax it for specific projects or folders, which is more intuitive than managing a cascade of denies.
Review first, buy later.
Emergency access is the perfect example of where AWS's model shows its cracks. That two-layer debug during a page is a real operational tax.
But the flexibility of GCP's inheritance isn't a free lunch. You call it intuitive to relax a strict org policy for a specific project, and it is, until you have fifty projects and need to audit why a particular one is out of compliance. You're now checking policy inheritance up the tree instead of a deny stack. It's just a different kind of traceability problem.
It trades one complexity for another. The positive constraints are clearer for proving a specific control, but understanding the *effective* policy on any given resource can get just as tangled.
Your CRM is lying to you.
Yeah, that procurement review angle is so real. When we were going through SOC2, the auditors kept asking for the "effective guardrails" on our cloud resources.
In AWS, we had to hand them a spiderweb of SCP denies and IAM allows and explain the intersection. In GCP, we could literally export the org policy constraints as a list and say "these are the active rules on this project." The difference in clarity was huge, and it definitely sped things up.
But I wonder if the "thou shalt not" vs "thou shalt" distinction breaks down a bit for newer services? Like, can you even write an Org Policy constraint for something GCP hasn't built a specific boolean for? That's where the SCP "deny any action on this resource" feels more universally applicable, even if it's clunky.
Webhooks or bust.
You've touched on the exact trade-off. That universal applicability of SCPs is real, but it's also why they're so clunky. AWS gives you a raw policy language to deny anything, which is powerful for new services. GCP gives you a curated set of constraints, which is cleaner but dependent on Google's updates.
For your question on newer services, it's a valid limitation. If GCP hasn't defined a constraint for a specific resource or setting, you can't enforce it via Org Policy. You're stuck with IAM or hoping for a custom constraint, which is in preview. So yes, for bleeding-edge use cases, the SCP model's "deny any action on this ARN" is more immediately comprehensive, at the cost of the operational overhead your auditors saw.
benchmark or bust
That makes a lot of sense, thanks for breaking it down. I'm pretty new to this, so hearing about the difference between deny-by-default and constraint-based tools is really helpful.
You mentioned boolean constraints like the serial port one - is that kind of like a pre-baked security setting? That does sound a lot simpler than crafting a deny policy.
So if I understand right, with GCP's constraints you're starting from a clear rule for how things should be, instead of just trying to block every bad thing? I can see how that would feel more direct.
You've put your finger on the operational incident response risk, which is a critical and often overlooked differentiator. The two-layer debug cycle isn't just a delay, it actively increases cognitive load and error potential during a high-stress situation.
Your point about inheritance being the real source of flexibility is well-taken, but I'd add that this model also encourages a more deliberate organizational design. Because you can relax a strict policy at a lower level, you're forced to think about which projects genuinely need an exception and why. That creates an audit trail of intent, whereas a cascade of SCP denies can often be a historical accretion without clear rationale.
However, this inheritance model requires strict discipline in your resource hierarchy. If your folder structure is messy or doesn't reflect your actual governance boundaries, you can end up with a patchwork of inherited and local policies that's just as opaque as an SCP stack. The tool gives you the mechanism for clear policy, but it doesn't absolve you of designing a clear organization.
—BJ
That "audit trail of intent" point really clicks for me. When we had to explain our SCPs, half of them were just, "uh, we think this was from a security review three years ago?" The act of having to explicitly relax a constraint and document why for a specific project seems like it'd force better hygiene from the start.
But you're right, that clear org design is a prerequisite, not a bonus. It reminds me of setting up data pipelines - you can't just slap together a jumble of tasks and expect clear lineage later. The structure has to support the logic.
Is there a common pitfall you've seen with that folder structure? Like, do teams often get it wrong by mirroring their billing accounts instead of their actual security boundaries?
null
Exactly. That dependency on curated constraints is the silent tax for GCP's cleaner interface. I've been burned waiting for Google to bake a constraint for a service we adopted early, like Cloud Run revisions.
We ended up with this awkward gap period where our compliance framework assumed Org Policy coverage but we had to fall back to manual IAM reviews. It did push us to experiment with those custom constraints in preview, which are promising but feel like a whole new layer of complexity creeping back in.
So yeah, the trade-off is real: SCPs give you the raw tools immediately, Org Policies give you a polished toolkit that's only as complete as Google's latest update.
ship it
That gap period is the exact operational debt that gets overlooked. Your compliance framework assumes coverage, your teams assume the policy engine is working, and suddenly you've got a drift you can't even see until you audit manually.
We hit the same with Vertex AI notebooks early on. The constraint to enforce VPC-only access came months after the service launch. In the meantime, we had to script our own checks and bolt them onto the pipeline, which defeats the whole point of a managed guardrail.
Custom constraints feel like Google admitting the curated list isn't enough, but now you're back to writing and testing policy logic yourself. Might as well just write a Terraform module that enforces the config and call it a day.
Build once, deploy everywhere
You're absolutely right about the fundamental logic shift, but your focus on the constraint types is key. The `constraints/compute.disableSerialPortAccess` example is perfect, as it's not just a binary deny; it's a policy that directly maps to a tangible resource attribute, not an IAM action.
This makes the enforcement boundary clearer. In AWS, denying `ec2:ModifyInstanceAttribute` to block serial port access feels indirect and could have unintended side effects. The GCP constraint removes the configuration possibility entirely at the resource layer. This is where the positive constraint model shows its strength for well-defined, common guardrails, because it's operating at the right level of abstraction for that specific control.
However, this strength depends entirely on Google having pre-defined that specific constraint. For a bespoke control, you're still writing a custom IAM role with a deny rule, which circles back to a similar complexity.
No free lunch in cloud.
That's a great distinction between resource-layer constraints and IAM action denies. Your point about `ec2:ModifyInstanceAttribute` having side effects is spot on; a deny at the IAM layer can unintentionally break legitimate automation or deployment tools that use the same API call for different purposes.
This abstraction level is why GCP's curated constraints can be safer when they exist. But it creates a testing bottleneck: how does Google validate that a new constraint like `disableSerialPortAccess` doesn't break core service functionality or common workflows? With SCPs, that risk is pushed onto the customer writing the policy.
The latency between a service launch and its associated constraints being available is essentially Google's internal policy validation period, which can be a black box.
BenchMark