From 86aacc5652895210df8ff601e7c401217162d079 Mon Sep 17 00:00:00 2001 From: Bence Wax Date: Fri, 11 Nov 2022 12:43:06 +0100 Subject: [PATCH 1/3] chore: changelog updated --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48fc8a6..c380283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Add support for `computed_members_map` +- Add support for `iam.condition` + ### Removed - BREAKING CHANGE: remove output `module_enabled` From d5fe31538b5488d1f243152119a2e4018e2c0212 Mon Sep 17 00:00:00 2001 From: Bence Wax Date: Fri, 11 Nov 2022 12:43:25 +0100 Subject: [PATCH 2/3] feat: add support for computed members and condition --- README.md | 24 +++++++++- README.tfdoc.hcl | 26 ++++++++++- iam.tf | 36 ++++++++++---- test/unit-complete/main.tf | 96 ++++++++++++++++++++++++++++++++++++++ variables.tf | 15 +++++- 5 files changed, 182 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8566e0a..a4b880c 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ See [variables.tf] and [examples/] for details and use-cases. Example: ```hcl - secondary_ip_range { + secondary_ip_range = { range_name = "tf-test-secondary-range-update1" ip_cidr_range = "192.168.10.0/24" } @@ -135,7 +135,7 @@ See [variables.tf] and [examples/] for details and use-cases. Example: ```hcl - log_config { + log_config = { aggregation_interval = "INTERVAL_10_MIN" flow_sampling = 0.5 metadata = "INCLUDE_ALL_METADATA" @@ -196,6 +196,7 @@ See [variables.tf] and [examples/] for details and use-cases. - `projectOwner:projectid`: Owners of the given project. For example, `projectOwner:my-example-project` - `projectEditor:projectid`: Editors of the given project. For example, `projectEditor:my-example-project` - `projectViewer:projectid`: Viewers of the given project. For example, `projectViewer:my-example-project` + - `computed:{identifier}`: An existing key from `var.computed_members_map`. Default is `[]`. @@ -209,6 +210,25 @@ See [variables.tf] and [examples/] for details and use-cases. Default is `true`. + - [**`condition`**](#attr-iam-condition): *(Optional `object(condition)`)* + + An IAM Condition for a given binding. + + Example: + + ```hcl + condition = { + expression = "request.time < timestamp(\"2022-01-01T00:00:00Z\")" + title = "expires_after_2021_12_31" + } + ``` + +- [**`computed_members_map`**](#var-computed_members_map): *(Optional `map(string)`)* + + A map of members to replace in `members` of various IAM settings to handle terraform computed values. + + Default is `{}`. + - [**`policy_bindings`**](#var-policy_bindings): *(Optional `list(policy_binding)`)* A list of IAM policy bindings. diff --git a/README.tfdoc.hcl b/README.tfdoc.hcl index 6a8c59d..14fb0d6 100644 --- a/README.tfdoc.hcl +++ b/README.tfdoc.hcl @@ -155,7 +155,7 @@ section { An array of configurations for secondary IP ranges for VM instances contained in this subnetwork. The primary IP of such VM must belong to the primary ipCidrRange of the subnetwork. The alias IPs may belong to either primary or secondary ranges. END readme_example = <<-END - secondary_ip_range { + secondary_ip_range = { range_name = "tf-test-secondary-range-update1" ip_cidr_range = "192.168.10.0/24" } @@ -185,7 +185,7 @@ section { END readme_example = <<-END - log_config { + log_config = { aggregation_interval = "INTERVAL_10_MIN" flow_sampling = 0.5 metadata = "INCLUDE_ALL_METADATA" @@ -261,6 +261,7 @@ section { - `projectOwner:projectid`: Owners of the given project. For example, `projectOwner:my-example-project` - `projectEditor:projectid`: Editors of the given project. For example, `projectEditor:my-example-project` - `projectViewer:projectid`: Viewers of the given project. For example, `projectViewer:my-example-project` + - `computed:{identifier}`: An existing key from `var.computed_members_map`. END } @@ -278,6 +279,27 @@ section { Whether to exclusively set (authoritative mode) or add (non-authoritative/additive mode) members to the role. END } + + attribute "condition" { + type = object(condition) + description = <<-END + An IAM Condition for a given binding. + END + readme_example = <<-END + condition = { + expression = "request.time < timestamp(\"2022-01-01T00:00:00Z\")" + title = "expires_after_2021_12_31" + } + END + } + } + + variable "computed_members_map" { + type = map(string) + description = <<-END + A map of members to replace in `members` of various IAM settings to handle terraform computed values. + END + default = {} } variable "policy_bindings" { diff --git a/iam.tf b/iam.tf index 0e37c57..8cd30ef 100644 --- a/iam.tf +++ b/iam.tf @@ -1,29 +1,47 @@ locals { - iam_map = { for iam in var.iam : iam.role => iam } + # filter all objects that define a single role + iam_role = [for iam in var.iam : iam if can(iam.role)] + + # filter all objects that define multiple roles and expand them to single roles + iam_roles = flatten([for iam in var.iam : + [for role in iam.roles : merge(iam, { role = role })] if can(iam.roles) + ]) + + iam = concat(local.iam_role, local.iam_roles) + + iam_map = { for idx, iam in local.iam : + try(iam._key, "${iam.role}/${iam.condition._key}", "${iam.role}/${md5(jsonencode(iam.condition))}", iam.role) => idx + } } module "iam" { - source = "github.com/mineiros-io/terraform-google-subnetwork-iam.git?ref=v0.0.1" + source = "github.com/mineiros-io/terraform-google-subnetwork-iam.git?ref=v0.1.0" for_each = var.policy_bindings == null ? local.iam_map : {} module_enabled = var.module_enabled module_depends_on = var.module_depends_on - subnetwork = try(google_compute_subnetwork.subnetwork[0].name, null) - role = each.value.role - members = each.value.members - authoritative = try(each.value.authoritative, true) + subnetwork = try(google_compute_subnetwork.subnetwork[0].name, null) + + role = local.iam[each.value].role + + members = try(local.iam[each.value].members, []) + computed_members_map = var.computed_members_map + + condition = try(local.iam[each.value].condition, null) + authoritative = try(local.iam[each.value].authoritative, true) } module "policy_bindings" { - source = "github.com/mineiros-io/terraform-google-subnetwork-iam.git?ref=v0.0.1" + source = "github.com/mineiros-io/terraform-google-subnetwork-iam.git?ref=v0.1.0" count = var.policy_bindings != null ? 1 : 0 module_enabled = var.module_enabled module_depends_on = var.module_depends_on - subnetwork = try(google_compute_subnetwork.subnetwork[0].name, null) - policy_bindings = var.policy_bindings + subnetwork = try(google_compute_subnetwork.subnetwork[0].name, null) + policy_bindings = var.policy_bindings + computed_members_map = var.computed_members_map } diff --git a/test/unit-complete/main.tf b/test/unit-complete/main.tf index 37f0b31..21a8838 100644 --- a/test/unit-complete/main.tf +++ b/test/unit-complete/main.tf @@ -1,11 +1,84 @@ +module "test-sa" { + source = "github.com/mineiros-io/terraform-google-service-account?ref=v0.0.12" + + account_id = "service-account-id-${local.random_suffix}" +} + module "test" { source = "../.." module_enabled = true + project = local.project_id + + # add all required arguments + network = "projects/test-project/global/networks/test-network" + name = "test-subnetwork" + description = "unit-complete" + ip_cidr_range = "10.2.0.0/16" + region = "us-central1" + secondary_ip_ranges = [ + { + range_name = "kubernetes-pods" + ip_cidr_range = "10.10.0.0/20" + } + ] + + private_ip_google_access = false + + # add all optional arguments that create additional resources + + # add most/all other optional arguments + + # module_tags = { + # Environment = "unknown" + # } + + log_config = { + aggregation_interval = "INTERVAL_10_MIN" + flow_sampling = 0.5 + metadata = "CUSTOM_METADATA" + metadata_fields = ["field0"] + filter_expr = true + } + + iam = [ + { + role = "roles/browser" + members = ["domain:example-domain"] + condition = { + title = "expires_after_2021_12_31" + description = "Expiring at midnight of 2021-12-31" + expression = "request.time < timestamp(\"2022-01-01T00:00:00Z\")" + } + }, + { + role = "roles/viewer" + members = ["domain:example-domain"] + authoritative = false + }, + { + role = "roles/editor" + members = ["computed:${module.test-sa.service_account.email}"] + } + ] + + computed_members_map = { + myserviceaccount = "serviceAccount:${module.test-sa.service_account.email}" + } + + module_depends_on = ["nothing"] +} + +module "test2" { + source = "../.." + + module_enabled = true + project = local.project_id # add all required arguments network = "projects/test-project/global/networks/test-network" name = "test-subnetwork" + description = "unit-complete" ip_cidr_range = "10.2.0.0/16" region = "us-central1" secondary_ip_ranges = [ @@ -15,6 +88,8 @@ module "test" { } ] + private_ip_google_access = false + # add all optional arguments that create additional resources # add most/all other optional arguments @@ -23,5 +98,26 @@ module "test" { # Environment = "unknown" # } + log_config = { + aggregation_interval = "INTERVAL_10_MIN" + flow_sampling = 0.5 + metadata = "INCLUDE_ALL_METADATA" + filter_expr = true + } + + policy_bindings = [{ + role = "roles/storage.admin" + members = ["user:member@example.com"] + condition = { + title = "expires_after_2021_12_31" + description = "Expiring at midnight of 2021-12-31" + expression = "request.time < timestamp(\"2022-01-01T00:00:00Z\")" + } + }] + + computed_members_map = { + myserviceaccount = "serviceAccount:${module.test-sa.service_account.email}" + } + module_depends_on = ["nothing"] } diff --git a/variables.tf b/variables.tf index d98a8a3..d770248 100644 --- a/variables.tf +++ b/variables.tf @@ -73,8 +73,8 @@ variable "iam" { # validate no invalid keys are in each object validation { - condition = alltrue([for x in var.iam : length(setsubtract(keys(x), ["role", "members", "authoritative"])) == 0]) - error_message = "Each object in var.iam does only support role, members and authoritative attributes." + condition = alltrue([for x in var.iam : length(setsubtract(keys(x), ["role", "members", "condition", "authoritative"])) == 0]) + error_message = "Each object in var.iam does only support role, members, condition and authoritative attributes." } } @@ -96,6 +96,17 @@ variable "policy_bindings" { } } +variable "computed_members_map" { + type = map(string) + description = "(Optional) A map of members to replace in 'members' to handle terraform computed values. Will be ignored when policy bindings are used." + default = {} + + validation { + condition = alltrue([for k, v in var.computed_members_map : can(regex("^(allUsers|allAuthenticatedUsers|(user|serviceAccount|group|domain|projectOwner|projectEditor|projectViewer):)", v))]) + error_message = "The value must be a non-empty list of strings where each entry is a valid principal type identified with `user:`, `serviceAccount:`, `group:`, `domain:`, `projectOwner:`, `projectEditor:` or `projectViewer:`." + } +} + # ------------------------------------------------------------------------------ # MODULE CONFIGURATION PARAMETERS # These variables are used to configure the module. From bd160ab3720568bbc70b388528629022698f6db5 Mon Sep 17 00:00:00 2001 From: Bence Wax Date: Fri, 11 Nov 2022 22:07:10 +0100 Subject: [PATCH 3/3] feat: add support for `iam.roles` --- README.md | 4 ++++ README.tfdoc.hcl | 7 +++++++ test/unit-complete/main.tf | 4 ++-- variables.tf | 8 ++++---- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a4b880c..999d048 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,10 @@ See [variables.tf] and [examples/] for details and use-cases. The role that should be applied. Note that custom roles must be of the format `[projects|organizations]/{parent-name}/roles/{role-name}`. + - [**`roles`**](#attr-iam-roles): *(Optional `list(string)`)* + + The set of roles that should be applied. Note that custom roles must be of the format `[projects|organizations]/{parent-name}/roles/{role-name}`. + - [**`authoritative`**](#attr-iam-authoritative): *(Optional `bool`)* Whether to exclusively set (authoritative mode) or add (non-authoritative/additive mode) members to the role. diff --git a/README.tfdoc.hcl b/README.tfdoc.hcl index 14fb0d6..7d550f6 100644 --- a/README.tfdoc.hcl +++ b/README.tfdoc.hcl @@ -272,6 +272,13 @@ section { END } + attribute "roles" { + type = list(string) + description = <<-END + The set of roles that should be applied. Note that custom roles must be of the format `[projects|organizations]/{parent-name}/roles/{role-name}`. + END + } + attribute "authoritative" { type = bool default = true diff --git a/test/unit-complete/main.tf b/test/unit-complete/main.tf index 21a8838..4186759 100644 --- a/test/unit-complete/main.tf +++ b/test/unit-complete/main.tf @@ -57,8 +57,8 @@ module "test" { authoritative = false }, { - role = "roles/editor" - members = ["computed:${module.test-sa.service_account.email}"] + roles = ["roles/editor", "roles/browser"] + members = ["computed:computed_sa"] } ] diff --git a/variables.tf b/variables.tf index d770248..94298ff 100644 --- a/variables.tf +++ b/variables.tf @@ -67,14 +67,14 @@ variable "iam" { # validate required keys in each object validation { - condition = alltrue([for x in var.iam : length(setintersection(keys(x), ["role", "members"])) == 2]) - error_message = "Each object in var.iam must specify a role and a set of members." + condition = alltrue([for x in var.iam : length(setintersection(keys(x), ["role", "roles", "members"])) == 2]) + error_message = "Each object in var.iam must specify a role or roles and a set of members." } # validate no invalid keys are in each object validation { - condition = alltrue([for x in var.iam : length(setsubtract(keys(x), ["role", "members", "condition", "authoritative"])) == 0]) - error_message = "Each object in var.iam does only support role, members, condition and authoritative attributes." + condition = alltrue([for x in var.iam : length(setsubtract(keys(x), ["role", "roles", "members", "condition", "authoritative"])) == 0]) + error_message = "Each object in var.iam does only support role, roles, members, condition and authoritative attributes." } }