Add support for org policy custom constraints

This commit is contained in:
Aleksandr Averbukh
2022-11-08 09:34:38 +01:00
parent 0d55de6ca9
commit 3562c52520
3 changed files with 182 additions and 13 deletions

View File

@@ -6,6 +6,7 @@ This module allows managing several organization properties:
- custom IAM roles
- audit logging configuration for services
- organization policies
- organization policy custom constraints
To manage organization policies, the `orgpolicy.googleapis.com` service should be enabled in the quota project.
@@ -22,7 +23,21 @@ module "org" {
"roles/resourcemanager.projectCreator" = ["group:cloud-admins@example.org"]
}
org_policy_custom_constraints = {
"custom.gkeEnableAutoUpgrade" = {
resource_types = ["container.googleapis.com/NodePool"]
method_types = ["CREATE"]
condition = "resource.management.autoUpgrade == true"
action_type = "ALLOW"
display_name = "Enable node auto-upgrade"
description = "All node pools must have node auto-upgrade enabled."
}
}
org_policies = {
"custom.gkeEnableAutoUpgrade" = {
enforce = true
}
"compute.disableGuestAttributesAccess" = {
enforce = true
}
@@ -61,7 +76,7 @@ module "org" {
}
}
}
# tftest modules=1 resources=10
# tftest modules=1 resources=12
```
## IAM
@@ -74,15 +89,100 @@ There are several mutually exclusive ways of managing IAM in this module
If you set audit policies via the `iam_audit_config_authoritative` variable, be sure to also configure IAM bindings via `iam_bindings_authoritative`, as audit policies use the underlying `google_organization_iam_policy` resource, which is also authoritative for any role.
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 taken 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).
### Org policy custom constraints
Refer to the [Creating and managing custom constraints](https://cloud.google.com/resource-manager/docs/organization-policy/creating-managing-custom-constraints) documentation for details on usage.
To manage organization policy custom constraints, the `orgpolicy.googleapis.com` service should be enabled in the quota project.
```hcl
module "org" {
source = "./fabric/modules/organization"
organization_id = var.organization_id
org_policy_custom_constraints = {
"custom.gkeEnableAutoUpgrade" = {
resource_types = ["container.googleapis.com/NodePool"]
method_types = ["CREATE"]
condition = "resource.management.autoUpgrade == true"
action_type = "ALLOW"
display_name = "Enable node auto-upgrade"
description = "All node pools must have node auto-upgrade enabled."
}
}
# not necessarily to enforce on the org level, policy may be applied on folder/project levels
org_policies = {
"custom.gkeEnableAutoUpgrade" = {
enforce = true
}
}
}
# tftest modules=1 resources=2
```
### Org policy custom constraints factory
Org policy custom constraints can be loaded from a directory containing YAML files where each file defines one or more custom constraints. The structure of the YAML files is exactly the same as the `org_policy_custom_constraints` variable.
The example below deploys a few org policy custom constraints split between two YAML files.
```hcl
module "org" {
source = "./fabric/modules/organization"
organization_id = var.organization_id
org_policy_custom_constraints_data_path = "/my/path"
}
# tftest skip
```
```yaml
# /my/path/gke.yaml
custom.gkeEnableLogging:
resource_types:
- container.googleapis.com/Cluster
method_types:
- CREATE
- UPDATE
condition: resource.loggingService == "none"
action_type: DENY
display_name: Do not disable Cloud Logging
custom.gkeEnableAutoUpgrade:
resource_types:
- container.googleapis.com/NodePool
method_types:
- CREATE
condition: resource.management.autoUpgrade == true
action_type: ALLOW
display_name: Enable node auto-upgrade
description: All node pools must have node auto-upgrade enabled.
```
```yaml
# /my/path/dataproc.yaml
custom.dataprocNoMoreThan10Workers
resource_types:
- dataproc.googleapis.com/Cluster
method_types:
- CREATE
- UPDATE
condition: resource.config.workerConfig.numInstances + resource.config.secondaryWorkerConfig.numInstances > 10
action_type: DENY
display_name: Total number of worker instances cannot be larger than 10
description: Cluster cannot have more than 10 workers, including primary and secondary workers.
```
## Hierarchical firewall policies
Hirerarchical firewall policies can be managed in two ways:
Hierarchical firewall policies can be managed in two ways:
- via the `firewall_policies` variable, to directly define policies and rules in Terraform
- via the `firewall_policy_factory` variable, to leverage external YaML files via a simple "factory" embedded in the module ([see here](../../blueprints/factories) for more context on factories)
@@ -314,7 +414,7 @@ module "org" {
| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | <code>google_organization_iam_audit_config</code> · <code>google_organization_iam_binding</code> · <code>google_organization_iam_custom_role</code> · <code>google_organization_iam_member</code> · <code>google_organization_iam_policy</code> |
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | <code>google_bigquery_dataset_iam_member</code> · <code>google_logging_organization_exclusion</code> · <code>google_logging_organization_sink</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_essential_contacts_contact</code> |
| [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | <code>google_org_policy_policy</code> |
| [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | <code>google_org_policy_custom_constraint</code> · <code>google_org_policy_policy</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [tags.tf](./tags.tf) | None | <code>google_tags_tag_binding</code> · <code>google_tags_tag_key</code> · <code>google_tags_tag_key_iam_binding</code> · <code>google_tags_tag_value</code> · <code>google_tags_tag_value_iam_binding</code> |
| [variables.tf](./variables.tf) | Module variables. | |
@@ -324,7 +424,7 @@ module "org" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [organization_id](variables.tf#L191) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
| [organization_id](variables.tf#L217) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
| [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. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [firewall_policies](variables.tf#L31) | Hierarchical firewall policy rules created in the organization. | <code title="map&#40;map&#40;object&#40;&#123;&#10; action &#61; string&#10; description &#61; string&#10; direction &#61; string&#10; logging &#61; bool&#10; ports &#61; map&#40;list&#40;string&#41;&#41;&#10; priority &#61; number&#10; ranges &#61; list&#40;string&#41;&#10; target_resources &#61; list&#40;string&#41;&#10; target_service_accounts &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;&#41;">map&#40;map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
@@ -340,9 +440,11 @@ 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_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_data_path](variables.tf#L200) | Path containing org policies in YAML format. | <code>string</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> |
| [org_policies_data_path](variables.tf#L191) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [org_policy_custom_constraints](variables.tf#L197) | Organization policiy custom constraints keyed by constraint name. | <code title="map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string&#41;&#10; description &#61; optional&#40;string&#41;&#10; action_type &#61; string&#10; condition &#61; string&#10; method_types &#61; list&#40;string&#41;&#10; resource_types &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policy_custom_constraints_data_path](variables.tf#L211) | Path containing org policy custom constraints in YAML format. | <code>string</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L227) | Tag bindings for this organization, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [tags](variables.tf#L233) | 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

View File

@@ -88,6 +88,37 @@ locals {
]
})
}
_custom_constraints_factory_data_raw = (
var.org_policy_custom_constraints_data_path == null
? tomap({})
: merge([
for f in fileset(var.org_policy_custom_constraints_data_path, "*.yaml") :
yamldecode(file("${var.org_policy_custom_constraints_data_path}/${f}"))
]...)
)
_custom_constraints_factory_data = {
for k, v in local._custom_constraints_factory_data_raw :
k => {
display_name = try(v.display_name, null)
description = try(v.description, null)
action_type = v.action_type
condition = v.condition
method_types = v.method_types
resource_types = v.resource_types
}
}
_custom_constraints = merge(local._custom_constraints_factory_data, var.org_policy_custom_constraints)
custom_constraints = {
for k, v in local._custom_constraints :
k => merge(v, {
name = k
parent = var.organization_id
})
}
}
resource "google_org_policy_policy" "default" {
@@ -150,5 +181,20 @@ resource "google_org_policy_policy" "default" {
google_organization_iam_custom_role.roles,
google_organization_iam_member.additive,
google_organization_iam_policy.authoritative,
google_org_policy_custom_constraint.constraint,
]
}
resource "google_org_policy_custom_constraint" "constraint" {
provider = google-beta
for_each = local.custom_constraints
name = each.value.name
parent = each.value.parent
display_name = each.value.display_name
description = each.value.description
action_type = each.value.action_type
condition = each.value.condition
method_types = each.value.method_types
resource_types = each.value.resource_types
}

View File

@@ -188,6 +188,32 @@ variable "org_policies" {
nullable = false
}
variable "org_policies_data_path" {
description = "Path containing org policies in YAML format."
type = string
default = null
}
variable "org_policy_custom_constraints" {
description = "Organization policiy custom constraints keyed by constraint name."
type = map(object({
display_name = optional(string)
description = optional(string)
action_type = string
condition = string
method_types = list(string)
resource_types = list(string)
}))
default = {}
nullable = false
}
variable "org_policy_custom_constraints_data_path" {
description = "Path containing org policy custom constraints in YAML format."
type = string
default = null
}
variable "organization_id" {
description = "Organization id in organizations/nnnnnn format."
type = string
@@ -197,11 +223,6 @@ variable "organization_id" {
}
}
variable "org_policies_data_path" {
description = "Path containing org policies in YAML format."
type = string
default = null
}
variable "tag_bindings" {
description = "Tag bindings for this organization, in key => tag value id format."