diff --git a/fast/stages/0-bootstrap/automation.tf b/fast/stages/0-bootstrap/automation.tf index 9a46d5eb98..b6489ef04d 100644 --- a/fast/stages/0-bootstrap/automation.tf +++ b/fast/stages/0-bootstrap/automation.tf @@ -131,6 +131,7 @@ module "automation-project" { "bigqueryreservation.googleapis.com", "bigquerystorage.googleapis.com", "billingbudgets.googleapis.com", + "cloudasset.googleapis.com", "cloudbilling.googleapis.com", "cloudkms.googleapis.com", "cloudresourcemanager.googleapis.com", diff --git a/fast/stages/0-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf index 9249b57545..e263e54a57 100644 --- a/fast/stages/0-bootstrap/organization.tf +++ b/fast/stages/0-bootstrap/organization.tf @@ -176,6 +176,7 @@ module "organization" { "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])", join(",", formatlist("'%s'", [ "roles/accesscontextmanager.policyAdmin", + "roles/cloudasset.viewer", "roles/compute.orgFirewallPolicyAdmin", "roles/compute.xpnAdmin", "roles/orgpolicy.policyAdmin", diff --git a/fast/stages/1-resman/organization-iam.tf b/fast/stages/1-resman/organization-iam.tf index c5c17b909d..aed79b3dfa 100644 --- a/fast/stages/1-resman/organization-iam.tf +++ b/fast/stages/1-resman/organization-iam.tf @@ -28,6 +28,10 @@ locals { member = module.branch-network-sa.iam_email role = "roles/compute.xpnAdmin" } + sa_sec_asset_viewer = { + member = module.branch-security-sa.iam_email + role = "roles/cloudasset.viewer" + } sa_sec_vpcsc_admin = { member = module.branch-security-sa.iam_email role = "roles/accesscontextmanager.policyAdmin" diff --git a/fast/stages/2-security/README.md b/fast/stages/2-security/README.md index a3c3f55316..c7b1e7497d 100644 --- a/fast/stages/2-security/README.md +++ b/fast/stages/2-security/README.md @@ -1,19 +1,21 @@ -# Shared security resources +# Shared security resources and VPC Service Controls This stage sets up security resources and configurations which impact the whole organization, or are shared across the hierarchy to other projects and teams. -The design of this stage is fairly general, and provides a reference example for [Cloud KMS](https://cloud.google.com/security-key-management) and a [VPC Service Controls](https://cloud.google.com/vpc-service-controls) configuration that sets up three perimeters (landing, development, production), their related bridge perimeters, and provides variables to configure their resources, access levels, and directional policies. +The design of this stage is fairly general, providing -Expanding this stage to include other security-related services like Secret Manager, is fairly simple by using the provided implementation for Cloud KMS, and leveraging the broad permissions on the top-level Security folder of the automation service account used. +- a reference example for [Cloud KMS](https://cloud.google.com/security-key-management) +- a simplified implementation of [VPC Service Controls](https://cloud.google.com/vpc-service-controls) that should work for most users -The following diagram illustrates the high-level design of created resources and a schema of the VPC SC design, which can be adapted to specific requirements via variables: +Expanding this stage to include other security-related services like Secret Manager is fairly simple by adapting the provided implementation for Cloud KMS, and leveraging the broad permissions granted on the top-level Security folder to the automation service account used here. + +The following diagram illustrates the high-level design of created resources and a schema of the VPC SC design:

- Security diagram + Security diagram

-## Table of contents - + - [Design overview and choices](#design-overview-and-choices) - [Cloud KMS](#cloud-kms) - [VPC Service Controls](#vpc-service-controls) @@ -21,14 +23,16 @@ The following diagram illustrates the high-level design of created resources and - [Provider and Terraform variables](#provider-and-terraform-variables) - [Impersonating the automation service account](#impersonating-the-automation-service-account) - [Variable configuration](#variable-configuration) + - [Using delayed billing association for projects](#using-delayed-billing-association-for-projects) - [Running the stage](#running-the-stage) - [Customizations](#customizations) - [KMS keys](#kms-keys) - [VPC Service Controls configuration](#vpc-service-controls-configuration) - - [Dry-run vs. enforced](#dry-run-vs-enforced) - - [Access levels](#access-levels) - - [Ingress and Egress policies](#ingress-and-egress-policies) - - [Perimeters](#perimeters) +- [Notes](#notes) +- [Files](#files) +- [Variables](#variables) +- [Outputs](#outputs) + ## Design overview and choices @@ -36,7 +40,7 @@ Project-level security resources are grouped into two separate projects, one per Cloud KMS is configured and designed mainly to encrypt GCP resources with a [Customer-managed encryption key](https://cloud.google.com/kms/docs/cmek) but it may be used to create cryptokeys used to [encrypt application data](https://cloud.google.com/kms/docs/encrypting-application-data) too. -IAM for management-related operations is already assigned at the folder level to the security team by the previous stage, but more granularity can be added here at the project level, to grant control of separate services across environments to different actors. +IAM for day to day operations is already assigned at the folder level to the security team by the previous stage, but more granularity can be added here at the project level, to grant control of separate services across environments to different actors. ### Cloud KMS @@ -48,15 +52,9 @@ IAM roles on keys can be configured at the logical level for all locations where ### VPC Service Controls -This stage also provisions the VPC Service Controls configuration on demand for the whole organization, implementing the straightforward design illustrated above: - -- one perimeter for each environment -- one perimeter for centralized services and the landing VPC -- bridge perimeters to connect the landing perimeter to each environment - -The VPC SC configuration is set to dry-run mode, but switching to enforced mode is a simple operation involving modifying a few lines of code highlighted by ad-hoc comments. Variables are designed to enable easy centralized management of VPC Service Controls, including access levels and [ingress/egress rules](https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules) as described below. +This stage also provisions the VPC Service Controls configuration that protects the whole organization, implementing a simplified design that leverages a single perimeter and optionally provides automatic enrollment of projects in the perimeter. -Some care needs to be taken with project membership in perimeters, which can only be implemented here instead of being delegated (all or partially) to different stages, until the [Google Provider feature request](https://github.com/hashicorp/terraform-provider-google/issues/7270) allowing using project-level association for both enforced and dry-run modes is implemented. +The VPC SC configuration is controlled via the top-level `vpc_sc` variable, and is disabled by default unless `vpc_sc.perimeter_default` is populated. Access levels and ingress/egress policies can be defined in code via the respective `vpc_sc` variable attributes, or via YAML-based factories configured via the usual `factories_config` variable. ## How to run this stage @@ -103,7 +101,7 @@ The preconfigured provider file uses impersonation to run with this stage's auto Variables in this stage -- like most other FAST stages -- are broadly divided into three separate sets: - variables which refer to global values for the whole organization (org id, billing account id, prefix, etc.), which are pre-populated via the `0-globals.auto.tfvars.json` file linked or copied above -- variables which refer to resources managed by previous stage, which are prepopulated here via the `0-bootstrap.auto.tfvars.json` and `1-resman.auto.tfvars.json` files linked or copied above +- variables which refer to resources managed by previous stages, which are prepopulated here via the `0-bootstrap.auto.tfvars.json` and `1-resman.auto.tfvars.json` files linked or copied above - and finally variables that optionally control this stage's behaviour and customizations, and can to be set in a custom `terraform.tfvars` file The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. @@ -176,95 +174,41 @@ The script will create one keyring for each specified location and keys on each ### VPC Service Controls configuration -A set of variables allows configuring the VPC SC perimeters described above: +The `vpc_sc` variable controls VPC-SC configuration and project auto-discovery via Cloud Asset Inventory. VPC-SC configuration can also leverage YAML factories via the `factories_config` variable. Both variables mostly pass through to the underlying [`vpc-sc` module](../../../modules/vpc-sc/), which serves as a reference for their individual types. -- `vpc_sc_perimeter_projects` configures project membership in the three regular perimeters -- `vpc_sc_access_levels` configures access levels, which can then be associated to perimeters by key using the `vpc_sc_perimeter_access_levels` -- `vpc_sc_egress_policies` configures directional egress policies, which can then be associated to perimeters by key using the `vpc_sc_perimeter_egress_policies` -- `vpc_sc_ingress_policies` configures directional ingress policies, which can then be associated to perimeters by key using the `vpc_sc_perimeter_ingress_policies` +The `vpc_sc` variable has the following attributes: -This allows configuring VPC SC in a fairly flexible and concise way, without repeating similar definitions. Bridges perimeters configuration will be computed automatically to allow communication between regular perimeters: `landing <-> prod` and `landing <-> dev`. +- `access_levels`, `egress_policies`, `ingress_policies` define the corresponding objects, internally merged with any data coming from the YAML factories +- `perimeter_default` configures the single organization-wide perimeter by referencing access levels and policies by key, setting included projects, and allowing to turn on dry run mode +- `resource_discovery` controls automatic discovery of projects via Asset Inventory, and allows defining inclusion and exclusions lists -#### Dry-run vs. enforced +A few things to note on the default perimeter -The VPC SC configuration is set up by default in dry-run mode to allow easy experimentation, and detecting violations before enforcement. Once everything is set up correctly, switching to enforced mode needs to be done in code, by changing the `vpc_sc_explicit_dry_run_spec` local. +- writer identities for sinks defined in the bootstrap stage are passed through via output files, and automatically included in an ingress policy +- the perimeter is brought up in enforced mode by default +- project discovery is turned on by default and includes all projects in the organization -#### Access levels +The following example configures the default perimeter, with a single broad geo-based access level. Refer to the [vpc-sc module](../../../modules/vpc-sc/) for details on how to configure ingress/egress policies, and how to leverage the YAML factories. The perimeter is set to enforced mode and leverages auto discovery of projects. -Access levels are defined via the `vpc_sc_access_levels` variable, and referenced by key in perimeter definitions: +The following YAML file leverages factories to configure the broad geo-based access level (the factory path can be changed via the `factories_config` variable): -```tfvars -vpc_sc_access_levels = { - onprem = { - conditions = [{ - ip_subnetworks = ["101.101.101.0/24"] - }] - } -} +```yaml +# data/vpc-sc/access-levels/geo-default.yaml +conditions: + - regions: + - IT + - ES ``` -#### Ingress and Egress policies - -Ingress and egress policy are defined via the `vpc_sc_egress_policies` and `vpc_sc_ingress_policies`, and referenced by key in perimeter definitions: - ```tfvars -vpc_sc_egress_policies = { - iac-gcs = { - from = { - identities = [ - "serviceAccount:xxx-prod-resman-security-0@xxx-prod-iac-core-0.iam.gserviceaccount.com" - ] - } - to = { - operations = [{ - method_selectors = ["*"] - service_name = "storage.googleapis.com" - }] - resources = ["projects/123456782"] - } - } -} -vpc_sc_ingress_policies = { - iac = { - from = { - identities = [ - "serviceAccount:xxx-prod-resman-security-0@xxx-prod-iac-core-0.iam.gserviceaccount.com" - ] - access_levels = ["*"] - } - to = { - operations = [{ method_selectors = [], service_name = "*" }] - resources = ["*"] - } - } -} -``` - -#### Perimeters - -Regular perimeters are defined via the the `vpc_sc_perimeters` variable, and bridge perimeters are automatically populated from that variable. - -Support for independently adding projects to perimeters outside of this Terraform setup is pending resolution of [this Google Terraform Provider issue](https://github.com/hashicorp/terraform-provider-google/issues/7270), which implements support for dry-run mode in the additive resource. - -Access levels and egress/ingress policies are referenced in perimeters via keys. +# terraform.tfvars -```tfvars -vpc_sc_perimeters = { - dev = { - egress_policies = ["iac-gcs"] - ingress_policies = ["iac"] - resources = ["projects/1111111111"] - } - landing = { - access_levels = ["onprem"] - egress_policies = ["iac-gcs"] - ingress_policies = ["iac"] - resources = ["projects/2222222222"] - } - prod = { - egress_policies = ["iac-gcs"] - ingress_policies = ["iac"] - resources = ["projects/0000000000"] +vpc_sc = { + perimeter_default = { + access_levels = ["geo-default"] + # dry run is disabled by default + dry_run = true + # resource discovery is enabled by default } } ``` @@ -286,7 +230,7 @@ Some references that might be useful in setting up this stage: | [main.tf](./main.tf) | Module-level locals and resources. | folder | | | [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | | [variables.tf](./variables.tf) | Module variables. | | | -| [vpc-sc.tf](./vpc-sc.tf) | None | vpc-sc | | +| [vpc-sc.tf](./vpc-sc.tf) | None | projects-data-source · vpc-sc | | ## Variables @@ -294,23 +238,22 @@ Some references that might be useful in setting up this stage: |---|---|:---:|:---:|:---:|:---:| | [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | | [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | -| [folder_ids](variables.tf#L44) | Folder name => id mappings, the 'security' folder name must exist. | object({…}) | ✓ | | 1-resman | -| [organization](variables.tf#L89) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L105) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | -| [service_accounts](variables.tf#L116) | Automation service accounts that can assign the encrypt/decrypt roles on keys. | object({…}) | ✓ | | 1-resman | +| [folder_ids](variables.tf#L58) | Folder name => id mappings, the 'security' folder name must exist. | object({…}) | ✓ | | 1-resman | +| [organization](variables.tf#L115) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L131) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [service_accounts](variables.tf#L141) | Automation service accounts that can assign the encrypt/decrypt roles on keys. | object({…}) | ✓ | | 1-resman | | [essential_contacts](variables.tf#L38) | Email used for essential contacts, unset if null. | string | | null | | -| [kms_keys](variables.tf#L52) | KMS keys to create, keyed by name. | map(object({…})) | | {} | | -| [outputs_location](variables.tf#L99) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | -| [vpc_sc_access_levels](variables.tf#L127) | VPC SC access level definitions. | map(object({…})) | | {} | | -| [vpc_sc_egress_policies](variables.tf#L156) | VPC SC egress policy definitions. | map(object({…})) | | {} | | -| [vpc_sc_ingress_policies](variables.tf#L176) | VPC SC ingress policy definitions. | map(object({…})) | | {} | | -| [vpc_sc_perimeters](variables.tf#L197) | VPC SC regular perimeter definitions. | object({…}) | | {} | | +| [factories_config](variables.tf#L44) | Paths to folders that enable factory functionality. | object({…}) | | {} | | +| [kms_keys](variables.tf#L66) | KMS keys to create, keyed by name. | map(object({…})) | | {} | | +| [logging](variables.tf#L105) | Log writer identities for organization / folders. | object({…}) | | null | 0-bootstrap | +| [outputs_location](variables.tf#L125) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | +| [vpc_sc](variables.tf#L152) | VPC SC configuration. | object({…}) | | {} | | ## Outputs | name | description | sensitive | consumers | |---|---|:---:|---| -| [kms_keys](outputs.tf#L59) | KMS key ids. | | | -| [stage_perimeter_projects](outputs.tf#L64) | Security project numbers. They can be added to perimeter resources. | | | -| [tfvars](outputs.tf#L74) | Terraform variable files for the following stages. | ✓ | | +| [kms_keys](outputs.tf#L55) | KMS key ids. | | | +| [tfvars](outputs.tf#L60) | Terraform variable files for the following stages. | ✓ | | +| [vpc_sc_perimeter_default](outputs.tf#L66) | Raw default perimeter resource. | ✓ | | diff --git a/fast/stages/2-security/vpc-sc-restricted-services.yaml b/fast/stages/2-security/data/vpc-sc/restricted-services.yaml similarity index 100% rename from fast/stages/2-security/vpc-sc-restricted-services.yaml rename to fast/stages/2-security/data/vpc-sc/restricted-services.yaml diff --git a/fast/stages/2-security/diagram.png b/fast/stages/2-security/diagram.png index 779c9039ec..2c7170b780 100644 Binary files a/fast/stages/2-security/diagram.png and b/fast/stages/2-security/diagram.png differ diff --git a/fast/stages/2-security/diagram.svg b/fast/stages/2-security/diagram.svg deleted file mode 100644 index 7dc82d45d9..0000000000 --- a/fast/stages/2-security/diagram.svg +++ /dev/null @@ -1,1157 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/fast/stages/2-security/outputs.tf b/fast/stages/2-security/outputs.tf index ff0c13eda8..5e9235cbd8 100644 --- a/fast/stages/2-security/outputs.tf +++ b/fast/stages/2-security/outputs.tf @@ -39,8 +39,6 @@ locals { } } -# generate files for subsequent stages - resource "local_file" "tfvars" { for_each = var.outputs_location == null ? {} : { 1 = 1 } file_permission = "0644" @@ -54,25 +52,19 @@ resource "google_storage_bucket_object" "tfvars" { content = jsonencode(local.tfvars) } -# outputs - output "kms_keys" { description = "KMS key ids." value = local.output_kms_keys } -output "stage_perimeter_projects" { - description = "Security project numbers. They can be added to perimeter resources." - value = { - dev = ["projects/${module.dev-sec-project.number}"] - prod = ["projects/${module.prod-sec-project.number}"] - } -} - -# ready to use variable values for subsequent stages - output "tfvars" { description = "Terraform variable files for the following stages." sensitive = true value = local.tfvars } + +output "vpc_sc_perimeter_default" { + description = "Raw default perimeter resource." + sensitive = true + value = try(module.vpc-sc.0.service_perimeters_regular["default"], null) +} diff --git a/fast/stages/2-security/variables.tf b/fast/stages/2-security/variables.tf index f4e1c0fb98..d6c8d12bc1 100644 --- a/fast/stages/2-security/variables.tf +++ b/fast/stages/2-security/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,20 @@ variable "essential_contacts" { default = null } +variable "factories_config" { + description = "Paths to folders that enable factory functionality." + type = object({ + vpc_sc = optional(object({ + access_levels = optional(string, "data/vpc-sc/access-levels") + egress_policies = optional(string, "data/vpc-sc/egress-policies") + ingress_policies = optional(string, "data/vpc-sc/ingress-policies") + restricted_services = optional(string, "data/vpc-sc/restricted-services.yaml") + }), {}) + }) + nullable = false + default = {} +} + variable "folder_ids" { # tfdoc:variable:source 1-resman description = "Folder name => id mappings, the 'security' folder name must exist." @@ -52,9 +66,11 @@ variable "folder_ids" { variable "kms_keys" { description = "KMS keys to create, keyed by name." type = map(object({ - rotation_period = optional(string, "7776000s") - labels = optional(map(string)) - locations = optional(list(string), ["europe", "europe-west1", "europe-west3", "global"]) + rotation_period = optional(string, "7776000s") + labels = optional(map(string)) + locations = optional(list(string), [ + "europe", "europe-west1", "europe-west3", "global" + ]) purpose = optional(string, "ENCRYPT_DECRYPT") skip_initial_version_creation = optional(bool, false) version_template = optional(object({ @@ -86,6 +102,16 @@ variable "kms_keys" { nullable = false } +variable "logging" { + # tfdoc:variable:source 0-bootstrap + description = "Log writer identities for organization / folders." + type = object({ + project_number = string + writer_identities = map(string) + }) + default = null +} + variable "organization" { # tfdoc:variable:source 0-bootstrap description = "Organization details." @@ -106,7 +132,6 @@ variable "prefix" { # tfdoc:variable:source 0-bootstrap description = "Prefix used for resources that need unique names. Use 9 characters or less." type = string - validation { condition = try(length(var.prefix), 0) < 10 error_message = "Use a maximum of 9 characters for prefix." @@ -124,96 +149,24 @@ variable "service_accounts" { }) } -variable "vpc_sc_access_levels" { - description = "VPC SC access level definitions." - type = map(object({ - combining_function = optional(string) - conditions = optional(list(object({ - device_policy = optional(object({ - allowed_device_management_levels = optional(list(string)) - allowed_encryption_statuses = optional(list(string)) - require_admin_approval = bool - require_corp_owned = bool - require_screen_lock = optional(bool) - os_constraints = optional(list(object({ - os_type = string - minimum_version = optional(string) - require_verified_chrome_os = optional(bool) - }))) - })) - ip_subnetworks = optional(list(string), []) - members = optional(list(string), []) - negate = optional(bool) - regions = optional(list(string), []) - required_access_levels = optional(list(string), []) - })), []) - description = optional(string) - })) - default = {} - nullable = false -} - -variable "vpc_sc_egress_policies" { - description = "VPC SC egress policy definitions." - type = map(object({ - from = object({ - identity_type = optional(string, "ANY_IDENTITY") - identities = optional(list(string)) - }) - to = object({ - operations = optional(list(object({ - method_selectors = optional(list(string)) - service_name = string - })), []) - resources = optional(list(string)) - resource_type_external = optional(bool, false) - }) - })) - default = {} - nullable = false -} - -variable "vpc_sc_ingress_policies" { - description = "VPC SC ingress policy definitions." - type = map(object({ - from = object({ - access_levels = optional(list(string), []) - identity_type = optional(string) - identities = optional(list(string)) - resources = optional(list(string), []) - }) - to = object({ - operations = optional(list(object({ - method_selectors = optional(list(string)) - service_name = string - })), []) - resources = optional(list(string)) - }) - })) - default = {} - nullable = false -} - -variable "vpc_sc_perimeters" { - description = "VPC SC regular perimeter definitions." +variable "vpc_sc" { + description = "VPC SC configuration." type = object({ - dev = optional(object({ - access_levels = optional(list(string), []) - egress_policies = optional(list(string), []) - ingress_policies = optional(list(string), []) - resources = optional(list(string), []) - }), {}) - landing = optional(object({ - access_levels = optional(list(string), []) - egress_policies = optional(list(string), []) - ingress_policies = optional(list(string), []) - resources = optional(list(string), []) - }), {}) - prod = optional(object({ + access_levels = optional(map(any), {}) + egress_policies = optional(map(any), {}) + ingress_policies = optional(map(any), {}) + perimeter_default = optional(object({ access_levels = optional(list(string), []) + dry_run = optional(bool, false) egress_policies = optional(list(string), []) ingress_policies = optional(list(string), []) resources = optional(list(string), []) + })) + resource_discovery = optional(object({ + enabled = optional(bool, true) + ignore_folders = optional(list(string), []) + ignore_projects = optional(list(string), []) + include_projects = optional(list(string), []) }), {}) }) default = {} diff --git a/fast/stages/2-security/vpc-sc.tf b/fast/stages/2-security/vpc-sc.tf index 953badf15a..4de5277b37 100644 --- a/fast/stages/2-security/vpc-sc.tf +++ b/fast/stages/2-security/vpc-sc.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,139 +15,77 @@ */ locals { - _vpc_sc_vpc_accessible_services = null - _vpc_sc_restricted_services = yamldecode( - file("${path.module}/vpc-sc-restricted-services.yaml") - ) - # compute the number of projects in each perimeter to detect which to create - vpc_sc_counts = { - for k, v in var.vpc_sc_perimeters : k => length(v.resources) - } - # define dry run spec at file level for convenience - vpc_sc_explicit_dry_run_spec = true - # compute perimeter bridge resources (projects) - vpc_sc_bridge_resources = { - landing_to_dev = concat( - var.vpc_sc_perimeters.landing.resources, - var.vpc_sc_perimeters.dev.resources - ) - landing_to_prod = concat( - var.vpc_sc_perimeters.landing.resources, - var.vpc_sc_perimeters.prod.resources - ) + vpc_sc_ingress_policies = var.logging == null ? {} : { + fast-org-log-sinks = { + from = { + access_levels = ["*"] + identities = values(var.logging.writer_identities) + } + to = { + operations = [{ service_name = "*" }] + resources = ["projects/${var.logging.project_number}"] + } + } } - # compute spec/status for each perimeter - vpc_sc_perimeters_spec_status = { - dev = merge(var.vpc_sc_perimeters.dev, { - restricted_services = local._vpc_sc_restricted_services - vpc_accessible_services = local._vpc_sc_vpc_accessible_services - }) - landing = merge(var.vpc_sc_perimeters.landing, { - restricted_services = local._vpc_sc_restricted_services - vpc_accessible_services = local._vpc_sc_vpc_accessible_services + vpc_sc_perimeter = ( + var.vpc_sc.perimeter_default == null + ? null + : merge(var.vpc_sc.perimeter_default, { + ingress_policies = concat( + var.vpc_sc.perimeter_default.ingress_policies, + ["fast-org-log-sinks"] + ) + restricted_services = yamldecode(file( + var.factories_config.vpc_sc.restricted_services + )) + resources = distinct(concat( + var.vpc_sc.perimeter_default.resources, + var.vpc_sc.resource_discovery.enabled != true ? [] : [ + for v in module.vpc-sc-discovery.0.project_numbers : + "projects/${v}" + ] + )) }) - prod = merge(var.vpc_sc_perimeters.prod, { - restricted_services = local._vpc_sc_restricted_services - vpc_accessible_services = local._vpc_sc_vpc_accessible_services - }) - } + ) +} + +module "vpc-sc-discovery" { + source = "../../../modules/projects-data-source" + count = var.vpc_sc.resource_discovery.enabled == true ? 1 : 0 + parent = "organizations/${var.organization.id}" + ignore_folders = var.vpc_sc.resource_discovery.ignore_folders + ignore_projects = var.vpc_sc.resource_discovery.ignore_projects + include_projects = var.vpc_sc.resource_discovery.include_projects } +# TODO(ludomagno): allow passing in restricted services via variable and factory file +# TODO(ludomagno): implement vpc accessible services via variable or factory file + module "vpc-sc" { source = "../../../modules/vpc-sc" - # only enable if we have projects defined for perimeters - count = anytrue([for k, v in local.vpc_sc_counts : v > 0]) ? 1 : 0 + # only enable if the default perimeter is defined + count = var.vpc_sc.perimeter_default == null ? 0 : 1 access_policy = null access_policy_create = { parent = "organizations/${var.organization.id}" title = "default" } - access_levels = var.vpc_sc_access_levels - egress_policies = var.vpc_sc_egress_policies - ingress_policies = var.vpc_sc_ingress_policies - service_perimeters_bridge = merge( - # landing to dev, only we have projects in landing and dev perimeters - local.vpc_sc_counts.landing * local.vpc_sc_counts.dev == 0 ? {} : { - landing_to_dev = { - spec_resources = ( - local.vpc_sc_explicit_dry_run_spec - ? local.vpc_sc_bridge_resources.landing_to_dev - : null - ) - status_resources = ( - local.vpc_sc_explicit_dry_run_spec - ? null - : local.vpc_sc_bridge_resources.landing_to_dev - ) - use_explicit_dry_run_spec = local.vpc_sc_explicit_dry_run_spec - } - }, - # landing to prod, only we have projects in landing and prod perimeters - local.vpc_sc_counts.landing * local.vpc_sc_counts.prod == 0 ? {} : { - landing_to_prod = { - spec_resources = ( - local.vpc_sc_explicit_dry_run_spec - ? local.vpc_sc_bridge_resources.landing_to_prod - : null - ) - status_resources = ( - local.vpc_sc_explicit_dry_run_spec - ? null - : local.vpc_sc_bridge_resources.landing_to_prod - ) - use_explicit_dry_run_spec = local.vpc_sc_explicit_dry_run_spec - } - } - ) - # regular type perimeters - service_perimeters_regular = merge( - # dev if we have projects in var.vpc_sc_perimeter_projects.dev - local.vpc_sc_counts.dev == 0 ? {} : { - dev = { - spec = ( - local.vpc_sc_explicit_dry_run_spec - ? local.vpc_sc_perimeters_spec_status.dev - : null - ) - status = ( - local.vpc_sc_explicit_dry_run_spec - ? null - : local.vpc_sc_perimeters_spec_status.dev - ) - use_explicit_dry_run_spec = local.vpc_sc_explicit_dry_run_spec - } - }, - # landing if we have projects in var.vpc_sc_perimeter_projects.landing - local.vpc_sc_counts.landing == 0 ? {} : { - landing = { - spec = ( - local.vpc_sc_explicit_dry_run_spec - ? local.vpc_sc_perimeters_spec_status.landing - : null - ) - status = ( - local.vpc_sc_explicit_dry_run_spec - ? null - : local.vpc_sc_perimeters_spec_status.landing - ) - use_explicit_dry_run_spec = local.vpc_sc_explicit_dry_run_spec - } - }, - # prod if we have projects in var.vpc_sc_perimeter_projects.prod - local.vpc_sc_counts.prod == 0 ? {} : { - prod = { - spec = ( - local.vpc_sc_explicit_dry_run_spec - ? local.vpc_sc_perimeters_spec_status.prod - : null - ) - status = ( - local.vpc_sc_explicit_dry_run_spec - ? null - : local.vpc_sc_perimeters_spec_status.prod - ) - use_explicit_dry_run_spec = local.vpc_sc_explicit_dry_run_spec - } - }, + access_levels = var.vpc_sc.access_levels + egress_policies = var.vpc_sc.egress_policies + ingress_policies = merge( + var.vpc_sc.ingress_policies, + local.vpc_sc_ingress_policies ) + factories_config = var.factories_config.vpc_sc + service_perimeters_regular = { + default = { + spec = ( + var.vpc_sc.perimeter_default.dry_run ? local.vpc_sc_perimeter : null + ) + status = ( + !var.vpc_sc.perimeter_default.dry_run ? local.vpc_sc_perimeter : null + ) + use_explicit_dry_run_spec = var.vpc_sc.perimeter_default.dry_run + } + } } diff --git a/tests/fast/stages/s0_bootstrap/checklist.yaml b/tests/fast/stages/s0_bootstrap/checklist.yaml index 466efd2fc5..8e4022c30e 100644 --- a/tests/fast/stages/s0_bootstrap/checklist.yaml +++ b/tests/fast/stages/s0_bootstrap/checklist.yaml @@ -194,7 +194,7 @@ values: module.organization.google_organization_iam_binding.bindings["organization_iam_admin_conditional"]: condition: - description: Automation service account delegated grants. - expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/accesscontextmanager.policyAdmin','roles/compute.orgFirewallPolicyAdmin','roles/compute.xpnAdmin','roles/orgpolicy.policyAdmin','roles/orgpolicy.policyViewer','roles/resourcemanager.organizationViewer','organizations/123456789012/roles/tenantNetworkAdmin']) + expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/accesscontextmanager.policyAdmin','roles/cloudasset.viewer','roles/compute.orgFirewallPolicyAdmin','roles/compute.xpnAdmin','roles/orgpolicy.policyAdmin','roles/orgpolicy.policyViewer','roles/resourcemanager.organizationViewer','organizations/123456789012/roles/tenantNetworkAdmin']) title: automation_sa_delegated_grants members: - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com @@ -369,8 +369,8 @@ counts: google_project: 3 google_project_iam_binding: 19 google_project_iam_member: 6 - google_project_service: 29 - google_project_service_identity: 3 + google_project_service: 30 + google_project_service_identity: 4 google_service_account: 4 google_service_account_iam_binding: 2 google_storage_bucket: 4 @@ -381,4 +381,4 @@ counts: google_tags_tag_key: 1 google_tags_tag_value: 1 modules: 17 - resources: 193 + resources: 195 diff --git a/tests/fast/stages/s0_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml index ab916dd50c..3851a07618 100644 --- a/tests/fast/stages/s0_bootstrap/simple.yaml +++ b/tests/fast/stages/s0_bootstrap/simple.yaml @@ -48,8 +48,8 @@ counts: google_project: 3 google_project_iam_binding: 19 google_project_iam_member: 6 - google_project_service: 29 - google_project_service_identity: 3 + google_project_service: 30 + google_project_service_identity: 4 google_service_account: 4 google_service_account_iam_binding: 2 google_storage_bucket: 3 @@ -61,7 +61,7 @@ counts: google_tags_tag_value: 1 local_file: 7 modules: 16 - resources: 184 + resources: 186 outputs: custom_roles: diff --git a/tests/fast/stages/s1_resman/checklist.yaml b/tests/fast/stages/s1_resman/checklist.yaml index a5f4d96449..fc14f66677 100644 --- a/tests/fast/stages/s1_resman/checklist.yaml +++ b/tests/fast/stages/s1_resman/checklist.yaml @@ -417,7 +417,7 @@ values: counts: google_folder: 57 google_folder_iam_binding: 69 - google_organization_iam_member: 5 + google_organization_iam_member: 6 google_project_iam_member: 4 google_service_account: 4 google_service_account_iam_binding: 4 @@ -429,4 +429,4 @@ counts: google_tags_tag_key: 3 google_tags_tag_value: 10 modules: 64 - resources: 176 + resources: 177 diff --git a/tests/fast/stages/s1_resman/simple.yaml b/tests/fast/stages/s1_resman/simple.yaml index 0f2aa097be..63f38a96ec 100644 --- a/tests/fast/stages/s1_resman/simple.yaml +++ b/tests/fast/stages/s1_resman/simple.yaml @@ -15,7 +15,7 @@ counts: google_folder: 5 google_folder_iam_binding: 25 - google_organization_iam_member: 5 + google_organization_iam_member: 6 google_project_iam_member: 4 google_service_account: 4 google_service_account_iam_binding: 4 @@ -27,4 +27,4 @@ counts: google_tags_tag_key: 3 google_tags_tag_value: 10 modules: 12 - resources: 80 + resources: 81 diff --git a/tests/fast/stages/s2_security/common.tfvars b/tests/fast/stages/s2_security/common.tfvars index 2b22698613..65558e1f9e 100644 --- a/tests/fast/stages/s2_security/common.tfvars +++ b/tests/fast/stages/s2_security/common.tfvars @@ -31,58 +31,24 @@ service_accounts = { project-factory-dev = "foobar@iam.gserviceaccount.com" project-factory-prod = "foobar@iam.gserviceaccount.com" } -vpc_sc_access_levels = { - onprem = { - conditions = [{ - ip_subnetworks = ["101.101.101.0/24"] - }] +factories_config = { + vpc-sc = { + access_levels = "../../../../tests/fast/stages/s2_security/data/vpc-sc/access-levels" + egress_policies = "../../../../tests/fast/stages/s2_security/data/vpc-sc/egress-policies" + ingress_policies = "../../../../tests/fast/stages/s2_security/data/vpc-sc/ingress-policies" } } -vpc_sc_egress_policies = { - iac-gcs = { - from = { - identities = [ - "serviceAccount:xxx-prod-resman-security-0@xxx-prod-iac-core-0.iam.gserviceaccount.com" - ] - } - to = { - operations = [{ - method_selectors = ["*"] - service_name = "storage.googleapis.com" - }] - resources = ["projects/123456782"] - } - } -} -vpc_sc_ingress_policies = { - iac = { - from = { - identities = [ - "serviceAccount:xxx-prod-resman-security-0@xxx-prod-iac-core-0.iam.gserviceaccount.com" - ] - access_levels = ["*"] - } - to = { - operations = [{ method_selectors = [], service_name = "*" }] - resources = ["*"] - } - } -} -vpc_sc_perimeters = { - dev = { - egress_policies = ["iac-gcs"] - ingress_policies = ["iac"] - resources = ["projects/1111111111"] - } - dev = { - egress_policies = ["iac-gcs"] - ingress_policies = ["iac"] - resources = ["projects/0000000000"] +vpc_sc = { + perimeter_default = { + access_levels = ["geo_it", "identity_me"] + egress_policies = ["test"] + ingress_policies = ["test"] + dry_run = true + resources = [ + "projects/1234567890" + ] } - dev = { - access_levels = ["onprem"] - egress_policies = ["iac-gcs"] - ingress_policies = ["iac"] - resources = ["projects/2222222222"] + resource_discovery = { + enabled = false } } diff --git a/tests/fast/stages/s2_security/data/vpc-sc/access-levels/geo_it.yaml b/tests/fast/stages/s2_security/data/vpc-sc/access-levels/geo_it.yaml new file mode 100644 index 0000000000..6b6d3e0e2a --- /dev/null +++ b/tests/fast/stages/s2_security/data/vpc-sc/access-levels/geo_it.yaml @@ -0,0 +1,17 @@ +# Copyright 2024 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. + +conditions: + - regions: + - IT \ No newline at end of file diff --git a/tests/fast/stages/s2_security/data/vpc-sc/access-levels/identity_me.yaml b/tests/fast/stages/s2_security/data/vpc-sc/access-levels/identity_me.yaml new file mode 100644 index 0000000000..618aeb8f85 --- /dev/null +++ b/tests/fast/stages/s2_security/data/vpc-sc/access-levels/identity_me.yaml @@ -0,0 +1,17 @@ +# Copyright 2024 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. + +conditions: + - members: + - user:user@fast.example.com \ No newline at end of file diff --git a/tests/fast/stages/s2_security/data/vpc-sc/egress-policies/test.yaml b/tests/fast/stages/s2_security/data/vpc-sc/egress-policies/test.yaml new file mode 100644 index 0000000000..86a7a3b91a --- /dev/null +++ b/tests/fast/stages/s2_security/data/vpc-sc/egress-policies/test.yaml @@ -0,0 +1,27 @@ +# Copyright 2024 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. + +from: + identities: + - user:user@fast.example.com +to: + operations: + - service_name: compute.googleapis.com + method_selectors: + - "*" + - service_name: monitoring.googleapis.com + method_selectors: + - "*" + resources: + - "*" \ No newline at end of file diff --git a/tests/fast/stages/s2_security/data/vpc-sc/ingress-policies/test.yaml b/tests/fast/stages/s2_security/data/vpc-sc/ingress-policies/test.yaml new file mode 100644 index 0000000000..a96bfadfaf --- /dev/null +++ b/tests/fast/stages/s2_security/data/vpc-sc/ingress-policies/test.yaml @@ -0,0 +1,29 @@ +# Copyright 2024 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. + +from: + access_levels: + - "*" + identities: + - user:user@fast.example.com +to: + operations: + - service_name: compute.googleapis.com + method_selectors: + - "*" + - service_name: monitoring.googleapis.com + method_selectors: + - "*" + resources: + - "*" \ No newline at end of file