Move VPC-SC to a separate module.

This commit is contained in:
Lorenzo Caggioni 2020-07-07 10:23:26 +02:00
parent 0d7f35b0ae
commit 39d2d90bcd
9 changed files with 412 additions and 172 deletions

View File

@ -36,8 +36,6 @@ module "org" {
| name | description | type | required | default |
|---|---|:---: |:---:|:---:|
| org_id | Organization id in nnnnnn format. | <code title="">number</code> | ✓ | |
| *access_levels* | Access Levels. | <code title="map&#40;object&#40;&#123;&#10;combining_function &#61; string&#10;conditions &#61; list&#40;object&#40;&#123;&#10;ip_subnetworks &#61; list&#40;string&#41;&#10;members &#61; list&#40;string&#41;&#10;negate &#61; string&#10;&#125;&#41;&#41;&#10;&#125;&#41;&#41;">map(object({...}))</code> | | <code title="">{}</code> |
| *access_policy_title* | Access Policy title to be created. | <code title="">string</code> | | <code title="">null</code> |
| *custom_roles* | Map of role name => list of permissions to create in this project. | <code title="map&#40;list&#40;string&#41;&#41;">map(list(string))</code> | | <code title="">{}</code> |
| *iam_additive_bindings* | Map of roles lists used to set non authoritative bindings, keyed by members. | <code title="map&#40;list&#40;string&#41;&#41;">map(list(string))</code> | | <code title="">{}</code> |
| *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. | <code title="map&#40;map&#40;list&#40;string&#41;&#41;&#41;">map(map(list(string)))</code> | | <code title="">{}</code> |
@ -45,14 +43,10 @@ module "org" {
| *iam_roles* | List of roles used to set authoritative bindings. | <code title="list&#40;string&#41;">list(string)</code> | | <code title="">[]</code> |
| *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | <code title="map&#40;bool&#41;">map(bool)</code> | | <code title="">{}</code> |
| *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. | <code title="map&#40;object&#40;&#123;&#10;inherit_from_parent &#61; bool&#10;suggested_value &#61; string&#10;status &#61; bool&#10;values &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map(object({...}))</code> | | <code title="">{}</code> |
| *vpc_sc_access_levels_perimeters* | Access Levels -Perimeter mapping. | <code title="map&#40;list&#40;string&#41;&#41;">map(list(string))</code> | | <code title="">{}</code> |
| *vpc_sc_perimeters* | Set of Perimeters. | <code title="map&#40;object&#40;&#123;&#10;type &#61; string&#10;dry_run_config &#61; object&#40;&#123;&#10;access_levels &#61; list&#40;string&#41;&#10;restricted_services &#61; list&#40;string&#41;&#10;vpc_accessible_services &#61; list&#40;string&#41;&#10;&#125;&#41;&#10;enforced_config &#61; object&#40;&#123;&#10;access_levels &#61; list&#40;string&#41;&#10;restricted_services &#61; list&#40;string&#41;&#10;vpc_accessible_services &#61; list&#40;string&#41;&#10;&#125;&#41;&#10;&#125;&#41;&#41;">map(object({...}))</code> | | <code title="">{}</code> |
| *vpc_sc_perimeter_projects* | Perimeter - Project Number mapping in `projects/project_number` format. | <code title="map&#40;list&#40;string&#41;&#41;">map(list(string))</code> | | <code title="">{}</code> |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| access_policy | Access Policy name. | |
| org_id | Organization id dependent on module resources. | |
<!-- END TFDOC -->

View File

@ -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" {

View File

@ -26,8 +26,3 @@ output "org_id" {
google_organization_policy.list
]
}
output "access_policy" {
description = "Access Policy name."
value = local.access_policy_name
}

View File

@ -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 = {}
}

113
modules/vpc-sc/README.md Normal file
View File

@ -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]
}
}
}
```
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---: |:---:|:---:|
| access_policy_title | Access Policy title to be created. | <code title="">string</code> | ✓ | |
| org_id | Organization id in nnnnnn format. | <code title="">number</code> | ✓ | |
| *access_level_perimeters* | Enforced mode -> Access Level -> Perimeters mapping. Enforced mode can be 'enforced' or 'dry_run' | <code title="map&#40;map&#40;list&#40;string&#41;&#41;&#41;">map(map(list(string)))</code> | | <code title="">{}</code> |
| *access_levels* | Access Levels. | <code title="map&#40;object&#40;&#123;&#10;combining_function &#61; string&#10;conditions &#61; list&#40;object&#40;&#123;&#10;ip_subnetworks &#61; list&#40;string&#41;&#10;members &#61; list&#40;string&#41;&#10;negate &#61; string&#10;&#125;&#41;&#41;&#10;&#125;&#41;&#41;">map(object({...}))</code> | | <code title="">{}</code> |
| *perimeter_projects* | Perimeter -> Enforced Mode -> Projects Number mapping. Enforced mode can be 'enforced' or 'dry_run'. | <code title="map&#40;map&#40;list&#40;number&#41;&#41;&#41;">map(map(list(number)))</code> | | <code title="">{}</code> |
| *perimeters* | Set of Perimeters. | <code title="map&#40;object&#40;&#123;&#10;type &#61; string&#10;dry_run_config &#61; object&#40;&#123;&#10;restricted_services &#61; list&#40;string&#41;&#10;vpc_accessible_services &#61; list&#40;string&#41;&#10;&#125;&#41;&#10;enforced_config &#61; object&#40;&#123;&#10;restricted_services &#61; list&#40;string&#41;&#10;vpc_accessible_services &#61; list&#40;string&#41;&#10;&#125;&#41;&#10;&#125;&#41;&#41;">map(object({...}))</code> | | <code title="">{}</code> |
## 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. | |
<!-- END TFDOC -->

157
modules/vpc-sc/main.tf Normal file
View File

@ -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,
]
}

57
modules/vpc-sc/outputs.tf Normal file
View File

@ -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
}
}

View File

@ -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 = {}
}

View File

@ -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"
}