CoursesGCP securityResource hierarchy & org policy

Resource hierarchy & org policy

Org → folders → projects, and inherited guardrails.

Advanced30 min · lesson 1 of 15

A chain of shops works because head office writes a rule once and every new branch is bound by it on opening day. Nobody reads the rulebook aloud to each new clerk. Google Cloud runs on the same idea. Everything you own hangs off one root, and the security controls you set near the top roll downhill to everything under them without you lifting a finger. Get the shape of that tree right and a single policy covers a thousand projects. Get it wrong and you spend your career fixing settings one project at a time, missing half of them.

Four layers, and everything runs downhill

Four layers, from the top down. The organization is your company, pinned to a domain name like acme.com. Folders are the grouping layer, usually one per team or per environment. Projects are the real containers where resources and the bill actually live. At the bottom sit the resources themselves: a virtual machine (a VM, a computer you rent by the hour inside Google's data centre), a storage bucket, a database. Two things slide down this tree on their own. The first is IAM (Identity and Access Management, Google's system for deciding which identity may call which API). The second is organization policy, which decides what configuration is allowed to exist at all. Grant somebody a role on a folder and that grant lands on every project inside, with no extra typing. The project is also your blast-radius line. If an attacker gets into one, the damage stops at its edge. That is why prod, staging and shared tooling each get a project of their own.

find your organization ID
gcloud organizations list
# output:
DISPLAY_NAME ID DIRECTORY_CUSTOMER_ID
acme.com 843906890471 C03xk9zzp

That number is the anchor for everything else. You will paste it into folder and policy commands all day, so grab it first. The directory customer ID, the one starting with C, points at your Google Workspace or Cloud Identity account: the master staff directory for your company. It matters later, when you fence off sharing so only your own people can be added to anything.

Folders are your blast-radius buckets

create the production folder under the org
gcloud resource-manager folders create \
--display-name=production \
--organization=843906890471
# output:
Waiting for [operations/cp.8891233707421] to finish...done.
Created folder [451219876340].

Folders nest. Organization, then a production folder, then a payments sub-folder if one team wants its own boundary inside prod. Here is the payoff. Grant a break-glass admin role (the access nobody touches on a normal day, saved for a real emergency) or a read-only auditor role once on the production folder, and every project underneath picks it up. The ones that exist today, and the ones somebody spins up next March. No extra work either time. The same machinery bites back. One lazy, broad grant high in the tree leaks downhill just as quietly, and nobody spots it until an auditor does.

How you slice the folders is a genuine design decision, not paperwork. Environment first (prod, non-prod, sandbox) makes it easy to drop one strict guardrail across all of production in a single move, which is normally what the security team is asking for. Team first maps neatly onto who owns what and who pays the bill. Most grown-up estates do both: environment folders on top, team folders underneath. Remember what splitting costs you, though. Quotas, billing, and switching on each API a project is allowed to call (an API is a Google service your code talks to, like the storage service or the compute service) all live at the project level. More projects means more of that plumbing to look after, bought in exchange for tighter isolation.

Org policy: house rules even an Owner cannot break

IAM and organization policy answer two different questions, and people mix them up constantly. IAM is the guest list: which identity may call which API. Organization policy is the building code: what you are allowed to build in the first place, whoever you are. A project Owner has near-total power inside their project. Set a policy at head office saying no VM may carry a public IP address (an internet-facing address that lets strangers reach the machine directly) and that Owner still cannot attach one. The constraint sits above their pay grade. That is what makes organization policy the preventive backbone of a landing zone, the locked-down baseline every new project is born into. You turn off service-account keys (long-lived passwords that let software log in as a robot user), force OS Login (so every server login traces back to a real Google identity instead of shared keys drifting around in chat), block public IPs, and restrict sharing to your own domain. Every one of those limits holds for the whole subtree under the node where you set it.

