diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
index 21c0746ae2..1c63b78dc0 100644
--- a/.github/workflows/linting.yml
+++ b/.github/workflows/linting.yml
@@ -53,12 +53,12 @@ jobs:
run: |
terraform fmt -recursive -check -diff $GITHUB_WORKSPACE
- - name: Check documentation (fabric)
+ - name: Check documentation
id: documentation-fabric
run: |
- python3 tools/check_documentation.py examples modules fast
+ python3 tools/check_documentation.py modules fast blueprints
- - name: Check documentation links (fabric)
+ - name: Check documentation links
id: documentation-links-fabric
run: |
python3 tools/check_links.py .
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/README.md b/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/README.md
index 40e00f8678..35198e8d1c 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/README.md
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/README.md
@@ -12,22 +12,22 @@ The codebase provisions the following list of resources:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [billing_account](variables.tf#L16) | Billing account id used as default for new projects. | string
| ✓ | |
-| [project_id](variables.tf#L38) | Existing project id. | string
| ✓ | |
-| [tfe_organization_id](variables.tf#L43) | |
| ✓ | |
-| [tfe_workspace_id](variables.tf#L48) | |
| ✓ | |
-| [issuer_uri](variables.tf#L65) | Terraform Enterprise uri. Replace the uri if a self hosted instance is used. | string
| | "https://app.terraform.io/"
|
+| [project_id](variables.tf#L43) | Existing project id. | string
| ✓ | |
+| [tfe_organization_id](variables.tf#L48) | TFE organization id. | string
| ✓ | |
+| [tfe_workspace_id](variables.tf#L53) | TFE workspace id. | string
| ✓ | |
+| [issuer_uri](variables.tf#L21) | Terraform Enterprise uri. Replace the uri if a self hosted instance is used. | string
| | "https://app.terraform.io/"
|
| [parent](variables.tf#L27) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string
| | null
|
-| [project_create](variables.tf#L21) | Create project instead of using an existing one. | bool
| | true
|
-| [workload_identity_pool_id](variables.tf#L53) | Workload identity pool id. | string
| | "tfe-pool"
|
-| [workload_identity_pool_provider_id](variables.tf#L59) | Workload identity pool provider id. | string
| | "tfe-provider"
|
+| [project_create](variables.tf#L37) | Create project instead of using an existing one. | bool
| | true
|
+| [workload_identity_pool_id](variables.tf#L58) | Workload identity pool id. | string
| | "tfe-pool"
|
+| [workload_identity_pool_provider_id](variables.tf#L64) | Workload identity pool provider id. | string
| | "tfe-provider"
|
## Outputs
| name | description | sensitive |
|---|---|:---:|
-| [impersonate_service_account_email](outputs.tf#L31) | | |
-| [project_id](outputs.tf#L16) | | |
-| [workload_identity_audience](outputs.tf#L26) | | |
-| [workload_identity_pool_provider_id](outputs.tf#L21) | GCP workload identity pool provider ID. | |
+| [impersonate_service_account_email](outputs.tf#L16) | Service account to be impersonated by workload identity. | |
+| [project_id](outputs.tf#L21) | GCP Project ID. | |
+| [workload_identity_audience](outputs.tf#L26) | TFC Workload Identity Audience. | |
+| [workload_identity_pool_provider_id](outputs.tf#L31) | GCP workload identity pool provider ID. | |
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/outputs.tf b/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/outputs.tf
index 79cea39a27..46d7f6b0f2 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/outputs.tf
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/outputs.tf
@@ -13,22 +13,22 @@
# limitations under the License.
+output "impersonate_service_account_email" {
+ description = "Service account to be impersonated by workload identity."
+ value = module.sa-tfe.email
+}
+
output "project_id" {
description = "GCP Project ID."
value = module.project.project_id
}
-output "workload_identity_pool_provider_id" {
- description = "GCP workload identity pool provider ID."
- value = google_iam_workload_identity_pool_provider.tfe-pool-provider.name
-}
-
output "workload_identity_audience" {
description = "TFC Workload Identity Audience."
value = "//iam.googleapis.com/${google_iam_workload_identity_pool_provider.tfe-pool-provider.name}"
}
-output "impersonate_service_account_email" {
- description = "Service account to be impersonated by workload identity."
- value = module.sa-tfe.email
+output "workload_identity_pool_provider_id" {
+ description = "GCP workload identity pool provider ID."
+ value = google_iam_workload_identity_pool_provider.tfe-pool-provider.name
}
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/variables.tf b/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/variables.tf
index 62163d1782..3719b1839e 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/variables.tf
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/gcp-workload-identity-provider/variables.tf
@@ -18,10 +18,10 @@ variable "billing_account" {
type = string
}
-variable "project_create" {
- description = "Create project instead of using an existing one."
- type = bool
- default = true
+variable "issuer_uri" {
+ description = "Terraform Enterprise uri. Replace the uri if a self hosted instance is used."
+ type = string
+ default = "https://app.terraform.io/"
}
variable "parent" {
@@ -34,6 +34,11 @@ variable "parent" {
}
}
+variable "project_create" {
+ description = "Create project instead of using an existing one."
+ type = bool
+ default = true
+}
variable "project_id" {
description = "Existing project id."
@@ -61,9 +66,3 @@ variable "workload_identity_pool_provider_id" {
type = string
default = "tfe-provider"
}
-
-variable "issuer_uri" {
- description = "Terraform Enterprise uri. Replace the uri if a self hosted instance is used."
- type = string
- default = "https://app.terraform.io/"
-}
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/README.md b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/README.md
index 5226dd64cc..9be8a09bda 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/README.md
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/README.md
@@ -5,15 +5,14 @@ This terraform code is a part of [GCP Workload Identity Federation for Terraform
The codebase provisions the following list of resources:
- GCS Bucket
-
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [impersonate_service_account_email](variables.tf#L26) | |
| ✓ | |
-| [project_id](variables.tf#L16) | |
| ✓ | |
-| [workload_identity_pool_provider_id](variables.tf#L21) | GCP workload identity pool provider ID. | string
| ✓ | |
+| [impersonate_service_account_email](variables.tf#L21) | Service account to be impersonated by workload identity. | string
| ✓ | |
+| [project_id](variables.tf#L16) | GCP project ID. | string
| ✓ | |
+| [workload_identity_pool_provider_id](variables.tf#L26) | GCP workload identity pool provider ID. | string
| ✓ | |
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/variables.tf b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/variables.tf
index 3f36c2ca65..3a1d81dc2a 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/variables.tf
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/variables.tf
@@ -18,12 +18,12 @@ variable "project_id" {
type = string
}
-variable "workload_identity_pool_provider_id" {
- description = "GCP workload identity pool provider ID."
+variable "impersonate_service_account_email" {
+ description = "Service account to be impersonated by workload identity."
type = string
}
-variable "impersonate_service_account_email" {
- description = "Service account to be impersonated by workload identity."
+variable "workload_identity_pool_provider_id" {
+ description = "GCP workload identity pool provider ID."
type = string
}
diff --git a/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf b/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf
index 7519fa8a5e..4ced84f230 100644
--- a/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf
+++ b/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf
@@ -67,8 +67,10 @@ module "orch-project" {
"roles/storage.objectViewer" = [module.load-sa-df-0.iam_email]
}
oslogin = false
- policy_boolean = {
- "constraints/compute.requireOsLogin" = false
+ org_policies = {
+ "constraints/compute.requireOsLogin" = {
+ enforce = false
+ }
}
services = concat(var.project_services, [
"artifactregistry.googleapis.com",
diff --git a/blueprints/data-solutions/data-playground/main.tf b/blueprints/data-solutions/data-playground/main.tf
index 2bcd69ab88..3fb6999a60 100644
--- a/blueprints/data-solutions/data-playground/main.tf
+++ b/blueprints/data-solutions/data-playground/main.tf
@@ -40,8 +40,10 @@ module "project" {
"storage.googleapis.com",
"storage-component.googleapis.com"
]
- policy_boolean = {
- # "constraints/compute.requireOsLogin" = false
+ org_policies = {
+ # "constraints/compute.requireOsLogin" = {
+ # enforce = false
+ # }
# Example of applying a project wide policy, mainly useful for Composer
}
service_encryption_key_ids = {
diff --git a/blueprints/factories/project-factory/README.md b/blueprints/factories/project-factory/README.md
index e496aa4d1d..c7bb0132fe 100644
--- a/blueprints/factories/project-factory/README.md
+++ b/blueprints/factories/project-factory/README.md
@@ -68,13 +68,13 @@ module "projects" {
iam = try(each.value.iam, {})
kms_service_agents = try(each.value.kms, {})
labels = try(each.value.labels, {})
- org_policies = try(each.value.org_policies, null)
+ org_policies = try(each.value.org_policies, {})
service_accounts = try(each.value.service_accounts, {})
services = try(each.value.services, [])
service_identities_iam = try(each.value.service_identities_iam, {})
vpc = try(each.value.vpc, null)
}
-# tftest modules=7 resources=27
+# tftest modules=7 resources=28
```
### Projects configuration
@@ -153,16 +153,16 @@ labels:
environment: prod
# [opt] Org policy overrides defined at project level
-org_policies:
- policy_boolean:
- constraints/compute.disableGuestAttributesAccess: true
- policy_list:
- constraints/compute.trustedImageProjects:
- inherit_from_parent: null
- status: true
- suggested_value: null
+org_policies:
+ constraints/compute.disableGuestAttributesAccess:
+ enforce: true
+ constraints/compute.trustedImageProjects:
+ allow:
values:
- - projects/fast-prod-iac-core-0
+ - projects/fast-dev-iac-core-0
+ constraints/compute.vmExternalIpAccess:
+ deny:
+ all: true
# [opt] Service account to create for the project and their roles on the project
# in name => [roles] format
@@ -221,7 +221,7 @@ vpc:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [billing_account_id](variables.tf#L17) | Billing account id. | string
| ✓ | |
-| [project_id](variables.tf#L119) | Project id. | string
| ✓ | |
+| [project_id](variables.tf#L145) | Project id. | string
| ✓ | |
| [billing_alert](variables.tf#L22) | Billing alert configuration. | object({…})
| | null
|
| [defaults](variables.tf#L35) | Project factory default values. | object({…})
| | null
|
| [dns_zones](variables.tf#L57) | DNS private zones to create as child of var.defaults.environment_dns_zone. | list(string)
| | []
|
@@ -231,13 +231,13 @@ vpc:
| [iam](variables.tf#L81) | Custom IAM settings in role => [principal] format. | map(list(string))
| | {}
|
| [kms_service_agents](variables.tf#L87) | KMS IAM configuration in as service => [key]. | map(list(string))
| | {}
|
| [labels](variables.tf#L93) | Labels to be assigned at project level. | map(string)
| | {}
|
-| [org_policies](variables.tf#L99) | Org-policy overrides at project level. | object({…})
| | null
|
-| [prefix](variables.tf#L113) | Prefix used for the project id. | string
| | null
|
-| [service_accounts](variables.tf#L124) | Service accounts to be created, and roles assigned them on the project. | map(list(string))
| | {}
|
-| [service_accounts_iam](variables.tf#L130) | IAM bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]} | map(map(list(string)))
| | {}
|
-| [service_identities_iam](variables.tf#L144) | Custom IAM settings for service identities in service => [role] format. | map(list(string))
| | {}
|
-| [services](variables.tf#L137) | Services to be enabled for the project. | list(string)
| | []
|
-| [vpc](variables.tf#L151) | VPC configuration for the project. | object({…})
| | null
|
+| [org_policies](variables.tf#L99) | Org-policy overrides at project level. | map(object({…}))
| | {}
|
+| [prefix](variables.tf#L139) | Prefix used for the project id. | string
| | null
|
+| [service_accounts](variables.tf#L150) | Service accounts to be created, and roles assigned them on the project. | map(list(string))
| | {}
|
+| [service_accounts_iam](variables.tf#L156) | IAM bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]} | map(map(list(string)))
| | {}
|
+| [service_identities_iam](variables.tf#L164) | Custom IAM settings for service identities in service => [role] format. | map(list(string))
| | {}
|
+| [services](variables.tf#L171) | Services to be enabled for the project. | list(string)
| | []
|
+| [vpc](variables.tf#L178) | VPC configuration for the project. | object({…})
| | null
|
## Outputs
diff --git a/blueprints/factories/project-factory/main.tf b/blueprints/factories/project-factory/main.tf
index 996b79e354..db80810b9f 100644
--- a/blueprints/factories/project-factory/main.tf
+++ b/blueprints/factories/project-factory/main.tf
@@ -148,9 +148,8 @@ module "project" {
contacts = { for c in local.essential_contacts : c => ["ALL"] }
iam = local.iam
labels = local.labels
+ org_policies = try(var.org_policies, {})
parent = var.folder_id
- policy_boolean = try(var.org_policies.policy_boolean, {})
- policy_list = try(var.org_policies.policy_list, {})
service_encryption_key_ids = var.kms_service_agents
services = local.services
shared_vpc_service_config = var.vpc == null ? null : {
diff --git a/blueprints/factories/project-factory/sample-data/projects/project.yaml b/blueprints/factories/project-factory/sample-data/projects/project.yaml
index 13a8f5f52e..88ba0bf50a 100644
--- a/blueprints/factories/project-factory/sample-data/projects/project.yaml
+++ b/blueprints/factories/project-factory/sample-data/projects/project.yaml
@@ -48,15 +48,15 @@ labels:
# [opt] Org policy overrides defined at project level
org_policies:
- policy_boolean:
- constraints/compute.disableGuestAttributesAccess: true
- policy_list:
- constraints/compute.trustedImageProjects:
- inherit_from_parent: null
- status: true
- suggested_value: null
+ constraints/compute.disableGuestAttributesAccess:
+ enforce: true
+ constraints/compute.trustedImageProjects:
+ allow:
values:
- projects/fast-dev-iac-core-0
+ constraints/compute.vmExternalIpAccess:
+ deny:
+ all: true
# [opt] Service account to create for the project and their roles on the project
# in name => [roles] format
diff --git a/blueprints/factories/project-factory/variables.tf b/blueprints/factories/project-factory/variables.tf
index 6154c032c1..cf5d8fb342 100644
--- a/blueprints/factories/project-factory/variables.tf
+++ b/blueprints/factories/project-factory/variables.tf
@@ -98,16 +98,42 @@ variable "labels" {
variable "org_policies" {
description = "Org-policy overrides at project level."
- type = object({
- policy_boolean = map(bool)
- policy_list = map(object({
- inherit_from_parent = bool
- suggested_value = string
- status = bool
- values = list(string)
+ type = map(object({
+ inherit_from_parent = optional(bool) # for list policies only.
+ reset = optional(bool)
+
+ # default (unconditional) values
+ allow = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
}))
- })
- default = null
+ deny = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ enforce = optional(bool, true) # for boolean policies only.
+
+ # conditional values
+ rules = optional(list(object({
+ allow = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ deny = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ enforce = optional(bool, true) # for boolean policies only.
+ condition = object({
+ description = optional(string)
+ expression = optional(string)
+ location = optional(string)
+ title = optional(string)
+ })
+ })), [])
+ }))
+ default = {}
+ nullable = false
}
variable "prefix" {
@@ -134,12 +160,6 @@ variable "service_accounts_iam" {
nullable = false
}
-variable "services" {
- description = "Services to be enabled for the project."
- type = list(string)
- default = []
- nullable = false
-}
variable "service_identities_iam" {
description = "Custom IAM settings for service identities in service => [role] format."
@@ -148,6 +168,13 @@ variable "service_identities_iam" {
nullable = false
}
+variable "services" {
+ description = "Services to be enabled for the project."
+ type = list(string)
+ default = []
+ nullable = false
+}
+
variable "vpc" {
description = "VPC configuration for the project."
type = object({
@@ -160,6 +187,3 @@ variable "vpc" {
})
default = null
}
-
-
-
diff --git a/blueprints/networking/filtering-proxy/main.tf b/blueprints/networking/filtering-proxy/main.tf
index 884fbd300f..97d6efec03 100644
--- a/blueprints/networking/filtering-proxy/main.tf
+++ b/blueprints/networking/filtering-proxy/main.tf
@@ -226,13 +226,10 @@ module "folder-apps" {
source = "../../../modules/folder"
parent = var.root_node
name = "apps"
- policy_list = {
+ org_policies = {
# prevent VMs with public IPs in the apps folder
"constraints/compute.vmExternalIpAccess" = {
- inherit_from_parent = false
- suggested_value = null
- status = false
- values = []
+ deny = { all = true }
}
}
}
diff --git a/fast/stages/01-resman/branch-sandbox.tf b/fast/stages/01-resman/branch-sandbox.tf
index 7ed154aae1..84995c15bd 100644
--- a/fast/stages/01-resman/branch-sandbox.tf
+++ b/fast/stages/01-resman/branch-sandbox.tf
@@ -32,16 +32,9 @@ module "branch-sandbox-folder" {
"roles/resourcemanager.folderAdmin" = [module.branch-sandbox-sa.0.iam_email]
"roles/resourcemanager.projectCreator" = [module.branch-sandbox-sa.0.iam_email]
}
- policy_boolean = {
- "constraints/sql.restrictPublicIp" = false
- }
- policy_list = {
- "constraints/compute.vmExternalIpAccess" = {
- inherit_from_parent = false
- suggested_value = null
- status = true
- values = []
- }
+ org_policies = {
+ "constraints/sql.restrictPublicIp" = { enforce = false }
+ "constraints/compute.vmExternalIpAccess" = { allow = { all = true } }
}
tag_bindings = {
context = try(
diff --git a/fast/stages/01-resman/organization.tf b/fast/stages/01-resman/organization.tf
index 6596f9c000..0e8430e109 100644
--- a/fast/stages/01-resman/organization.tf
+++ b/fast/stages/01-resman/organization.tf
@@ -18,18 +18,11 @@
locals {
- list_allow = {
- inherit_from_parent = false
- suggested_value = null
- status = true
- values = []
- }
- list_deny = {
- inherit_from_parent = false
- suggested_value = null
- status = false
- values = []
- }
+ all_drs_domains = concat(
+ [var.organization.customer_id],
+ try(local.policy_configs.allowed_policy_member_domains, [])
+ )
+
policy_configs = (
var.organization_policy_configs == null
? {}
@@ -74,74 +67,55 @@ module "organization" {
} : {}
)
# sample subset of useful organization policies, edit to suit requirements
- policy_boolean = {
- # "constraints/cloudfunctions.requireVPCConnector" = true
- # "constraints/compute.disableGuestAttributesAccess" = true
- # "constraints/compute.disableInternetNetworkEndpointGroup" = true
- # "constraints/compute.disableNestedVirtualization" = true
- # "constraints/compute.disableSerialPortAccess" = true
- "constraints/compute.requireOsLogin" = true
- # "constraints/compute.restrictXpnProjectLienRemoval" = true
- "constraints/compute.skipDefaultNetworkCreation" = true
- # "constraints/compute.setNewProjectDefaultToZonalDNSOnly" = true
- "constraints/iam.automaticIamGrantsForDefaultServiceAccounts" = true
- "constraints/iam.disableServiceAccountKeyCreation" = true
- # "constraints/iam.disableServiceAccountKeyUpload" = true
- "constraints/sql.restrictPublicIp" = true
- "constraints/sql.restrictAuthorizedNetworks" = true
- "constraints/storage.uniformBucketLevelAccess" = true
- }
- policy_list = {
- # "constraints/cloudfunctions.allowedIngressSettings" = merge(
- # local.list_allow, { values = ["is:ALLOW_INTERNAL_ONLY"] }
- # )
- # "constraints/cloudfunctions.allowedVpcConnectorEgressSettings" = merge(
- # local.list_allow, { values = ["is:PRIVATE_RANGES_ONLY"] }
- # )
- "constraints/compute.restrictLoadBalancerCreationForTypes" = merge(
- local.list_allow, { values = ["in:INTERNAL"] }
- )
- "constraints/compute.vmExternalIpAccess" = local.list_deny
- "constraints/iam.allowedPolicyMemberDomains" = merge(
- local.list_allow, {
- values = concat(
- [var.organization.customer_id],
- try(local.policy_configs.allowed_policy_member_domains, [])
- )
- })
- "constraints/run.allowedIngress" = merge(
- local.list_allow, { values = ["is:internal"] }
- )
- # "constraints/run.allowedVPCEgress" = merge(
- # local.list_allow, { values = ["is:private-ranges-only"] }
- # )
- # "constraints/compute.restrictCloudNATUsage" = local.list_deny
- # "constraints/compute.restrictDedicatedInterconnectUsage" = local.list_deny
- # "constraints/compute.restrictPartnerInterconnectUsage" = local.list_deny
- # "constraints/compute.restrictProtocolForwardingCreationForTypes" = local.list_deny
- # "constraints/compute.restrictSharedVpcHostProjects" = local.list_deny
- # "constraints/compute.restrictSharedVpcSubnetworks" = local.list_deny
- # "constraints/compute.restrictVpcPeering" = local.list_deny
- # "constraints/compute.restrictVpnPeerIPs" = local.list_deny
- # "constraints/compute.vmCanIpForward" = local.list_deny
- # "constraints/gcp.resourceLocations" = {
- # inherit_from_parent = false
- # suggested_value = null
- # status = true
- # values = local.allowed_regions
+
+ org_policies = {
+ "compute.disableGuestAttributesAccess" = { enforce = true }
+ "compute.requireOsLogin" = { enforce = true }
+ "compute.restrictLoadBalancerCreationForTypes" = { allow = { values = ["in:INTERNAL"] } }
+ "compute.skipDefaultNetworkCreation" = { enforce = true }
+ "compute.vmExternalIpAccess" = { deny = { all = true } }
+ "iam.allowedPolicyMemberDomains" = { allow = { values = local.all_drs_domains } }
+ "iam.automaticIamGrantsForDefaultServiceAccounts" = { enforce = true }
+ "iam.disableServiceAccountKeyCreation" = { enforce = true }
+ "iam.disableServiceAccountKeyUpload" = { enforce = true }
+ "run.allowedIngress" = { allow = { values = ["is:INTERNAL"] } }
+ "sql.restrictAuthorizedNetworks" = { enforce = true }
+ "sql.restrictPublicIp" = { enforce = true }
+ "storage.uniformBucketLevelAccess" = { enforce = true }
+
+ # "cloudfunctions.allowedIngressSettings" = {
+ # allow = { values = ["is:ALLOW_INTERNAL_ONLY"] }
+ # }
+ # "cloudfunctions.allowedVpcConnectorEgressSettings" = {
+ # allow = { values = ["is:PRIVATE_RANGES_ONLY"] }
+ # }
+ # "cloudfunctions.requireVPCConnector" = { enforce = true }
+ # "compute.disableInternetNetworkEndpointGroup" = { enforce = true }
+ # "compute.disableNestedVirtualization" = { enforce = true }
+ # "compute.disableSerialPortAccess" = { enforce = true }
+ # "compute.restrictCloudNATUsage" = { deny = { all = true }}
+ # "compute.restrictDedicatedInterconnectUsage" = { deny = { all = true }}
+ # "compute.restrictPartnerInterconnectUsage" = { deny = { all = true }}
+ # "compute.restrictProtocolForwardingCreationForTypes" = { deny = { all = true }}
+ # "compute.restrictSharedVpcHostProjects" = { deny = { all = true }}
+ # "compute.restrictSharedVpcSubnetworks" = { deny = { all = true }}
+ # "compute.restrictVpcPeering" = { deny = { all = true }}
+ # "compute.restrictVpnPeerIPs" = { deny = { all = true }}
+ # "compute.restrictXpnProjectLienRemoval" = { enforce = true }
+ # "compute.setNewProjectDefaultToZonalDNSOnly" = { enforce = true }
+ # "compute.vmCanIpForward" = { deny = { all = true }}
+ # "gcp.resourceLocations" = {
+ # allow = { values = local.allowed_regions }
+ # }
+ # "iam.workloadIdentityPoolProviders" = {
+ # allow = {
+ # values = [
+ # for k, v in coalesce(var.automation.federated_identity_providers, {}) :
+ # v.issuer_uri
+ # ]
+ # }
# }
- # https://cloud.google.com/iam/docs/manage-workload-identity-pools-providers#restrict
- # "constraints/iam.workloadIdentityPoolProviders" = merge(
- # local.list_allow, { values = [
- # for k, v in coalesce(var.automation.federated_identity_providers, {}) :
- # v.issuer_uri
- # ] }
- # )
- # "constraints/iam.workloadIdentityPoolAwsAccounts" = merge(
- # local.list_allow, { values = [
- #
- # ] }
- # )
+ # "run.allowedVPCEgress" = { allow = { values = ["is:private-ranges-only"] } }
}
tags = {
(var.tag_names.context) = {
diff --git a/fast/stages/03-project-factory/dev/data/projects/project.yaml.sample b/fast/stages/03-project-factory/dev/data/projects/project.yaml.sample
index 13a8f5f52e..88ba0bf50a 100644
--- a/fast/stages/03-project-factory/dev/data/projects/project.yaml.sample
+++ b/fast/stages/03-project-factory/dev/data/projects/project.yaml.sample
@@ -48,15 +48,15 @@ labels:
# [opt] Org policy overrides defined at project level
org_policies:
- policy_boolean:
- constraints/compute.disableGuestAttributesAccess: true
- policy_list:
- constraints/compute.trustedImageProjects:
- inherit_from_parent: null
- status: true
- suggested_value: null
+ constraints/compute.disableGuestAttributesAccess:
+ enforce: true
+ constraints/compute.trustedImageProjects:
+ allow:
values:
- projects/fast-dev-iac-core-0
+ constraints/compute.vmExternalIpAccess:
+ deny:
+ all: true
# [opt] Service account to create for the project and their roles on the project
# in name => [roles] format
diff --git a/modules/folder/README.md b/modules/folder/README.md
index 8f4c1bc384..0014e7616a 100644
--- a/modules/folder/README.md
+++ b/modules/folder/README.md
@@ -31,20 +31,46 @@ module "folder" {
source = "./fabric/modules/folder"
parent = "organizations/1234567890"
name = "Folder name"
- policy_boolean = {
- "constraints/compute.disableGuestAttributesAccess" = true
- "constraints/compute.skipDefaultNetworkCreation" = true
- }
- policy_list = {
+ org_policies = {
+ "compute.disableGuestAttributesAccess" = {
+ enforce = true
+ }
+ "constraints/compute.skipDefaultNetworkCreation" = {
+ enforce = true
+ }
+ "iam.disableServiceAccountKeyCreation" = {
+ enforce = true
+ }
+ "iam.disableServiceAccountKeyUpload" = {
+ enforce = false
+ rules = [
+ {
+ condition = {
+ expression = "resource.matchTagId(\"tagKeys/1234\", \"tagValues/1234\")"
+ title = "condition"
+ description = "test condition"
+ location = "somewhere"
+ }
+ enforce = true
+ }
+ ]
+ }
+ "constraints/iam.allowedPolicyMemberDomains" = {
+ allow = {
+ values = ["C0xxxxxxx", "C0yyyyyyy"]
+ }
+ }
"constraints/compute.trustedImageProjects" = {
- inherit_from_parent = null
- suggested_value = null
- status = true
- values = ["projects/my-project"]
+ allow = {
+ values = ["projects/my-project"]
+ }
+ }
+ "constraints/compute.vmExternalIpAccess" = {
+ deny = { all = true }
}
}
}
-# tftest modules=1 resources=4
+# tftest modules=1 resources=8
```
### Firewall policy factory
@@ -259,7 +285,7 @@ module "folder" {
| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | google_folder_iam_binding
· google_folder_iam_member
|
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member
· google_logging_folder_exclusion
· google_logging_folder_sink
· google_project_iam_member
· google_pubsub_topic_iam_member
· google_storage_bucket_iam_member
|
| [main.tf](./main.tf) | Module-level locals and resources. | google_essential_contacts_contact
· google_folder
|
-| [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | google_folder_organization_policy
|
+| [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | google_org_policy_policy
|
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [tags.tf](./tags.tf) | None | google_tags_tag_binding
|
| [variables.tf](./variables.tf) | Module variables. | |
@@ -282,10 +308,9 @@ module "folder" {
| [logging_exclusions](variables.tf#L98) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string)
| | {}
|
| [logging_sinks](variables.tf#L105) | Logging sinks to create for this folder. | map(object({…}))
| | {}
|
| [name](variables.tf#L126) | Folder name. | string
| | null
|
-| [parent](variables.tf#L132) | Parent in folders/folder_id or organizations/org_id format. | string
| | null
|
-| [policy_boolean](variables.tf#L142) | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool)
| | {}
|
-| [policy_list](variables.tf#L149) | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({…}))
| | {}
|
-| [tag_bindings](variables.tf#L161) | Tag bindings for this folder, in key => tag value id format. | map(string)
| | null
|
+| [org_policies](variables.tf#L132) | Organization policies applied to this folder keyed by policy name. | map(object({…}))
| | {}
|
+| [parent](variables.tf#L172) | Parent in folders/folder_id or organizations/org_id format. | string
| | null
|
+| [tag_bindings](variables.tf#L182) | Tag bindings for this folder, in key => tag value id format. | map(string)
| | null
|
## Outputs
@@ -295,7 +320,7 @@ module "folder" {
| [firewall_policy_id](outputs.tf#L21) | Map of firewall policy ids created in this folder. | |
| [folder](outputs.tf#L26) | Folder resource. | |
| [id](outputs.tf#L31) | Folder id. | |
-| [name](outputs.tf#L41) | Folder name. | |
-| [sink_writer_identities](outputs.tf#L46) | Writer identities created for each sink. | |
+| [name](outputs.tf#L40) | Folder name. | |
+| [sink_writer_identities](outputs.tf#L45) | Writer identities created for each sink. | |
diff --git a/modules/folder/organization-policies.tf b/modules/folder/organization-policies.tf
index 177a3d8041..da47806390 100644
--- a/modules/folder/organization-policies.tf
+++ b/modules/folder/organization-policies.tf
@@ -16,75 +16,79 @@
# tfdoc:file:description Folder-level organization policies.
-resource "google_folder_organization_policy" "boolean" {
- for_each = var.policy_boolean
- folder = local.folder.name
- constraint = each.key
-
- dynamic "boolean_policy" {
- for_each = each.value == null ? [] : [each.value]
- iterator = policy
- content {
- enforced = policy.value
- }
- }
-
- dynamic "restore_policy" {
- for_each = each.value == null ? [""] : []
- content {
- default = true
- }
+locals {
+ org_policies = {
+ for k, v in var.org_policies :
+ k => merge(v, {
+ is_boolean_policy = v.allow == null && v.deny == null
+ has_values = (
+ length(coalesce(try(v.allow.values, []), [])) > 0 ||
+ length(coalesce(try(v.deny.values, []), [])) > 0
+ )
+ rules = [
+ for r in v.rules :
+ merge(r, {
+ has_values = (
+ length(coalesce(try(r.allow.values, []), [])) > 0 ||
+ length(coalesce(try(r.deny.values, []), [])) > 0
+ )
+ })
+ ]
+ })
}
}
-resource "google_folder_organization_policy" "list" {
- for_each = var.policy_list
- folder = local.folder.name
- constraint = each.key
+resource "google_org_policy_policy" "default" {
+ for_each = local.org_policies
+ name = "${local.folder.name}/policies/${each.key}"
+ parent = local.folder.name
- dynamic "list_policy" {
- for_each = each.value.status == null ? [] : [each.value]
- iterator = policy
- content {
- inherit_from_parent = policy.value.inherit_from_parent
- suggested_value = policy.value.suggested_value
- dynamic "allow" {
- for_each = policy.value.status ? [""] : []
- content {
- values = (
- try(length(policy.value.values) > 0, false)
- ? policy.value.values
- : null
- )
- all = (
- try(length(policy.value.values) > 0, false)
- ? null
- : true
- )
- }
- }
- dynamic "deny" {
- for_each = policy.value.status ? [] : [""]
+ spec {
+ inherit_from_parent = each.value.inherit_from_parent
+ reset = each.value.reset
+
+ rules {
+ allow_all = try(each.value.allow.all, null) == true ? "TRUE" : null
+ deny_all = try(each.value.deny.all, null) == true ? "TRUE" : null
+ enforce = (
+ each.value.is_boolean_policy && each.value.enforce != null
+ ? upper(tostring(each.value.enforce))
+ : null
+ )
+ dynamic "values" {
+ for_each = each.value.has_values ? [1] : []
content {
- values = (
- try(length(policy.value.values) > 0, false)
- ? policy.value.values
- : null
- )
- all = (
- try(length(policy.value.values) > 0, false)
- ? null
- : true
- )
+ allowed_values = try(each.value.allow.values, null)
+ denied_values = try(each.value.deny.values, null)
}
}
}
- }
- dynamic "restore_policy" {
- for_each = each.value.status == null ? [true] : []
- content {
- default = true
+ dynamic "rules" {
+ for_each = each.value.rules
+ iterator = rule
+ content {
+ allow_all = try(rule.value.allow.all, false) == true ? "TRUE" : null
+ deny_all = try(rule.value.deny.all, false) == true ? "TRUE" : null
+ enforce = (
+ each.value.is_boolean_policy && rule.value.enforce != null
+ ? upper(tostring(rule.value.enforce))
+ : null
+ )
+ condition {
+ description = rule.value.condition.description
+ expression = rule.value.condition.expression
+ location = rule.value.condition.location
+ title = rule.value.condition.title
+ }
+ dynamic "values" {
+ for_each = rule.value.has_values ? [1] : []
+ content {
+ allowed_values = try(rule.value.allow.values, null)
+ denied_values = try(rule.value.deny.values, null)
+ }
+ }
+ }
}
}
}
diff --git a/modules/folder/outputs.tf b/modules/folder/outputs.tf
index 37babc6f6c..8073951bfa 100644
--- a/modules/folder/outputs.tf
+++ b/modules/folder/outputs.tf
@@ -33,8 +33,7 @@ output "id" {
value = local.folder.name
depends_on = [
google_folder_iam_binding.authoritative,
- google_folder_organization_policy.boolean,
- google_folder_organization_policy.list
+ google_org_policy_policy.default,
]
}
diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf
index 19ed18f3de..a00e147f65 100644
--- a/modules/folder/variables.tf
+++ b/modules/folder/variables.tf
@@ -129,6 +129,46 @@ variable "name" {
default = null
}
+variable "org_policies" {
+ description = "Organization policies applied to this folder keyed by policy name."
+ type = map(object({
+ inherit_from_parent = optional(bool) # for list policies only.
+ reset = optional(bool)
+
+ # default (unconditional) values
+ allow = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ deny = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ enforce = optional(bool, true) # for boolean policies only.
+
+ # conditional values
+ rules = optional(list(object({
+ allow = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ deny = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ enforce = optional(bool, true) # for boolean policies only.
+ condition = object({
+ description = optional(string)
+ expression = optional(string)
+ location = optional(string)
+ title = optional(string)
+ })
+ })), [])
+ }))
+ default = {}
+ nullable = false
+}
+
variable "parent" {
description = "Parent in folders/folder_id or organizations/org_id format."
type = string
@@ -139,25 +179,6 @@ variable "parent" {
}
}
-variable "policy_boolean" {
- description = "Map of boolean org policies and enforcement value, set value to null for policy restore."
- type = map(bool)
- default = {}
- nullable = false
-}
-
-variable "policy_list" {
- description = "Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny."
- type = map(object({
- inherit_from_parent = bool
- suggested_value = string
- status = bool
- values = list(string)
- }))
- default = {}
- nullable = false
-}
-
variable "tag_bindings" {
description = "Tag bindings for this folder, in key => tag value id format."
type = map(string)
diff --git a/modules/organization/README.md b/modules/organization/README.md
index 2377c6cc8e..fb9197c318 100644
--- a/modules/organization/README.md
+++ b/modules/organization/README.md
@@ -19,20 +19,47 @@ module "org" {
iam = {
"roles/resourcemanager.projectCreator" = ["group:cloud-admins@example.org"]
}
- policy_boolean = {
- "constraints/compute.disableGuestAttributesAccess" = true
- "constraints/compute.skipDefaultNetworkCreation" = true
- }
- policy_list = {
+
+ org_policies = {
+ "compute.disableGuestAttributesAccess" = {
+ enforce = true
+ }
+ "constraints/compute.skipDefaultNetworkCreation" = {
+ enforce = true
+ }
+ "iam.disableServiceAccountKeyCreation" = {
+ enforce = true
+ }
+ "iam.disableServiceAccountKeyUpload" = {
+ enforce = false
+ rules = [
+ {
+ condition = {
+ expression = "resource.matchTagId(\"tagKeys/1234\", \"tagValues/1234\")"
+ title = "condition"
+ description = "test condition"
+ location = "somewhere"
+ }
+ enforce = true
+ }
+ ]
+ }
+ "constraints/iam.allowedPolicyMemberDomains" = {
+ allow = {
+ values = ["C0xxxxxxx", "C0yyyyyyy"]
+ }
+ }
"constraints/compute.trustedImageProjects" = {
- inherit_from_parent = null
- suggested_value = null
- status = true
- values = ["projects/my-project"]
+ allow = {
+ values = ["projects/my-project"]
+ }
+ }
+ "constraints/compute.vmExternalIpAccess" = {
+ deny = { all = true }
}
}
}
-# tftest modules=1 resources=6
+# tftest modules=1 resources=10
```
## IAM
@@ -281,7 +308,7 @@ module "org" {
| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | google_organization_iam_audit_config
· google_organization_iam_binding
· google_organization_iam_custom_role
· google_organization_iam_member
· google_organization_iam_policy
|
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member
· google_logging_organization_exclusion
· google_logging_organization_sink
· google_project_iam_member
· google_pubsub_topic_iam_member
· google_storage_bucket_iam_member
|
| [main.tf](./main.tf) | Module-level locals and resources. | google_essential_contacts_contact
|
-| [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | google_organization_policy
|
+| [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | google_org_policy_policy
|
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [tags.tf](./tags.tf) | None | google_tags_tag_binding
· google_tags_tag_key
· google_tags_tag_key_iam_binding
· google_tags_tag_value
· google_tags_tag_value_iam_binding
|
| [variables.tf](./variables.tf) | Module variables. | |
@@ -291,7 +318,7 @@ module "org" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [organization_id](variables.tf#L151) | Organization id in organizations/nnnnnn format. | string
| ✓ | |
+| [organization_id](variables.tf#L191) | Organization id in organizations/nnnnnn format. | string
| ✓ | |
| [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string))
| | {}
|
| [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | map(list(string))
| | {}
|
| [firewall_policies](variables.tf#L31) | Hierarchical firewall policy rules created in the organization. | map(map(object({…})))
| | {}
|
@@ -306,10 +333,9 @@ module "org" {
| [iam_bindings_authoritative](variables.tf#L116) | IAM authoritative bindings, in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared. Bindings should also be authoritative when using authoritative audit config. Use with caution. | map(list(string))
| | null
|
| [logging_exclusions](variables.tf#L122) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string)
| | {}
|
| [logging_sinks](variables.tf#L129) | Logging sinks to create for this organization. | map(object({…}))
| | {}
|
-| [policy_boolean](variables.tf#L160) | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool)
| | {}
|
-| [policy_list](variables.tf#L167) | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({…}))
| | {}
|
-| [tag_bindings](variables.tf#L179) | Tag bindings for this organization, in key => tag value id format. | map(string)
| | null
|
-| [tags](variables.tf#L185) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…}))
| | null
|
+| [org_policies](variables.tf#L151) | Organization policies applied to this organization keyed by policy name. | map(object({…}))
| | {}
|
+| [tag_bindings](variables.tf#L200) | Tag bindings for this organization, in key => tag value id format. | map(string)
| | null
|
+| [tags](variables.tf#L206) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…}))
| | null
|
## Outputs
@@ -320,8 +346,8 @@ module "org" {
| [firewall_policies](outputs.tf#L36) | Map of firewall policy resources created in the organization. | |
| [firewall_policy_id](outputs.tf#L41) | Map of firewall policy ids created in the organization. | |
| [organization_id](outputs.tf#L46) | Organization id dependent on module resources. | |
-| [sink_writer_identities](outputs.tf#L64) | Writer identities created for each sink. | |
-| [tag_keys](outputs.tf#L72) | Tag key resources. | |
-| [tag_values](outputs.tf#L79) | Tag value resources. | |
+| [sink_writer_identities](outputs.tf#L63) | Writer identities created for each sink. | |
+| [tag_keys](outputs.tf#L71) | Tag key resources. | |
+| [tag_values](outputs.tf#L78) | Tag value resources. | |
diff --git a/modules/organization/organization-policies.tf b/modules/organization/organization-policies.tf
index f23a98b488..defa11b0bd 100644
--- a/modules/organization/organization-policies.tf
+++ b/modules/organization/organization-policies.tf
@@ -16,83 +16,79 @@
# tfdoc:file:description Organization-level organization policies.
-resource "google_organization_policy" "boolean" {
- for_each = var.policy_boolean
- org_id = local.organization_id_numeric
- constraint = each.key
-
- dynamic "boolean_policy" {
- for_each = each.value == null ? [] : [each.value]
- iterator = policy
- content {
- enforced = policy.value
- }
- }
-
- dynamic "restore_policy" {
- for_each = each.value == null ? [""] : []
- content {
- default = true
- }
+locals {
+ org_policies = {
+ for k, v in var.org_policies :
+ k => merge(v, {
+ is_boolean_policy = v.allow == null && v.deny == null
+ has_values = (
+ length(coalesce(try(v.allow.values, []), [])) > 0 ||
+ length(coalesce(try(v.deny.values, []), [])) > 0
+ )
+ rules = [
+ for r in v.rules :
+ merge(r, {
+ has_values = (
+ length(coalesce(try(r.allow.values, []), [])) > 0 ||
+ length(coalesce(try(r.deny.values, []), [])) > 0
+ )
+ })
+ ]
+ })
}
-
- depends_on = [
- google_organization_iam_audit_config.config,
- google_organization_iam_binding.authoritative,
- google_organization_iam_custom_role.roles,
- google_organization_iam_member.additive,
- google_organization_iam_policy.authoritative,
- ]
}
-resource "google_organization_policy" "list" {
- for_each = var.policy_list
- org_id = local.organization_id_numeric
- constraint = each.key
+resource "google_org_policy_policy" "default" {
+ for_each = local.org_policies
+ name = "${var.organization_id}/policies/${each.key}"
+ parent = var.organization_id
- dynamic "list_policy" {
- for_each = each.value.status == null ? [] : [each.value]
- iterator = policy
- content {
- inherit_from_parent = policy.value.inherit_from_parent
- suggested_value = policy.value.suggested_value
- dynamic "allow" {
- for_each = policy.value.status ? [""] : []
+ spec {
+ inherit_from_parent = each.value.inherit_from_parent
+ reset = each.value.reset
+
+ rules {
+ allow_all = try(each.value.allow.all, null) == true ? "TRUE" : null
+ deny_all = try(each.value.deny.all, null) == true ? "TRUE" : null
+ enforce = (
+ each.value.is_boolean_policy && each.value.enforce != null
+ ? upper(tostring(each.value.enforce))
+ : null
+ )
+ dynamic "values" {
+ for_each = each.value.has_values ? [1] : []
content {
- values = (
- try(length(policy.value.values) > 0, false)
- ? policy.value.values
- : null
- )
- all = (
- try(length(policy.value.values) > 0, false)
- ? null
- : true
- )
- }
- }
- dynamic "deny" {
- for_each = policy.value.status ? [] : [""]
- content {
- values = (
- try(length(policy.value.values) > 0, false)
- ? policy.value.values
- : null
- )
- all = (
- try(length(policy.value.values) > 0, false)
- ? null
- : true
- )
+ allowed_values = try(each.value.allow.values, null)
+ denied_values = try(each.value.deny.values, null)
}
}
}
- }
- dynamic "restore_policy" {
- for_each = each.value.status == null ? [true] : []
- content {
- default = true
+ dynamic "rules" {
+ for_each = each.value.rules
+ iterator = rule
+ content {
+ allow_all = try(rule.value.allow.all, false) == true ? "TRUE" : null
+ deny_all = try(rule.value.deny.all, false) == true ? "TRUE" : null
+ enforce = (
+ each.value.is_boolean_policy && rule.value.enforce != null
+ ? upper(tostring(rule.value.enforce))
+ : null
+ )
+ condition {
+ description = rule.value.condition.description
+ expression = rule.value.condition.expression
+ location = rule.value.condition.location
+ title = rule.value.condition.title
+ }
+ dynamic "values" {
+ for_each = rule.value.has_values ? [1] : []
+ content {
+ allowed_values = try(rule.value.allow.values, null)
+ denied_values = try(rule.value.deny.values, null)
+ }
+ }
+ }
}
}
@@ -103,4 +99,5 @@ resource "google_organization_policy" "list" {
google_organization_iam_member.additive,
google_organization_iam_policy.authoritative,
]
+
}
diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf
index 1679a1d70b..198b3c8df7 100644
--- a/modules/organization/outputs.tf
+++ b/modules/organization/outputs.tf
@@ -52,8 +52,7 @@ output "organization_id" {
google_organization_iam_custom_role.roles,
google_organization_iam_member.additive,
google_organization_iam_policy.authoritative,
- google_organization_policy.boolean,
- google_organization_policy.list,
+ google_org_policy_policy.default,
google_tags_tag_key.default,
google_tags_tag_key_iam_binding.default,
google_tags_tag_value.default,
diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf
index 9ffce95ce9..7499d6d676 100644
--- a/modules/organization/variables.tf
+++ b/modules/organization/variables.tf
@@ -148,6 +148,46 @@ variable "logging_sinks" {
nullable = false
}
+variable "org_policies" {
+ description = "Organization policies applied to this organization keyed by policy name."
+ type = map(object({
+ inherit_from_parent = optional(bool) # for list policies only.
+ reset = optional(bool)
+
+ # default (unconditional) values
+ allow = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ deny = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ enforce = optional(bool, true) # for boolean policies only.
+
+ # conditional values
+ rules = optional(list(object({
+ allow = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ deny = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ enforce = optional(bool, true) # for boolean policies only.
+ condition = object({
+ description = optional(string)
+ expression = optional(string)
+ location = optional(string)
+ title = optional(string)
+ })
+ })), [])
+ }))
+ default = {}
+ nullable = false
+}
+
variable "organization_id" {
description = "Organization id in organizations/nnnnnn format."
type = string
@@ -157,25 +197,6 @@ variable "organization_id" {
}
}
-variable "policy_boolean" {
- description = "Map of boolean org policies and enforcement value, set value to null for policy restore."
- type = map(bool)
- default = {}
- nullable = false
-}
-
-variable "policy_list" {
- description = "Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny."
- type = map(object({
- inherit_from_parent = bool
- suggested_value = string
- status = bool
- values = list(string)
- }))
- default = {}
- nullable = false
-}
-
variable "tag_bindings" {
description = "Tag bindings for this organization, in key => tag value id format."
type = map(string)
diff --git a/modules/project/README.md b/modules/project/README.md
index 9df30d1800..8e6c64d4fc 100644
--- a/modules/project/README.md
+++ b/modules/project/README.md
@@ -167,20 +167,46 @@ module "project" {
"container.googleapis.com",
"stackdriver.googleapis.com"
]
- policy_boolean = {
- "constraints/compute.disableGuestAttributesAccess" = true
- "constraints/compute.skipDefaultNetworkCreation" = true
- }
- policy_list = {
+ org_policies = {
+ "compute.disableGuestAttributesAccess" = {
+ enforce = true
+ }
+ "constraints/compute.skipDefaultNetworkCreation" = {
+ enforce = true
+ }
+ "iam.disableServiceAccountKeyCreation" = {
+ enforce = true
+ }
+ "iam.disableServiceAccountKeyUpload" = {
+ enforce = false
+ rules = [
+ {
+ condition = {
+ expression = "resource.matchTagId(\"tagKeys/1234\", \"tagValues/1234\")"
+ title = "condition"
+ description = "test condition"
+ location = "somewhere"
+ }
+ enforce = true
+ }
+ ]
+ }
+ "constraints/iam.allowedPolicyMemberDomains" = {
+ allow = {
+ values = ["C0xxxxxxx", "C0yyyyyyy"]
+ }
+ }
"constraints/compute.trustedImageProjects" = {
- inherit_from_parent = null
- suggested_value = null
- status = true
- values = ["projects/my-project"]
+ allow = {
+ values = ["projects/my-project"]
+ }
+ }
+ "constraints/compute.vmExternalIpAccess" = {
+ deny = { all = true }
}
}
}
-# tftest modules=1 resources=6
+# tftest modules=1 resources=10
```
## Logging Sinks
@@ -349,7 +375,7 @@ output "compute_robot" {
| [iam.tf](./iam.tf) | Generic and OSLogin-specific IAM bindings and roles. | google_project_iam_binding
· google_project_iam_custom_role
· google_project_iam_member
|
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member
· google_logging_project_exclusion
· google_logging_project_sink
· google_project_iam_member
· google_pubsub_topic_iam_member
· google_storage_bucket_iam_member
|
| [main.tf](./main.tf) | Module-level locals and resources. | google_compute_project_metadata_item
· google_essential_contacts_contact
· google_monitoring_monitored_project
· google_project
· google_project_service
· google_resource_manager_lien
|
-| [organization-policies.tf](./organization-policies.tf) | Project-level organization policies. | google_project_organization_policy
|
+| [organization-policies.tf](./organization-policies.tf) | Project-level organization policies. | google_org_policy_policy
|
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [service-accounts.tf](./service-accounts.tf) | Service identities and supporting resources. | google_kms_crypto_key_iam_member
· google_project_default_service_accounts
· google_project_iam_member
· google_project_service_identity
|
| [shared-vpc.tf](./shared-vpc.tf) | Shared VPC project-level configuration. | google_compute_shared_vpc_host_project
· google_compute_shared_vpc_service_project
· google_project_iam_member
|
@@ -367,8 +393,8 @@ output "compute_robot" {
| [billing_account](variables.tf#L23) | Billing account id. | string
| | null
|
| [contacts](variables.tf#L29) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string))
| | {}
|
| [custom_roles](variables.tf#L36) | Map of role name => list of permissions to create in this project. | map(list(string))
| | {}
|
-| [default_service_account](variables.tf#L49) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | string
| | "keep"
|
-| [descriptive_name](variables.tf#L43) | Name of the project name. Used for project name instead of `name` variable. | string
| | null
|
+| [default_service_account](variables.tf#L43) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | string
| | "keep"
|
+| [descriptive_name](variables.tf#L49) | Name of the project name. Used for project name instead of `name` variable. | string
| | null
|
| [group_iam](variables.tf#L55) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string))
| | {}
|
| [iam](variables.tf#L62) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
| [iam_additive](variables.tf#L69) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
@@ -378,23 +404,22 @@ output "compute_robot" {
| [logging_exclusions](variables.tf#L95) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string)
| | {}
|
| [logging_sinks](variables.tf#L102) | Logging sinks to create for this project. | map(object({…}))
| | {}
|
| [metric_scopes](variables.tf#L124) | List of projects that will act as metric scopes for this project. | list(string)
| | []
|
-| [oslogin](variables.tf#L136) | Enable OS Login. | bool
| | false
|
-| [oslogin_admins](variables.tf#L142) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string)
| | []
|
-| [oslogin_users](variables.tf#L150) | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string)
| | []
|
-| [parent](variables.tf#L157) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string
| | null
|
-| [policy_boolean](variables.tf#L167) | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool)
| | {}
|
-| [policy_list](variables.tf#L174) | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({…}))
| | {}
|
-| [prefix](variables.tf#L186) | Prefix used to generate project id and name. | string
| | null
|
-| [project_create](variables.tf#L192) | Create project. When set to false, uses a data source to reference existing project. | bool
| | true
|
-| [service_config](variables.tf#L198) | Configure service API activation. | object({…})
| | {…}
|
-| [service_encryption_key_ids](variables.tf#L210) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string))
| | {}
|
-| [service_perimeter_bridges](variables.tf#L217) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string)
| | null
|
-| [service_perimeter_standard](variables.tf#L224) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string
| | null
|
-| [services](variables.tf#L230) | Service APIs to enable. | list(string)
| | []
|
-| [shared_vpc_host_config](variables.tf#L236) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…})
| | null
|
-| [shared_vpc_service_config](variables.tf#L245) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…})
| | null
|
-| [skip_delete](variables.tf#L255) | Allows the underlying resources to be destroyed without destroying the project itself. | bool
| | false
|
-| [tag_bindings](variables.tf#L261) | Tag bindings for this project, in key => tag value id format. | map(string)
| | null
|
+| [org_policies](variables.tf#L136) | Organization policies applied to this project keyed by policy name. | map(object({…}))
| | {}
|
+| [oslogin](variables.tf#L176) | Enable OS Login. | bool
| | false
|
+| [oslogin_admins](variables.tf#L182) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string)
| | []
|
+| [oslogin_users](variables.tf#L190) | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string)
| | []
|
+| [parent](variables.tf#L197) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string
| | null
|
+| [prefix](variables.tf#L207) | Prefix used to generate project id and name. | string
| | null
|
+| [project_create](variables.tf#L213) | Create project. When set to false, uses a data source to reference existing project. | bool
| | true
|
+| [service_config](variables.tf#L219) | Configure service API activation. | object({…})
| | {…}
|
+| [service_encryption_key_ids](variables.tf#L231) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string))
| | {}
|
+| [service_perimeter_bridges](variables.tf#L238) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string)
| | null
|
+| [service_perimeter_standard](variables.tf#L245) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string
| | null
|
+| [services](variables.tf#L251) | Service APIs to enable. | list(string)
| | []
|
+| [shared_vpc_host_config](variables.tf#L257) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…})
| | null
|
+| [shared_vpc_service_config](variables.tf#L266) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…})
| | null
|
+| [skip_delete](variables.tf#L276) | Allows the underlying resources to be destroyed without destroying the project itself. | bool
| | false
|
+| [tag_bindings](variables.tf#L282) | Tag bindings for this project, in key => tag value id format. | map(string)
| | null
|
## Outputs
@@ -402,9 +427,9 @@ output "compute_robot" {
|---|---|:---:|
| [custom_roles](outputs.tf#L17) | Ids of the created custom roles. | |
| [name](outputs.tf#L25) | Project name. | |
-| [number](outputs.tf#L38) | Project number. | |
-| [project_id](outputs.tf#L56) | Project id. | |
-| [service_accounts](outputs.tf#L76) | Product robot service accounts in project. | |
-| [sink_writer_identities](outputs.tf#L92) | Writer identities created for each sink. | |
+| [number](outputs.tf#L37) | Project number. | |
+| [project_id](outputs.tf#L54) | Project id. | |
+| [service_accounts](outputs.tf#L73) | Product robot service accounts in project. | |
+| [sink_writer_identities](outputs.tf#L89) | Writer identities created for each sink. | |
diff --git a/modules/project/organization-policies.tf b/modules/project/organization-policies.tf
index 6870754897..ae4a85012e 100644
--- a/modules/project/organization-policies.tf
+++ b/modules/project/organization-policies.tf
@@ -16,75 +16,79 @@
# tfdoc:file:description Project-level organization policies.
-resource "google_project_organization_policy" "boolean" {
- for_each = var.policy_boolean
- project = local.project.project_id
- constraint = each.key
-
- dynamic "boolean_policy" {
- for_each = each.value == null ? [] : [each.value]
- iterator = policy
- content {
- enforced = policy.value
- }
- }
-
- dynamic "restore_policy" {
- for_each = each.value == null ? [""] : []
- content {
- default = true
- }
+locals {
+ org_policies = {
+ for k, v in var.org_policies :
+ k => merge(v, {
+ is_boolean_policy = v.allow == null && v.deny == null
+ has_values = (
+ length(coalesce(try(v.allow.values, []), [])) > 0 ||
+ length(coalesce(try(v.deny.values, []), [])) > 0
+ )
+ rules = [
+ for r in v.rules :
+ merge(r, {
+ has_values = (
+ length(coalesce(try(r.allow.values, []), [])) > 0 ||
+ length(coalesce(try(r.deny.values, []), [])) > 0
+ )
+ })
+ ]
+ })
}
}
-resource "google_project_organization_policy" "list" {
- for_each = var.policy_list
- project = local.project.project_id
- constraint = each.key
+resource "google_org_policy_policy" "default" {
+ for_each = local.org_policies
+ name = "projects/${local.project.project_id}/policies/${each.key}"
+ parent = "projects/${local.project.project_id}"
- dynamic "list_policy" {
- for_each = each.value.status == null ? [] : [each.value]
- iterator = policy
- content {
- inherit_from_parent = policy.value.inherit_from_parent
- suggested_value = policy.value.suggested_value
- dynamic "allow" {
- for_each = policy.value.status ? [""] : []
- content {
- values = (
- try(length(policy.value.values) > 0, false)
- ? policy.value.values
- : null
- )
- all = (
- try(length(policy.value.values) > 0, false)
- ? null
- : true
- )
- }
- }
- dynamic "deny" {
- for_each = policy.value.status ? [] : [""]
+ spec {
+ inherit_from_parent = each.value.inherit_from_parent
+ reset = each.value.reset
+
+ rules {
+ allow_all = try(each.value.allow.all, null) == true ? "TRUE" : null
+ deny_all = try(each.value.deny.all, null) == true ? "TRUE" : null
+ enforce = (
+ each.value.is_boolean_policy && each.value.enforce != null
+ ? upper(tostring(each.value.enforce))
+ : null
+ )
+ dynamic "values" {
+ for_each = each.value.has_values ? [1] : []
content {
- values = (
- try(length(policy.value.values) > 0, false)
- ? policy.value.values
- : null
- )
- all = (
- try(length(policy.value.values) > 0, false)
- ? null
- : true
- )
+ allowed_values = try(each.value.allow.values, null)
+ denied_values = try(each.value.deny.values, null)
}
}
}
- }
- dynamic "restore_policy" {
- for_each = each.value.status == null ? [true] : []
- content {
- default = true
+ dynamic "rules" {
+ for_each = each.value.rules
+ iterator = rule
+ content {
+ allow_all = try(rule.value.allow.all, false) == true ? "TRUE" : null
+ deny_all = try(rule.value.deny.all, false) == true ? "TRUE" : null
+ enforce = (
+ each.value.is_boolean_policy && rule.value.enforce != null
+ ? upper(tostring(rule.value.enforce))
+ : null
+ )
+ condition {
+ description = rule.value.condition.description
+ expression = rule.value.condition.expression
+ location = rule.value.condition.location
+ title = rule.value.condition.title
+ }
+ dynamic "values" {
+ for_each = rule.value.has_values ? [1] : []
+ content {
+ allowed_values = try(rule.value.allow.values, null)
+ denied_values = try(rule.value.deny.values, null)
+ }
+ }
+ }
}
}
}
diff --git a/modules/project/outputs.tf b/modules/project/outputs.tf
index 3b7efc9077..cb940d010d 100644
--- a/modules/project/outputs.tf
+++ b/modules/project/outputs.tf
@@ -26,8 +26,7 @@ output "name" {
description = "Project name."
value = local.project.name
depends_on = [
- google_project_organization_policy.boolean,
- google_project_organization_policy.list,
+ google_org_policy_policy.default,
google_project_service.project_services,
google_compute_shared_vpc_service_project.service_projects,
google_project_iam_member.shared_vpc_host_robots,
@@ -39,8 +38,7 @@ output "number" {
description = "Project number."
value = local.project.number
depends_on = [
- google_project_organization_policy.boolean,
- google_project_organization_policy.list,
+ google_org_policy_policy.default,
google_project_service.project_services,
google_compute_shared_vpc_host_project.shared_vpc_host,
google_compute_shared_vpc_service_project.shared_vpc_service,
@@ -59,8 +57,7 @@ output "project_id" {
depends_on = [
google_project.project,
data.google_project.project,
- google_project_organization_policy.boolean,
- google_project_organization_policy.list,
+ google_org_policy_policy.default,
google_project_service.project_services,
google_compute_shared_vpc_host_project.shared_vpc_host,
google_compute_shared_vpc_service_project.shared_vpc_service,
diff --git a/modules/project/variables.tf b/modules/project/variables.tf
index 41d3163faf..a58affc92f 100644
--- a/modules/project/variables.tf
+++ b/modules/project/variables.tf
@@ -40,18 +40,18 @@ variable "custom_roles" {
nullable = false
}
-variable "descriptive_name" {
- description = "Name of the project name. Used for project name instead of `name` variable."
- type = string
- default = null
-}
-
variable "default_service_account" {
description = "Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`."
default = "keep"
type = string
}
+variable "descriptive_name" {
+ description = "Name of the project name. Used for project name instead of `name` variable."
+ type = string
+ default = null
+}
+
variable "group_iam" {
description = "Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable."
type = map(list(string))
@@ -133,6 +133,46 @@ variable "name" {
type = string
}
+variable "org_policies" {
+ description = "Organization policies applied to this project keyed by policy name."
+ type = map(object({
+ inherit_from_parent = optional(bool) # for list policies only.
+ reset = optional(bool)
+
+ # default (unconditional) values
+ allow = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ deny = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ enforce = optional(bool, true) # for boolean policies only.
+
+ # conditional values
+ rules = optional(list(object({
+ allow = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ deny = optional(object({
+ all = optional(bool)
+ values = optional(list(string))
+ }))
+ enforce = optional(bool, true) # for boolean policies only.
+ condition = object({
+ description = optional(string)
+ expression = optional(string)
+ location = optional(string)
+ title = optional(string)
+ })
+ })), [])
+ }))
+ default = {}
+ nullable = false
+}
+
variable "oslogin" {
description = "Enable OS Login."
type = bool
@@ -164,25 +204,6 @@ variable "parent" {
}
}
-variable "policy_boolean" {
- description = "Map of boolean org policies and enforcement value, set value to null for policy restore."
- type = map(bool)
- default = {}
- nullable = false
-}
-
-variable "policy_list" {
- description = "Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny."
- type = map(object({
- inherit_from_parent = bool
- suggested_value = string
- status = bool
- values = list(string)
- }))
- default = {}
- nullable = false
-}
-
variable "prefix" {
description = "Prefix used to generate project id and name."
type = string
diff --git a/tests/modules/folder/fixture/main.tf b/tests/modules/folder/fixture/main.tf
index 2fa1b4fd09..8290f82ecf 100644
--- a/tests/modules/folder/fixture/main.tf
+++ b/tests/modules/folder/fixture/main.tf
@@ -22,10 +22,9 @@ module "test" {
iam = var.iam
iam_additive = var.iam_additive
iam_additive_members = var.iam_additive_members
- policy_boolean = var.policy_boolean
- policy_list = var.policy_list
firewall_policies = var.firewall_policies
firewall_policy_association = var.firewall_policy_association
logging_sinks = var.logging_sinks
logging_exclusions = var.logging_exclusions
+ org_policies = var.org_policies
}
diff --git a/tests/modules/folder/fixture/variables.tf b/tests/modules/folder/fixture/variables.tf
index da676debf9..7c03e05683 100644
--- a/tests/modules/folder/fixture/variables.tf
+++ b/tests/modules/folder/fixture/variables.tf
@@ -34,16 +34,6 @@ variable "iam_additive_members" {
default = {}
}
-variable "policy_boolean" {
- type = any
- default = {}
-}
-
-variable "policy_list" {
- type = any
- default = {}
-}
-
variable "firewall_policies" {
type = any
default = {}
@@ -63,3 +53,8 @@ variable "logging_exclusions" {
type = any
default = {}
}
+
+variable "org_policies" {
+ type = any
+ default = {}
+}
diff --git a/tests/modules/folder/test_plan_org_policies.py b/tests/modules/folder/test_plan_org_policies.py
index b7ae96c260..f84d50fb06 100644
--- a/tests/modules/folder/test_plan_org_policies.py
+++ b/tests/modules/folder/test_plan_org_policies.py
@@ -12,56 +12,212 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-def test_sink(plan_runner):
- "Test folder-level sink."
- policy_boolean = '{policy-a = true, policy-b = false, policy-c = null}'
- _, resources = plan_runner(policy_boolean=policy_boolean)
- assert len(resources) == 4
- resources = [r for r in resources if r['type']
- == 'google_folder_organization_policy']
- assert sorted([r['index'] for r in resources]) == [
- 'policy-a',
- 'policy-b',
- 'policy-c',
- ]
- policy_values = []
- for resource in resources:
- for policy in ('boolean_policy', 'restore_policy'):
- value = resource['values'][policy]
- if value:
- policy_values.append((resource['index'], policy,) + value[0].popitem())
- assert sorted(policy_values) == [
- ('policy-a', 'boolean_policy', 'enforced', True),
- ('policy-b', 'boolean_policy', 'enforced', False),
- ('policy-c', 'restore_policy', 'default', True),
- ]
+def test_policy_boolean(plan_runner):
+ "Test boolean org policy."
+ policies = '''{
+ "iam.disableServiceAccountKeyCreation" = {
+ enforce = true
+ }
+ "iam.disableServiceAccountKeyUpload" = {
+ enforce = false
+ rules = [
+ {
+ condition = {
+ expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
+ title = "condition"
+ description = "test condition"
+ location = "xxx"
+ }
+ enforce = true
+ }
+ ]
+ }
+ }'''
+ _, resources = plan_runner(org_policies=policies)
+ assert len(resources) == 3
+
+ policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
+ assert len(policies) == 2
+
+ p1 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'iam.disableServiceAccountKeyCreation'
+ ][0]
+
+ assert p1['inherit_from_parent'] is None
+ assert p1['reset'] is None
+ assert p1['rules'] == [{
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': None,
+ 'enforce': 'TRUE',
+ 'values': []
+ }]
+
+ p2 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'iam.disableServiceAccountKeyUpload'
+ ][0]
+
+ assert p2['inherit_from_parent'] is None
+ assert p2['reset'] is None
+ assert len(p2['rules']) == 2
+ assert p2['rules'][0] == {
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': None,
+ 'enforce': 'FALSE',
+ 'values': []
+ }
+ assert p2['rules'][1] == {
+ 'allow_all': None,
+ 'condition': [{
+ 'description': 'test condition',
+ 'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")',
+ 'location': 'xxx',
+ 'title': 'condition'
+ }],
+ 'deny_all': None,
+ 'enforce': 'TRUE',
+ 'values': []
+ }
-def test_exclussions(plan_runner):
- "Test folder-level logging exclusions."
- policy_list = (
- '{'
- 'policy-a = {inherit_from_parent = true, suggested_value = null, status = true, values = []}, '
- 'policy-b = {inherit_from_parent = null, suggested_value = "foo", status = false, values = ["bar"]}, '
- 'policy-c = {inherit_from_parent = null, suggested_value = true, status = null, values = null}'
- '}'
- )
- _, resources = plan_runner(policy_list=policy_list)
+def test_policy_list(plan_runner):
+ "Test list org policy."
+ policies = '''{
+ "compute.vmExternalIpAccess" = {
+ deny = { all = true }
+ }
+ "iam.allowedPolicyMemberDomains" = {
+ allow = {
+ values = ["C0xxxxxxx", "C0yyyyyyy"]
+ }
+ }
+ "compute.restrictLoadBalancerCreationForTypes" = {
+ deny = { values = ["in:EXTERNAL"] }
+ rules = [
+ {
+ condition = {
+ expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
+ title = "condition"
+ description = "test condition"
+ location = "xxx"
+ }
+ allow = {
+ values = ["EXTERNAL_1"]
+ }
+ },
+ {
+ condition = {
+ expression = "resource.matchTagId(\\"tagKeys/12345\\", \\"tagValues/12345\\")"
+ title = "condition2"
+ description = "test condition2"
+ location = "xxx"
+ }
+ allow = {
+ all = true
+ }
+ }
+ ]
+ }
+ }'''
+ _, resources = plan_runner(org_policies=policies)
assert len(resources) == 4
- resources = [r for r in resources if r['type']
- == 'google_folder_organization_policy']
- assert sorted([r['index'] for r in resources]) == [
- 'policy-a',
- 'policy-b',
- 'policy-c',
- ]
- values = [r['values'] for r in resources]
- assert [r['constraint'] for r in values] == [
- 'policy-a', 'policy-b', 'policy-c'
- ]
- assert values[0]['list_policy'][0]['allow'] == [
- {'all': True, 'values': None}]
- assert values[1]['list_policy'][0]['deny'] == [
- {'all': False, 'values': ["bar"]}]
- assert values[2]['restore_policy'] == [{'default': True}]
+
+ policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
+ assert len(policies) == 3
+
+ p1 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'compute.vmExternalIpAccess'
+ ][0]
+ assert p1['inherit_from_parent'] is None
+ assert p1['reset'] is None
+ assert p1['rules'] == [{
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': 'TRUE',
+ 'enforce': None,
+ 'values': []
+ }]
+
+ p2 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'iam.allowedPolicyMemberDomains'
+ ][0]
+ assert p2['inherit_from_parent'] is None
+ assert p2['reset'] is None
+ assert p2['rules'] == [{
+ 'allow_all':
+ None,
+ 'condition': [],
+ 'deny_all':
+ None,
+ 'enforce':
+ None,
+ 'values': [{
+ 'allowed_values': [
+ 'C0xxxxxxx',
+ 'C0yyyyyyy',
+ ],
+ 'denied_values': None
+ }]
+ }]
+
+ p3 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'compute.restrictLoadBalancerCreationForTypes'
+ ][0]
+ assert p3['inherit_from_parent'] is None
+ assert p3['reset'] is None
+ assert len(p3['rules']) == 3
+ assert p3['rules'][0] == {
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': None,
+ 'enforce': None,
+ 'values': [{
+ 'allowed_values': None,
+ 'denied_values': ['in:EXTERNAL']
+ }]
+ }
+
+ assert p3['rules'][1] == {
+ 'allow_all': None,
+ 'condition': [{
+ 'description': 'test condition',
+ 'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")',
+ 'location': 'xxx',
+ 'title': 'condition'
+ }],
+ 'deny_all': None,
+ 'enforce': None,
+ 'values': [{
+ 'allowed_values': ['EXTERNAL_1'],
+ 'denied_values': None
+ }]
+ }
+
+ assert p3['rules'][2] == {
+ 'allow_all': 'TRUE',
+ 'condition': [{
+ 'description':
+ 'test condition2',
+ 'expression':
+ 'resource.matchTagId("tagKeys/12345", "tagValues/12345")',
+ 'location':
+ 'xxx',
+ 'title':
+ 'condition2'
+ }],
+ 'deny_all': None,
+ 'enforce': None,
+ 'values': []
+ }
diff --git a/tests/modules/organization/fixture/main.tf b/tests/modules/organization/fixture/main.tf
index 04ae4adf01..13f8e335f4 100644
--- a/tests/modules/organization/fixture/main.tf
+++ b/tests/modules/organization/fixture/main.tf
@@ -28,8 +28,7 @@ module "test" {
iam_audit_config = var.iam_audit_config
logging_sinks = var.logging_sinks
logging_exclusions = var.logging_exclusions
- policy_boolean = var.policy_boolean
- policy_list = var.policy_list
+ org_policies = var.org_policies
tag_bindings = var.tag_bindings
tags = var.tags
}
diff --git a/tests/modules/organization/fixture/variables.tf b/tests/modules/organization/fixture/variables.tf
index 1d7ca88d7e..f56e51dcc7 100644
--- a/tests/modules/organization/fixture/variables.tf
+++ b/tests/modules/organization/fixture/variables.tf
@@ -44,16 +44,6 @@ variable "iam_audit_config" {
default = {}
}
-variable "policy_boolean" {
- type = any
- default = {}
-}
-
-variable "policy_list" {
- type = any
- default = {}
-}
-
variable "firewall_policies" {
type = any
default = {}
@@ -79,6 +69,11 @@ variable "logging_exclusions" {
default = {}
}
+variable "org_policies" {
+ type = any
+ default = {}
+}
+
variable "tag_bindings" {
type = any
default = null
diff --git a/tests/modules/organization/test_plan.py b/tests/modules/organization/test_plan.py
index a40758a21d..37860ab6d4 100644
--- a/tests/modules/organization/test_plan.py
+++ b/tests/modules/organization/test_plan.py
@@ -12,13 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+
def test_audit_config(plan_runner):
"Test audit config."
iam_audit_config = '{allServices={DATA_READ=[], DATA_WRITE=["user:me@example.org"]}}'
_, resources = plan_runner(iam_audit_config=iam_audit_config)
assert len(resources) == 1
- log_types = set(r['log_type']
- for r in resources[0]['values']['audit_log_config'])
+ log_types = set(
+ r['log_type'] for r in resources[0]['values']['audit_log_config'])
assert log_types == set(['DATA_READ', 'DATA_WRITE'])
@@ -28,21 +29,21 @@ def test_iam(plan_runner):
'{'
'"owners@example.org" = ["roles/owner", "roles/resourcemanager.folderAdmin"],'
'"viewers@example.org" = ["roles/viewer"]'
- '}'
- )
- iam = (
- '{'
- '"roles/owner" = ["user:one@example.org", "user:two@example.org"],'
- '"roles/browser" = ["domain:example.org"]'
- '}'
- )
+ '}')
+ iam = ('{'
+ '"roles/owner" = ["user:one@example.org", "user:two@example.org"],'
+ '"roles/browser" = ["domain:example.org"]'
+ '}')
_, resources = plan_runner(group_iam=group_iam, iam=iam)
roles = sorted([(r['values']['role'], sorted(r['values']['members']))
- for r in resources if r['type'] == 'google_organization_iam_binding'])
+ for r in resources
+ if r['type'] == 'google_organization_iam_binding'])
assert roles == [
('roles/browser', ['domain:example.org']),
- ('roles/owner', ['group:owners@example.org', 'user:one@example.org',
- 'user:two@example.org']),
+ ('roles/owner', [
+ 'group:owners@example.org', 'user:one@example.org',
+ 'user:two@example.org'
+ ]),
('roles/resourcemanager.folderAdmin', ['group:owners@example.org']),
('roles/viewer', ['group:viewers@example.org']),
]
@@ -50,55 +51,12 @@ def test_iam(plan_runner):
def test_iam_additive_members(plan_runner):
"Test IAM additive members."
- iam = (
- '{"user:one@example.org" = ["roles/owner"],'
- '"user:two@example.org" = ["roles/owner", "roles/editor"]}'
- )
+ iam = ('{"user:one@example.org" = ["roles/owner"],'
+ '"user:two@example.org" = ["roles/owner", "roles/editor"]}')
_, resources = plan_runner(iam_additive_members=iam)
roles = set((r['values']['role'], r['values']['member'])
- for r in resources if r['type'] == 'google_organization_iam_member')
- assert roles == set([
- ('roles/owner', 'user:one@example.org'),
- ('roles/owner', 'user:two@example.org'),
- ('roles/editor', 'user:two@example.org')
- ])
-
-
-def test_policy_boolean(plan_runner):
- "Test boolean org policy."
- policy_boolean = '{policy-a = true, policy-b = false, policy-c = null}'
- _, resources = plan_runner(policy_boolean=policy_boolean)
- assert len(resources) == 3
- constraints = set(r['values']['constraint'] for r in resources)
- assert set(constraints) == set(['policy-a', 'policy-b', 'policy-c'])
- policies = []
- for resource in resources:
- for policy in ('boolean_policy', 'restore_policy'):
- value = resource['values'][policy]
- if value:
- policies.append((policy,) + value[0].popitem())
- assert set(policies) == set([
- ('boolean_policy', 'enforced', True),
- ('boolean_policy', 'enforced', False),
- ('restore_policy', 'default', True)])
-
-
-def test_policy_list(plan_runner):
- "Test list org policy."
- policy_list = (
- '{'
- 'policy-a = {inherit_from_parent = true, suggested_value = null, status = true, values = []}, '
- 'policy-b = {inherit_from_parent = null, suggested_value = "foo", status = false, values = ["bar"]}, '
- 'policy-c = {inherit_from_parent = null, suggested_value = true, status = null, values = null}'
- '}'
- )
- _, resources = plan_runner(policy_list=policy_list)
- assert len(resources) == 3
- values = [r['values'] for r in resources]
- assert [r['constraint']
- for r in values] == ['policy-a', 'policy-b', 'policy-c']
- assert values[0]['list_policy'][0]['allow'] == [
- {'all': True, 'values': None}]
- assert values[1]['list_policy'][0]['deny'] == [
- {'all': False, 'values': ["bar"]}]
- assert values[2]['restore_policy'] == [{'default': True}]
+ for r in resources
+ if r['type'] == 'google_organization_iam_member')
+ assert roles == set([('roles/owner', 'user:one@example.org'),
+ ('roles/owner', 'user:two@example.org'),
+ ('roles/editor', 'user:two@example.org')])
diff --git a/tests/modules/organization/test_plan_org_policies.py b/tests/modules/organization/test_plan_org_policies.py
new file mode 100644
index 0000000000..63ff2e7637
--- /dev/null
+++ b/tests/modules/organization/test_plan_org_policies.py
@@ -0,0 +1,227 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def test_policy_boolean(plan_runner):
+ "Test boolean org policy."
+ policies = '''{
+ "iam.disableServiceAccountKeyCreation" = {
+ enforce = true
+ }
+ "iam.disableServiceAccountKeyUpload" = {
+ enforce = false
+ rules = [
+ {
+ condition = {
+ expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
+ title = "condition"
+ description = "test condition"
+ location = "xxx"
+ }
+ enforce = true
+ }
+ ]
+ }
+ }'''
+ _, resources = plan_runner(org_policies=policies)
+ assert len(resources) == 2
+
+ policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
+ assert len(policies) == 2
+ assert all(
+ x['values']['parent'] == 'organizations/1234567890' for x in policies)
+
+ p1 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'iam.disableServiceAccountKeyCreation'
+ ][0]
+
+ assert p1['inherit_from_parent'] is None
+ assert p1['reset'] is None
+ assert p1['rules'] == [{
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': None,
+ 'enforce': 'TRUE',
+ 'values': []
+ }]
+
+ p2 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'iam.disableServiceAccountKeyUpload'
+ ][0]
+
+ assert p2['inherit_from_parent'] is None
+ assert p2['reset'] is None
+ assert len(p2['rules']) == 2
+ assert p2['rules'][0] == {
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': None,
+ 'enforce': 'FALSE',
+ 'values': []
+ }
+ assert p2['rules'][1] == {
+ 'allow_all': None,
+ 'condition': [{
+ 'description': 'test condition',
+ 'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")',
+ 'location': 'xxx',
+ 'title': 'condition'
+ }],
+ 'deny_all': None,
+ 'enforce': 'TRUE',
+ 'values': []
+ }
+
+
+def test_policy_list(plan_runner):
+ "Test list org policy."
+ policies = '''{
+ "compute.vmExternalIpAccess" = {
+ deny = { all = true }
+ }
+ "iam.allowedPolicyMemberDomains" = {
+ allow = {
+ values = ["C0xxxxxxx", "C0yyyyyyy"]
+ }
+ }
+ "compute.restrictLoadBalancerCreationForTypes" = {
+ deny = { values = ["in:EXTERNAL"] }
+ rules = [
+ {
+ condition = {
+ expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
+ title = "condition"
+ description = "test condition"
+ location = "xxx"
+ }
+ allow = {
+ values = ["EXTERNAL_1"]
+ }
+ },
+ {
+ condition = {
+ expression = "resource.matchTagId(\\"tagKeys/12345\\", \\"tagValues/12345\\")"
+ title = "condition2"
+ description = "test condition2"
+ location = "xxx"
+ }
+ allow = {
+ all = true
+ }
+ }
+ ]
+ }
+ }'''
+ _, resources = plan_runner(org_policies=policies)
+ assert len(resources) == 3
+
+ policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
+ assert len(policies) == 3
+ assert all(
+ x['values']['parent'] == 'organizations/1234567890' for x in policies)
+
+ p1 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'compute.vmExternalIpAccess'
+ ][0]
+ assert p1['inherit_from_parent'] is None
+ assert p1['reset'] is None
+ assert p1['rules'] == [{
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': 'TRUE',
+ 'enforce': None,
+ 'values': []
+ }]
+
+ p2 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'iam.allowedPolicyMemberDomains'
+ ][0]
+ assert p2['inherit_from_parent'] is None
+ assert p2['reset'] is None
+ assert p2['rules'] == [{
+ 'allow_all':
+ None,
+ 'condition': [],
+ 'deny_all':
+ None,
+ 'enforce':
+ None,
+ 'values': [{
+ 'allowed_values': [
+ 'C0xxxxxxx',
+ 'C0yyyyyyy',
+ ],
+ 'denied_values': None
+ }]
+ }]
+
+ p3 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'compute.restrictLoadBalancerCreationForTypes'
+ ][0]
+ assert p3['inherit_from_parent'] is None
+ assert p3['reset'] is None
+ assert len(p3['rules']) == 3
+ assert p3['rules'][0] == {
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': None,
+ 'enforce': None,
+ 'values': [{
+ 'allowed_values': None,
+ 'denied_values': ['in:EXTERNAL']
+ }]
+ }
+
+ assert p3['rules'][1] == {
+ 'allow_all': None,
+ 'condition': [{
+ 'description': 'test condition',
+ 'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")',
+ 'location': 'xxx',
+ 'title': 'condition'
+ }],
+ 'deny_all': None,
+ 'enforce': None,
+ 'values': [{
+ 'allowed_values': ['EXTERNAL_1'],
+ 'denied_values': None
+ }]
+ }
+
+ assert p3['rules'][2] == {
+ 'allow_all': 'TRUE',
+ 'condition': [{
+ 'description':
+ 'test condition2',
+ 'expression':
+ 'resource.matchTagId("tagKeys/12345", "tagValues/12345")',
+ 'location':
+ 'xxx',
+ 'title':
+ 'condition2'
+ }],
+ 'deny_all': None,
+ 'enforce': None,
+ 'values': []
+ }
diff --git a/tests/modules/project/fixture/main.tf b/tests/modules/project/fixture/main.tf
index a9867e5d9b..4c7441ac55 100644
--- a/tests/modules/project/fixture/main.tf
+++ b/tests/modules/project/fixture/main.tf
@@ -25,12 +25,11 @@ module "test" {
iam_additive_members = var.iam_additive_members
labels = var.labels
lien_reason = var.lien_reason
+ org_policies = var.org_policies
oslogin = var.oslogin
oslogin_admins = var.oslogin_admins
oslogin_users = var.oslogin_users
parent = var.parent
- policy_boolean = var.policy_boolean
- policy_list = var.policy_list
prefix = var.prefix
service_encryption_key_ids = var.service_encryption_key_ids
services = var.services
@@ -63,4 +62,3 @@ module "test-svpc-service" {
}
}
}
-
diff --git a/tests/modules/project/fixture/variables.tf b/tests/modules/project/fixture/variables.tf
index 2a4d95d1e7..236cb69f32 100644
--- a/tests/modules/project/fixture/variables.tf
+++ b/tests/modules/project/fixture/variables.tf
@@ -64,6 +64,11 @@ variable "lien_reason" {
default = ""
}
+variable "org_policies" {
+ type = any
+ default = {}
+}
+
variable "oslogin" {
type = bool
default = false
@@ -84,21 +89,6 @@ variable "parent" {
default = null
}
-variable "policy_boolean" {
- type = map(bool)
- default = {}
-}
-
-variable "policy_list" {
- type = map(object({
- inherit_from_parent = bool
- suggested_value = string
- status = bool
- values = list(string)
- }))
- default = {}
-}
-
variable "prefix" {
type = string
default = null
diff --git a/tests/modules/project/test_plan_org_policies.py b/tests/modules/project/test_plan_org_policies.py
index 645db0dfe0..a9c4df68b3 100644
--- a/tests/modules/project/test_plan_org_policies.py
+++ b/tests/modules/project/test_plan_org_policies.py
@@ -12,47 +12,214 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+
def test_policy_boolean(plan_runner):
"Test boolean org policy."
- policy_boolean = '{policy-a = true, policy-b = false, policy-c = null}'
- _, resources = plan_runner(policy_boolean=policy_boolean)
- assert len(resources) == 7
- resources = [r for r in resources if r['type']
- == 'google_project_organization_policy']
- assert sorted([r['index'] for r in resources]) == [
- 'policy-a', 'policy-b', 'policy-c'
- ]
- policy_values = []
- for resource in resources:
- for policy in ('boolean_policy', 'restore_policy'):
- value = resource['values'][policy]
- if value:
- policy_values.append((policy,) + value[0].popitem())
- assert sorted(policy_values) == [
- ('boolean_policy', 'enforced', False),
- ('boolean_policy', 'enforced', True),
- ('restore_policy', 'default', True)
- ]
+ policies = '''{
+ "iam.disableServiceAccountKeyCreation" = {
+ enforce = true
+ }
+ "iam.disableServiceAccountKeyUpload" = {
+ enforce = false
+ rules = [
+ {
+ condition = {
+ expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
+ title = "condition"
+ description = "test condition"
+ location = "xxx"
+ }
+ enforce = true
+ }
+ ]
+ }
+ }'''
+ _, resources = plan_runner(org_policies=policies)
+ assert len(resources) == 6
+
+ policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
+ assert len(policies) == 2
+ assert all(x['values']['parent'] == 'projects/my-project' for x in policies)
+
+ p1 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'iam.disableServiceAccountKeyCreation'
+ ][0]
+
+ assert p1['inherit_from_parent'] is None
+ assert p1['reset'] is None
+ assert p1['rules'] == [{
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': None,
+ 'enforce': 'TRUE',
+ 'values': []
+ }]
+
+ p2 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'iam.disableServiceAccountKeyUpload'
+ ][0]
+
+ assert p2['inherit_from_parent'] is None
+ assert p2['reset'] is None
+ assert len(p2['rules']) == 2
+ assert p2['rules'][0] == {
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': None,
+ 'enforce': 'FALSE',
+ 'values': []
+ }
+ assert p2['rules'][1] == {
+ 'allow_all': None,
+ 'condition': [{
+ 'description': 'test condition',
+ 'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")',
+ 'location': 'xxx',
+ 'title': 'condition'
+ }],
+ 'deny_all': None,
+ 'enforce': 'TRUE',
+ 'values': []
+ }
def test_policy_list(plan_runner):
"Test list org policy."
- policy_list = (
- '{'
- 'policy-a = {inherit_from_parent = true, suggested_value = null, status = true, values = []}, '
- 'policy-b = {inherit_from_parent = null, suggested_value = "foo", status = false, values = ["bar"]}, '
- 'policy-c = {inherit_from_parent = null, suggested_value = true, status = null, values = null}'
- '}'
- )
- _, resources = plan_runner(policy_list=policy_list)
+ policies = '''{
+ "compute.vmExternalIpAccess" = {
+ deny = { all = true }
+ }
+ "iam.allowedPolicyMemberDomains" = {
+ allow = {
+ values = ["C0xxxxxxx", "C0yyyyyyy"]
+ }
+ }
+ "compute.restrictLoadBalancerCreationForTypes" = {
+ deny = { values = ["in:EXTERNAL"] }
+ rules = [
+ {
+ condition = {
+ expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
+ title = "condition"
+ description = "test condition"
+ location = "xxx"
+ }
+ allow = {
+ values = ["EXTERNAL_1"]
+ }
+ },
+ {
+ condition = {
+ expression = "resource.matchTagId(\\"tagKeys/12345\\", \\"tagValues/12345\\")"
+ title = "condition2"
+ description = "test condition2"
+ location = "xxx"
+ }
+ allow = {
+ all = true
+ }
+ }
+ ]
+ }
+ }'''
+ _, resources = plan_runner(org_policies=policies)
assert len(resources) == 7
- values = [r['values'] for r in resources if r['type']
- == 'google_project_organization_policy']
- assert [r['constraint'] for r in values] == [
- 'policy-a', 'policy-b', 'policy-c'
- ]
- assert values[0]['list_policy'][0]['allow'] == [
- {'all': True, 'values': None}]
- assert values[1]['list_policy'][0]['deny'] == [
- {'all': False, 'values': ["bar"]}]
- assert values[2]['restore_policy'] == [{'default': True}]
+
+ policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
+ assert len(policies) == 3
+ assert all(x['values']['parent'] == 'projects/my-project' for x in policies)
+
+ p1 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'compute.vmExternalIpAccess'
+ ][0]
+ assert p1['inherit_from_parent'] is None
+ assert p1['reset'] is None
+ assert p1['rules'] == [{
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': 'TRUE',
+ 'enforce': None,
+ 'values': []
+ }]
+
+ p2 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'iam.allowedPolicyMemberDomains'
+ ][0]
+ assert p2['inherit_from_parent'] is None
+ assert p2['reset'] is None
+ assert p2['rules'] == [{
+ 'allow_all':
+ None,
+ 'condition': [],
+ 'deny_all':
+ None,
+ 'enforce':
+ None,
+ 'values': [{
+ 'allowed_values': [
+ 'C0xxxxxxx',
+ 'C0yyyyyyy',
+ ],
+ 'denied_values': None
+ }]
+ }]
+
+ p3 = [
+ r['values']['spec'][0]
+ for r in policies
+ if r['index'] == 'compute.restrictLoadBalancerCreationForTypes'
+ ][0]
+ assert p3['inherit_from_parent'] is None
+ assert p3['reset'] is None
+ assert len(p3['rules']) == 3
+ assert p3['rules'][0] == {
+ 'allow_all': None,
+ 'condition': [],
+ 'deny_all': None,
+ 'enforce': None,
+ 'values': [{
+ 'allowed_values': None,
+ 'denied_values': ['in:EXTERNAL']
+ }]
+ }
+
+ assert p3['rules'][1] == {
+ 'allow_all': None,
+ 'condition': [{
+ 'description': 'test condition',
+ 'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")',
+ 'location': 'xxx',
+ 'title': 'condition'
+ }],
+ 'deny_all': None,
+ 'enforce': None,
+ 'values': [{
+ 'allowed_values': ['EXTERNAL_1'],
+ 'denied_values': None
+ }]
+ }
+
+ assert p3['rules'][2] == {
+ 'allow_all': 'TRUE',
+ 'condition': [{
+ 'description':
+ 'test condition2',
+ 'expression':
+ 'resource.matchTagId("tagKeys/12345", "tagValues/12345")',
+ 'location':
+ 'xxx',
+ 'title':
+ 'condition2'
+ }],
+ 'deny_all': None,
+ 'enforce': None,
+ 'values': []
+ }