diff --git a/modules/organization/README.md b/modules/organization/README.md
index 500c11afd..c95bba8fc 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 b6dd8f4dd..a96ff141a 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 c0cc469cf..2a829c4dd 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 8ab1eb524..240e920f6 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 000000000..8f1583a8d
--- /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 000000000..50fd3d4f4
--- /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 000000000..b4e1a2cf5
--- /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 000000000..d9fecf965
--- /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 000000000..bc4c2a9d7
--- /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"
+}