Chapter 31
Organization Hardening
Scope. This chapter is a specification, not a tutorial. §2.30 and §2.31 own how an organization policy is evaluated, inherited, dry-run, and overridden, and nothing here repeats them. What is here is one concrete baseline: every constraint, its value, its legitimate exceptions, and — the part that is hardest to find anywhere — what breaks when you turn it on. Chapter 32 applies the equivalent settings per workload; §29.11 owns proving a policy was in force. Prerequisites. §2.30 Organization Policy, §2.31 Hierarchical Policy Inheritance, Chapter 3 (IAM), Chapter 14 (Cloud KMS). Verified against. Google Cloud console and API surface as of 2026-09, Cloud SDK 583.0.0; every constraint ID verified against the organization policy constraint reference; see sources at end.
An organization policy baseline is worth exactly as much as the list of exceptions attached to it. A baseline with no exceptions has not been deployed; a baseline with undocumented exceptions is a list of controls someone turned off for a reason nobody recorded.
Read this chapter as a deliverable. Each section gives constraints to set, their values, and the failure to expect. The failures are the important part: every constraint below breaks something legitimate, and a team meeting the breakage first in production removes the constraint rather than fixing the workload.
Two facts change what your baseline needs to contain. First, organizations created on or after 2024-05-03 receive a security baseline automatically — seven constraints are already enforced, and setting them again is harmless but tells you nothing. Older organizations have none of them. Second, Google is migrating constraints to a *.managed.* form; some controls exist in both forms, some in only one, and the managed form is generally the one to prefer where it exists.
Roll out in dry-run and read the logs. §2.30 owns the mechanism. Every constraint below can be set to report rather than deny, and the report is the list of things you are about to break.
31.1 Organization Policy Baseline §
The baseline is the set applied at the organization node, inherited everywhere, with exceptions granted at a folder rather than by weakening the root.
Start by finding out what you already have. For an organization created on or after 2024-05-03, seven constraints are enforced automatically:
| Constraint | What it does |
|---|---|
constraints/iam.managed.disableServiceAccountKeyCreation | No persistent service account keys |
constraints/iam.disableServiceAccountKeyUpload | No external public keys uploaded |
constraints/iam.automaticIamGrantsForDefaultServiceAccounts | No basic role on default service accounts |
constraints/iam.allowedPolicyMemberDomains | Your domain only, in IAM policies |
constraints/essentialcontacts.managed.allowedContactDomains | Contacts restricted to your domains |
constraints/compute.managed.restrictProtocolForwardingCreationForTypes | Internal protocol forwarding only |
constraints/storage.uniformBucketLevelAccess | No per-object ACLs |
Some organizations created between February and April 2024 also have them, so check rather than assume:
gcloud org-policies list --organization=123456789012
gcloud org-policies describe iam.allowedPolicyMemberDomains \
--organization=123456789012 --effective
Beyond the automatic set, these belong in every baseline and are not applied for you: constraints/compute.requireOsLogin, constraints/compute.requireShieldedVm, constraints/compute.disableSerialPortAccess, and constraints/compute.skipDefaultNetworkCreation.
Write the baseline as code. gcloud org-policies set-policy takes a YAML or JSON document, and google_org_policy_policy is the Terraform resource (§26.34). A baseline applied by hand is a baseline nobody can diff.
Note that gcloud org-policies allow and gcloud org-policies deny do not exist. The command group is delete, describe, list, reset, set-policy, and the custom-constraint commands. Everything that writes a policy goes through set-policy and a file.
Pitfall. A constraint set at the organization and overridden at a folder still reads as enforced at the root. Only --effective at the resource answers the real question, and §29.11's evidence must come from that rather than from the definition.
31.2 Restricting Resource Locations §
constraints/gcp.resourceLocations limits where resources may be created. It is the data-residency control and it is the one most likely to break a build on day one.
It is a list constraint taking location values, at three granularities: a specific region (us-central1), a multi-region (us), or a value group (in:us-locations). Value groups are the maintainable form — they track Google's own membership as regions are added.
name: organizations/123456789012/policies/gcp.resourceLocations
spec:
rules:
- values:
allowedValues:
- in:us-locations
- in:eu-locations
What breaks, and it breaks immediately:
- Global and multi-region resources. A bucket in the
USmulti-region, a global load balancer, a global address — these have no single region and are evaluated against the multi-region or global value. - Services with no regional presence in your allowed set, which fail at creation with a location error naming a region nobody chose.
- Some services are exempt entirely, and the documented exemption list is the thing to check before writing the policy rather than after.
Apply it at a folder, not the organization, unless the whole estate is genuinely single-jurisdiction. A shared services folder usually needs a wider set than a regulated workload folder, and expressing that as inheritance is cleaner than as an exception.
This is the constraint where dry-run pays for itself most. Enforcement blocks resource creation, and the resources it blocks are often ones a pipeline creates transitively — a Cloud Build worker, a backup location — where the error appears far from the change.
Pitfall. The constraint governs where resources are created, not where data ends up. A regional resource can still send data elsewhere; residency for data in motion is a network and perimeter question (Chapter 20), and treating this constraint as a residency guarantee overstates it.
31.3 Restricting External IP Addresses §
Removing external IPs from VMs is the highest-value single constraint in the baseline, because it eliminates an entire class of exposure rather than mitigating it.
Two forms exist and the managed one is preferred:
| Constraint | Form |
|---|---|
constraints/compute.managed.vmExternalIpAccess | Managed — use this |
constraints/compute.vmExternalIpAccess | Legacy list constraint |
Deny by default and allow specific instances by name. The legacy form takes instance identifiers as values, so an exception is an enumerated instance rather than a blanket relaxation.
Also set constraints/compute.disableVpcExternalIpv6 and constraints/compute.managed.restrictProtocolForwardingCreationForTypes, which closes the equivalent path for forwarded protocols.
What breaks — in order of how soon you meet it:
- Package installation and agent registration, because an instance with no external IP has no path to the internet. Cloud NAT is the prerequisite, not the workaround (§5.x), and it must exist before the constraint is enforced.
- Direct SSH, which is intended: the replacement is IAP TCP forwarding (§8.21), and it needs the firewall rule permitting
35.235.240.0/20(§5.14). - Anything that assumed a public egress IP for allowlisting elsewhere. Cloud NAT gives you a stable set, which is usually an improvement, but it is a change to an external party's configuration.
The instances that legitimately need one are few — a NAT appliance you run yourself, a bastion you have chosen not to replace with IAP — and they belong in a named exception with an owner.
Pitfall. Enforcing this before Cloud NAT exists produces a fleet that boots, fails to reach a repository, and appears to be a networking outage. Deploy NAT, verify egress, then enforce — and dry-run in between.
31.4 Restricting Service Account Keys §
Service account keys are the most common credential-leak vector in Google Cloud, and this is the constraint set that removes them as an option.
| Constraint | Effect |
|---|---|
constraints/iam.managed.disableServiceAccountKeyCreation | No new keys |
constraints/iam.disableServiceAccountKeyUpload | No externally generated public keys |
constraints/iam.serviceAccountKeyExpiryHours | Bounds the lifetime of keys that are created |
constraints/iam.managed.disableServiceAccountCreation | No new service accounts at all, where appropriate |
The first two are in the automatic baseline for organizations created on or after 2024-05-03. The expiry constraint is the useful third option for a folder that has a documented exception: if a key must exist, it does not have to be permanent.
What breaks is real and it is the point. Every integration that assumed a downloadable key stops working, and the replacements are established:
- CI outside Google Cloud → Workload Identity Federation (§4.3, §4.7, §26.4).
- Workloads inside Google Cloud → an attached service account (§33.7's correct form).
- Ansible →
machineaccountorapplication(§28.6). - A third-party SaaS integration → federation where the vendor supports it, and a documented, expiry-bounded exception where it does not.
The last category is where honesty matters. Some vendors only accept a key. That is an exception with a named owner, a rotation schedule, and a review date — not a reason to leave the constraint unset for the whole organization.
Pitfall. The constraint stops key creation; it does not revoke keys that already exist. Enforcing it on an established organization freezes the problem rather than solving it, so pair it with an inventory of existing keys (§33.5) and a deletion plan.
31.5 Domain-Restricted Sharing §
This constraint limits which identities may appear in any IAM policy in the organization. It is the control that prevents a resource being shared with an external account, and it has the most surprising breakage in the chapter.
Two implementations, and they are not equivalent:
| Constraint | Notes |
|---|---|
constraints/iam.managed.allowedPolicyMembers | Managed and parameterized; takes allowedMemberSubjects and allowedPrincipalSets |
constraints/iam.allowedPolicyMemberDomains | Legacy; allowed values only, no deny |
Your own organization is not allowed automatically. Both forms require you to list your own organization principal set or Workspace customer ID explicitly, and omitting it locks the organization out of its own IAM.
Listing your organization's principal set covers your service agents, along with your service accounts, workforce pools, and workload pools — so the common fear that this constraint breaks Google's own automation is unfounded for agents inside your organization.
What breaks is the Google-owned accounts that live outside it. Several first-party integrations are performed by accounts in Google's own projects, and they are blocked:
- A BigQuery log sink for a billing account.
- Cloud Storage access logging.
- Pub/Sub endpoints for Google Chat apps, Play developer notifications, and Cloud Billing budget and cost-anomaly alerts.
- Signed URLs and private origin authentication with Cloud CDN.
Each has a documented principal identifier to allow. Find the list before enforcing, not after a billing alert stops arriving.
allUsers and allAuthenticatedUsers are blocked, which directly constrains §10.12 — the book's one permitted public binding. Google documents that an exception for the public principals is only possible using a custom organization policy, implemented with a tag key and a conditional policy. If a public Cloud Run service is required, that is the mechanism.
The error signature is distinctive: FAILED_PRECONDITION: One or more users named in the policy do not belong to a permitted customer. Recognizing it saves an afternoon.
Pitfall. constraints/iam.managed.allowedPolicyMembers can block folder and project creation, because those operations perform automatic role grants to principals the constraint has not been told to allow. A baseline that stops you creating projects is discovered at the worst moment; dry-run it and create a test project.
31.6 Disabling Default Networks §
Every new project gets a default VPC with permissive firewall rules unless you prevent it, and those rules include SSH from anywhere.
constraints/compute.skipDefaultNetworkCreation is a boolean and should be enforced organization-wide. There is no legitimate exception: a project that needs a network gets one built deliberately (§26.28), and the default network's rule set is not one anyone would write on purpose.
name: organizations/123456789012/policies/compute.skipDefaultNetworkCreation
spec:
rules:
- enforce: true
Pair it with constraints/compute.setNewProjectDefaultToZonalDNSOnly, which restricts new projects' internal DNS to the zonal form. It is documented as part of Google's baseline set, and it removes a legacy global DNS behavior that a new project does not need.
What breaks: a workflow that created a project and expected a usable network — common in sandboxes and quickstart automation. The fix is the project factory (§2.26, §26.27) creating the network too.
This constraint is not retroactive. Existing projects keep their default networks, so enforcing it must be paired with an inventory of the ones that already exist and a plan to remove them. §33.9 owns why those rules matter.
Pitfall. Deleting an existing default network is blocked while anything uses it, and something usually does — a forgotten instance, a Cloud Build worker with no VPC configuration. Enumerate what is attached before scheduling the deletion, or the cleanup stalls halfway and leaves the network in place with the constraint enforced above it, which looks compliant and is not.
31.7 Restricting Public Resources §
Public exposure is spread across services, so this is a set rather than a single constraint. Each one closes the default that produces §33.1 and §33.2.
| Constraint | Closes |
|---|---|
constraints/storage.publicAccessPrevention | Public buckets |
constraints/storage.uniformBucketLevelAccess | Per-object ACLs that route around bucket IAM |
constraints/storage.restrictAuthTypes | Legacy authentication to Cloud Storage |
constraints/sql.restrictPublicIp | Cloud SQL instances with a public address |
constraints/sql.restrictAuthorizedNetworks | Open authorized-network ranges |
constraints/run.allowedIngress | Cloud Run services reachable from the internet |
constraints/run.managed.requireInvokerIam | Cloud Run services with no invoker check |
constraints/compute.disableInternetNetworkEndpointGroup | Backends pointing outside the estate |
constraints/compute.restrictLoadBalancerCreationForTypes | External load balancer types |
constraints/storage.uniformBucketLevelAccess is already enforced for organizations created on or after 2024-05-03.
Note the interaction with §31.5. Domain-restricted sharing already blocks allUsers, so these constraints are defense in depth for the storage and database cases — and independently necessary, because a Cloud SQL public IP is not an IAM binding at all.
What breaks: any intentionally public asset. A public documentation bucket, a marketing site, a public API. These are legitimate and they need an exception at a folder holding only public assets — which is the right structure anyway, because it makes "what is public" a question with an enumerable answer.
constraints/run.allowedIngress is a list constraint whose values are the ingress settings a service may use; restricting it to internal and load-balancer traffic makes §32.3's hardened default the only option.
Pitfall. constraints/storage.publicAccessPrevention prevents new public access and does not remove existing public buckets. On an established estate the constraint reads as enforced while public data is still public, and only an inventory (§33.1's detection command) closes the gap.
31.8 Restricting APIs §
Every enabled API is attack surface and a potential billing surprise. Two constraints bound which services can be used at all.
constraints/gcp.restrictServiceUsage is the modern control and takes service names as values:
name: organizations/123456789012/policies/gcp.restrictServiceUsage
spec:
rules:
- values:
deniedValues:
- bigtable.googleapis.com
Prefer a deny list to an allow list, at least initially. An allow list is stronger and it is also a full-time job: Google adds services continuously, and a service enabled transitively by another will fail naming an API nobody has heard of. Move to an allow list only for a folder whose workload set is fixed.
constraints/serviceuser.services is the legacy equivalent and constraints/gcp.restrictEndpointUsage restricts which API endpoints may be called — useful alongside VPC Service Controls (Chapter 20) rather than instead of it.
What breaks is broader than it looks. Services depend on each other: enabling Cloud Run enables several supporting APIs, and a restriction on one of those blocks the visible service with an unrelated error. This is the constraint where the dry-run log is not optional.
A denied API also blocks its service agent's operations, which surfaces as a permission error rather than as a policy error — the least legible failure mode in this chapter.
Pitfall. An allow list at the organization silently governs every future project, including the one someone creates in three years for a service that did not exist when the list was written. If you use an allow list, put a review date on it and name an owner, or the first symptom is a project that cannot enable anything.
31.9 Enforcing Encryption Requirements §
Chapter 14 owns Cloud KMS. These constraints make CMEK mandatory rather than available.
| Constraint | Effect |
|---|---|
constraints/gcp.restrictNonCmekServices | Named services must use CMEK |
constraints/gcp.restrictCmekCryptoKeyProjects | Keys must come from approved projects |
constraints/cloudkms.allowedProtectionLevels | Bounds key protection level (software, HSM, external) |
constraints/gcp.restrictTLSVersion | Rejects weak TLS versions |
constraints/gcp.restrictTLSCipherSuites | Bounds cipher suites |
constraints/storage.secureHttpTransport | Requires TLS for Cloud Storage |
constraints/gcp.restrictNonCmekServices takes service names as values, so it is rolled out service by service — the right shape, because each service's CMEK story differs.
constraints/gcp.restrictCmekCryptoKeyProjects is the one that makes the control meaningful. Requiring CMEK without constraining where keys come from allows a workload to encrypt with a key in its own project, which defeats §2.21's separation entirely. Restrict key projects to the security project.
What breaks, and one case is unrecoverable:
- Cloud SQL CMEK is creation-time only and cannot be retrofitted. Enforcing the constraint does not migrate existing instances; it prevents new non-CMEK ones and leaves the old ones as they are, permanently.
- Every service needs its service agent granted
roles/cloudkms.cryptoKeyEncrypterDecrypteron the specific key before it can create anything (§14.8). Enforce the constraint before those grants exist and every creation fails. - Key availability becomes an availability dependency. A key disabled, destroyed, or in an unreachable region makes the data undecryptable — which is the intended property of crypto-shredding and an outage when it is unintended.
Pitfall. constraints/gcp.detailedAuditLoggingMode looks like an encryption or general logging control and is Cloud Storage-specific. Do not include it in an encryption baseline expecting estate-wide effect; it belongs in §31.10 with its scope stated.
31.10 Enforcing Logging Requirements §
Logging is the weakest area for organization policy, and knowing that is more useful than a longer list.
There is no organization policy constraint that enables Data Access audit logs. They are configured in the IAM policy's auditConfigs, not by a constraint, and there are no logging.* constraints at all. The most commonly wanted logging control in Google Cloud simply cannot be set this way.
What organization policy can do:
| Constraint | Effect | Scope |
|---|---|---|
constraints/iam.disableAuditLoggingExemption | Prevents exempting principals from audit logging | IAM-wide |
constraints/gcp.detailedAuditLoggingMode | Extends logged request and response content | Cloud Storage only |
constraints/compute.requireVpcFlowLogs | Requires flow logs on subnets | Compute |
constraints/compute.disableSerialPortLogging | Blocks serial port output to logs | Compute |
constraints/storage.retentionPolicySeconds | Bounds bucket retention periods | Cloud Storage |
constraints/iam.disableAuditLoggingExemption is the important one. An auditConfig can exempt named principals from Data Access logging, and an exempted service account is a principal whose reads are invisible. This constraint removes the exemption mechanism, which is the closest organization policy gets to guaranteeing the audit trail.
constraints/compute.requireVpcFlowLogs takes a sampling level, so it enforces that flow logs exist rather than merely permitting them. Flow logs cost real money at full sampling; the constraint lets you require a floor.
Everything else is Chapter 17's, applied through configuration rather than policy: the aggregated organization sink (§2.22), log bucket retention and locking (§17.13, §17.14), and the auditConfigs that turn Data Access logging on.
Pitfall. Because no constraint enables Data Access logs, an organization can pass every policy check in this chapter and have no record of who read its data. §33.11 is that failure, and the only defense is to verify auditConfigs directly as part of the baseline's evidence (§29.3).
Chapter Summary §
- A baseline's exception list is the deliverable; a baseline with undocumented exceptions is a set of controls someone disabled for an unrecorded reason.
- Organizations created on or after 2024-05-03 already enforce seven constraints automatically; some created between February and April 2024 do too. Check rather than assume.
gcloud org-policies allowanddenydo not exist — every write goes throughset-policywith a file, or throughgoogle_org_policy_policy.- Only
--effectiveat the resource answers whether a constraint is really in force. constraints/gcp.resourceLocationsgoverns where resources are created, not where data travels.- Removing external IPs requires Cloud NAT to exist first; enforcing before it does produces what looks like a networking outage.
- IAP TCP forwarding replaces direct SSH and needs the firewall rule for
35.235.240.0/20. - Disabling key creation freezes the existing key problem; pair it with an inventory and a deletion plan.
- Domain-restricted sharing does not block your own service agents if your organization principal set is listed — and your own set is not allowed automatically.
- It does block Google-owned accounts outside your organization: billing log sinks, storage access logging, Chat/Play/billing Pub/Sub endpoints, and Cloud CDN signing.
- It blocks
allUsers, and an exception is possible only through a custom organization policy — which directly constrains §10.12. constraints/iam.managed.allowedPolicyMemberscan block folder and project creation.constraints/compute.skipDefaultNetworkCreationhas no legitimate exception and is not retroactive.constraints/storage.publicAccessPreventionprevents new public access and does not remove existing public buckets.- Prefer a deny list to an allow list for APIs: services enable each other, and a transitively blocked API fails with an unrelated error.
- Requiring CMEK without
constraints/gcp.restrictCmekCryptoKeyProjectslets a workload use its own key, defeating §2.21. - Cloud SQL CMEK is creation-time only; the constraint prevents new non-CMEK instances and cannot fix existing ones.
- No organization policy constraint enables Data Access audit logs, and there are no
logging.*constraints. They are set in the IAM policy'sauditConfigs. constraints/gcp.detailedAuditLoggingModeis Cloud Storage-specific despite its name.constraints/iam.disableAuditLoggingExemptionis the closest policy gets to guaranteeing an audit trail, by removing the exemption mechanism.
Security Checklist §
| Control | Why it matters | How to verify (CLI + Console) |
|---|---|---|
| Automatic baseline status determined | Seven constraints may already be enforced, or none | gcloud org-policies list --organization=ORG_ID |
| Baseline stored as code and applied by pipeline | A hand-applied baseline cannot be diffed or reviewed | google_org_policy_policy resources in the repository |
| Every constraint dry-run before enforcement | The dry-run log is the list of things about to break | Policy dryRunSpec and the resulting audit entries |
| Every exception has an owner and a review date | An undocumented exception is an unexplained hole | The exception register; folder-level policy overrides |
| Effective policy checked at the resource | An override below the root is invisible at the root | gcloud org-policies describe CONSTRAINT --project=PROJECT_ID --effective |
| Cloud NAT deployed before external IPs are denied | Otherwise the fleet cannot reach any repository | gcloud compute routers nats list --router=ROUTER --region=REGION |
| IAP firewall rule exists before SSH is removed | 35.235.240.0/20 to 22, or every session hangs | gcloud compute network-firewall-policies describe POLICY --global |
| Existing service account keys inventoried | The constraint stops creation, not existing keys | gcloud iam service-accounts keys list --iam-account=SA --managed-by=user |
| Own organization principal set listed in domain sharing | It is not allowed automatically; omitting it locks you out | gcloud org-policies describe iam.allowedPolicyMemberDomains --organization=ORG_ID |
| Google-owned external accounts allowed where needed | Billing sinks, CDN signing, and Pub/Sub endpoints break silently | The documented exception list versus the policy values |
| Project and folder creation tested after domain sharing | Automatic role grants can block creation | Create a test project in a dry-run scope |
| Existing default networks enumerated and removed | The constraint is not retroactive | gcloud compute networks list --filter='name=default' |
| Existing public buckets inventoried | Public access prevention is not retroactive | §33.1's detection command |
| Public assets isolated to their own folder | Makes "what is public" enumerable | Folder structure and its policy overrides |
| CMEK key projects constrained | Otherwise a workload encrypts with its own key | gcloud org-policies describe gcp.restrictCmekCryptoKeyProjects --organization=ORG_ID |
| Service agents granted on the key before CMEK is required | Otherwise every resource creation fails | Key IAM policy (§14.8) |
auditConfigs verified directly | No constraint enables Data Access logs | gcloud projects get-iam-policy PROJECT_ID --format='yaml(auditConfigs)' |
| Audit logging exemptions disabled | An exempted principal's reads are invisible | gcloud org-policies describe iam.disableAuditLoggingExemption --organization=ORG_ID |
Sources §
- https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints — the constraint reference: every ID, its type, and its allowed values (last validated 2026-09-04)
- https://cloud.google.com/resource-manager/docs/secure-by-default-organizations — the automatic security baseline and the organizations that receive it (last validated 2026-09-04)
- https://cloud.google.com/organization-policy/restrict-domains — domain-restricted sharing, the Google-owned account exceptions, and the public-principal limitation (last validated 2026-09-04)
- https://cloud.google.com/organization-policy/restrict-locations — resource location values and value groups (last validated 2026-09-04)
- https://cloud.google.com/organization-policy/reference/restrict-locations-supported-services — which services the location constraint governs (last validated 2026-09-04)
- https://cloud.google.com/organization-policy/restrict-services — restricting service usage (last validated 2026-09-04)
- https://cloud.google.com/organization-policy/restrict-service-accounts — the service account key constraints (last validated 2026-09-04)
- https://cloud.google.com/organization-policy/create-organization-policies — the policy document schema and
set-policy(last validated 2026-09-04) - https://cloud.google.com/organization-policy/test-policies — dry-run rollout (last validated 2026-09-04)
- https://cloud.google.com/organization-policy/create-custom-constraints — custom constraints, the mechanism for a public-principal exception (last validated 2026-09-04)