service_control.tf (379 lines of code) (raw):
/**
* Copyright 2021 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 {
perimeter_members_data_ingestion = distinct(concat([
"serviceAccount:${module.data_ingestion.dataflow_controller_service_account_email}",
"serviceAccount:${module.data_ingestion.storage_writer_service_account_email}",
"serviceAccount:${module.data_ingestion.pubsub_writer_service_account_email}",
"serviceAccount:${module.data_ingestion.scheduler_service_account_email}",
"serviceAccount:${var.terraform_service_account}"
], var.perimeter_additional_members))
perimeter_members_governance = distinct(concat([
"serviceAccount:${var.terraform_service_account}"
], var.perimeter_additional_members))
perimeter_members_confidential = distinct(concat([
"serviceAccount:${var.terraform_service_account}"
], var.perimeter_additional_members))
data_ingestion_vpc_sc_resources = {
data_ingestion = data.google_project.data_ingestion_project.number
non_confidential = data.google_project.non_confidential_data_project.number
}
data_governance_vpc_sc_resources = {
governance = data.google_project.governance_project.number
}
confidential_data_vpc_sc_resources = {
confidential = data.google_project.confidential_project.number
}
actual_policy = var.access_context_manager_policy_id != "" ? var.access_context_manager_policy_id : google_access_context_manager_access_policy.access_policy[0].name
data_ingestion_default_egress_rule = var.sdx_project_number == "" ? [] : [
{
"from" = {
"identity_type" = ""
"identities" = distinct(concat(
var.data_ingestion_dataflow_deployer_identities,
["serviceAccount:${var.terraform_service_account}", "serviceAccount:${module.data_ingestion.dataflow_controller_service_account_email}"]
))
},
"to" = {
"resources" = ["projects/${var.sdx_project_number}"]
"operations" = {
"storage.googleapis.com" = {
"methods" = [
"google.storage.objects.get"
]
},
"artifactregistry.googleapis.com" = {
"methods" = [
"*"
]
}
}
}
},
]
data_ingestion_egress_policies = distinct(concat(
local.data_ingestion_default_egress_rule,
[
for k, a in var.data_ingestion_egress_policies : {
from = {
identity_type = lookup(a.from, "identity_type", null)
identities = try([
for item in a.from.identities : replace(
replace(item, "CONFIDENTIAL_DATA_DATAFLOW_CONTROLLER_SA", module.bigquery_confidential_data.confidential_dataflow_controller_service_account_email),
"DATA_INGESTION_DATAFLOW_CONTROLLER_SA", module.data_ingestion.dataflow_controller_service_account_email)
], null)
}
to = a.to
}
]
))
data_ingestion_ingress_policies = [
for k, a in var.data_ingestion_ingress_policies : {
from = {
identity_type = lookup(a.from, "identity_type", null)
identities = try([
for item in a.from.identities : replace(
replace(item, "CONFIDENTIAL_DATA_DATAFLOW_CONTROLLER_SA", module.bigquery_confidential_data.confidential_dataflow_controller_service_account_email),
"DATA_INGESTION_DATAFLOW_CONTROLLER_SA", module.data_ingestion.dataflow_controller_service_account_email)
], null)
sources = lookup(a.from, "sources", null)
}
to = a.to
}]
confidential_data_default_egress_rule = var.sdx_project_number == "" ? [] : [
{
"from" = {
"identity_type" = ""
"identities" = distinct(concat(
var.confidential_data_dataflow_deployer_identities,
["serviceAccount:${var.terraform_service_account}", "serviceAccount:${module.bigquery_confidential_data.confidential_dataflow_controller_service_account_email}"]
))
},
"to" = {
"resources" = ["projects/${var.sdx_project_number}"]
"operations" = {
"storage.googleapis.com" = {
"methods" = [
"google.storage.objects.get"
]
},
"artifactregistry.googleapis.com" = {
"methods" = [
"*"
]
}
}
}
},
]
confidential_data_egress_policies = distinct(concat(
local.confidential_data_default_egress_rule,
[
for k, a in var.confidential_data_egress_policies : {
from = {
identity_type = lookup(a.from, "identity_type", null)
identities = try([
for item in a.from.identities : replace(
replace(item, "CONFIDENTIAL_DATA_DATAFLOW_CONTROLLER_SA", module.bigquery_confidential_data.confidential_dataflow_controller_service_account_email),
"DATA_INGESTION_DATAFLOW_CONTROLLER_SA", module.data_ingestion.dataflow_controller_service_account_email)
], null)
}
to = a.to
}]
))
confidential_data_ingress_policies = [
for k, a in var.confidential_data_ingress_policies : {
from = {
identity_type = lookup(a.from, "identity_type", null)
identities = try([
for item in a.from.identities : replace(
replace(item, "CONFIDENTIAL_DATA_DATAFLOW_CONTROLLER_SA", module.bigquery_confidential_data.confidential_dataflow_controller_service_account_email),
"DATA_INGESTION_DATAFLOW_CONTROLLER_SA", module.data_ingestion.dataflow_controller_service_account_email)
], null)
sources = lookup(a.from, "sources", null)
}
to = a.to
}]
data_governance_egress_policies = [
for k, a in var.data_governance_egress_policies : {
from = {
identity_type = lookup(a.from, "identity_type", null)
identities = try([
for item in a.from.identities : replace(
replace(item, "CONFIDENTIAL_DATA_DATAFLOW_CONTROLLER_SA", module.bigquery_confidential_data.confidential_dataflow_controller_service_account_email),
"DATA_INGESTION_DATAFLOW_CONTROLLER_SA", module.data_ingestion.dataflow_controller_service_account_email)
], null)
}
to = a.to
}]
data_governance_ingress_policies = [
for k, a in var.data_governance_ingress_policies : {
from = {
identity_type = lookup(a.from, "identity_type", null)
identities = try([
for item in a.from.identities : replace(
replace(item, "CONFIDENTIAL_DATA_DATAFLOW_CONTROLLER_SA", module.bigquery_confidential_data.confidential_dataflow_controller_service_account_email),
"DATA_INGESTION_DATAFLOW_CONTROLLER_SA", module.data_ingestion.dataflow_controller_service_account_email)
], null)
sources = lookup(a.from, "sources", null)
}
to = a.to
}]
}
resource "google_access_context_manager_access_policy" "access_policy" {
count = var.access_context_manager_policy_id != "" ? 0 : 1
parent = "organizations/${var.org_id}"
title = "default policy"
}
data "google_project" "data_ingestion_project" {
project_id = var.data_ingestion_project_id
}
data "google_project" "governance_project" {
project_id = var.data_governance_project_id
}
data "google_project" "non_confidential_data_project" {
project_id = var.non_confidential_data_project_id
}
data "google_project" "confidential_project" {
project_id = var.confidential_data_project_id
}
resource "random_id" "suffix" {
byte_length = 4
}
# It's necessary to use the forces_wait_propagation to guarantee the resources that use this VPC do not have issues related to the propagation.
# See: https://cloud.google.com/vpc-service-controls/docs/manage-service-perimeters#update.
resource "time_sleep" "forces_wait_propagation" {
destroy_duration = "330s"
depends_on = [
module.data_ingestion,
module.org_policies,
module.data_governance,
module.bigquery_confidential_data
]
}
# Default VPC Service Controls perimeter and access list.
module "data_ingestion_vpc_sc" {
source = ".//modules/dwh-vpc-sc"
count = var.data_ingestion_perimeter == "" ? 1 : 0
access_context_manager_policy_id = local.actual_policy
common_name = "data_ingestion"
common_suffix = random_id.suffix.hex
resources = local.data_ingestion_vpc_sc_resources
perimeter_members = local.perimeter_members_data_ingestion
access_level_combining_function = var.data_ingestion_access_level_combining_function
access_level_ip_subnetworks = var.data_ingestion_access_level_ip_subnetworks
access_level_negate = var.data_ingestion_access_level_negate
access_level_require_screen_lock = var.data_ingestion_access_level_require_screen_lock
access_level_require_corp_owned = var.data_ingestion_access_level_require_corp_owned
access_level_allowed_encryption_statuses = var.data_ingestion_access_level_allowed_encryption_statuses
access_level_allowed_device_management_levels = var.data_ingestion_access_level_allowed_device_management_levels
access_level_minimum_version = var.data_ingestion_access_level_minimum_version
access_level_os_type = var.data_ingestion_access_level_os_type
required_access_levels = var.data_ingestion_required_access_levels
access_level_regions = var.data_ingestion_access_level_regions
egress_policies = local.data_ingestion_egress_policies
ingress_policies = local.data_ingestion_ingress_policies
# depends_on needed to prevent intermittent errors
# when the VPC-SC is created but perimeter member
# not yet propagated.
depends_on = [
time_sleep.forces_wait_propagation
]
}
# Adding project to an existing VPC Service Controls Perimeter
# instead of the default VPC Service Controls perimeter.
# The default VPC Service Controls perimeter and access list will not be created.
resource "google_access_context_manager_service_perimeter_resource" "ingestion_perimeter_resource" {
count = var.data_ingestion_perimeter != "" ? 1 : 0
perimeter_name = "accessPolicies/${local.actual_policy}/servicePerimeters/${var.data_ingestion_perimeter}"
resource = "projects/${data.google_project.data_ingestion_project.number}"
depends_on = [
time_sleep.forces_wait_propagation
]
}
resource "google_access_context_manager_service_perimeter_resource" "non_confidential_perimeter_resource" {
count = var.data_ingestion_perimeter != "" ? 1 : 0
perimeter_name = "accessPolicies/${local.actual_policy}/servicePerimeters/${var.data_ingestion_perimeter}"
resource = "projects/${data.google_project.non_confidential_data_project.number}"
depends_on = [
time_sleep.forces_wait_propagation
]
}
# Default VPC Service Controls perimeter and access list.
module "data_governance_vpc_sc" {
source = ".//modules/dwh-vpc-sc"
count = var.data_governance_perimeter == "" ? 1 : 0
access_context_manager_policy_id = local.actual_policy
common_name = "data_governance"
common_suffix = random_id.suffix.hex
resources = local.data_governance_vpc_sc_resources
perimeter_members = local.perimeter_members_governance
egress_policies = local.data_governance_egress_policies
ingress_policies = local.data_governance_ingress_policies
access_level_combining_function = var.data_governance_access_level_combining_function
access_level_ip_subnetworks = var.data_governance_access_level_ip_subnetworks
access_level_negate = var.data_governance_access_level_negate
access_level_require_screen_lock = var.data_governance_access_level_require_screen_lock
access_level_require_corp_owned = var.data_governance_access_level_require_corp_owned
access_level_allowed_encryption_statuses = var.data_governance_access_level_allowed_encryption_statuses
access_level_allowed_device_management_levels = var.data_governance_access_level_allowed_device_management_levels
access_level_minimum_version = var.data_governance_access_level_minimum_version
access_level_os_type = var.data_governance_access_level_os_type
required_access_levels = var.data_governance_required_access_levels
access_level_regions = var.data_governance_access_level_regions
# depends_on needed to prevent intermittent errors
# when the VPC-SC is created but perimeter member
# not yet propagated.
depends_on = [
time_sleep.forces_wait_propagation
]
}
# Adding project to an existing VPC Service Controls Perimeter
# instead of the default VPC Service Controls perimeter.
# The default VPC Service Controls perimeter and access list will not be created.
resource "google_access_context_manager_service_perimeter_resource" "governance_perimeter_resource" {
count = var.data_governance_perimeter != "" ? 1 : 0
perimeter_name = "accessPolicies/${local.actual_policy}/servicePerimeters/${var.data_governance_perimeter}"
resource = "projects/${data.google_project.governance_project.number}"
depends_on = [
time_sleep.forces_wait_propagation
]
}
# Default VPC Service Controls perimeter and access list.
module "confidential_data_vpc_sc" {
source = ".//modules/dwh-vpc-sc"
count = var.confidential_data_perimeter == "" ? 1 : 0
access_context_manager_policy_id = local.actual_policy
common_name = "confidential_data"
common_suffix = random_id.suffix.hex
resources = local.confidential_data_vpc_sc_resources
perimeter_members = local.perimeter_members_confidential
egress_policies = local.confidential_data_egress_policies
ingress_policies = local.confidential_data_ingress_policies
access_level_combining_function = var.confidential_data_access_level_combining_function
access_level_ip_subnetworks = var.confidential_data_access_level_ip_subnetworks
access_level_negate = var.confidential_data_access_level_negate
access_level_require_screen_lock = var.confidential_data_access_level_require_screen_lock
access_level_require_corp_owned = var.confidential_data_access_level_require_corp_owned
access_level_allowed_encryption_statuses = var.confidential_data_access_level_allowed_encryption_statuses
access_level_allowed_device_management_levels = var.confidential_data_access_level_allowed_device_management_levels
access_level_minimum_version = var.confidential_data_access_level_minimum_version
access_level_os_type = var.confidential_data_access_level_os_type
required_access_levels = var.confidential_data_required_access_levels
access_level_regions = var.confidential_data_access_level_regions
# depends_on needed to prevent intermittent errors
# when the VPC-SC is created but perimeter member
# not yet propagated.
depends_on = [
time_sleep.forces_wait_propagation
]
}
# Adding project to an existing VPC Service Controls Perimeter
# instead of the default VPC Service Controls perimeter.
# The default VPC Service Controls perimeter and access list will not be created.
resource "google_access_context_manager_service_perimeter_resource" "confidential_perimeter_resource" {
count = var.confidential_data_perimeter != "" ? 1 : 0
perimeter_name = "accessPolicies/${local.actual_policy}/servicePerimeters/${var.confidential_data_perimeter}"
resource = "projects/${data.google_project.confidential_project.number}"
depends_on = [
time_sleep.forces_wait_propagation
]
}
module "vpc_sc_bridge_data_ingestion_governance" {
source = "terraform-google-modules/vpc-service-controls/google//modules/bridge_service_perimeter"
version = "~> 5.1"
policy = local.actual_policy
perimeter_name = "vpc_sc_bridge_data_ingestion_governance_${random_id.suffix.hex}"
description = "VPC-SC bridge between data ingestion and data governance"
resources = [
data.google_project.data_ingestion_project.number,
data.google_project.governance_project.number,
data.google_project.non_confidential_data_project.number
]
resource_keys = [0, 1, 2]
depends_on = [
time_sleep.forces_wait_propagation,
module.data_governance_vpc_sc,
module.data_ingestion_vpc_sc,
google_access_context_manager_service_perimeter_resource.ingestion_perimeter_resource,
google_access_context_manager_service_perimeter_resource.governance_perimeter_resource,
google_access_context_manager_service_perimeter_resource.non_confidential_perimeter_resource,
]
}
module "vpc_sc_bridge_confidential_governance" {
source = "terraform-google-modules/vpc-service-controls/google//modules/bridge_service_perimeter"
version = "~> 5.1"
policy = local.actual_policy
perimeter_name = "vpc_sc_bridge_confidential_governance_${random_id.suffix.hex}"
description = "VPC-SC bridge between confidential data and data governance"
resources = [
data.google_project.confidential_project.number,
data.google_project.governance_project.number
]
resource_keys = [0, 1]
depends_on = [
time_sleep.forces_wait_propagation,
module.confidential_data_vpc_sc,
module.data_governance_vpc_sc,
google_access_context_manager_service_perimeter_resource.confidential_perimeter_resource,
google_access_context_manager_service_perimeter_resource.governance_perimeter_resource
]
}
module "vpc_sc_bridge_confidential_data_ingestion" {
source = "terraform-google-modules/vpc-service-controls/google//modules/bridge_service_perimeter"
version = "~> 5.1"
policy = local.actual_policy
perimeter_name = "vpc_sc_bridge_confidential_data_ingestion_${random_id.suffix.hex}"
description = "VPC-SC bridge between confidential data and data ingestion"
resources = [
data.google_project.confidential_project.number,
data.google_project.non_confidential_data_project.number
]
resource_keys = [0, 1]
depends_on = [
time_sleep.forces_wait_propagation,
module.confidential_data_vpc_sc,
module.data_ingestion_vpc_sc,
google_access_context_manager_service_perimeter_resource.confidential_perimeter_resource,
google_access_context_manager_service_perimeter_resource.non_confidential_perimeter_resource,
google_access_context_manager_service_perimeter_resource.ingestion_perimeter_resource
]
}
resource "time_sleep" "wait_for_bridge_propagation" {
create_duration = "240s"
depends_on = [
module.vpc_sc_bridge_confidential_data_ingestion,
module.vpc_sc_bridge_confidential_governance,
module.vpc_sc_bridge_data_ingestion_governance
]
}