no-external-ip.yaml (pin production VMs to internal-only)
# org policy v2 file, applied at the production folder
name: folders/451219876340/policies/compute.vmExternalIpAccess
spec:
rules:
- denyAll: true
apply the policy to the folder
gcloud org-policies set-policy no-external-ip.yaml
# output:
name: folders/451219876340/policies/compute.vmExternalIpAccess
spec:
etag: CMuk_r0GEND1w-4C
rules:
- denyAll: true
updateTime: '2026-07-16T14:07:52.331046Z'
confirm it took effect on a child project
gcloud org-policies describe compute.vmExternalIpAccess \
--project=payments-prod --effective
# output (project never set this — it's inheriting the folder rule):
name: projects/739104556201/policies/compute.vmExternalIpAccess
spec:
rules:
- denyAll: true

Read that output again. payments-prod never set the policy. It is reading the folder's rule as its own effective policy, resolved from the top down at the moment the request is evaluated. The merge rules are worth learning now, so nothing surprises you at 2am. A boolean constraint (enforce, or do not) takes the most specific setting on the path, and a child can only loosen a parent by writing enforce:false at its own level. A list constraint (a set of allowed or denied values) either replaces the parent's list outright or, if you add inheritFromParent:true, extends it, with deny winning whenever the two disagree. Set reset:true and that node falls back to Google's platform default. When you have no idea what a new policy will break, write a dry-run spec first. It records the violations it would have caught in Cloud Audit Logs and blocks nothing at all, so you can watch the real blast radius for a week before you switch enforcement on.

