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.