-
Notifications
You must be signed in to change notification settings - Fork 909
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #722 from GoogleCloudPlatform/org-policy-rework
OrgPolicy module (factory) using new org-policy API, #698
- Loading branch information
Showing
15 changed files
with
614 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
# Google Cloud Organization Policy | ||
|
||
This module allows creation and management of [GCP Organization Policies](https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints) by defining them in a well formatted `yaml` files or with HCL. | ||
|
||
Yaml based factory can simplify centralized management of Org Policies for a DevSecOps team by providing a simple way to define/structure policies and exclusions. | ||
|
||
> **_NOTE:_** This module uses experimental feature `module_variable_optional_attrs` which will be included into [terraform release 1.3](https://github.com/hashicorp/terraform/releases/tag/v1.3.0-alpha20220706). | ||
## Example | ||
|
||
### Terraform code | ||
|
||
```hcl | ||
# using configuration provided in a set of yaml files | ||
module "org-policy-factory" { | ||
source = "./modules/organization-policy" | ||
config_directory = "./policies" | ||
} | ||
# using configuration provided in the module variable | ||
module "org-policy" { | ||
source = "./modules/organization-policy" | ||
policies = { | ||
"folders/1234567890" = { | ||
# enforce boolean policy with no conditions | ||
"iam.disableServiceAccountKeyUpload" = { | ||
rules = [ | ||
{ | ||
enforce = true | ||
} | ||
] | ||
}, | ||
# Deny All for compute.vmCanIpForward policy | ||
"compute.vmCanIpForward" = { | ||
inherit_from_parent = false | ||
rules = [ | ||
deny = [] # stands for deny_all | ||
] | ||
} | ||
}, | ||
"organizations/1234567890" = { | ||
# allow only internal ingress when match condition env=prod | ||
"run.allowedIngress" = { | ||
rules = [ | ||
{ | ||
allow = ["internal"] | ||
condition = { | ||
description= "allow ingress" | ||
expression = "resource.matchTag('123456789/environment', 'prod')" | ||
title = "allow-for-prod-org" | ||
} | ||
} | ||
] | ||
} | ||
} | ||
} | ||
} | ||
# tftest skip | ||
``` | ||
|
||
## Org Policy definition format and structure | ||
|
||
### Structure of `policies` variable | ||
|
||
```hcl | ||
policies = { | ||
"parent_id" = { # parent id in format projects/project-id, folders/1234567890 or organizations/1234567890. | ||
"policy_name" = { # policy constraint id, for example compute.vmExternalIpAccess. | ||
inherit_from_parent = true|false # (Optional) Only for list constraints. Determines the inheritance behavior for this policy. | ||
reset = true|false # (Optional) Ignores policies set above this resource and restores the constraint_default enforcement behavior. | ||
rules = [ # Up to 10 PolicyRules are allowed. | ||
{ | ||
allow = ["value1", "value2"] # (Optional) Only for list constraints. Stands for `allow_all` if set to empty list `[]` or to `values.allowed_values` if set to a list of values | ||
denyl = ["value3", "value4"] # (Optional) Only for list constraints. Stands for `deny_all` if set to empty list `[]` or to `values.denied_values` if set to a list of values | ||
enforce = true|false # (Optional) Only for boolean constraints. If true, then the Policy is enforced. | ||
condition = { # (Optional) A condition which determines whether this rule is used in the evaluation of the policy. | ||
description = "Condition description" # (Optional) | ||
expression = "Condition expression" # (Optional) For example "resource.matchTag('123456789/environment', 'prod')". | ||
location = "policy-error.log" # (Optional) String indicating the location of the expression for error reporting. | ||
title = "condition-title" # (Optional) | ||
} | ||
} | ||
] | ||
} | ||
} | ||
} | ||
# tftest skip | ||
``` | ||
|
||
### Structure of configuration provided in a yaml file/s | ||
|
||
Configuration should be placed in a set of yaml files in the config directory. Policy entry structure as follows: | ||
|
||
```yaml | ||
parent_id: # parent id in format projects/project-id, folders/1234567890 or organizations/1234567890. | ||
policy_name1: # policy constraint id, for example compute.vmExternalIpAccess. | ||
inherit_from_parent: true|false # (Optional) Only for list constraints. Determines the inheritance behavior for this policy. | ||
reset: true|false # (Optional) Ignores policies set above this resource and restores the constraint_default enforcement behavior. | ||
rules: | ||
- allow: ["value1", "value2"] # (Optional) Only for list constraints. Stands for `allow_all` if set to empty list `[]` or to `values.allowed_values` if set to a list of values | ||
deny: ["value3", "value4"] # (Optional) Only for list constraints. Stands for `deny_all` if set to empty list `[]` or to `values.denied_values` if set to a list of values | ||
enforce: true|false # (Optional) Only for boolean constraints. If true, then the Policy is enforced. | ||
condition: # (Optional) A condition which determines whether this rule is used in the evaluation of the policy. | ||
description: Condition description # (Optional) | ||
expression: Condition expression # (Optional) For example resource.matchTag("123456789/environment", "prod") | ||
location: policy-error.log # (Optional) String indicating the location of the expression for error reporting. | ||
title: condition-title # (Optional) | ||
``` | ||
Module allows policies to be distributed into multiple yaml files for a better management and navigation. | ||
```bash | ||
├── org-policies | ||
│ ├── baseline.yaml | ||
│ ├── image-import-projects.yaml | ||
│ └── exclusions.yaml | ||
``` | ||
|
||
Organization policies example yaml configuration | ||
|
||
```bash | ||
cat ./policies/baseline.yaml | ||
organizations/1234567890: | ||
constraints/compute.vmExternalIpAccess: | ||
rules: | ||
- deny_all: true | ||
folders/1234567890: | ||
compute.vmCanIpForward: | ||
inherit_from_parent: false | ||
reset: false | ||
rules: | ||
- allow: [] # Stands for allow_all = true | ||
projects/my-project-id: | ||
run.allowedIngress: | ||
inherit_from_parent: true | ||
rules: | ||
- condition: | ||
description: allow internal ingress | ||
expression: resource.matchTag("123456789/environment", "prod") | ||
location: test.log | ||
title: allow-for-prod | ||
values: | ||
allowed_values: ['internal'] | ||
iam.allowServiceAccountCredentialLifetimeExtension: | ||
rules: | ||
- deny: [] # Stands for deny_all = true | ||
compute.disableGlobalLoadBalancing: | ||
reset: true | ||
``` | ||
<!-- BEGIN TFDOC --> | ||
|
||
## Variables | ||
|
||
| name | description | type | required | default | | ||
|---|---|:---:|:---:|:---:| | ||
| [config_directory](variables.tf#L17) | Paths to a folder where organization policy configs are stored in yaml format. Files suffix must be `.yaml`. | <code>string</code> | | <code>null</code> | | ||
| [policies](variables.tf#L23) | Organization policies keyed by parent in format `projects/project-id`, `folders/1234567890` or `organizations/1234567890`. | <code title="map(map(object({ inherit_from_parent = optional(bool) # List policy only. reset = optional(bool) rules = optional( list(object({ allow = optional(list(string)) # List policy only. Stands for `allow_all` if set to empty list `[]` or to `values.allowed_values` if set to a list of values deny = optional(list(string)) # List policy only. Stands for `deny_all` if set to empty list `[]` or to `values.denied_values` if set to a list of values enforce = optional(bool) # Boolean policy only. condition = optional( object({ description = optional(string) expression = optional(string) location = optional(string) title = optional(string) }) ) })) ) })))">map(map(object({…})))</code> | | <code>{}</code> | | ||
|
||
## Outputs | ||
|
||
| name | description | sensitive | | ||
|---|---|:---:| | ||
| [policies](outputs.tf#L17) | Organization policies. | | | ||
|
||
<!-- END TFDOC --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# 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 | ||
# | ||
# https://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. | ||
|
||
|
||
terraform { | ||
# TODO: Remove once Terraform 1.3 is released https://github.com/hashicorp/terraform/releases/tag/v1.3.0-alpha20220622 | ||
experiments = [module_variable_optional_attrs] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/** | ||
* 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. | ||
*/ | ||
|
||
|
||
locals { | ||
policy_files = var.config_directory == null ? [] : concat( | ||
[ | ||
for config_file in fileset("${path.root}/${var.config_directory}", "**/*.yaml") : | ||
"${path.root}/${var.config_directory}/${config_file}" | ||
] | ||
) | ||
|
||
policies_raw = merge( | ||
merge( | ||
[ | ||
for config_file in local.policy_files : | ||
try(yamldecode(file(config_file)), {}) | ||
]... | ||
), var.policies) | ||
|
||
policies_list = flatten([ | ||
for parent, policies in local.policies_raw : [ | ||
for policy_name, policy in policies : { | ||
parent = parent, | ||
policy_name = policy_name, | ||
inherit_from_parent = try(policy["inherit_from_parent"], null), | ||
reset = try(policy["reset"], null), | ||
rules = [ | ||
for rule in try(policy["rules"], []) : { | ||
allow_all = try(length(rule["allow"]), -1) == 0 ? "TRUE" : null | ||
deny_all = try(length(rule["deny"]), -1) == 0 ? "TRUE" : null | ||
enforce = try(rule["enforce"], null) == true ? "TRUE" : try( | ||
rule["enforce"], null) == false ? "FALSE" : null, | ||
condition = try(rule["condition"], null) != null ? { | ||
description = try(rule["condition"]["description"], null), | ||
expression = try(rule["condition"]["expression"], null), | ||
location = try(rule["condition"]["location"], null), | ||
title = try(rule["condition"]["title"], null) | ||
} : null, | ||
values = try(length(rule["allow"]), 0) > 0 || try(length(rule["deny"]), 0) > 0 ? { | ||
allowed_values = try(length(rule["allow"]), 0) > 0 ? rule["allow"] : null | ||
denied_values = try(length(rule["deny"]), 0) > 0 ? rule["deny"] : null | ||
} : null | ||
} | ||
] | ||
} | ||
] | ||
]) | ||
|
||
policies_map = { | ||
for item in local.policies_list : | ||
format("%s-%s", item["parent"], item["policy_name"]) => item | ||
} | ||
} | ||
|
||
resource "google_org_policy_policy" "primary" { | ||
for_each = local.policies_map | ||
name = format("%s/policies/%s", each.value.parent, each.value.policy_name) | ||
parent = each.value.parent | ||
|
||
spec { | ||
inherit_from_parent = each.value.inherit_from_parent | ||
reset = each.value.reset | ||
dynamic "rules" { | ||
for_each = each.value.rules | ||
content { | ||
allow_all = rules.value.allow_all | ||
deny_all = rules.value.deny_all | ||
enforce = rules.value.enforce | ||
dynamic "condition" { | ||
for_each = rules.value.condition != null ? [""] : [] | ||
content { | ||
description = rules.value.condition.description | ||
expression = rules.value.condition.expression | ||
location = rules.value.condition.location | ||
title = rules.value.condition.title | ||
} | ||
} | ||
dynamic "values" { | ||
for_each = rules.value.values != null ? [""] : [] | ||
content { | ||
allowed_values = rules.value.values.allowed_values | ||
denied_values = rules.value.values.denied_values | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
* 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. | ||
*/ | ||
|
||
output "policies" { | ||
description = "Organization policies." | ||
value = google_org_policy_policy.primary | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/** | ||
* 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. | ||
*/ | ||
|
||
variable "config_directory" { | ||
description = "Paths to a folder where organization policy configs are stored in yaml format. Files suffix must be `.yaml`." | ||
type = string | ||
default = null | ||
} | ||
|
||
variable "policies" { | ||
description = "Organization policies keyed by parent in format `projects/project-id`, `folders/1234567890` or `organizations/1234567890`." | ||
type = map(map(object({ | ||
inherit_from_parent = optional(bool) # List policy only. | ||
reset = optional(bool) | ||
rules = optional( | ||
list(object({ | ||
allow = optional(list(string)) # List policy only. Stands for `allow_all` if set to empty list `[]` or to `values.allowed_values` if set to a list of values | ||
deny = optional(list(string)) # List policy only. Stands for `deny_all` if set to empty list `[]` or to `values.denied_values` if set to a list of values | ||
enforce = optional(bool) # Boolean policy only. | ||
condition = optional( | ||
object({ | ||
description = optional(string) | ||
expression = optional(string) | ||
location = optional(string) | ||
title = optional(string) | ||
}) | ||
) | ||
})) | ||
) | ||
}))) | ||
default = {} | ||
} |
Oops, something went wrong.