This Terraform configuration demonstrates how to implement a series of security guardrails within a Google Cloud organization. It leverages IAM Deny Policies and Organization Policies to restrict high-privilege permissions and enforce organizational standards.
This Terraform configuration sets up a series of security guardrails within a Google Cloud organization using IAM Deny Policies and Organization Policies. It aims to restrict specific high-privilege permissions and enforce organizational standards at both the organization and a designated folder level.
Key components include:
- An organization-level IAM Deny Policy targeting specific administrative permissions on resources tagged with a user-defined tag (e.g.,
iam_deny=enabled). Note: You must replace the placeholder tag IDs inmain.tfwith your actual tag key/value IDs. - A folder-level IAM Deny Policy restricting Billing, Security (including numerous Security Command Center permissions), and Networking permissions on resources unless they have any tag applied.
- A Custom Organization Policy Constraint to prevent the use of the primitive
roles/ownerrole. - An Organization Policy restricting the usage of specific Google Cloud services (
securitycenter.googleapis.com,accessapproval.googleapis.com) within a designated folder.
- Applies granular IAM Deny policies based on permissions defined in external JSON files (
billing.json,networking.json,securitycenter.json) and an internal list (denied_perms.tf). - Utilizes a user-defined resource tag (e.g.,
iam_deny=enabled) to conditionally apply the organization-level deny policy. Requires updating placeholder IDs inmain.tf. - Applies folder-level deny policies based on the absence of any resource tags, covering Billing, Networking, and Security Center permissions.
- Provides exceptions for specific principals (e.g., dedicated groups for networking, billing, security) for each deny policy rule.
- Enforces a custom constraint against the
roles/ownerrole. - Restricts specified Google Cloud service usage (e.g.,
securitycenter.googleapis.com,accessapproval.googleapis.com) within a designated folder using a standard Organization Policy.
- Terraform: Terraform CLI installed (see
provider.tffor version constraints). - Google Cloud Provider Authentication: Configured authentication for the Terraform Google providers. This is typically done via
gcloud auth application-default loginor by setting theGOOGLE_APPLICATION_CREDENTIALSenvironment variable to point to a service account key JSON file. - Required Permissions (Organization Level): The identity running Terraform needs sufficient permissions. Consider assigning or ensuring the identity has roles like:
- IAM Deny Admin (
roles/iam.denyAdmin) - Organization Policy Administrator (
roles/orgpolicy.policyAdmin) - Tag Admin (
roles/resourcemanager.tagAdmin) or Tag User (roles/resourcemanager.tagUser) for creating and using tags. - Organization Viewer (
roles/resourcemanager.organizationViewer) or Folder Admin (roles/resourcemanager.folderAdmin)
- IAM Deny Admin (
- Google Cloud Organization ID: Your numeric Organization ID (e.g.,
123456789012). - Target Google Cloud Folder ID: The numeric ID of the specific Google Cloud Folder (e.g.,
987654321098) where folder-level policies will be applied. - Tag Setup:
- You need to create a suitable tag (e.g., key
iam_denywith valueenabled) within your Google Cloud organization that will be used by the organization-level deny policy. - Obtain the specific numeric IDs for the Tag Key (e.g.,
tagKeys/123...) and Tag Value (e.g.,tagValues/456...).
- You need to create a suitable tag (e.g., key
- Permission Definition Files: The example JSON files defining permissions for different profiles (
billing.json,networking.json,securitycenter.json) are located within theterraform/profiles/directory of this example.
-
Clone Repository: If you haven't already, clone the repository containing this example to your local machine.
# Example: Replace with the actual repository URL git clone https://github.com/kevinschmidtG/professional-services.git cd examples/iam-deny
-
Navigate to Terraform Directory: Change into the Terraform configuration directory for this example.
cd terraform(All subsequent Terraform commands should be run from this
terraformdirectory) -
CRITICAL: Update Tag IDs in
main.tfThis is a crucial step for the organization-level deny policy to function correctly.
- Open the
main.tffile. - Locate the
google_iam_deny_policy.top_level_denyresource (around line 32). - Inside its
denial_conditionblock, you MUST REPLACE the placeholder tag key ID ('tagKeys/*') and tag value ID ('tagValues/*') in the expression:Replace# Before: expression = "resource.matchTagId('tagKeys/*', 'tagValues/*')" # After: expression = "resource.matchTagId('tagKeys/YOUR_TAG_KEY_ID', 'tagValues/YOUR_TAG_VALUE_ID')"
YOUR_TAG_KEY_IDandYOUR_TAG_VALUE_IDwith the actual numeric IDs of the tag key and value you created in the prerequisites.
- Open the
-
Prepare
terraform.tfvarsFile:- Copy the example variables file:
cp terraform.tfvars.example terraform.tfvars
- Edit the new
terraform.tfvarsfile. - IMPORTANT: Replace all placeholder values (like
YOUR_ORG_ID,YOUR_FOLDER_ID, and example group emails...@example.com) with your actual Organization ID, target Folder ID, and principal/group identifiers for policy exceptions. Refer to the Inputs section for details on each variable.
- Copy the example variables file:
-
Initialize Terraform:
terraform init
-
Review Plan: Terraform will automatically load variables from
terraform.tfvars.terraform plan
-
Apply Configuration:
terraform apply
This configuration will create the following Google Cloud resources:
google_iam_deny_policy.top_level_deny: An IAM Deny Policy attached at the organization level. It denies a broad set of administrative permissions (defined indenied_perms.tf) on any resource tagged with the specific tag you configured inmain.tf.google_iam_deny_policy.profile-deny-policy: An IAM Deny Policy attached at the folder level (specified byvar.folder_id). It denies specific Billing, Security, and Networking permissions on resources within that folder unless those resources have any tag applied to them. The specific sets of permissions for these profiles are defined in JSON files within the./terraform/profiles/directory (e.g.,billing.json,networking.json,securitycenter.json). This structure allows for clear organization of denied permissions and the application of distinct exception principals for each functional area.google_org_policy_custom_constraint.deny_owner: A Custom Organization Policy Constraint defined at the organization level. This constraint (custom.denyOwner) specifies a condition to deny the assignment of the primitiveroles/ownerrole.google_org_policy_policy.enforce_deny_owner_constraint: An Organization Policy that enforces thecustom.denyOwnerconstraint at the organization level.module "gcp_org_policy_v2"(Resource:google_org_policy_policy): This module creates an Organization Policy attached at the folder level (specified byvar.folder_id). It enforces thegcp.restrictServiceUsageconstraint to deny the usage of specified services (e.g.,securitycenter.googleapis.com,accessapproval.googleapis.com) within that folder.
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
org_id |
Your Google Cloud Organization ID (numeric string). Must be set in terraform.tfvars. |
string |
"" (Effectively N/A - Must be provided) |
Yes |
folder_id |
The folder ID (numeric string, without "folders/" prefix) where folder-level policies will be attached. Must be set in terraform.tfvars. |
string |
"" (Effectively N/A - Must be provided) |
Yes |
networking_exception_principals |
List of principals exempt from the networking deny rule. Example format: principalSet://goog/group/GROUP_EMAIL_ADDRESS. See IAM Principals. |
list(string) |
[] |
No |
billing_exception_principals |
List of principals exempt from the billing deny rule. | list(string) |
[] |
No |
sec_exception_principals |
List of principals exempt from the security deny rule. | list(string) |
[] |
No |
top_exception_principals |
List of principals exempt from the organization-level deny policy. | list(string) |
[] |
No |
folder_path |
The prefix for the folder resource path used in IAM policies. | string |
"cloudresourcemanager.googleapis.com/folders/" |
No |
region |
The default Google Cloud region for the provider. | string |
"us-central1" |
No |
zone |
The default Google Cloud zone for the provider. | string |
"us-central1-c" |
No |
Important Note on terraform.tfvars: The terraform.tfvars.example file provides the structure for your terraform.tfvars file. You must update org_id, folder_id, and any desired exception principals in terraform.tfvars before applying the configuration. The default empty string "" for org_id and folder_id in the table above are placeholders in the variable definitions; they will cause errors if not overridden. Exception principal lists default to empty [] if not specified, meaning no exceptions by default.
No outputs are defined in this configuration.
| Name | Version |
|---|---|
| hashicorp/google | >= 5.0.0 |
| hashicorp/google-beta | >= 5.0.0 |
| Name | Source | Version |
|---|---|---|
| gcp_org_policy_v2 | terraform-google-modules/org-policy/google//modules/org_policy_v2 | ~> 5.3.0 |
This repository focuses on preventative controls using IAM Deny and Organization Policies. For managing temporary, just-in-time elevated access (which might be needed for principals requiring exceptions to these policies), consider using a Privileged Access Management approach.
Google Cloud provides a reference implementation for PAM using Terraform:
- terraform-google-pam: https://github.com/GoogleCloudPlatform/terraform-google-pam/tree/main
This PAM module is separate and addresses a different aspect of access control, typically implemented based on specific operational needs for managing temporary elevation.
Contributions are welcome! Please refer to the main repository's contributing guidelines for more information.
Licensed under the Apache License, Version 2.0. See the LICENSE file for the full license text. (Note: Adjust the path to the main LICENSE file if this example is moved).