Merge pull request #945 from GoogleCloudPlatform/jccb/org-policy-factory

Org policy factory
This commit is contained in:
Julio Castillo 2022-11-03 12:30:57 +01:00 committed by GitHub
commit 8b786a7b23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 598 additions and 240 deletions

View File

@ -75,6 +75,10 @@ module "folder" {
# tftest modules=1 resources=8 # tftest modules=1 resources=8
``` ```
### Organization policy factory
See the [organization policy factory in the project module](../project#organization-policy-factory).
### Firewall policy factory ### Firewall policy factory
In the same way as for the [organization](../organization) module, the in-built factory allows you to define a single policy, using one file for rules, and an optional file for CIDR range substitution variables. Remember that non-absolute paths are relative to the root module (the folder where you run `terraform`). In the same way as for the [organization](../organization) module, the in-built factory allows you to define a single policy, using one file for rules, and an optional file for CIDR range substitution variables. Remember that non-absolute paths are relative to the root module (the folder where you run `terraform`).
@ -311,8 +315,9 @@ module "folder" {
| [logging_sinks](variables.tf#L105) | Logging sinks to create for this folder. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; include_children &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [logging_sinks](variables.tf#L105) | Logging sinks to create for this folder. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; include_children &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [name](variables.tf#L126) | Folder name. | <code>string</code> | | <code>null</code> | | [name](variables.tf#L126) | Folder name. | <code>string</code> | | <code>null</code> |
| [org_policies](variables.tf#L132) | Organization policies applied to this folder keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [org_policies](variables.tf#L132) | Organization policies applied to this folder keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [parent](variables.tf#L172) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> | | [org_policies_data_path](variables.tf#L172) | | <code>string</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L182) | Tag bindings for this folder, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [parent](variables.tf#L178) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L188) | Tag bindings for this folder, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -17,8 +17,57 @@
# tfdoc:file:description Folder-level organization policies. # tfdoc:file:description Folder-level organization policies.
locals { locals {
_factory_data_raw = (
var.org_policies_data_path == null
? tomap({})
: merge([
for f in fileset(var.org_policies_data_path, "*.yaml") :
yamldecode(file("${var.org_policies_data_path}/${f}"))
]...)
)
# simulate applying defaults to data coming from yaml files
_factory_data = {
for k, v in local._factory_data_raw :
k => {
inherit_from_parent = try(v.inherit_from_parent, null)
reset = try(v.reset, null)
allow = can(v.allow) ? {
all = try(v.allow.all, null)
values = try(v.allow.values, null)
} : null
deny = can(v.deny) ? {
all = try(v.deny.all, null)
values = try(v.deny.values, null)
} : null
enforce = try(v.enforce, true)
rules = [
for r in try(v.rules, []) : {
allow = can(r.allow) ? {
all = try(r.allow.all, null)
values = try(r.allow.values, null)
} : null
deny = can(r.deny) ? {
all = try(r.deny.all, null)
values = try(r.deny.values, null)
} : null
enforce = try(r.enforce, true)
condition = {
description = try(r.condition.description, null)
expression = try(r.condition.expression, null)
location = try(r.condition.location, null)
title = try(r.condition.title, null)
}
}
]
}
}
_org_policies = merge(local._factory_data, var.org_policies)
org_policies = { org_policies = {
for k, v in var.org_policies : for k, v in local._org_policies :
k => merge(v, { k => merge(v, {
name = "${local.folder.name}/policies/${k}" name = "${local.folder.name}/policies/${k}"
parent = local.folder.name parent = local.folder.name

View File

@ -169,6 +169,12 @@ variable "org_policies" {
nullable = false nullable = false
} }
variable "org_policies_data_path" {
description = ""
type = string
default = null
}
variable "parent" { variable "parent" {
description = "Parent in folders/folder_id or organizations/org_id format." description = "Parent in folders/folder_id or organizations/org_id format."
type = string type = string

View File

@ -76,6 +76,10 @@ If you set audit policies via the `iam_audit_config_authoritative` variable, be
Some care must also be takend with the `groups_iam` variable (and in some situations with the additive variables) to ensure that variable keys are static values, so that Terraform is able to compute the dependency graph. Some care must also be takend with the `groups_iam` variable (and in some situations with the additive variables) to ensure that variable keys are static values, so that Terraform is able to compute the dependency graph.
### Organization policy factory
See the [organization policy factory in the project module](../project#organization-policy-factory).
## Hierarchical firewall policies ## Hierarchical firewall policies
Hirerarchical firewall policies can be managed in two ways: Hirerarchical firewall policies can be managed in two ways:
@ -336,8 +340,9 @@ module "org" {
| [logging_exclusions](variables.tf#L122) | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [logging_exclusions](variables.tf#L122) | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_sinks](variables.tf#L129) | Logging sinks to create for this organization. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; include_children &#61; bool&#10; bq_partitioned_table &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [logging_sinks](variables.tf#L129) | Logging sinks to create for this organization. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; include_children &#61; bool&#10; bq_partitioned_table &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies](variables.tf#L151) | Organization policies applied to this organization keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [org_policies](variables.tf#L151) | Organization policies applied to this organization keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [tag_bindings](variables.tf#L200) | Tag bindings for this organization, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [org_policies_data_path](variables.tf#L200) | | <code>string</code> | | <code>null</code> |
| [tags](variables.tf#L206) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; values &#61; map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | | [tag_bindings](variables.tf#L206) | Tag bindings for this organization, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [tags](variables.tf#L212) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; values &#61; map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -17,8 +17,57 @@
# tfdoc:file:description Organization-level organization policies. # tfdoc:file:description Organization-level organization policies.
locals { locals {
_factory_data_raw = (
var.org_policies_data_path == null
? tomap({})
: merge([
for f in fileset(var.org_policies_data_path, "*.yaml") :
yamldecode(file("${var.org_policies_data_path}/${f}"))
]...)
)
# simulate applying defaults to data coming from yaml files
_factory_data = {
for k, v in local._factory_data_raw :
k => {
inherit_from_parent = try(v.inherit_from_parent, null)
reset = try(v.reset, null)
allow = can(v.allow) ? {
all = try(v.allow.all, null)
values = try(v.allow.values, null)
} : null
deny = can(v.deny) ? {
all = try(v.deny.all, null)
values = try(v.deny.values, null)
} : null
enforce = try(v.enforce, true)
rules = [
for r in try(v.rules, []) : {
allow = can(r.allow) ? {
all = try(r.allow.all, null)
values = try(r.allow.values, null)
} : null
deny = can(r.deny) ? {
all = try(r.deny.all, null)
values = try(r.deny.values, null)
} : null
enforce = try(r.enforce, true)
condition = {
description = try(r.condition.description, null)
expression = try(r.condition.expression, null)
location = try(r.condition.location, null)
title = try(r.condition.title, null)
}
}
]
}
}
_org_policies = merge(local._factory_data, var.org_policies)
org_policies = { org_policies = {
for k, v in var.org_policies : for k, v in local._org_policies :
k => merge(v, { k => merge(v, {
name = "${var.organization_id}/policies/${k}" name = "${var.organization_id}/policies/${k}"
parent = var.organization_id parent = var.organization_id

View File

@ -197,6 +197,12 @@ variable "organization_id" {
} }
} }
variable "org_policies_data_path" {
description = ""
type = string
default = null
}
variable "tag_bindings" { variable "tag_bindings" {
description = "Tag bindings for this organization, in key => tag value id format." description = "Tag bindings for this organization, in key => tag value id format."
type = map(string) type = map(string)

View File

@ -211,6 +211,69 @@ module "project" {
# tftest modules=1 resources=10 # tftest modules=1 resources=10
``` ```
### Organization policy factory
Organization policies can be loaded from a directory containing YAML files where each file defines one or more constraints. The structure of the YAML files is exactly the same as the `org_policies` variable.
The example below deploys the same organization policies shown in the previous section using two YAML files.
```hcl
module "folder" {
source = "./fabric/modules/folder"
parent = "organizations/1234567890"
name = "Folder name"
org_policies_data_path = "/my/path"
}
# tftest skip
```
```yaml
# /my/path/boolean.yaml
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
```
```yaml
# /my/path/list.yaml
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
allow:
values: ["in:EXTERNAL"]
- condition:
expression: resource.matchTagId("tagKeys/12345", "tagValues/12345")
title: condition2
description: test condition2
allow:
all: true
```
## Logging Sinks ## Logging Sinks
```hcl ```hcl
@ -407,21 +470,22 @@ output "compute_robot" {
| [logging_sinks](variables.tf#L102) | Logging sinks to create for this project. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; iam &#61; bool&#10; unique_writer &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [logging_sinks](variables.tf#L102) | Logging sinks to create for this project. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; iam &#61; bool&#10; unique_writer &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [metric_scopes](variables.tf#L124) | List of projects that will act as metric scopes for this project. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [metric_scopes](variables.tf#L124) | List of projects that will act as metric scopes for this project. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [org_policies](variables.tf#L136) | Organization policies applied to this project keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [org_policies](variables.tf#L136) | Organization policies applied to this project keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [oslogin](variables.tf#L176) | Enable OS Login. | <code>bool</code> | | <code>false</code> | | [org_policies_data_path](variables.tf#L176) | | <code>string</code> | | <code>null</code> |
| [oslogin_admins](variables.tf#L182) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [oslogin](variables.tf#L182) | Enable OS Login. | <code>bool</code> | | <code>false</code> |
| [oslogin_users](variables.tf#L190) | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [oslogin_admins](variables.tf#L188) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [parent](variables.tf#L197) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> | | [oslogin_users](variables.tf#L196) | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [prefix](variables.tf#L207) | Prefix used to generate project id and name. | <code>string</code> | | <code>null</code> | | [parent](variables.tf#L203) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> |
| [project_create](variables.tf#L213) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> | | [prefix](variables.tf#L213) | Prefix used to generate project id and name. | <code>string</code> | | <code>null</code> |
| [service_config](variables.tf#L219) | Configure service API activation. | <code title="object&#40;&#123;&#10; disable_on_destroy &#61; bool&#10; disable_dependent_services &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; disable_on_destroy &#61; false&#10; disable_dependent_services &#61; false&#10;&#125;">&#123;&#8230;&#125;</code> | | [project_create](variables.tf#L219) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> |
| [service_encryption_key_ids](variables.tf#L231) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [service_config](variables.tf#L225) | Configure service API activation. | <code title="object&#40;&#123;&#10; disable_on_destroy &#61; bool&#10; disable_dependent_services &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; disable_on_destroy &#61; false&#10; disable_dependent_services &#61; false&#10;&#125;">&#123;&#8230;&#125;</code> |
| [service_perimeter_bridges](variables.tf#L238) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list&#40;string&#41;</code> | | <code>null</code> | | [service_encryption_key_ids](variables.tf#L237) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeter_standard](variables.tf#L245) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> | | [service_perimeter_bridges](variables.tf#L244) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list&#40;string&#41;</code> | | <code>null</code> |
| [services](variables.tf#L251) | Service APIs to enable. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [service_perimeter_standard](variables.tf#L251) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> |
| [shared_vpc_host_config](variables.tf#L257) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object&#40;&#123;&#10; enabled &#61; bool&#10; service_projects &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [services](variables.tf#L257) | Service APIs to enable. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [shared_vpc_service_config](variables.tf#L266) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [shared_vpc_host_config](variables.tf#L263) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object&#40;&#123;&#10; enabled &#61; bool&#10; service_projects &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [skip_delete](variables.tf#L276) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> | | [shared_vpc_service_config](variables.tf#L272) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L282) | Tag bindings for this project, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [skip_delete](variables.tf#L282) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> |
| [tag_bindings](variables.tf#L288) | Tag bindings for this project, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -17,8 +17,57 @@
# tfdoc:file:description Project-level organization policies. # tfdoc:file:description Project-level organization policies.
locals { locals {
_factory_data_raw = (
var.org_policies_data_path == null
? tomap({})
: merge([
for f in fileset(var.org_policies_data_path, "*.yaml") :
yamldecode(file("${var.org_policies_data_path}/${f}"))
]...)
)
# simulate applying defaults to data coming from yaml files
_factory_data = {
for k, v in local._factory_data_raw :
k => {
inherit_from_parent = try(v.inherit_from_parent, null)
reset = try(v.reset, null)
allow = can(v.allow) ? {
all = try(v.allow.all, null)
values = try(v.allow.values, null)
} : null
deny = can(v.deny) ? {
all = try(v.deny.all, null)
values = try(v.deny.values, null)
} : null
enforce = try(v.enforce, true)
rules = [
for r in try(v.rules, []) : {
allow = can(r.allow) ? {
all = try(r.allow.all, null)
values = try(r.allow.values, null)
} : null
deny = can(r.deny) ? {
all = try(r.deny.all, null)
values = try(r.deny.values, null)
} : null
enforce = try(r.enforce, true)
condition = {
description = try(r.condition.description, null)
expression = try(r.condition.expression, null)
location = try(r.condition.location, null)
title = try(r.condition.title, null)
}
}
]
}
}
_org_policies = merge(local._factory_data, var.org_policies)
org_policies = { org_policies = {
for k, v in var.org_policies : for k, v in local._org_policies :
k => merge(v, { k => merge(v, {
name = "projects/${local.project.project_id}/policies/${k}" name = "projects/${local.project.project_id}/policies/${k}"
parent = "projects/${local.project.project_id}" parent = "projects/${local.project.project_id}"

View File

@ -173,6 +173,12 @@ variable "org_policies" {
nullable = false nullable = false
} }
variable "org_policies_data_path" {
description = ""
type = string
default = null
}
variable "oslogin" { variable "oslogin" {
description = "Enable OS Login." description = "Enable OS Login."
type = bool type = bool

View File

@ -27,4 +27,5 @@ module "test" {
logging_sinks = var.logging_sinks logging_sinks = var.logging_sinks
logging_exclusions = var.logging_exclusions logging_exclusions = var.logging_exclusions
org_policies = var.org_policies org_policies = var.org_policies
org_policies_data_path = var.org_policies_data_path
} }

View File

@ -58,3 +58,8 @@ variable "org_policies" {
type = any type = any
default = {} default = {}
} }
variable "org_policies_data_path" {
type = any
default = null
}

View File

@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import hcl2
import yaml
def test_policy_boolean(plan_runner): BOOLEAN_POLICIES = '''{
"Test boolean org policy."
policies = '''{
"iam.disableServiceAccountKeyCreation" = { "iam.disableServiceAccountKeyCreation" = {
enforce = true enforce = true
} }
@ -24,7 +24,7 @@ def test_policy_boolean(plan_runner):
rules = [ rules = [
{ {
condition = { condition = {
expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")" expression = "resource.matchTagId(aa, bb)"
title = "condition" title = "condition"
description = "test condition" description = "test condition"
location = "xxx" location = "xxx"
@ -33,10 +33,85 @@ def test_policy_boolean(plan_runner):
} }
] ]
} }
}''' }'''
_, resources = plan_runner(org_policies=policies)
assert len(resources) == 3
LIST_POLICIES = '''{
"compute.vmExternalIpAccess" = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
}
"compute.restrictLoadBalancerCreationForTypes" = {
deny = { values = ["in:EXTERNAL"] }
rules = [
{
condition = {
expression = "resource.matchTagId(aa, bb)"
title = "condition"
description = "test condition"
location = "xxx"
}
allow = {
values = ["EXTERNAL_1"]
}
},
{
condition = {
expression = "resource.matchTagId(cc, dd)"
title = "condition2"
description = "test condition2"
location = "xxx"
}
allow = {
all = true
}
}
]
}
}'''
def test_policy_boolean(plan_runner):
"Test boolean org policy."
_, resources = plan_runner(org_policies=BOOLEAN_POLICIES)
validate_policy_boolean_resources(resources)
def test_policy_list(plan_runner):
"Test list org policy."
_, resources = plan_runner(org_policies=LIST_POLICIES)
validate_policy_list_resources(resources)
def test_policy_boolean_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {BOOLEAN_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_boolean_resources(resources)
def test_policy_list_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {LIST_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_list_resources(resources)
def validate_policy_boolean_resources(resources):
assert len(resources) == 3
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
assert len(policies) == 2 assert len(policies) == 2
@ -76,7 +151,7 @@ def test_policy_boolean(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -86,46 +161,7 @@ def test_policy_boolean(plan_runner):
} }
def test_policy_list(plan_runner): def validate_policy_list_resources(resources):
"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 assert len(resources) == 4
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
@ -193,7 +229,7 @@ def test_policy_list(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -208,14 +244,10 @@ def test_policy_list(plan_runner):
assert p3['rules'][2] == { assert p3['rules'][2] == {
'allow_all': 'TRUE', 'allow_all': 'TRUE',
'condition': [{ 'condition': [{
'description': 'description': 'test condition2',
'test condition2', 'expression': 'resource.matchTagId(cc, dd)',
'expression': 'location': 'xxx',
'resource.matchTagId("tagKeys/12345", "tagValues/12345")', 'title': 'condition2'
'location':
'xxx',
'title':
'condition2'
}], }],
'deny_all': None, 'deny_all': None,
'enforce': None, 'enforce': None,

View File

@ -29,6 +29,7 @@ module "test" {
logging_sinks = var.logging_sinks logging_sinks = var.logging_sinks
logging_exclusions = var.logging_exclusions logging_exclusions = var.logging_exclusions
org_policies = var.org_policies org_policies = var.org_policies
org_policies_data_path = var.org_policies_data_path
tag_bindings = var.tag_bindings tag_bindings = var.tag_bindings
tags = var.tags tags = var.tags
} }

View File

@ -74,6 +74,11 @@ variable "org_policies" {
default = {} default = {}
} }
variable "org_policies_data_path" {
type = any
default = null
}
variable "tag_bindings" { variable "tag_bindings" {
type = any type = any
default = null default = null

View File

@ -15,10 +15,10 @@
import difflib import difflib
from pathlib import Path from pathlib import Path
import hcl2
import yaml
def test_policy_boolean(plan_runner): BOOLEAN_POLICIES = '''{
"Test boolean org policy."
policies = '''{
"iam.disableServiceAccountKeyCreation" = { "iam.disableServiceAccountKeyCreation" = {
enforce = true enforce = true
} }
@ -27,7 +27,7 @@ def test_policy_boolean(plan_runner):
rules = [ rules = [
{ {
condition = { condition = {
expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")" expression = "resource.matchTagId(aa, bb)"
title = "condition" title = "condition"
description = "test condition" description = "test condition"
location = "xxx" location = "xxx"
@ -36,10 +36,85 @@ def test_policy_boolean(plan_runner):
} }
] ]
} }
}''' }'''
_, resources = plan_runner(org_policies=policies)
assert len(resources) == 2
LIST_POLICIES = '''{
"compute.vmExternalIpAccess" = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
}
"compute.restrictLoadBalancerCreationForTypes" = {
deny = { values = ["in:EXTERNAL"] }
rules = [
{
condition = {
expression = "resource.matchTagId(aa, bb)"
title = "condition"
description = "test condition"
location = "xxx"
}
allow = {
values = ["EXTERNAL_1"]
}
},
{
condition = {
expression = "resource.matchTagId(cc, dd)"
title = "condition2"
description = "test condition2"
location = "xxx"
}
allow = {
all = true
}
}
]
}
}'''
def test_policy_boolean(plan_runner):
"Test boolean org policy."
_, resources = plan_runner(org_policies=BOOLEAN_POLICIES)
validate_policy_boolean_resources(resources)
def test_policy_list(plan_runner):
"Test list org policy."
_, resources = plan_runner(org_policies=LIST_POLICIES)
validate_policy_list_resources(resources)
def test_policy_boolean_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {BOOLEAN_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_boolean_resources(resources)
def test_policy_list_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {LIST_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_list_resources(resources)
def validate_policy_boolean_resources(resources):
assert len(resources) == 2
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
assert len(policies) == 2 assert len(policies) == 2
assert all( assert all(
@ -81,7 +156,7 @@ def test_policy_boolean(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -91,46 +166,7 @@ def test_policy_boolean(plan_runner):
} }
def test_policy_list(plan_runner): def validate_policy_list_resources(resources):
"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 assert len(resources) == 3
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
@ -200,7 +236,7 @@ def test_policy_list(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -215,14 +251,10 @@ def test_policy_list(plan_runner):
assert p3['rules'][2] == { assert p3['rules'][2] == {
'allow_all': 'TRUE', 'allow_all': 'TRUE',
'condition': [{ 'condition': [{
'description': 'description': 'test condition2',
'test condition2', 'expression': 'resource.matchTagId(cc, dd)',
'expression': 'location': 'xxx',
'resource.matchTagId("tagKeys/12345", "tagValues/12345")', 'title': 'condition2'
'location':
'xxx',
'title':
'condition2'
}], }],
'deny_all': None, 'deny_all': None,
'enforce': None, 'enforce': None,
@ -244,7 +276,7 @@ def test_policy_implementation(plan_runner):
assert list(diff1) == [ assert list(diff1) == [
'--- \n', '--- \n',
'+++ \n', '+++ \n',
'@@ -14,14 +14,14 @@\n', '@@ -14,7 +14,7 @@\n',
' * limitations under the License.\n', ' * limitations under the License.\n',
' */\n', ' */\n',
' \n', ' \n',
@ -252,8 +284,10 @@ def test_policy_implementation(plan_runner):
'+# tfdoc:file:description Folder-level organization policies.\n', '+# tfdoc:file:description Folder-level organization policies.\n',
' \n', ' \n',
' locals {\n', ' locals {\n',
' _factory_data_raw = (\n',
'@@ -69,8 +69,8 @@\n',
' org_policies = {\n', ' org_policies = {\n',
' for k, v in var.org_policies :\n', ' for k, v in local._org_policies :\n',
' k => merge(v, {\n', ' k => merge(v, {\n',
'- name = "projects/${local.project.project_id}/policies/${k}"\n', '- name = "projects/${local.project.project_id}/policies/${k}"\n',
'- parent = "projects/${local.project.project_id}"\n', '- parent = "projects/${local.project.project_id}"\n',
@ -268,7 +302,7 @@ def test_policy_implementation(plan_runner):
assert list(diff2) == [ assert list(diff2) == [
'--- \n', '--- \n',
'+++ \n', '+++ \n',
'@@ -14,14 +14,14 @@\n', '@@ -14,7 +14,7 @@\n',
' * limitations under the License.\n', ' * limitations under the License.\n',
' */\n', ' */\n',
' \n', ' \n',
@ -276,8 +310,10 @@ def test_policy_implementation(plan_runner):
'+# tfdoc:file:description Organization-level organization policies.\n', '+# tfdoc:file:description Organization-level organization policies.\n',
' \n', ' \n',
' locals {\n', ' locals {\n',
' _factory_data_raw = (\n',
'@@ -69,8 +69,8 @@\n',
' org_policies = {\n', ' org_policies = {\n',
' for k, v in var.org_policies :\n', ' for k, v in local._org_policies :\n',
' k => merge(v, {\n', ' k => merge(v, {\n',
'- name = "${local.folder.name}/policies/${k}"\n', '- name = "${local.folder.name}/policies/${k}"\n',
'- parent = local.folder.name\n', '- parent = local.folder.name\n',
@ -286,7 +322,7 @@ def test_policy_implementation(plan_runner):
' \n', ' \n',
' is_boolean_policy = v.allow == null && v.deny == null\n', ' is_boolean_policy = v.allow == null && v.deny == null\n',
' has_values = (\n', ' has_values = (\n',
'@@ -94,4 +94,12 @@\n', '@@ -143,4 +143,12 @@\n',
' }\n', ' }\n',
' }\n', ' }\n',
' }\n', ' }\n',

View File

@ -26,6 +26,7 @@ module "test" {
labels = var.labels labels = var.labels
lien_reason = var.lien_reason lien_reason = var.lien_reason
org_policies = var.org_policies org_policies = var.org_policies
org_policies_data_path = var.org_policies_data_path
oslogin = var.oslogin oslogin = var.oslogin
oslogin_admins = var.oslogin_admins oslogin_admins = var.oslogin_admins
oslogin_users = var.oslogin_users oslogin_users = var.oslogin_users

View File

@ -69,6 +69,11 @@ variable "org_policies" {
default = {} default = {}
} }
variable "org_policies_data_path" {
type = any
default = null
}
variable "oslogin" { variable "oslogin" {
type = bool type = bool
default = false default = false

View File

@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import hcl2
import yaml
def test_policy_boolean(plan_runner): BOOLEAN_POLICIES = '''{
"Test boolean org policy."
policies = '''{
"iam.disableServiceAccountKeyCreation" = { "iam.disableServiceAccountKeyCreation" = {
enforce = true enforce = true
} }
@ -24,7 +24,7 @@ def test_policy_boolean(plan_runner):
rules = [ rules = [
{ {
condition = { condition = {
expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")" expression = "resource.matchTagId(aa, bb)"
title = "condition" title = "condition"
description = "test condition" description = "test condition"
location = "xxx" location = "xxx"
@ -33,10 +33,85 @@ def test_policy_boolean(plan_runner):
} }
] ]
} }
}''' }'''
_, resources = plan_runner(org_policies=policies)
assert len(resources) == 6
LIST_POLICIES = '''{
"compute.vmExternalIpAccess" = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
}
"compute.restrictLoadBalancerCreationForTypes" = {
deny = { values = ["in:EXTERNAL"] }
rules = [
{
condition = {
expression = "resource.matchTagId(aa, bb)"
title = "condition"
description = "test condition"
location = "xxx"
}
allow = {
values = ["EXTERNAL_1"]
}
},
{
condition = {
expression = "resource.matchTagId(cc, dd)"
title = "condition2"
description = "test condition2"
location = "xxx"
}
allow = {
all = true
}
}
]
}
}'''
def test_policy_boolean(plan_runner):
"Test boolean org policy."
_, resources = plan_runner(org_policies=BOOLEAN_POLICIES)
validate_policy_boolean_resources(resources)
def test_policy_list(plan_runner):
"Test list org policy."
_, resources = plan_runner(org_policies=LIST_POLICIES)
validate_policy_list_resources(resources)
def test_policy_boolean_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {BOOLEAN_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_boolean_resources(resources)
def test_policy_list_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {LIST_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_list_resources(resources)
def validate_policy_boolean_resources(resources):
assert len(resources) == 6
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
assert len(policies) == 2 assert len(policies) == 2
assert all(x['values']['parent'] == 'projects/my-project' for x in policies) assert all(x['values']['parent'] == 'projects/my-project' for x in policies)
@ -77,7 +152,7 @@ def test_policy_boolean(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -87,46 +162,7 @@ def test_policy_boolean(plan_runner):
} }
def test_policy_list(plan_runner): def validate_policy_list_resources(resources):
"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) == 7 assert len(resources) == 7
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
@ -195,7 +231,7 @@ def test_policy_list(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -210,14 +246,10 @@ def test_policy_list(plan_runner):
assert p3['rules'][2] == { assert p3['rules'][2] == {
'allow_all': 'TRUE', 'allow_all': 'TRUE',
'condition': [{ 'condition': [{
'description': 'description': 'test condition2',
'test condition2', 'expression': 'resource.matchTagId(cc, dd)',
'expression': 'location': 'xxx',
'resource.matchTagId("tagKeys/12345", "tagValues/12345")', 'title': 'condition2'
'location':
'xxx',
'title':
'condition2'
}], }],
'deny_all': None, 'deny_all': None,
'enforce': None, 'enforce': None,

View File

@ -3,3 +3,4 @@ PyYAML>=6.0
tftest>=1.7.6 tftest>=1.7.6
marko>=1.2.0 marko>=1.2.0
deepdiff>=5.7.0 deepdiff>=5.7.0
python-hcl2>=3.0.5