From b0bb441df5fa428683fe5c2db03414905721bd5a Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Thu, 2 Jul 2020 18:12:34 +0200 Subject: [PATCH 01/14] Add VPC-SC perimeters support --- modules/organization/main.tf | 53 +++++++++++++++++++++++++++++++ modules/organization/variables.tf | 27 ++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/modules/organization/main.tf b/modules/organization/main.tf index 91e01b5f..b243d9d9 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -25,6 +25,59 @@ locals { for pair in local.iam_additive_pairs : "${pair.role}-${pair.member}" => pair } + + standard_perimeters = { + for key, value in var.vpc_sc_perimeters : + key => value + if value.type == "PERIMETER_TYPE_REGULAR" + } + + perimeter_create = var.access_policy_name != null || var.access_policy_title != null ? true : false + + bridge_perimeters = { + for key, value in var.vpc_sc_perimeters : + key => value + if value.type == "PERIMETER_TYPE_BRIDGE" + } + + access_policy_name = ( + var.access_policy_name == null + ? try(google_access_context_manager_access_policy.default.0.name, null) + : try(var.access_policy_name, null) + ) +} + +resource "google_access_context_manager_access_policy" "default" { + count = var.access_policy_name == null ? 1 : 0 + parent = format("organizations/%s", var.org_id) + title = var.access_policy_title +} + +resource "google_access_context_manager_service_perimeter" "standard" { + for_each = local.perimeter_create ? local.standard_perimeters : {} + parent = "accessPolicies/${local.access_policy_name}" + name = "accessPolicies/${local.access_policy_name}/servicePerimeters/${each.key}" + title = each.key + perimeter_type = each.value.type + status { + resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) + restricted_services = each.value.restricted_services + } +} + +resource "google_access_context_manager_service_perimeter" "bridge" { + for_each = local.perimeter_create != null ? local.bridge_perimeters : {} + parent = "accessPolicies/${local.access_policy_name}" + name = "accessPolicies/${local.access_policy_name}/servicePerimeters/${each.key}" + title = each.key + perimeter_type = each.value.type + status { + resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) + restricted_services = each.value.restricted_services + } + depends_on = [ + google_access_context_manager_service_perimeter.standard, + ] } resource "google_organization_iam_custom_role" "roles" { diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index 05d636bc..dee59b46 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -14,6 +14,18 @@ * limitations under the License. */ +variable "access_policy_title" { + description = "Access Policy title to be created." + type = string + default = "" +} + +variable "access_policy_name" { + description = "Access Policy name. No Access Policy will be created." + type = string + default = null +} + variable "custom_roles" { description = "Map of role name => list of permissions to create in this project." type = map(list(string)) @@ -76,3 +88,18 @@ variable "policy_list" { })) default = {} } + +variable "vpc_sc_perimeters" { + description = "Set of Perimeters." + type = map(object({ + type = string + restricted_services = list(string) + })) + default = {} +} + +variable "vpc_sc_perimeters_projects" { + description = "Perimeter - Project Number mapping in `projects/project_number` format.." + type = map(list(string)) + default = {} +} From bef0f77e67a45728c181ff2376fdac3d47f295c3 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Thu, 2 Jul 2020 19:01:36 +0200 Subject: [PATCH 02/14] Add Project level support for VPC-SC --- modules/organization/README.md | 5 +++++ modules/organization/main.tf | 9 +++++++++ modules/organization/outputs.tf | 5 +++++ modules/project/README.md | 2 ++ modules/project/main.tf | 15 +++++++++++++++ modules/project/variables.tf | 12 ++++++++++++ 6 files changed, 48 insertions(+) diff --git a/modules/organization/README.md b/modules/organization/README.md index 53ac2cbb..e38e62f9 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -36,6 +36,8 @@ module "org" { | name | description | type | required | default | |---|---|:---: |:---:|:---:| | org_id | Organization id in nnnnnn format. | number | ✓ | | +| *access_policy_name* | Access Policy name. No Access Policy will be created. | string | | null | +| *access_policy_title* | Access Policy title to be created. | string | | | | *custom_roles* | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | | *iam_additive_members* | Map of member lists used to set non authoritative bindings, keyed by role. | map(list(string)) | | {} | | *iam_additive_roles* | List of roles used to set non authoritative bindings. | list(string) | | [] | @@ -44,10 +46,13 @@ module "org" { | *iam_roles* | List of roles used to set authoritative bindings. | list(string) | | [] | | *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} | | *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} | +| *vpc_sc_perimeters* | Set of Perimeters. | map(object({...})) | | {} | +| *vpc_sc_perimeters_projects* | Perimeter - Project Number mapping in `projects/project_number` format.. | map(list(string)) | | {} | ## Outputs | name | description | sensitive | |---|---|:---:| +| access_policy | Access Policy name. | | | org_id | Organization id dependent on module resources. | | diff --git a/modules/organization/main.tf b/modules/organization/main.tf index b243d9d9..37f43c78 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -63,6 +63,10 @@ resource "google_access_context_manager_service_perimeter" "standard" { resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) restricted_services = each.value.restricted_services } + + lifecycle { + ignore_changes = [status[0].resources] + } } resource "google_access_context_manager_service_perimeter" "bridge" { @@ -75,6 +79,11 @@ resource "google_access_context_manager_service_perimeter" "bridge" { resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) restricted_services = each.value.restricted_services } + + lifecycle { + ignore_changes = [status[0].resources] + } + depends_on = [ google_access_context_manager_service_perimeter.standard, ] diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf index 2a829c4d..c0cc469c 100644 --- a/modules/organization/outputs.tf +++ b/modules/organization/outputs.tf @@ -26,3 +26,8 @@ output "org_id" { google_organization_policy.list ] } + +output "access_policy" { + description = "Access Policy name." + value = local.access_policy_name +} \ No newline at end of file diff --git a/modules/project/README.md b/modules/project/README.md index 582b7b29..bdb8cfda 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -75,6 +75,8 @@ module "project" { | *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} | | *prefix* | Prefix used to generate project id and name. | string | | null | | *services* | Service APIs to enable. | list(string) | | [] | +| *vpc_sc_perimeter* | Name of the VPC-SC perimeter the project belong to. | string | | null | +| *vpc_sc_perimeter_bridges* | List of VPC-SC perimeter bridges the project belong to. Must be of the form accessPolicies/{policy_id}/servicePerimeters/{short_name} | list(string) | | [] | ## Outputs diff --git a/modules/project/main.tf b/modules/project/main.tf index 7e4aaeb0..f1c74413 100644 --- a/modules/project/main.tf +++ b/modules/project/main.tf @@ -201,3 +201,18 @@ resource "google_project_organization_policy" "list" { } } } + +resource "google_access_context_manager_service_perimeter_resource" "standard" { + count = var.vpc_sc_perimeter != "" ? 1 : 0 + perimeter_name = var.vpc_sc_perimeter + resource = format("projects/%s", google_project.project.number) +} + +resource "google_access_context_manager_service_perimeter_resource" "bridges" { + count = length(var.vpc_sc_perimeter_bridges) + perimeter_name = var.vpc_sc_perimeter_bridges[count.index] + resource = format("projects/%s", google_project.project.number) + depends_on = [ + google_access_context_manager_service_perimeter_resource.standard, + ] +} diff --git a/modules/project/variables.tf b/modules/project/variables.tf index fc6e12ab..f6cb8b59 100644 --- a/modules/project/variables.tf +++ b/modules/project/variables.tf @@ -124,3 +124,15 @@ variable "services" { type = list(string) default = [] } + +variable "vpc_sc_perimeter" { + description = "Name of the VPC-SC perimeter the project belong to. Must be of the form accessPolicies/{policy_id}/servicePerimeters/{short_name}" + type = string + default = null +} + +variable "vpc_sc_perimeter_bridges" { + description = "List of VPC-SC perimeter bridges the project belong to. Must be of the form accessPolicies/{policy_id}/servicePerimeters/{short_name}" + type = list(string) + default = [] +} From c414ca550593f4fb68c586ce2f73fa9347f8e28b Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Mon, 6 Jul 2020 14:30:25 +0200 Subject: [PATCH 03/14] Fixes based on PR comments: - fix typos - use for_each - fix code layout --- modules/organization/main.tf | 24 +++++++++++++----------- modules/organization/variables.tf | 2 +- modules/project/main.tf | 12 ++++++------ modules/project/variables.tf | 14 ++++++++++++-- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/modules/organization/main.tf b/modules/organization/main.tf index 37f43c78..83b29b8e 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -28,16 +28,14 @@ locals { standard_perimeters = { for key, value in var.vpc_sc_perimeters : - key => value - if value.type == "PERIMETER_TYPE_REGULAR" + key => value if value.type == "PERIMETER_TYPE_REGULAR" } perimeter_create = var.access_policy_name != null || var.access_policy_title != null ? true : false bridge_perimeters = { for key, value in var.vpc_sc_perimeters : - key => value - if value.type == "PERIMETER_TYPE_BRIDGE" + key => value if value.type == "PERIMETER_TYPE_BRIDGE" } access_policy_name = ( @@ -49,7 +47,7 @@ locals { resource "google_access_context_manager_access_policy" "default" { count = var.access_policy_name == null ? 1 : 0 - parent = format("organizations/%s", var.org_id) + parent = "organizations/${var.org_id}" title = var.access_policy_title } @@ -64,9 +62,11 @@ resource "google_access_context_manager_service_perimeter" "standard" { restricted_services = each.value.restricted_services } - lifecycle { - ignore_changes = [status[0].resources] - } + # Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`, + # so they don't fight over which resources should be in the policy. + # lifecycle { + # ignore_changes = [status[0].resources] + # } } resource "google_access_context_manager_service_perimeter" "bridge" { @@ -80,9 +80,11 @@ resource "google_access_context_manager_service_perimeter" "bridge" { restricted_services = each.value.restricted_services } - lifecycle { - ignore_changes = [status[0].resources] - } + # Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`, + # so they don't fight over which resources should be in the policy. + # lifecycle { + # ignore_changes = [status[0].resources] + # } depends_on = [ google_access_context_manager_service_perimeter.standard, diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index dee59b46..a9031384 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -99,7 +99,7 @@ variable "vpc_sc_perimeters" { } variable "vpc_sc_perimeters_projects" { - description = "Perimeter - Project Number mapping in `projects/project_number` format.." + description = "Perimeter - Project Number mapping in `projects/project_number` format." type = map(list(string)) default = {} } diff --git a/modules/project/main.tf b/modules/project/main.tf index f1c74413..7f441bc6 100644 --- a/modules/project/main.tf +++ b/modules/project/main.tf @@ -203,15 +203,15 @@ resource "google_project_organization_policy" "list" { } resource "google_access_context_manager_service_perimeter_resource" "standard" { - count = var.vpc_sc_perimeter != "" ? 1 : 0 - perimeter_name = var.vpc_sc_perimeter - resource = format("projects/%s", google_project.project.number) + for_each = toset([var.vpc_sc_perimeter]) + perimeter_name = each.key + resource = "projects/${google_project.project.number}" } resource "google_access_context_manager_service_perimeter_resource" "bridges" { - count = length(var.vpc_sc_perimeter_bridges) - perimeter_name = var.vpc_sc_perimeter_bridges[count.index] - resource = format("projects/%s", google_project.project.number) + for_each = toset(var.vpc_sc_perimeter_bridges) + perimeter_name = each.key + resource = "projects/${google_project.project.number}" depends_on = [ google_access_context_manager_service_perimeter_resource.standard, ] diff --git a/modules/project/variables.tf b/modules/project/variables.tf index f6cb8b59..769d3ff0 100644 --- a/modules/project/variables.tf +++ b/modules/project/variables.tf @@ -126,13 +126,23 @@ variable "services" { } variable "vpc_sc_perimeter" { - description = "Name of the VPC-SC perimeter the project belong to. Must be of the form accessPolicies/{policy_id}/servicePerimeters/{short_name}" + description = < Date: Mon, 6 Jul 2020 15:28:23 +0200 Subject: [PATCH 04/14] Remove create/no_create logic. I will add it in a future PR. --- modules/organization/main.tf | 20 +++++++------------- modules/organization/variables.tf | 6 ------ 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/modules/organization/main.tf b/modules/organization/main.tf index 66add2bd..7044c458 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -15,6 +15,8 @@ */ locals { + access_policy_name = try(google_access_context_manager_access_policy.default[var.access_policy_title].name, null) + iam_additive_pairs = flatten([ for member, roles in var.iam_additive_bindings : [ for role in roles : @@ -31,28 +33,20 @@ locals { key => value if value.type == "PERIMETER_TYPE_REGULAR" } - perimeter_create = var.access_policy_name != null || var.access_policy_title != null ? true : false - bridge_perimeters = { for key, value in var.vpc_sc_perimeters : key => value if value.type == "PERIMETER_TYPE_BRIDGE" } - - access_policy_name = ( - var.access_policy_name == null - ? try(google_access_context_manager_access_policy.default.0.name, null) - : try(var.access_policy_name, null) - ) } resource "google_access_context_manager_access_policy" "default" { - count = var.access_policy_name == null ? 1 : 0 - parent = "organizations/${var.org_id}" - title = var.access_policy_title + for_each = toset([var.access_policy_title]) + parent = "organizations/${var.org_id}" + title = each.key } resource "google_access_context_manager_service_perimeter" "standard" { - for_each = local.perimeter_create ? local.standard_perimeters : {} + for_each = local.standard_perimeters parent = "accessPolicies/${local.access_policy_name}" name = "accessPolicies/${local.access_policy_name}/servicePerimeters/${each.key}" title = each.key @@ -70,7 +64,7 @@ resource "google_access_context_manager_service_perimeter" "standard" { } resource "google_access_context_manager_service_perimeter" "bridge" { - for_each = local.perimeter_create != null ? local.bridge_perimeters : {} + for_each = local.bridge_perimeters parent = "accessPolicies/${local.access_policy_name}" name = "accessPolicies/${local.access_policy_name}/servicePerimeters/${each.key}" title = each.key diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index 4badecf8..7209c39f 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -20,12 +20,6 @@ variable "access_policy_title" { default = "" } -variable "access_policy_name" { - description = "Access Policy name. No Access Policy will be created." - type = string - default = null -} - variable "custom_roles" { description = "Map of role name => list of permissions to create in this project." type = map(list(string)) From 35571096caf8bdc69ce705ac591c7ae1d97fe878 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Mon, 6 Jul 2020 15:30:29 +0200 Subject: [PATCH 05/14] Update READMEs --- modules/organization/README.md | 3 +-- modules/project/README.md | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/organization/README.md b/modules/organization/README.md index bb22cdef..e124333d 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -36,7 +36,6 @@ module "org" { | name | description | type | required | default | |---|---|:---: |:---:|:---:| | org_id | Organization id in nnnnnn format. | number | ✓ | | -| *access_policy_name* | Access Policy name. No Access Policy will be created. | string | | null | | *access_policy_title* | Access Policy title to be created. | string | | | | *custom_roles* | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | | *iam_additive_bindings* | Map of roles lists used to set non authoritative bindings, keyed by members. | map(list(string)) | | {} | @@ -46,7 +45,7 @@ module "org" { | *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} | | *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} | | *vpc_sc_perimeters* | Set of Perimeters. | map(object({...})) | | {} | -| *vpc_sc_perimeters_projects* | Perimeter - Project Number mapping in `projects/project_number` format.. | map(list(string)) | | {} | +| *vpc_sc_perimeters_projects* | Perimeter - Project Number mapping in `projects/project_number` format. | map(list(string)) | | {} | ## Outputs diff --git a/modules/project/README.md b/modules/project/README.md index 5d5c6bb3..8f861788 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -103,8 +103,8 @@ module "project" { | *project_create* | Create project. When set to false, uses a data source to reference existing project. | bool | | true | | *service_config* | Configure service API activation. | object({...}) | | ... | | *services* | Service APIs to enable. | list(string) | | [] | -| *vpc_sc_perimeter* | Name of the VPC-SC perimeter the project belong to. | string | | null | -| *vpc_sc_perimeter_bridges* | List of VPC-SC perimeter bridges the project belong to. Must be of the form accessPolicies/{policy_id}/servicePerimeters/{short_name} | list(string) | | [] | +| *vpc_sc_perimeter* | None | string | | null | +| *vpc_sc_perimeter_bridges* | None | list(string) | | [] | ## Outputs From 96808b89ff74da9523228761dc610b2712327bf4 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Mon, 6 Jul 2020 16:53:02 +0200 Subject: [PATCH 06/14] Add dry run mode support --- modules/organization/main.tf | 30 ++++++++++++++++++++++++++++-- modules/organization/variables.tf | 13 +++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/modules/organization/main.tf b/modules/organization/main.tf index 7044c458..dcd75725 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -53,7 +53,34 @@ resource "google_access_context_manager_service_perimeter" "standard" { perimeter_type = each.value.type status { resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) - restricted_services = each.value.restricted_services + restricted_services = each.value.enforced_config.restricted_services + + dynamic "vpc_accessible_services" { + for_each = each.value.enforced_config.vpc_accessible_services != [] ? [""] : [] + + content { + enable_restriction = true + allowed_services = each.value.enforced_config.vpc_accessible_services + } + } + } + use_explicit_dry_run_spec = each.value.dry_run_config != [] ? true : false + dynamic "spec" { + for_each = each.value.dry_run_config != [] ? [""] : [] + + content { + resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) + restricted_services = try(each.value.dry_run_config.restricted_services, null) + + dynamic "vpc_accessible_services" { + for_each = try(each.value.dry_run_config.vpc_accessible_services != [] ? [""] : [],[]) + + content { + enable_restriction = true + allowed_services = try(each.value.dry_run_config.vpc_accessible_services, null) + } + } + } } # Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`, @@ -71,7 +98,6 @@ resource "google_access_context_manager_service_perimeter" "bridge" { perimeter_type = each.value.type status { resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) - restricted_services = each.value.restricted_services } # Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`, diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index 7209c39f..9053af0b 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -80,8 +80,17 @@ variable "policy_list" { variable "vpc_sc_perimeters" { description = "Set of Perimeters." type = map(object({ - type = string - restricted_services = list(string) + type = string + dry_run_config = object({ + access_levels = list(string) + restricted_services = list(string) + vpc_accessible_services = list(string) + }) + enforced_config = object({ + access_levels = list(string) + restricted_services = list(string) + vpc_accessible_services = list(string) + }) })) default = {} } From 3e2706be10763a5604fa8d6dbe17c8ead9dedf9b Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Mon, 6 Jul 2020 18:12:25 +0200 Subject: [PATCH 07/14] Add basic Access Level support --- modules/organization/main.tf | 20 ++++++++++++++++++++ modules/organization/variables.tf | 21 ++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/modules/organization/main.tf b/modules/organization/main.tf index dcd75725..de7f78c4 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -45,6 +45,26 @@ resource "google_access_context_manager_access_policy" "default" { title = each.key } +resource "google_access_context_manager_access_level" "access-level" { + for_each = var.access_levels + parent = "accessPolicies/${local.access_policy_name}" + name = "accessPolicies/${local.access_policy_name}/accessLevels/${each.key}" + title = each.key + + dynamic "basic" { + for_each = try(toset(each.value.conditions), []) + + content { + combining_function = try(each.value.combining_function, null) + conditions { + ip_subnetworks = try(basic.value.ip_subnetworks,null) + members = try(basic.value.members,null) + negate = try(basic.value.negate,null) + } + } + } +} + resource "google_access_context_manager_service_perimeter" "standard" { for_each = local.standard_perimeters parent = "accessPolicies/${local.access_policy_name}" diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index 9053af0b..ac80690f 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -14,10 +14,23 @@ * limitations under the License. */ +variable "access_levels" { + description = "Access Levels." + type = map(object({ + combining_function = string + conditions = list(object({ + ip_subnetworks = list(string) + members = list(string) + negate = string + })) + })) + default = {} +} + variable "access_policy_title" { description = "Access Policy title to be created." type = string - default = "" + default = null } variable "custom_roles" { @@ -100,3 +113,9 @@ variable "vpc_sc_perimeters_projects" { type = map(list(string)) default = {} } + +variable "vpc_sc_access_levels_perimeters" { + description = "Access Levels -Perimeter mapping." + type = map(list(string)) + default = {} +} From 43e4ffc95d997b198d4709a93f739e420aaacf16 Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Mon, 6 Jul 2020 18:35:42 +0200 Subject: [PATCH 08/14] Support Access Levels - Perimeters mapping --- modules/organization/README.md | 6 ++++-- modules/organization/main.tf | 10 +++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/organization/README.md b/modules/organization/README.md index e124333d..ddb18e22 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -36,7 +36,8 @@ module "org" { | name | description | type | required | default | |---|---|:---: |:---:|:---:| | org_id | Organization id in nnnnnn format. | number | ✓ | | -| *access_policy_title* | Access Policy title to be created. | string | | | +| *access_levels* | Access Levels. | map(object({...})) | | {} | +| *access_policy_title* | Access Policy title to be created. | string | | null | | *custom_roles* | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | | *iam_additive_bindings* | Map of roles lists used to set non authoritative bindings, keyed by members. | map(list(string)) | | {} | | *iam_audit_config* | Service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service. | map(map(list(string))) | | {} | @@ -44,7 +45,8 @@ module "org" { | *iam_roles* | List of roles used to set authoritative bindings. | list(string) | | [] | | *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} | | *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} | -| *vpc_sc_perimeters* | Set of Perimeters. | map(object({...})) | | {} | +| *vpc_sc_access_levels_perimeters* | Access Levels -Perimeter mapping. | map(list(string)) | | {} | +| *vpc_sc_perimeters* | Set of Perimeters. | map(object({...})) | | {} | | *vpc_sc_perimeters_projects* | Perimeter - Project Number mapping in `projects/project_number` format. | map(list(string)) | | {} | ## Outputs diff --git a/modules/organization/main.tf b/modules/organization/main.tf index de7f78c4..a7c4be38 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -37,6 +37,8 @@ locals { for key, value in var.vpc_sc_perimeters : key => value if value.type == "PERIMETER_TYPE_BRIDGE" } + + perimeters_access_levels = try(transpose(var.vpc_sc_access_levels_perimeters), null) } resource "google_access_context_manager_access_policy" "default" { @@ -45,7 +47,7 @@ resource "google_access_context_manager_access_policy" "default" { title = each.key } -resource "google_access_context_manager_access_level" "access-level" { +resource "google_access_context_manager_access_level" "default" { for_each = var.access_levels parent = "accessPolicies/${local.access_policy_name}" name = "accessPolicies/${local.access_policy_name}/accessLevels/${each.key}" @@ -74,6 +76,7 @@ resource "google_access_context_manager_service_perimeter" "standard" { status { resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) restricted_services = each.value.enforced_config.restricted_services + access_levels = formatlist("accessPolicies/${local.access_policy_name}/accessLevels/%s", lookup(local.perimeters_access_levels, each.key, [])) dynamic "vpc_accessible_services" { for_each = each.value.enforced_config.vpc_accessible_services != [] ? [""] : [] @@ -108,6 +111,10 @@ resource "google_access_context_manager_service_perimeter" "standard" { # lifecycle { # ignore_changes = [status[0].resources] # } + + depends_on = [ + google_access_context_manager_access_level.default, + ] } resource "google_access_context_manager_service_perimeter" "bridge" { @@ -128,6 +135,7 @@ resource "google_access_context_manager_service_perimeter" "bridge" { depends_on = [ google_access_context_manager_service_perimeter.standard, + google_access_context_manager_access_level.default, ] } From 0d7f35b0aeb70340885dd7890b847cb1c5be6a9c Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Mon, 6 Jul 2020 21:58:40 +0200 Subject: [PATCH 09/14] Fix variable name based on PR feedback --- modules/organization/README.md | 2 +- modules/organization/main.tf | 6 +++--- modules/organization/variables.tf | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/organization/README.md b/modules/organization/README.md index ddb18e22..500c11af 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -47,7 +47,7 @@ module "org" { | *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} | | *vpc_sc_access_levels_perimeters* | Access Levels -Perimeter mapping. | map(list(string)) | | {} | | *vpc_sc_perimeters* | Set of Perimeters. | map(object({...})) | | {} | -| *vpc_sc_perimeters_projects* | Perimeter - Project Number mapping in `projects/project_number` format. | map(list(string)) | | {} | +| *vpc_sc_perimeter_projects* | Perimeter - Project Number mapping in `projects/project_number` format. | map(list(string)) | | {} | ## Outputs diff --git a/modules/organization/main.tf b/modules/organization/main.tf index a7c4be38..b6dd8f4d 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -74,7 +74,7 @@ resource "google_access_context_manager_service_perimeter" "standard" { title = each.key perimeter_type = each.value.type status { - resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) + resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeter_projects, each.key, [])) restricted_services = each.value.enforced_config.restricted_services access_levels = formatlist("accessPolicies/${local.access_policy_name}/accessLevels/%s", lookup(local.perimeters_access_levels, each.key, [])) @@ -92,7 +92,7 @@ resource "google_access_context_manager_service_perimeter" "standard" { for_each = each.value.dry_run_config != [] ? [""] : [] content { - resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) + resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeter_projects, each.key, [])) restricted_services = try(each.value.dry_run_config.restricted_services, null) dynamic "vpc_accessible_services" { @@ -124,7 +124,7 @@ resource "google_access_context_manager_service_perimeter" "bridge" { title = each.key perimeter_type = each.value.type status { - resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeters_projects, each.key, [])) + resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeter_projects, each.key, [])) } # Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`, diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index ac80690f..8ab1eb52 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -108,7 +108,7 @@ variable "vpc_sc_perimeters" { default = {} } -variable "vpc_sc_perimeters_projects" { +variable "vpc_sc_perimeter_projects" { description = "Perimeter - Project Number mapping in `projects/project_number` format." type = map(list(string)) default = {} From 39d2d90bcd38caf16afcb3ebecad0822a44c028f Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Tue, 7 Jul 2020 10:23:26 +0200 Subject: [PATCH 10/14] Move VPC-SC to a separate module. --- modules/organization/README.md | 6 -- modules/organization/main.tf | 112 --------------------- modules/organization/outputs.tf | 5 - modules/organization/variables.tf | 49 ---------- modules/vpc-sc/README.md | 113 +++++++++++++++++++++ modules/vpc-sc/main.tf | 157 ++++++++++++++++++++++++++++++ modules/vpc-sc/outputs.tf | 57 +++++++++++ modules/vpc-sc/variables.tf | 66 +++++++++++++ modules/vpc-sc/versions.tf | 19 ++++ 9 files changed, 412 insertions(+), 172 deletions(-) create mode 100644 modules/vpc-sc/README.md create mode 100644 modules/vpc-sc/main.tf create mode 100644 modules/vpc-sc/outputs.tf create mode 100644 modules/vpc-sc/variables.tf create mode 100644 modules/vpc-sc/versions.tf diff --git a/modules/organization/README.md b/modules/organization/README.md index 500c11af..c95bba8f 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -36,8 +36,6 @@ module "org" { | name | description | type | required | default | |---|---|:---: |:---:|:---:| | org_id | Organization id in nnnnnn format. | number | ✓ | | -| *access_levels* | Access Levels. | map(object({...})) | | {} | -| *access_policy_title* | Access Policy title to be created. | string | | null | | *custom_roles* | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | | *iam_additive_bindings* | Map of roles lists used to set non authoritative bindings, keyed by members. | map(list(string)) | | {} | | *iam_audit_config* | Service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service. | map(map(list(string))) | | {} | @@ -45,14 +43,10 @@ module "org" { | *iam_roles* | List of roles used to set authoritative bindings. | list(string) | | [] | | *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} | | *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} | -| *vpc_sc_access_levels_perimeters* | Access Levels -Perimeter mapping. | map(list(string)) | | {} | -| *vpc_sc_perimeters* | Set of Perimeters. | map(object({...})) | | {} | -| *vpc_sc_perimeter_projects* | Perimeter - Project Number mapping in `projects/project_number` format. | map(list(string)) | | {} | ## Outputs | name | description | sensitive | |---|---|:---:| -| access_policy | Access Policy name. | | | org_id | Organization id dependent on module resources. | | diff --git a/modules/organization/main.tf b/modules/organization/main.tf index b6dd8f4d..a96ff141 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -15,8 +15,6 @@ */ locals { - access_policy_name = try(google_access_context_manager_access_policy.default[var.access_policy_title].name, null) - iam_additive_pairs = flatten([ for member, roles in var.iam_additive_bindings : [ for role in roles : @@ -27,116 +25,6 @@ locals { for pair in local.iam_additive_pairs : "${pair.role}-${pair.member}" => pair } - - standard_perimeters = { - for key, value in var.vpc_sc_perimeters : - key => value if value.type == "PERIMETER_TYPE_REGULAR" - } - - bridge_perimeters = { - for key, value in var.vpc_sc_perimeters : - key => value if value.type == "PERIMETER_TYPE_BRIDGE" - } - - perimeters_access_levels = try(transpose(var.vpc_sc_access_levels_perimeters), null) -} - -resource "google_access_context_manager_access_policy" "default" { - for_each = toset([var.access_policy_title]) - parent = "organizations/${var.org_id}" - title = each.key -} - -resource "google_access_context_manager_access_level" "default" { - for_each = var.access_levels - parent = "accessPolicies/${local.access_policy_name}" - name = "accessPolicies/${local.access_policy_name}/accessLevels/${each.key}" - title = each.key - - dynamic "basic" { - for_each = try(toset(each.value.conditions), []) - - content { - combining_function = try(each.value.combining_function, null) - conditions { - ip_subnetworks = try(basic.value.ip_subnetworks,null) - members = try(basic.value.members,null) - negate = try(basic.value.negate,null) - } - } - } -} - -resource "google_access_context_manager_service_perimeter" "standard" { - for_each = local.standard_perimeters - parent = "accessPolicies/${local.access_policy_name}" - name = "accessPolicies/${local.access_policy_name}/servicePerimeters/${each.key}" - title = each.key - perimeter_type = each.value.type - status { - resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeter_projects, each.key, [])) - restricted_services = each.value.enforced_config.restricted_services - access_levels = formatlist("accessPolicies/${local.access_policy_name}/accessLevels/%s", lookup(local.perimeters_access_levels, each.key, [])) - - dynamic "vpc_accessible_services" { - for_each = each.value.enforced_config.vpc_accessible_services != [] ? [""] : [] - - content { - enable_restriction = true - allowed_services = each.value.enforced_config.vpc_accessible_services - } - } - } - use_explicit_dry_run_spec = each.value.dry_run_config != [] ? true : false - dynamic "spec" { - for_each = each.value.dry_run_config != [] ? [""] : [] - - content { - resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeter_projects, each.key, [])) - restricted_services = try(each.value.dry_run_config.restricted_services, null) - - dynamic "vpc_accessible_services" { - for_each = try(each.value.dry_run_config.vpc_accessible_services != [] ? [""] : [],[]) - - content { - enable_restriction = true - allowed_services = try(each.value.dry_run_config.vpc_accessible_services, null) - } - } - } - } - - # Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`, - # so they don't fight over which resources should be in the policy. - # lifecycle { - # ignore_changes = [status[0].resources] - # } - - depends_on = [ - google_access_context_manager_access_level.default, - ] -} - -resource "google_access_context_manager_service_perimeter" "bridge" { - for_each = local.bridge_perimeters - parent = "accessPolicies/${local.access_policy_name}" - name = "accessPolicies/${local.access_policy_name}/servicePerimeters/${each.key}" - title = each.key - perimeter_type = each.value.type - status { - resources = formatlist("projects/%s", lookup(var.vpc_sc_perimeter_projects, each.key, [])) - } - - # Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`, - # so they don't fight over which resources should be in the policy. - # lifecycle { - # ignore_changes = [status[0].resources] - # } - - depends_on = [ - google_access_context_manager_service_perimeter.standard, - google_access_context_manager_access_level.default, - ] } resource "google_organization_iam_custom_role" "roles" { diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf index c0cc469c..2a829c4d 100644 --- a/modules/organization/outputs.tf +++ b/modules/organization/outputs.tf @@ -26,8 +26,3 @@ output "org_id" { google_organization_policy.list ] } - -output "access_policy" { - description = "Access Policy name." - value = local.access_policy_name -} \ No newline at end of file diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index 8ab1eb52..240e920f 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -14,25 +14,6 @@ * limitations under the License. */ -variable "access_levels" { - description = "Access Levels." - type = map(object({ - combining_function = string - conditions = list(object({ - ip_subnetworks = list(string) - members = list(string) - negate = string - })) - })) - default = {} -} - -variable "access_policy_title" { - description = "Access Policy title to be created." - type = string - default = null -} - variable "custom_roles" { description = "Map of role name => list of permissions to create in this project." type = map(list(string)) @@ -89,33 +70,3 @@ variable "policy_list" { })) default = {} } - -variable "vpc_sc_perimeters" { - description = "Set of Perimeters." - type = map(object({ - type = string - dry_run_config = object({ - access_levels = list(string) - restricted_services = list(string) - vpc_accessible_services = list(string) - }) - enforced_config = object({ - access_levels = list(string) - restricted_services = list(string) - vpc_accessible_services = list(string) - }) - })) - default = {} -} - -variable "vpc_sc_perimeter_projects" { - description = "Perimeter - Project Number mapping in `projects/project_number` format." - type = map(list(string)) - default = {} -} - -variable "vpc_sc_access_levels_perimeters" { - description = "Access Levels -Perimeter mapping." - type = map(list(string)) - default = {} -} diff --git a/modules/vpc-sc/README.md b/modules/vpc-sc/README.md new file mode 100644 index 00000000..8f1583a8 --- /dev/null +++ b/modules/vpc-sc/README.md @@ -0,0 +1,113 @@ +# VPC Service Control Module + +This module allows managing VPC Service Control (VPC-SC) properties: + +- [Access Policy](https://cloud.google.com/access-context-manager/docs/create-access-policy) +- [Access Levels](https://cloud.google.com/access-context-manager/docs/manage-access-levels) +- [VPC-SC Perimeters](https://cloud.google.com/vpc-service-controls/docs/service-perimeters) + +Before you begin, check you are running the script with a service account having the [correct permissions](https://cloud.google.com/access-context-manager/docs/access-control) to use Access Context Manager. + +## Example VCP-SC standard perimeter + +```hcl +module "vpc-sc" { + source = "../../modules/vpc-sc" + org_id = 1234567890 + access_policy_title = "My Access Policy" + access_levels = { + my_trusted_proxy = { + combining_function = "AND" + conditions = [{ + ip_subnetworks = ["85.85.85.52/32"] + members = [] + negate = false + }] + } + } + access_level_perimeters = { + my_trusted_proxy = ["perimeter"] + } + perimeters = { + perimeter = { + type = "PERIMETER_TYPE_REGULAR" + dry_run_config = null + enforced_config = { + restricted_services = ["storage.googleapis.com"] + vpc_accessible_services = ["storage.googleapis.com"] + } + } + } + perimeter_projects = { + perimeter = { + enforced = [111111111,222222222] + } + } +} +``` + +## Example VCP-SC standard perimeter with one service and one project in dry run mode +```hcl +module "vpc-sc" { + source = "../../modules/vpc-sc" + org_id = 1234567890 + access_policy_title = "My Access Policy" + access_levels = { + my_trusted_proxy = { + combining_function = "AND" + conditions = [{ + ip_subnetworks = ["85.85.85.52/32"] + members = [] + negate = false + }] + } + } + access_level_perimeters = { + enforced = { + my_trusted_proxy = ["perimeter"] + } + } + perimeters = { + perimeter = { + type = "PERIMETER_TYPE_REGULAR" + dry_run_config = { + restricted_services = ["storage.googleapis.com", "bigquery.googleapis.com"] + vpc_accessible_services = ["storage.googleapis.com", "bigquery.googleapis.com"] + } + enforced_config = { + restricted_services = ["storage.googleapis.com"] + vpc_accessible_services = ["storage.googleapis.com"] + } + } + } + perimeter_projects = { + perimeter = { + enforced = [111111111,222222222] + dry_run = [333333333] + } + } +} +``` + + +## Variables + +| name | description | type | required | default | +|---|---|:---: |:---:|:---:| +| access_policy_title | Access Policy title to be created. | string | ✓ | | +| org_id | Organization id in nnnnnn format. | number | ✓ | | +| *access_level_perimeters* | Enforced mode -> Access Level -> Perimeters mapping. Enforced mode can be 'enforced' or 'dry_run' | map(map(list(string))) | | {} | +| *access_levels* | Access Levels. | map(object({...})) | | {} | +| *perimeter_projects* | Perimeter -> Enforced Mode -> Projects Number mapping. Enforced mode can be 'enforced' or 'dry_run'. | map(map(list(number))) | | {} | +| *perimeters* | Set of Perimeters. | map(object({...})) | | {} | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| access_levels | Access Levels. | | +| access_policy_name | Access Policy resource | | +| org_id | Organization id dependent on module resources. | | +| perimeters_bridge | VPC-SC bridge perimeter resources. | | +| perimeters_standard | VPC-SC standard perimeter resources. | | + diff --git a/modules/vpc-sc/main.tf b/modules/vpc-sc/main.tf new file mode 100644 index 00000000..50fd3d4f --- /dev/null +++ b/modules/vpc-sc/main.tf @@ -0,0 +1,157 @@ +/** + * Copyright 2020 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 { + access_policy_name = try(google_access_context_manager_access_policy.default[var.access_policy_title].name, null) + + standard_perimeters = { + for key, value in var.perimeters : + key => value if value.type == "PERIMETER_TYPE_REGULAR" + } + + bridge_perimeters = { + for key, value in var.perimeters : + key => value if value.type == "PERIMETER_TYPE_BRIDGE" + } + + perimeter_access_levels_enforced = try(transpose(var.access_level_perimeters.enforced), null) + perimeter_access_levels_dry_run = try(transpose(var.access_level_perimeters.dry_run), null) +} + +resource "google_access_context_manager_access_policy" "default" { + for_each = toset([var.access_policy_title]) + parent = "organizations/${var.org_id}" + title = each.key +} + +resource "google_access_context_manager_access_level" "default" { + for_each = var.access_levels + parent = "accessPolicies/${local.access_policy_name}" + name = "accessPolicies/${local.access_policy_name}/accessLevels/${each.key}" + title = each.key + + dynamic "basic" { + for_each = try(toset(each.value.conditions), []) + + content { + combining_function = try(each.value.combining_function, null) + conditions { + ip_subnetworks = try(basic.value.ip_subnetworks,null) + members = try(basic.value.members,null) + negate = try(basic.value.negate,null) + } + } + } +} + +resource "google_access_context_manager_service_perimeter" "standard" { + for_each = local.standard_perimeters + parent = "accessPolicies/${local.access_policy_name}" + description = "Terraform managed." + name = "accessPolicies/${local.access_policy_name}/servicePerimeters/${each.key}" + title = each.key + perimeter_type = each.value.type + + # Enforced mode configuration + dynamic "status" { + for_each = each.value.enforced_config != null ? [""] : [] + + content { + resources = formatlist("projects/%s", try(lookup(var.perimeter_projects, each.key, {}).enforced, [])) + restricted_services = each.value.enforced_config.restricted_services + access_levels = formatlist("accessPolicies/${local.access_policy_name}/accessLevels/%s", try(lookup(local.perimeter_access_levels_enforced, each.key, []), [])) + + dynamic "vpc_accessible_services" { + for_each = each.value.enforced_config.vpc_accessible_services != [] ? [""] : [] + + content { + enable_restriction = true + allowed_services = each.value.enforced_config.vpc_accessible_services + } + } + } + } + + # Dry run mode configuration + use_explicit_dry_run_spec = each.value.dry_run_config != null ? true : false + dynamic "spec" { + for_each = each.value.dry_run_config != null ? [""] : [] + + content { + resources = formatlist("projects/%s", try(lookup(var.perimeter_projects, each.key, {}).dry_run, [])) + restricted_services = try(each.value.dry_run_config.restricted_services, null) + access_levels = formatlist("accessPolicies/${local.access_policy_name}/accessLevels/%s", try(lookup(local.perimeter_access_levels_dry_run, each.key, []), [])) + + + dynamic "vpc_accessible_services" { + for_each = try(each.value.dry_run_config.vpc_accessible_services != [] ? [""] : [],[]) + + content { + enable_restriction = true + allowed_services = try(each.value.dry_run_config.vpc_accessible_services, null) + } + } + } + } + + # Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`, + # so they don't fight over which resources should be in the policy. + # lifecycle { + # ignore_changes = [status[0].resources] + # } + + depends_on = [ + google_access_context_manager_access_level.default, + ] +} + +resource "google_access_context_manager_service_perimeter" "bridge" { + for_each = local.bridge_perimeters + parent = "accessPolicies/${local.access_policy_name}" + description = "Terraform managed." + name = "accessPolicies/${local.access_policy_name}/servicePerimeters/${each.key}" + title = each.key + perimeter_type = each.value.type + + # Enforced mode configuration + dynamic "status" { + for_each = try(lookup(var.perimeter_projects, each.key, {}).enforced, []) != null ? [""] : [] + + content { + resources = formatlist("projects/%s", try(lookup(var.perimeter_projects, each.key, {}).enforced, [])) + } + } + + # Dry run mode configuration + dynamic "spec" { + for_each = try(lookup(var.perimeter_projects, each.key, {}).dry_run, []) != null ? [""] : [] + + content { + resources = formatlist("projects/%s", try(lookup(var.perimeter_projects, each.key, {}).dry_run, [])) + } + } + + # Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`, + # so they don't fight over which resources should be in the policy. + # lifecycle { + # ignore_changes = [status[0].resources] + # } + + depends_on = [ + google_access_context_manager_service_perimeter.standard, + google_access_context_manager_access_level.default, + ] +} diff --git a/modules/vpc-sc/outputs.tf b/modules/vpc-sc/outputs.tf new file mode 100644 index 00000000..b4e1a2cf --- /dev/null +++ b/modules/vpc-sc/outputs.tf @@ -0,0 +1,57 @@ +/** + * Copyright 2020 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 "org_id" { + description = "Organization id dependent on module resources." + value = var.org_id + depends_on = [ + google_organization_iam_audit_config, + google_organization_iam_binding.authoritative, + google_organization_iam_custom_role.roles, + google_organization_iam_member.additive, + google_organization_policy.boolean, + google_organization_policy.list + ] +} + +output "access_policy_name" { + description = "Access Policy resource" + value = local.access_policy_name +} + +output "access_levels" { + description = "Access Levels." + value = { + for key, value in google_access_context_manager_access_level.default : + key => value + } +} + +output "perimeters_standard" { + description = "VPC-SC standard perimeter resources." + value = { + for key, value in google_access_context_manager_service_perimeter.standard : + key => value + } +} + +output "perimeters_bridge" { + description = "VPC-SC bridge perimeter resources." + value = { + for key, value in google_access_context_manager_service_perimeter.bridge : + key => value + } +} diff --git a/modules/vpc-sc/variables.tf b/modules/vpc-sc/variables.tf new file mode 100644 index 00000000..d9fecf96 --- /dev/null +++ b/modules/vpc-sc/variables.tf @@ -0,0 +1,66 @@ +/** + * Copyright 2020 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 "access_levels" { + description = "Access Levels." + type = map(object({ + combining_function = string + conditions = list(object({ + ip_subnetworks = list(string) + members = list(string) + negate = string + })) + })) + default = {} +} + +variable "access_level_perimeters" { + description = "Enforced mode -> Access Level -> Perimeters mapping. Enforced mode can be 'enforced' or 'dry_run'" + type = map(map(list(string))) + default = {} +} + +variable "access_policy_title" { + description = "Access Policy title to be created." + type = string +} + +variable "org_id" { + description = "Organization id in nnnnnn format." + type = number +} + +variable "perimeters" { + description = "Set of Perimeters." + type = map(object({ + type = string + dry_run_config = object({ + restricted_services = list(string) + vpc_accessible_services = list(string) + }) + enforced_config = object({ + restricted_services = list(string) + vpc_accessible_services = list(string) + }) + })) + default = {} +} + +variable "perimeter_projects" { + description = "Perimeter -> Enforced Mode -> Projects Number mapping. Enforced mode can be 'enforced' or 'dry_run'." + type = map(map(list(number))) + default = {} +} diff --git a/modules/vpc-sc/versions.tf b/modules/vpc-sc/versions.tf new file mode 100644 index 00000000..bc4c2a9d --- /dev/null +++ b/modules/vpc-sc/versions.tf @@ -0,0 +1,19 @@ +/** + * Copyright 2020 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. + */ + +terraform { + required_version = ">= 0.12.6" +} From 31ac6ee094858c207fef237ec3128871cecd0e9d Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Tue, 7 Jul 2020 10:49:06 +0200 Subject: [PATCH 11/14] Remove Project level VPC-SC handling. The configuration option is too limited (for example: no dry_run mode supported). --- modules/project/README.md | 2 -- modules/project/main.tf | 15 --------------- modules/project/variables.tf | 22 ---------------------- 3 files changed, 39 deletions(-) diff --git a/modules/project/README.md b/modules/project/README.md index 8f861788..db182e04 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -103,8 +103,6 @@ module "project" { | *project_create* | Create project. When set to false, uses a data source to reference existing project. | bool | | true | | *service_config* | Configure service API activation. | object({...}) | | ... | | *services* | Service APIs to enable. | list(string) | | [] | -| *vpc_sc_perimeter* | None | string | | null | -| *vpc_sc_perimeter_bridges* | None | list(string) | | [] | ## Outputs diff --git a/modules/project/main.tf b/modules/project/main.tf index b3bd29c8..a9239970 100644 --- a/modules/project/main.tf +++ b/modules/project/main.tf @@ -212,18 +212,3 @@ resource "google_project_organization_policy" "list" { } } } - -resource "google_access_context_manager_service_perimeter_resource" "standard" { - for_each = toset([var.vpc_sc_perimeter]) - perimeter_name = each.key - resource = "projects/${google_project.project.number}" -} - -resource "google_access_context_manager_service_perimeter_resource" "bridges" { - for_each = toset(var.vpc_sc_perimeter_bridges) - perimeter_name = each.key - resource = "projects/${google_project.project.number}" - depends_on = [ - google_access_context_manager_service_perimeter_resource.standard, - ] -} diff --git a/modules/project/variables.tf b/modules/project/variables.tf index e7507037..44677620 100644 --- a/modules/project/variables.tf +++ b/modules/project/variables.tf @@ -138,25 +138,3 @@ variable "service_config" { disable_dependent_services = true } } - -variable "vpc_sc_perimeter" { - description = < Date: Tue, 7 Jul 2020 10:56:11 +0200 Subject: [PATCH 12/14] Update READMEs and CHANGELOG --- CHANGELOG.md | 1 + README.md | 2 +- modules/README.md | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 365956b2..640f3b32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - fix external IP assignment in `compute-vm` +- new `vpc-sc` module ## [2.3.0] - 2020-07-02 diff --git a/README.md b/README.md index 8ca9bca1..aac40a52 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The current list of modules supports most of the core foundational and networkin Currently available modules: - **foundational** - [folders](./modules/folders), [log sinks](./modules/logging-sinks), [organization](./modules/organization), [project](./modules/project), [service accounts](./modules/iam-service-accounts) -- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [VPN HA](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/cloudenpoints) +- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [VPC Service Control](./modules/vpc-sc), [VPN HA](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/cloudenpoints) - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [GKE cluster](./modules/gke-cluster), [GKE nodepool](./modules/gke-nodepool), [COS container](./modules/cos-container) (coredns, mysql, onprem, squid) - **data** - [GCS](./modules/gcs), [BigQuery dataset](./modules/bigquery-dataset), [Pub/Sub](./modules/pubsub), [Datafusion](./modules/datafusion), [Bigtable instance](./modules/bigtable-instance) - **development** - [Cloud Source Repository](./modules/source-repository), [Container Registry](./modules/container-registry), [Artifact Registry](./modules/artifact-registry) diff --git a/modules/README.md b/modules/README.md index 4c73872a..64343d6a 100644 --- a/modules/README.md +++ b/modules/README.md @@ -27,6 +27,7 @@ Specific modules also offer support for non-authoritative bindings (e.g. `google - [VPC](./net-vpc) - [VPC firewall](./net-vpc-firewall) - [VPC peering](./net-vpc-peering) +- [VPC Service Control](./vpc-sc) - [VPN static](./net-vpn-static) - [VPN dynamic](./net-vpn-dynamic) - [VPN HA](./net-vpn-ha) From c9b6d66e7e65b26fb5eac527ba4330176e0f7b2f Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Tue, 7 Jul 2020 11:05:34 +0200 Subject: [PATCH 13/14] Fix README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aac40a52..9b97215d 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The current list of modules supports most of the core foundational and networkin Currently available modules: - **foundational** - [folders](./modules/folders), [log sinks](./modules/logging-sinks), [organization](./modules/organization), [project](./modules/project), [service accounts](./modules/iam-service-accounts) -- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [VPC Service Control](./modules/vpc-sc), [VPN HA](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/cloudenpoints) +- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPC Service Control](./modules/vpc-sc), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [VPN HA](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/cloudenpoints) - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [GKE cluster](./modules/gke-cluster), [GKE nodepool](./modules/gke-nodepool), [COS container](./modules/cos-container) (coredns, mysql, onprem, squid) - **data** - [GCS](./modules/gcs), [BigQuery dataset](./modules/bigquery-dataset), [Pub/Sub](./modules/pubsub), [Datafusion](./modules/datafusion), [Bigtable instance](./modules/bigtable-instance) - **development** - [Cloud Source Repository](./modules/source-repository), [Container Registry](./modules/container-registry), [Artifact Registry](./modules/artifact-registry) From 7cf3990d27f08bea1d6bd5c9bcbc11f589a03ecc Mon Sep 17 00:00:00 2001 From: Lorenzo Caggioni Date: Fri, 10 Jul 2020 07:22:57 +0200 Subject: [PATCH 14/14] - Fixes based on PR comments - Movig module under Security - Formatting TF files --- CHANGELOG.md | 3 +- README.md | 4 +-- modules/README.md | 2 +- modules/vpc-sc/README.md | 2 +- modules/vpc-sc/main.tf | 56 +++++++++++++++++++++---------------- modules/vpc-sc/outputs.tf | 8 +++--- modules/vpc-sc/variables.tf | 14 +++++----- 7 files changed, 49 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c010f154..9eb44e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,11 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- new `vpc-sc` module + ## [2.4.1] - 2020-07-06 - better fix external IP assignment in `compute-vm` -- new `vpc-sc` module ## [2.4.0] - 2020-07-06 diff --git a/README.md b/README.md index 9b97215d..883394b5 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ The current list of modules supports most of the core foundational and networkin Currently available modules: - **foundational** - [folders](./modules/folders), [log sinks](./modules/logging-sinks), [organization](./modules/organization), [project](./modules/project), [service accounts](./modules/iam-service-accounts) -- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPC Service Control](./modules/vpc-sc), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [VPN HA](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/cloudenpoints) +- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [VPN HA](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/cloudenpoints) - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [GKE cluster](./modules/gke-cluster), [GKE nodepool](./modules/gke-nodepool), [COS container](./modules/cos-container) (coredns, mysql, onprem, squid) - **data** - [GCS](./modules/gcs), [BigQuery dataset](./modules/bigquery-dataset), [Pub/Sub](./modules/pubsub), [Datafusion](./modules/datafusion), [Bigtable instance](./modules/bigtable-instance) - **development** - [Cloud Source Repository](./modules/source-repository), [Container Registry](./modules/container-registry), [Artifact Registry](./modules/artifact-registry) -- **security** - [KMS](./modules/kms), [SecretManager](./modules/secret-manager) +- **security** - [KMS](./modules/kms), [SecretManager](./modules/secret-manager), [VPC Service Control](./modules/vpc-sc) - **serverless** - [Cloud Functions](./cloud-functions) For more information and usage examples see each module's README file. diff --git a/modules/README.md b/modules/README.md index 64343d6a..722b5ee4 100644 --- a/modules/README.md +++ b/modules/README.md @@ -27,7 +27,6 @@ Specific modules also offer support for non-authoritative bindings (e.g. `google - [VPC](./net-vpc) - [VPC firewall](./net-vpc-firewall) - [VPC peering](./net-vpc-peering) -- [VPC Service Control](./vpc-sc) - [VPN static](./net-vpn-static) - [VPN dynamic](./net-vpn-dynamic) - [VPN HA](./net-vpn-ha) @@ -59,6 +58,7 @@ Specific modules also offer support for non-authoritative bindings (e.g. `google - [Cloud KMS](./kms) - [Secret Manager](./secret-manager) +- [VPC Service Control](./vpc-sc) ## Serverless diff --git a/modules/vpc-sc/README.md b/modules/vpc-sc/README.md index 8f1583a8..5840e58f 100644 --- a/modules/vpc-sc/README.md +++ b/modules/vpc-sc/README.md @@ -6,7 +6,7 @@ This module allows managing VPC Service Control (VPC-SC) properties: - [Access Levels](https://cloud.google.com/access-context-manager/docs/manage-access-levels) - [VPC-SC Perimeters](https://cloud.google.com/vpc-service-controls/docs/service-perimeters) -Before you begin, check you are running the script with a service account having the [correct permissions](https://cloud.google.com/access-context-manager/docs/access-control) to use Access Context Manager. +The Use of this module requires credentials with the [correct permissions](https://cloud.google.com/access-context-manager/docs/access-control) to use Access Context Manager. ## Example VCP-SC standard perimeter diff --git a/modules/vpc-sc/main.tf b/modules/vpc-sc/main.tf index 50fd3d4f..9f7d13f3 100644 --- a/modules/vpc-sc/main.tf +++ b/modules/vpc-sc/main.tf @@ -15,7 +15,7 @@ */ locals { - access_policy_name = try(google_access_context_manager_access_policy.default[var.access_policy_title].name, null) + access_policy_name = google_access_context_manager_access_policy.default.name standard_perimeters = { for key, value in var.perimeters : @@ -32,9 +32,8 @@ locals { } resource "google_access_context_manager_access_policy" "default" { - for_each = toset([var.access_policy_title]) - parent = "organizations/${var.org_id}" - title = each.key + parent = "organizations/${var.org_id}" + title = var.access_policy_title } resource "google_access_context_manager_access_level" "default" { @@ -48,10 +47,10 @@ resource "google_access_context_manager_access_level" "default" { content { combining_function = try(each.value.combining_function, null) - conditions { - ip_subnetworks = try(basic.value.ip_subnetworks,null) - members = try(basic.value.members,null) - negate = try(basic.value.negate,null) + conditions { + ip_subnetworks = try(basic.value.ip_subnetworks, null) + members = try(basic.value.members, null) + negate = try(basic.value.negate, null) } } } @@ -70,18 +69,23 @@ resource "google_access_context_manager_service_perimeter" "standard" { for_each = each.value.enforced_config != null ? [""] : [] content { - resources = formatlist("projects/%s", try(lookup(var.perimeter_projects, each.key, {}).enforced, [])) - restricted_services = each.value.enforced_config.restricted_services - access_levels = formatlist("accessPolicies/${local.access_policy_name}/accessLevels/%s", try(lookup(local.perimeter_access_levels_enforced, each.key, []), [])) + resources = formatlist( + "projects/%s", try(lookup(var.perimeter_projects, each.key, {}).enforced, []) + ) + restricted_services = each.value.enforced_config.restricted_services + access_levels = formatlist( + "accessPolicies/${local.access_policy_name}/accessLevels/%s", + try(lookup(local.perimeter_access_levels_enforced, each.key, []), []) + ) - dynamic "vpc_accessible_services" { - for_each = each.value.enforced_config.vpc_accessible_services != [] ? [""] : [] + dynamic "vpc_accessible_services" { + for_each = each.value.enforced_config.vpc_accessible_services != [] ? [""] : [] - content { - enable_restriction = true - allowed_services = each.value.enforced_config.vpc_accessible_services - } + content { + enable_restriction = true + allowed_services = each.value.enforced_config.vpc_accessible_services } + } } } @@ -91,22 +95,26 @@ resource "google_access_context_manager_service_perimeter" "standard" { for_each = each.value.dry_run_config != null ? [""] : [] content { - resources = formatlist("projects/%s", try(lookup(var.perimeter_projects, each.key, {}).dry_run, [])) + resources = formatlist( + "projects/%s", try(lookup(var.perimeter_projects, each.key, {}).dry_run, []) + ) restricted_services = try(each.value.dry_run_config.restricted_services, null) - access_levels = formatlist("accessPolicies/${local.access_policy_name}/accessLevels/%s", try(lookup(local.perimeter_access_levels_dry_run, each.key, []), [])) - + access_levels = formatlist( + "accessPolicies/${local.access_policy_name}/accessLevels/%s", + try(lookup(local.perimeter_access_levels_dry_run, each.key, []), []) + ) dynamic "vpc_accessible_services" { - for_each = try(each.value.dry_run_config.vpc_accessible_services != [] ? [""] : [],[]) + for_each = try(each.value.dry_run_config.vpc_accessible_services != [] ? [""] : [], []) content { enable_restriction = true - allowed_services = try(each.value.dry_run_config.vpc_accessible_services, null) + allowed_services = try(each.value.dry_run_config.vpc_accessible_services, null) } } } } - + # Uncomment if used alongside `google_access_context_manager_service_perimeter_resource`, # so they don't fight over which resources should be in the policy. # lifecycle { @@ -152,6 +160,6 @@ resource "google_access_context_manager_service_perimeter" "bridge" { depends_on = [ google_access_context_manager_service_perimeter.standard, - google_access_context_manager_access_level.default, + google_access_context_manager_access_level.default, ] } diff --git a/modules/vpc-sc/outputs.tf b/modules/vpc-sc/outputs.tf index b4e1a2cf..e3e56c1c 100644 --- a/modules/vpc-sc/outputs.tf +++ b/modules/vpc-sc/outputs.tf @@ -17,7 +17,7 @@ output "org_id" { description = "Organization id dependent on module resources." value = var.org_id - depends_on = [ + depends_on = [ google_organization_iam_audit_config, google_organization_iam_binding.authoritative, google_organization_iam_custom_role.roles, @@ -34,7 +34,7 @@ output "access_policy_name" { output "access_levels" { description = "Access Levels." - value = { + value = { for key, value in google_access_context_manager_access_level.default : key => value } @@ -42,7 +42,7 @@ output "access_levels" { output "perimeters_standard" { description = "VPC-SC standard perimeter resources." - value = { + value = { for key, value in google_access_context_manager_service_perimeter.standard : key => value } @@ -50,7 +50,7 @@ output "perimeters_standard" { output "perimeters_bridge" { description = "VPC-SC bridge perimeter resources." - value = { + value = { for key, value in google_access_context_manager_service_perimeter.bridge : key => value } diff --git a/modules/vpc-sc/variables.tf b/modules/vpc-sc/variables.tf index d9fecf96..adcb6b79 100644 --- a/modules/vpc-sc/variables.tf +++ b/modules/vpc-sc/variables.tf @@ -16,12 +16,12 @@ variable "access_levels" { description = "Access Levels." - type = map(object({ + type = map(object({ combining_function = string - conditions = list(object({ - ip_subnetworks = list(string) - members = list(string) - negate = string + conditions = list(object({ + ip_subnetworks = list(string) + members = list(string) + negate = string })) })) default = {} @@ -46,8 +46,8 @@ variable "org_id" { variable "perimeters" { description = "Set of Perimeters." type = map(object({ - type = string - dry_run_config = object({ + type = string + dry_run_config = object({ restricted_services = list(string) vpc_accessible_services = list(string) })