Simplified module interface, proper data structure for policies

This commit is contained in:
Aleksandr Averbukh 2022-07-08 12:24:18 +02:00
parent 3c593f9220
commit 63ac142005
9 changed files with 88 additions and 77 deletions

View File

@ -20,27 +20,34 @@ module "org-policy-factory" {
module "org-policy" {
source = "./modules/organization-policy"
organization_policies = {
policies = {
"folders/1234567890" = {
"constraints/iam.disableServiceAccountKeyUpload" = {
rules = [
{
enforce = true
}
]
}
# 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"
},
values = {
allowed_values = ["internal"]
}
}
]
@ -53,29 +60,25 @@ module "org-policy" {
## Org Policy definition format and structure
### Structure of `organization_policies` variable
### Structure of `policies` variable
```hcl
organization_policies = {
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_all = true|false # (Optional) Only for list constraints. Setting this to true means that all values are allowed.
deny_all = true|false # (Optional) Only for list constraints. Setting this to true means that all values are denied.
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.
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)
}
values = { # (Optional) Only for list constraints. List of values to be used for this PolicyRule.
allowed_values = ["value1", "value2"] # (Optional) List of values allowed at this resource.
denied_values = ["value3", "value4"] # (Optional) List of values denied at this resource.
}
}
]
}
@ -94,18 +97,14 @@ parent_id: # parent id in format projects/project-id, folders/1234567890 or orga
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_all: true|false # (Optional) Only for list constraints. Setting this to true means that all values are allowed.
deny_all: true|false # (Optional) Only for list constraints. Setting this to true means that all values are denied.
- 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)
values: # (Optional) Only for list constraints. List of values to be used for this PolicyRule.
allowed_values: ['value1', 'value2'] # (Optional) List of values allowed at this resource.
denied_values: ['value3', 'value4'] # (Optional) List of values denied at this resource.
```
Module allows policies to be distributed into multiple yaml files for a better management and navigation.
@ -130,7 +129,7 @@ folders/1234567890:
inherit_from_parent: false
reset: false
rules:
- allow_all: true
- allow: [] # Stands for allow_all = true
projects/my-project-id:
run.allowedIngress:
inherit_from_parent: true
@ -144,7 +143,7 @@ projects/my-project-id:
allowed_values: ['internal']
iam.allowServiceAccountCredentialLifetimeExtension:
rules:
- allow_all: true
- deny: [] # Stands for deny_all = true
compute.disableGlobalLoadBalancing:
reset: true
```
@ -155,12 +154,12 @@ projects/my-project-id:
| 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> |
| [organization_policies](variables.tf#L26) | Organization policies keyed by parent in format `projects/project-id`, `folders/1234567890` or `organizations/1234567890`. | <code>any</code> | | <code>&#123;&#125;</code> |
| [policies](variables.tf#L23) | Organization policies keyed by parent in format `projects/project-id`, `folders/1234567890` or `organizations/1234567890`. | <code title="map&#40;map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; List policy only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;&#10; list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;list&#40;string&#41;&#41; &#35; List policy only. Stands for &#96;allow_all&#96; if set to empty list &#96;&#91;&#93;&#96; or to &#96;values.allowed_values&#96; if set to a list of values &#10; deny &#61; optional&#40;list&#40;string&#41;&#41; &#35; List policy only. Stands for &#96;deny_all&#96; if set to empty list &#96;&#91;&#93;&#96; or to &#96;values.denied_values&#96; if set to a list of values&#10; enforce &#61; optional&#40;bool&#41; &#35; Boolean policy only. &#10; condition &#61; optional&#40;&#10; 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; &#41;&#10; &#125;&#41;&#41;&#10; &#41;&#10;&#125;&#41;&#41;&#41;">map&#40;map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [organization_policies](outputs.tf#L17) | Organization policies. | |
| [policies](outputs.tf#L17) | Organization policies. | |
<!-- END TFDOC -->

View File

@ -16,23 +16,23 @@
locals {
org_policy_files = var.config_directory == null ? [] : concat(
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}"
]
)
org_policies_raw = merge(
policies_raw = merge(
merge(
[
for config_file in local.org_policy_files :
for config_file in local.policy_files :
try(yamldecode(file(config_file)), {})
]...
), var.organization_policies)
), var.policies)
org_policies_list = flatten([
for parent, policies in local.org_policies_raw : [
policies_list = flatten([
for parent, policies in local.policies_raw : [
for policy_name, policy in policies : {
parent = parent,
policy_name = policy_name,
@ -40,12 +40,8 @@ locals {
reset = try(policy["reset"], null),
rules = [
for rule in try(policy["rules"], []) : {
allow_all = try(
rule["allow_all"], null) == true ? "TRUE" : try(
rule["allow_all"], null) == false ? "FALSE" : null,
deny_all = try(
rule["deny_all"], null) == true ? "TRUE" : try(
rule["deny_all"], null) == false ? "FALSE" : null,
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 ? {
@ -54,9 +50,9 @@ locals {
location = try(rule["condition"]["location"], null),
title = try(rule["condition"]["title"], null)
} : null,
values = try(rule["values"], null) != null ? {
allowed_values = try(rule["values"]["allowed_values"], null),
denied_values = try(rule["values"]["denied_values"], 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
}
]
@ -64,14 +60,14 @@ locals {
]
])
org_policies_map = {
for item in local.org_policies_list :
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.org_policies_map
for_each = local.policies_map
name = format("%s/policies/%s", each.value.parent, each.value.policy_name)
parent = each.value.parent

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
output "organization_policies" {
output "policies" {
description = "Organization policies."
value = google_org_policy_policy.primary
}

View File

@ -20,11 +20,26 @@ variable "config_directory" {
default = null
}
# TODO: convert to a proper data structure map(map(object({...}))) once tf1.3 is released and optional object keys are avaliable,
# for now it will cause multiple keys to be set to null for every policy definition
# https://github.com/hashicorp/terraform/releases/tag/v1.3.0-alpha20220622
variable "organization_policies" {
variable "policies" {
description = "Organization policies keyed by parent in format `projects/project-id`, `folders/1234567890` or `organizations/1234567890`."
type = any
default = {}
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 = {}
}

View File

@ -14,6 +14,10 @@
terraform {
required_version = ">= 1.1.0"
# TODO: Remove once Terraform 1.3 is released https://github.com/hashicorp/terraform/releases/tag/v1.3.0-alpha20220622
experiments = [module_variable_optional_attrs]
required_providers {
google = {
source = "hashicorp/google"

View File

@ -17,6 +17,6 @@
module "org-policy" {
source = "../../../../modules/organization-policy"
config_directory = var.config_directory
organization_policies = var.organization_policies
config_directory = var.config_directory
policies = var.policies
}

View File

@ -22,20 +22,19 @@ folders/1234567890:
inherit_from_parent: false
reset: false
rules:
- allow_all: true
- allow: []
projects/my-project-id:
run.allowedIngress:
inherit_from_parent: true
rules:
- condition:
- allow: ['internal']
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:
- allow_all: true
- deny: []
compute.disableGlobalLoadBalancing:
reset: true

View File

@ -24,7 +24,7 @@ variable "config_directory" {
# TODO: convert to a proper data structure map(map(object({...}))) once tf1.3 is released and optional object keys are avaliable,
# for now it will cause multiple keys to be set to null for every policy definition
# https://github.com/hashicorp/terraform/releases/tag/v1.3.0-alpha20220622
variable "organization_policies" {
variable "policies" {
description = "Organization policies keyed by parent in format `projects/project-id`, `folders/1234567890` or `organizations/1234567890`."
type = any
default = {}

View File

@ -17,25 +17,23 @@ def test_org_policy_simple(plan_runner):
org_policies = (
'{'
'"folders/1234567890" = {'
' "constraints/iam.disableServiceAccountKeyUpload" = {'
' rules = ['
' {'
' enforce = true'
' }'
' ]'
' }'
' "constraints/iam.disableServiceAccountKeyUpload" = {'
' rules = ['
' {'
' enforce = true,'
' }'
' ]'
' }'
' },'
' "organizations/1234567890" = {'
' "run.allowedIngress" = {'
' rules = ['
' {'
' allow = ["internal"],'
' condition = {'
' description= "allow ingress",'
' expression = "resource.matchTag(\'123456789/environment\', \'prod\')",'
' title = "allow-for-prod-org",'
' },'
' values = {'
' allowed_values = ["internal"]'
' title = "allow-for-prod-org"'
' }'
' }'
' ]'
@ -44,7 +42,7 @@ def test_org_policy_simple(plan_runner):
'}'
)
_, resources = plan_runner(
organization_policies = org_policies
policies = org_policies
)
assert len(resources) == 2
@ -85,7 +83,7 @@ def test_combined_org_policy_config(plan_runner):
)
_, resources = plan_runner(
config_directory="./policies",
organization_policies = org_policies
policies = org_policies
)
assert len(resources) == 6