From 8a8b7ea35ffd275da5575d974c8954566d5c21fd Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Fri, 17 Mar 2023 11:12:34 +0100 Subject: [PATCH] Add support for `iam_additive` and simplify factory interface in net VPC module (#1259) * initial implementation, no tests * change interface, align tests * add examples ToC * fix variable type, test module-level variable --- modules/net-vpc/README.md | 49 +++++++++++------ modules/net-vpc/subnets.tf | 53 +++++++++++++++---- modules/net-vpc/variables.tf | 7 +++ .../modules/net_vpc/examples/subnet-iam.yaml | 18 ++++--- 4 files changed, 93 insertions(+), 34 deletions(-) diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 6e82910d..7f992660 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -1,10 +1,20 @@ -# Minimalistic VPC module +# VPC module -This module allows creation and management of VPC networks including subnetworks and subnetwork IAM bindings, Shared VPC activation and service project registration, and one-to-one peering. +This module allows creation and management of VPC networks including subnetworks and subnetwork IAM bindings, and most features and options related to VPCs and subnets. ## Examples -The module allows for several different VPC configurations, some of the most common are shown below. +- [Simple VPC](#simple-vpc) +- [Subnet Options](#subnet-options) +- [Subnet IAM](#subnet-iam) +- [Peering](#peering) +- [Shared VPC](#shared-vpc) +- [Private Service Networking](#private-service-networking) +- [Private Service Networking with Peering Routes](#private-service-networking-with-peering-routes) +- [Subnets for Private Service Connect, Proxy-only subnets](#subnets-for-private-service-connect-proxy-only-subnets) +- [DNS Policies](#dns-policies) +- [Subnet Factory](#subnet-factory) +- [Custom Routes](#custom-routes) ### Simple VPC @@ -105,6 +115,8 @@ module "vpc" { "user:user1@example.com", "group:group1@example.com" ] } + } + subnet_iam_additive = { "europe-west1/subnet-2" = { "roles/compute.networkUser" = [ "user:user2@example.com", "group:group2@example.com" @@ -112,7 +124,7 @@ module "vpc" { } } } -# tftest modules=1 resources=5 inventory=subnet-iam.yaml +# tftest modules=1 resources=6 inventory=subnet-iam.yaml ``` ### Peering @@ -315,7 +327,7 @@ module "vpc" { name = "my-network" data_folder = "config/subnets" } -# tftest modules=1 resources=7 files=subnet-simple,subnet-simple-2,subnet-detailed,subnet-proxy,subnet-psc inventory=factory.yaml +# tftest modules=1 resources=9 files=subnet-simple,subnet-simple-2,subnet-detailed,subnet-proxy,subnet-psc inventory=factory.yaml ``` ```yaml @@ -338,13 +350,17 @@ region: europe-west1 description: Sample description ip_cidr_range: 10.0.0.0/24 # optional attributes -enable_private_access: false # defaults to true -iam_users: ["foobar@example.com"] # grant compute/networkUser to users -iam_groups: ["lorem@example.com"] # grant compute/networkUser to groups -iam_service_accounts: ["fbz@prj.iam.gserviceaccount.com"] -secondary_ip_ranges: # map of secondary ip ranges +enable_private_access: false # defaults to true +iam: # grant roles/compute.networkUser + - group:lorem@example.com + - serviceAccount:fbz@prj.iam.gserviceaccount.com + - user:foobar@example.com +iam_additive: # grant roles/compute.networkUser + - user:foo@example.com + - serviceAccount:fbx@prj.iam.gserviceaccount.com +secondary_ip_ranges: # map of secondary ip ranges secondary-range-a: 192.168.0.0/24 -flow_logs: # enable, set to empty map to use defaults +flow_logs: # enable, set to empty map to use defaults aggregation_interval: "INTERVAL_5_SEC" flow_sampling: 0.5 metadata: "INCLUDE_ALL_METADATA" @@ -402,6 +418,7 @@ module "vpc" { } # tftest modules=5 resources=15 inventory=routes.yaml ``` + ## Variables @@ -422,10 +439,11 @@ module "vpc" { | [shared_vpc_host](variables.tf#L121) | Enable shared VPC for this project. | bool | | false | | [shared_vpc_service_projects](variables.tf#L127) | Shared VPC service projects to register with this host. | list(string) | | [] | | [subnet_iam](variables.tf#L133) | Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format. | map(map(list(string))) | | {} | -| [subnets](variables.tf#L139) | Subnet configuration. | list(object({…})) | | [] | -| [subnets_proxy_only](variables.tf#L164) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] | -| [subnets_psc](variables.tf#L176) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | -| [vpc_create](variables.tf#L187) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true | +| [subnet_iam_additive](variables.tf#L139) | Subnet IAM additive bindings in {REGION/NAME => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | +| [subnets](variables.tf#L146) | Subnet configuration. | list(object({…})) | | [] | +| [subnets_proxy_only](variables.tf#L171) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] | +| [subnets_psc](variables.tf#L183) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | +| [vpc_create](variables.tf#L194) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true | ## Outputs @@ -445,4 +463,3 @@ module "vpc" { | [subnets_psc](outputs.tf#L112) | Private Service Connect subnet resources. | | -The key format is `subnet_region/subnet_name`. For example `europe-west1/my_subnet`. diff --git a/modules/net-vpc/subnets.tf b/modules/net-vpc/subnets.tf index 7719d298..322a7c40 100644 --- a/modules/net-vpc/subnets.tf +++ b/modules/net-vpc/subnets.tf @@ -31,24 +31,39 @@ locals { flow_logs_config = try(v.flow_logs, null) ipv6 = try(v.ipv6, null) secondary_ip_ranges = try(v.secondary_ip_ranges, null) - iam_groups = try(v.iam_groups, []) - iam_users = try(v.iam_users, []) - iam_service_accounts = try(v.iam_service_accounts, []) + iam = try(v.iam, []) + iam_additive = try(v.iam_additive, []) purpose = try(v.purpose, null) active = try(v.active, null) } } + _factory_subnets_iam_additive = flatten([ + for k, v in local._factory_subnets : [ + for member in lookup(v, "iam_additive", []) : { + member = member + subnet = k + role = "roles/compute.networkUser" + } + ] if v.purpose == null + ]) _factory_subnets_iam = [ for k, v in local._factory_subnets : { - subnet = k - role = "roles/compute.networkUser" - members = concat( - formatlist("group:%s", lookup(v, "iam_groups", [])), - formatlist("user:%s", lookup(v, "iam_users", [])), - formatlist("serviceAccount:%s", lookup(v, "iam_service_accounts", [])) - ) - } if v.purpose == null + subnet = k + role = "roles/compute.networkUser" + members = v.iam + } if v.purpose == null && v.iam != null ] + _subnet_iam_additive_members = flatten([ + for subnet, roles in var.subnet_iam_additive : [ + for role, members in roles : [ + for member in members : { + member = member + role = role + subnet = subnet + } + ] + ] + ]) _subnet_iam_members = flatten([ for subnet, roles in(var.subnet_iam == null ? {} : var.subnet_iam) : [ for role, members in roles : { @@ -58,6 +73,10 @@ locals { } ] ]) + subnet_iam_additive_members = concat( + local._factory_subnets_iam_additive, + local._subnet_iam_additive_members + ) subnet_iam_members = concat( [for k in local._factory_subnets_iam : k if length(k.members) > 0], local._subnet_iam_members @@ -151,3 +170,15 @@ resource "google_compute_subnetwork_iam_binding" "binding" { role = each.value.role members = each.value.members } + +resource "google_compute_subnetwork_iam_member" "binding" { + for_each = { + for binding in local.subnet_iam_additive_members : + "${binding.subnet}.${binding.role}.${binding.member}" => binding + } + project = var.project_id + subnetwork = google_compute_subnetwork.subnetwork[each.value.subnet].name + region = google_compute_subnetwork.subnetwork[each.value.subnet].region + role = each.value.role + member = each.value.member +} diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf index a7aa2077..e05ece3f 100644 --- a/modules/net-vpc/variables.tf +++ b/modules/net-vpc/variables.tf @@ -136,6 +136,13 @@ variable "subnet_iam" { default = {} } +variable "subnet_iam_additive" { + description = "Subnet IAM additive bindings in {REGION/NAME => {ROLE => [MEMBERS]}} format." + type = map(map(list(string))) + default = {} + nullable = false +} + variable "subnets" { description = "Subnet configuration." type = list(object({ diff --git a/tests/modules/net_vpc/examples/subnet-iam.yaml b/tests/modules/net_vpc/examples/subnet-iam.yaml index cb53ecd8..ce853c71 100644 --- a/tests/modules/net_vpc/examples/subnet-iam.yaml +++ b/tests/modules/net_vpc/examples/subnet-iam.yaml @@ -34,11 +34,16 @@ values: region: europe-west1 role: roles/compute.networkUser subnetwork: subnet-1 - module.vpc.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-2.roles/compute.networkUser"]: + module.vpc.google_compute_subnetwork_iam_member.binding["europe-west1/subnet-2.roles/compute.networkUser.user:user2@example.com"]: condition: [] - members: - - group:group2@example.com - - user:user2@example.com + member: user:user2@example.com + project: my-project + region: europe-west1 + role: roles/compute.networkUser + subnetwork: subnet-2 + module.vpc.google_compute_subnetwork_iam_member.binding["europe-west1/subnet-2.roles/compute.networkUser.group:group2@example.com"]: + condition: [] + member: group:group2@example.com project: my-project region: europe-west1 role: roles/compute.networkUser @@ -47,8 +52,7 @@ values: counts: google_compute_network: 1 google_compute_subnetwork: 2 - google_compute_subnetwork_iam_binding: 2 - modules: 1 - resources: 5 + google_compute_subnetwork_iam_binding: 1 + google_compute_subnetwork_iam_member: 2 outputs: {}