Blocking public IPs also blocks the way out
The moment that folder policy lands, new VMs boot with no public address, which also means no route out to the internet: no operating-system patches, no package installs, no calls to any API that is not reachable on a private endpoint. Creating the VM throws no error. The box sits there unable to reach anything, and the ticket that arrives on your desk reads like a mysterious network outage. The fix is Cloud NAT (Network Address Translation), a shared mailroom for outbound traffic. Every machine's outgoing post leaves under one building return address, and nothing from outside can walk back in to a specific desk. Internal-only VMs get to reach out while staying completely unreachable from the internet. Ship it in the same change as the policy, not after the pager goes off.
give internal-only VMs a safe way out
gcloud compute routers create prod-nat-router \
--network=payments-vpc --region=us-central1 --project=payments-prod
gcloud compute routers nats create prod-nat-config \
--router=prod-nat-router --region=us-central1 --project=payments-prod \
--auto-allocate-nat-external-ips --nat-all-subnet-ip-ranges
# output:
Created [https://www.googleapis.com/compute/v1/projects/payments-prod/regions/us-central1/routers/prod-nat-router].
Creating NAT [prod-nat-config] in router [prod-nat-router]...done.
Guardrails set high, inherited all the way down
Organization root: acme.com (843906890471)
Org Admin / Owner
the master key: break-glass group only, hardware MFA, alert on every use
org-wide constraints
sharing limited to your domain, service-account keys off; caps everything below
aggregated log sink
exports every child's audit logs to a locked, immutable bucket
Folders: group the blast radius, inherit then tighten
security
home of the org policies and the logging project
production
stricter: no external IP, OS Login enforced
sandbox
looser rules, and never any real data
Projects: the isolation and billing boundary
payments-prod
picks up every production guardrail on its own
web-prod
same inherited floor, its own IAM stacked on top
logging
locked buckets plus the sink's writer service account
A brand-new project dropped into the production folder is born with that folder's guardrails already switched on. No per-project setup, no forgotten checkbox. That inheritance is the whole reason to get the shape of the tree right before you scale.

One habit to build on day one. Whoever holds Organization Admin or Owner at the root controls every folder, every policy and every log sink underneath, so treat it like the master key to the whole building. Give it to a tiny break-glass group behind hardware MFA (multi-factor authentication, here a physical key you tap to prove it is really you), reach for it almost never, and fire an alert the second it is used. Everything you do on a normal Tuesday should run through narrow roles further down the tree.

A workable tree for a mid-size company looks roughly like this. Under the organization, one folder for shared platform services (networking, logging sinks, Security Command Center wiring), one folder per business unit or product line, and under each of those a pair of folders for non-prod and prod. Projects hang off those environment folders, never off the org root. That nesting is what lets a single org policy, a single IAM deny policy and a single logging sink cover every project anyone creates next year, with nobody re-applying anything by hand.

The two things flowing downhill behave differently, and the difference matters. An allow binding is additive: put it on a folder and it reaches every project underneath, stacked on top of whatever those projects already grant. An org policy is a ceiling: set it at the organization with inheritance on and it reaches every folder and project below unless a child writes a carefully scoped exception. That is why you almost never grant roles/owner at the org, and why an exception folder deserves the same treatment as a drum of hazardous waste. Every exception is a permanent hole somebody has to remember exists.

When a new team arrives, do not hand them a bare project under the org root. Hand them a folder under the right business unit, with the landing-zone scripts already stamping on the same org policies, log sinks and essential contacts the rest of the estate uses. The tree does the teaching. The runbook only adds another leaf.

Try this

In a scratch organization, or a folder you already own, list the tree and then list the org policies that already reach a project inside it. You are proving inheritance with your own eyes, not designing a new tree from nothing.

terminal
gcloud organizations list
gcloud resource-manager folders list --organization=ORGANIZATION_ID
gcloud projects describe payments-prod --format="yaml(name,parent,projectId)"
gcloud org-policies list --project=payments-prod --format="table(constraint)"
output
DISPLAY_NAME: Acme Corp
ID: 123456789012
DIRECTORY_CUSTOMER_ID: C012abcd
folders/111111111111 platform
folders/222222222222 workloads
name: projects/802451296328
projectId: payments-prod
parent:
type: folder
id: '222222222222'
CONSTRAINT
constraints/compute.requireOsLogin
constraints/iam.disableServiceAccountKeyCreation
constraints/storage.uniformBucketLevelAccess

Takeaway

The tree is the control plane. Folders are blast-radius buckets, org policies are house rules a project Owner cannot peel off, and both get resolved from the root down at the moment a request is evaluated. Do the thinking about inheritance once, at the shape-of-the-tree stage, and you stop chasing settings project by project for the rest of the estate's life.

Next you tighten who can open which door inside that tree: IAM roles, conditions and deny policies. A beautifully shaped hierarchy with sloppy grants still leaks.

Quick check
01An org policy on the production folder denies all external IP addresses. A teammate who holds Owner on payments-prod files a ticket: they cannot attach a public IP to a new VM, and they are Owner, so surely that should work. What is really going on?
Incorrect — Owner is fine. Org policy is evaluated on its own, separately from IAM and above it, so no role, Owner included, can build something a constraint forbids.
Correct — Org policy is a preventive guardrail that flows down the tree and outranks IAM power. Owner decides who may act, never what configuration is legal.
Incorrect — They do flow down, which is the entire reason to set it once on the folder. The describe --effective output on payments-prod showed it reading the folder's rule.
Incorrect — Quota is not what is stopping it. denyAll refuses external addresses outright, however much quota is sitting unused.
02You want to roll a brand-new organization policy across all of production, but first you want to find out what it would break without breaking anything. The lesson names the mechanism for that. What does setting a dry-run policy actually do?
Correct — A dry-run spec reports what the constraint would have caught while stopping nothing, so you can watch the logs for a week before switching enforcement on.
Incorrect — No. A dry-run is not scoped by how old a project is. It evaluates across the whole subtree and blocks nothing anywhere.
Incorrect — No. Nothing expires by itself. You promote a dry-run to real enforcement yourself, when you decide you are ready.
Incorrect — No on both counts. An Owner cannot override org policy, and a dry-run is not blocking anything to begin with.
03You apply the deny-all-external-IP org policy to the production folder. New virtual machines boot with no errors at all, but they cannot install operating-system patches or pull software packages, and a 'network outage' ticket lands in your queue. What happened, and what fixes it?
Incorrect — No. The constraint only removes external addresses, internal traffic is untouched, and throwing away the guardrail is the wrong move.
Correct — With no public address there is no outbound path for patches or package installs. Cloud NAT gives them one shared, outbound-only exit.
Incorrect — No. The policy forbids external addresses outright, so spare quota changes nothing.
Incorrect — No. OS Login governs who may log in over SSH. It has nothing to do with a machine reaching a package mirror.

Related