Top 10 Organization Policy Constraints for Google Cloud
Apply the right guardrails to keep your Cloud workloads secure.
Google Cloud lets you create and manage virtual machines, containers, Kubernetes clusters, databases and storage, and run pretty much any application you can think of. When you use Google Cloud, it’s important to set technical boundaries that enforce your company’s security and privacy policies. Office buildings are secured with locks and badge readers that keep people out of certain areas, and your Cloud environment needs the same kind of safeguards to stay secure and compliant.
To help you put those safeguards (guardrails) in place, Google Cloud offers the Organization Policy Service. It gives you centralised, programmatic control over what can and can’t be created in your Cloud. With so many constraints available, it’s hard to know where to begin, so I’ve put together my top recommendations to get you started.
Update 2026: In early 2024 I published a Top 5 of this list on Medium. A lot has changed since then. Google now enforces a security baseline on every new organization, managed constraints have reached General Availability, and dry-run testing is built into the Terraform provider. My Top 5 was no longer enough, so this is now a Top 10, and every Terraform example has been checked against the current API.
Why a Top 10 now?
Two things changed.
1. Google now secures new organizations by default. Every organization created on or after 3 May 2024 gets a secure-by-default baseline, which includes blocking service account key creation and upload, domain-restricted sharing, and uniform bucket-level access. That’s great news. But most companies I work with created their organization years before that date, so none of those guardrails are active for them. If that’s you, you need to add them yourself.
2. Managed constraints make rollouts safer. Many classic constraints now have a managed version (you’ll recognise them by .managed. in the name). These are built on the same platform as custom constraints, so they support dry-run mode and the Policy Simulator. You can see what would break before you enforce anything. Where a managed version exists and fits the use case, I use it in the examples below.
So five constraints no longer cover what I’d consider a solid baseline. Ten does.
First things first: how to structure your Google Cloud environment
Before we dive into the recommendations, let’s start with the basics: how your Google Cloud is set up. How you structure your environment decides how much you get out of the Organization Policy Service.
The organization is the highest level in Google Cloud and is tied to your company’s domain. Every resource lives in a project, which is an isolated part of the organization with its own resources, IAM permissions, virtual machines and so on. Grouping projects into folders (for example per business unit or per environment) makes administration a lot easier.
Organization (yourcompany.com)
├── Folder: Production
│ ├── Project: webshop-prod
│ └── Project: payments-prod
├── Folder: Non-Production
│ ├── Project: webshop-dev
│ └── Project: sandbox-team-a
└── Folder: Shared Services
└── Project: network-hub
Policies set at the organization level are inherited by every folder and project below it. When a specific folder or project needs something different, you can override the policy at that level, or (my preferred approach) use Tags to create targeted exceptions. More on that later.
My advice: set your guardrails at the organization level and make exceptions the exception.
Terraform setup
For every constraint below I’ve added a Terraform example, so you can deploy the guardrails as Infrastructure as Code. All examples use the google_org_policy_policy resource (the v2 Org Policy API) and a single variable for your organization ID:
variable "org_id" {
description = "Your Google Cloud organization ID"
type = string
}
locals {
org = "organizations/${var.org_id}"
}
You’ll need the Organization Policy Administrator role (roles/orgpolicy.policyAdmin) at the organization level to apply these. Also use a recent version of the Google provider, because the parameters and dry_run_spec fields are relatively new.
1. Resource Location Restriction
Constraint: gcp.resourceLocations
This constraint limits the physical locations where new Cloud resources can be deployed. For example, you can allow resources only in the European Union or only in the United States. This is often a compliance requirement, for instance under the GDPR or the growing number of digital sovereignty requirements in Europe.
Restricting locations to regions close to your end users also reduces network latency, which leads to better performance and a better user experience.
It can also support your company’s sustainability goals. Google publishes the carbon-free energy percentage for each region, so picking low-carbon regions is a simple step towards a greener Cloud.
Instead of listing single regions, use value groups such as in:eu-locations or in:europe-west4-locations. Google keeps these groups up to date as new regions and zones launch.
resource "google_org_policy_policy" "resource_locations" {
name = "${local.org}/policies/gcp.resourceLocations"
parent = local.org
spec {
rules {
values {
allowed_values = ["in:eu-locations"]
}
}
}
}
Good to know: this constraint applies only to services that support resource locations, and global resources aren’t affected. Check the list of supported services to see what is and isn’t covered.
2. Domain Restricted Sharing
Constraint: iam.managed.allowedPolicyMembers (managed), or the legacy iam.allowedPolicyMemberDomains
This constraint controls who can be granted access to your resources. It makes sure IAM roles can only be granted to identities from your own organization, so nobody can hand access to a personal Gmail account or to an unknown external party.
This matters a lot for offboarding. If an employee uses a personal account to access company data, they keep that access after they leave the company. The same goes for external contractors who were given access for a project and never removed.
The managed version is more flexible than the legacy one. You can allow whole organizations (principal sets) and specific individual identities, and you can dry-run it.
resource "google_org_policy_policy" "allowed_policy_members" {
name = "${local.org}/policies/iam.managed.allowedPolicyMembers"
parent = local.org
spec {
rules {
enforce = "TRUE"
parameters = jsonencode({
allowedPrincipalSets = [
"//cloudresourcemanager.googleapis.com/organizations/${var.org_id}"
]
})
}
}
}
Good to know: this also blocks allUsers and allAuthenticatedUsers. That is exactly what you want, but if you host a public website from a Cloud Storage bucket, you’ll need a Tag-based exception for that project.
3. Disable Service Account Key Creation and Upload
Constraints: iam.managed.disableServiceAccountKeyCreation and iam.managed.disableServiceAccountKeyUpload
Leaked service account keys are still one of the most common ways attackers get into Cloud environments. A JSON key is a long-lived credential that ends up in Git repositories, laptops and CI/CD variables, and it never expires by default.
In 2026 there’s rarely a good reason to create one. Use Workload Identity Federation for GitHub Actions, GitLab, AWS or on-premises workloads, attached service accounts for workloads running on Google Cloud, and service account impersonation for humans.
resource "google_org_policy_policy" "disable_sa_key_creation" {
name = "${local.org}/policies/iam.managed.disableServiceAccountKeyCreation"
parent = local.org
spec {
rules {
enforce = "TRUE"
}
}
}
resource "google_org_policy_policy" "disable_sa_key_upload" {
name = "${local.org}/policies/iam.managed.disableServiceAccountKeyUpload"
parent = local.org
spec {
rules {
enforce = "TRUE"
}
}
}
4. Restrict External IP Addresses for VM Instances
Constraint: compute.vmExternalIpAccess
By default, a VM can get a public IP address and become reachable from the internet. This constraint lets you decide which VM instances (if any) are allowed to have an external IP.
Denying public IPs removes a huge part of your attack surface. It forces teams to use safer patterns: Cloud NAT for outbound traffic, Identity-Aware Proxy (IAP) for SSH/RDP access, and a load balancer with Cloud Armor for inbound web traffic. It also makes unusual traffic much easier to spot.
Deny all external IPs:
resource "google_org_policy_policy" "vm_external_ip_access" {
name = "${local.org}/policies/compute.vmExternalIpAccess"
parent = local.org
spec {
rules {
deny_all = "TRUE"
}
}
}
Or allow only specific, approved instances (for example a bastion or a network appliance):
spec {
rules {
values {
allowed_values = [
"projects/network-hub/zones/europe-west4-a/instances/nva-firewall-01",
]
}
}
}
5. Enforce Public Access Prevention (and Uniform Bucket-Level Access)
Constraints: storage.publicAccessPrevention and storage.uniformBucketLevelAccess
Publicly exposed storage buckets have been behind countless data breaches. Public Access Prevention makes sure no data in Cloud Storage can be made public, whatever the IAM or ACL settings say. This policy is a simple step with huge benefits.
I always pair it with Uniform Bucket-Level Access, which disables legacy object-level ACLs so access is managed only through IAM. One access model is much easier to audit than two.
resource "google_org_policy_policy" "public_access_prevention" {
name = "${local.org}/policies/storage.publicAccessPrevention"
parent = local.org
spec {
rules {
enforce = "TRUE"
}
}
}
resource "google_org_policy_policy" "uniform_bucket_level_access" {
name = "${local.org}/policies/storage.uniformBucketLevelAccess"
parent = local.org
spec {
rules {
enforce = "TRUE"
}
}
}
6. Define Trusted Image Projects
Constraint: compute.trustedImageProjects
This constraint defines which projects are trusted sources for Compute Engine disk images. Teams can only create VMs from images you’ve approved, such as your own hardened golden images or specific public OS images.
This keeps your VM fleet consistent, stops untested or malicious images from being used, and helps with cost control and licence compliance.
resource "google_org_policy_policy" "trusted_image_projects" {
name = "${local.org}/policies/compute.trustedImageProjects"
parent = local.org
spec {
rules {
values {
allowed_values = [
"projects/your-golden-images", # your hardened images
"projects/debian-cloud",
"projects/cos-cloud",
"projects/rhel-cloud",
]
}
}
}
}
Good to know: GKE nodes use images from Google-owned projects such as gke-node-images and ubuntu-os-gke-cloud. If you run GKE, add those to the list or your node pools won’t start.
7. Skip Default Network Creation
Constraint: compute.skipDefaultNetworkCreation
Every new project gets a default VPC network, with a subnet in every region and firewall rules that allow SSH, RDP and ICMP from 0.0.0.0/0. That’s handy for a quick demo, but in an enterprise environment it’s an open door nobody asked for.
Skip it, and let your platform team provide properly designed networks (for example through Shared VPC).
resource "google_org_policy_policy" "skip_default_network" {
name = "${local.org}/policies/compute.skipDefaultNetworkCreation"
parent = local.org
spec {
rules {
enforce = "TRUE"
}
}
}
8. Require OS Login
Constraint: compute.managed.requireOsLogin
OS Login ties SSH access to your VMs to IAM identities, so there are no more loose SSH keys scattered across metadata. Access is granted and revoked through IAM roles, you can require 2-step verification, and every login can be traced back to a person.
This goes hand in hand with blocking project-wide SSH keys, which I wrote about in Secure your Compute Engine by blocking project-wide SSH keys.
resource "google_org_policy_policy" "require_os_login" {
name = "${local.org}/policies/compute.managed.requireOsLogin"
parent = local.org
spec {
rules {
enforce = "TRUE"
}
}
}
9. Restrict Public IP on Cloud SQL Instances
Constraint: sql.managed.restrictPublicIp
A database should never be reachable from the internet. This constraint stops anyone from configuring a public IP on a Cloud SQL instance, so all connections go through private IP (Private Service Access or Private Service Connect).
resource "google_org_policy_policy" "sql_restrict_public_ip" {
name = "${local.org}/policies/sql.managed.restrictPublicIp"
parent = local.org
spec {
rules {
enforce = "TRUE"
}
}
}
10. Disable VM Serial Port Access
Constraint: compute.managed.disableSerialPortAccess
The interactive serial console is a powerful troubleshooting tool, but it’s also a backdoor that ignores your firewall rules. Disable it by default and allow it only where you really need it, for example in a sandbox folder, using a Tag-based exception.
resource "google_org_policy_policy" "disable_serial_port_access" {
name = "${local.org}/policies/compute.managed.disableSerialPortAccess"
parent = local.org
spec {
rules {
enforce = "TRUE"
}
}
}
Honourable mentions
A few more constraints that deserve a spot in your baseline:
iam.automaticIamGrantsForDefaultServiceAccounts: stops default service accounts from automatically getting the very powerful Editor role.compute.managed.restrictProtocolForwardingCreationForTypes: limits protocol forwarding to internal IP addresses only.essentialcontacts.managed.allowedContactDomains: makes sure security notifications from Google only go to your own domains.gcp.restrictServiceUsage: allow only the Google Cloud services your company has approved.- Custom constraints: when no predefined constraint fits, write your own in CEL with the
google_org_policy_custom_constraintresource. For example: “GKE clusters must have Binary Authorization enabled”.
Keep it DRY: all boolean guardrails in one block
Most of the constraints above are simple on/off (boolean) switches. Instead of repeating the same resource ten times, you can use a for_each:
locals {
boolean_constraints = toset([
"iam.managed.disableServiceAccountKeyCreation",
"iam.managed.disableServiceAccountKeyUpload",
"iam.automaticIamGrantsForDefaultServiceAccounts",
"storage.publicAccessPrevention",
"storage.uniformBucketLevelAccess",
"compute.skipDefaultNetworkCreation",
"compute.managed.requireOsLogin",
"compute.managed.disableSerialPortAccess",
"sql.managed.restrictPublicIp",
])
}
resource "google_org_policy_policy" "boolean" {
for_each = local.boolean_constraints
name = "${local.org}/policies/${each.value}"
parent = local.org
spec {
rules {
enforce = "TRUE"
}
}
}
Roll out safely: dry-run first, then enforce
Don’t forget to evaluate and test the constraint of your choice before you apply it to production workloads. A guardrail that breaks your release pipeline on a Friday afternoon won’t make you any friends.
Step 1: dry-run. Add a dry_run_spec block. The policy is then evaluated but not enforced, and every violation is written to your audit logs. Let it run for a week or two and check the logs.
resource "google_org_policy_policy" "disable_serial_port_access" {
name = "${local.org}/policies/compute.managed.disableSerialPortAccess"
parent = local.org
dry_run_spec {
rules {
enforce = "TRUE"
}
}
}
Step 2: simulate. Use the Policy Simulator for Organization Policy to see which existing resources would violate the policy:
gcloud policy-intelligence simulate orgpolicy \
--organization=ORGANIZATION_ID \
--policies=policy.yaml
Step 3: enforce with exceptions. When you’re confident, move the rules from dry_run_spec to spec. For the few projects that really need an exception, use Tags instead of per-project overrides. Exceptions then stay visible, auditable and in one place:
resource "google_org_policy_policy" "disable_serial_port_access" {
name = "${local.org}/policies/compute.managed.disableSerialPortAccess"
parent = local.org
spec {
# Exception: allow serial port access where the tag is set
rules {
condition {
title = "serial-port-exception"
expression = "resource.matchTag('${var.org_id}/serial-port-access', 'allowed')"
}
enforce = "FALSE"
}
# Default: enforce everywhere else
rules {
enforce = "TRUE"
}
}
}
Remember: Organization Policies only apply to new resources and changes. Existing resources that violate a policy aren’t touched or deleted. Use Security Command Center to find and fix those.
Get started today with Organization Policy Constraints!
These ten recommendations will help keep your company’s resources and data safe as you build in Google Cloud. If your organization was created before May 2024, start with the secure-by-default constraints (numbers 2, 3 and 5), because you’re most likely missing them today. In the end, which additional constraints you use depends on your company’s specific needs.
Want to explore more constraints? Take a look at the full list: Organization policy constraints.
Follow me on LinkedIn for more tips on how to take your Google Cloud to the next level.
Jorge Liauw Calo
Security Engineer at Google Cloud (Google Cybershield) with 13+ years of experience in Cybersecurity across highly regulated industries including Semiconductor, Banking, Fintech, and Insurance. Active member of the Google Cloud Community BeNeLux and Google Cloud Security Community Amsterdam.