From 932fd82fe23500ee7681797b2c260d307073871f Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Tue, 18 Nov 2025 14:32:06 +0100 Subject: [PATCH] Drop the 2-secops stage and minimally refactor 3-secops-dev (#3537) * drop 2-secops and minimally refactor 3-secops * remove stage 2 tests * tfdoc --- fast/stages/2-secops/.fast-stage.env | 4 - fast/stages/2-secops/README.md | 205 ------------------ fast/stages/2-secops/diagram.png | Bin 44855 -> 0 bytes fast/stages/2-secops/fast_version.txt | 15 -- .../2-secops/identity-providers-defs.tf | 42 ---- fast/stages/2-secops/identity-providers.tf | 48 ---- fast/stages/2-secops/main.tf | 81 ------- fast/stages/2-secops/moved/.gitkeep | 0 fast/stages/2-secops/outputs.tf | 62 ------ fast/stages/2-secops/variables-fast.tf | 115 ---------- fast/stages/2-secops/variables.tf | 43 ---- fast/stages/3-secops-dev/README.md | 48 ++-- fast/stages/3-secops-dev/main.tf | 70 ++++-- fast/stages/3-secops-dev/variables-fast.tf | 46 +++- fast/stages/3-secops-dev/variables.tf | 31 ++- fast/stages/3-secops-dev/workspace.tf | 41 ++-- tests/fast/stages/s2_secops/simple.tfvars | 32 --- tests/fast/stages/s2_secops/simple.yaml | 34 --- tests/fast/stages/s2_secops/tftest.yaml | 18 -- tests/fast/stages/s3_secops_dev/simple.tfvars | 7 +- tests/fast/stages/s3_secops_dev/simple.yaml | 2 +- 21 files changed, 165 insertions(+), 779 deletions(-) delete mode 100644 fast/stages/2-secops/.fast-stage.env delete mode 100644 fast/stages/2-secops/README.md delete mode 100644 fast/stages/2-secops/diagram.png delete mode 100644 fast/stages/2-secops/fast_version.txt delete mode 100644 fast/stages/2-secops/identity-providers-defs.tf delete mode 100644 fast/stages/2-secops/identity-providers.tf delete mode 100644 fast/stages/2-secops/main.tf delete mode 100644 fast/stages/2-secops/moved/.gitkeep delete mode 100644 fast/stages/2-secops/outputs.tf delete mode 100644 fast/stages/2-secops/variables-fast.tf delete mode 100644 fast/stages/2-secops/variables.tf delete mode 100644 tests/fast/stages/s2_secops/simple.tfvars delete mode 100644 tests/fast/stages/s2_secops/simple.yaml delete mode 100644 tests/fast/stages/s2_secops/tftest.yaml diff --git a/fast/stages/2-secops/.fast-stage.env b/fast/stages/2-secops/.fast-stage.env deleted file mode 100644 index 57d50b2e5..000000000 --- a/fast/stages/2-secops/.fast-stage.env +++ /dev/null @@ -1,4 +0,0 @@ -FAST_STAGE_DESCRIPTION="secops" -FAST_STAGE_LEVEL=2 -FAST_STAGE_NAME=secops -FAST_STAGE_DEPS="0-globals 0-org-setup 1-resman" diff --git a/fast/stages/2-secops/README.md b/fast/stages/2-secops/README.md deleted file mode 100644 index fa0d61de3..000000000 --- a/fast/stages/2-secops/README.md +++ /dev/null @@ -1,205 +0,0 @@ -# SecOps Stage - -This stage sets up an area dedicated to hosting SecOps projects in the Google Cloud organization. - -The design of this stage is fairly simple, as it is only responsible for creating GCP projects that will be linked to SecOps instances as per the [following documentation](https://cloud.google.com/chronicle/docs/onboard/configure-cloud-project). - -After creating the projects please refer to your Google Cloud Security representative for instructions on how to bind your Google SecOps instance to the Google Cloud project/s created in this stage. - -The following diagram illustrates the high-level design of resources managed here: - -

- Security diagram -

- - -- [Design overview and choices](#design-overview-and-choices) - - [Workforce Identity Federation](#workforce-identity-federation) -- [How to run this stage](#how-to-run-this-stage) - - [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) - - [Workforce Identity Federation](#workforce-identity-federation) -- [Files](#files) -- [Variables](#variables) -- [Outputs](#outputs) - - -## Design overview and choices - -This stage will deploy 1 SecOps project for each environment available from the 0-globals input variables, of course such a behaviour might be updated to either deploy a single production instance or different number of environments with respect to the foundations ones. - -IAM for day to day operations is already assigned at the folder level to the secops 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 as well as in the later 3-secops-dev/prod stages. - -### Workforce Identity Federation - -This stage supports configuration of [Workforce Identity Federation](https://cloud.google.com/iam/docs/workforce-identity-federation) which lets an external identity provider (IdP) to authenticate and authorize a group of users (usually employees) using IAM, so that the users can access Google Cloud services. - -The following example shows an example on how to define a Workforce Identity pool for the organization. - -```hcl -# stage 2 secops wif tfvars -workforce_identity_providers = { - test = { - issuer = "azuread" - display_name = "wif-provider" - description = "Workforce Identity pool" - saml = { - idp_metadata_xml = "..." - } - } -} -# tftest skip -``` - -## How to run this stage - -This stage is meant to be executed after the [bootstrap](../0-org-setup) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured there. - -It's of course possible to run this stage in isolation, but that's outside the scope of this document, and you would need to refer to the code for the previous stages for the environmental requirements. - -Before running this stage, you need to make sure you have the correct credentials and permissions, and localize variables by assigning values that match your configuration. - -### Provider and Terraform variables - -As all other FAST stages, the [mechanism used to pass variable values and pre-built provider files from one stage to the next](../0-org-setup/README.md#output-files-and-cross-stage-variables) is also leveraged here. - -The commands to link or copy the provider and terraform variable files can be easily derived from the `fast-links.sh` script in the FAST stages folder, passing it a single argument with the local output files folder (if configured) or the GCS output bucket in the automation project (derived from stage 0 outputs). The following examples demonstrate both cases, and the resulting commands that then need to be copy/pasted and run. - -```bash -../fast-links.sh ~/fast-config - -# File linking commands for security stage - -# provider file -ln -s ~/fast-config/fast-test-00/providers/2-secops-providers.tf ./ - -# input files from other stages -ln -s ~/fast-config/fast-test-00/tfvars/0-globals.auto.tfvars.json ./ -ln -s ~/fast-config/fast-test-00/tfvars/0-org-setup.auto.tfvars.json ./ -ln -s ~/fast-config/fast-test-00/tfvars/1-resman.auto.tfvars.json ./ - -# conventional place for stage tfvars (manually created) -ln -s ~/fast-config/fast-test-00/2-secops.auto.tfvars ./ -``` - -```bash -../fast-links.sh gs://xxx-prod-iac-core-outputs-0 - -# File linking commands for security stage - -# provider file -gcloud storage cp gs://xxx-prod-iac-core-outputs-0/providers/2-secops-providers.tf ./ - -# input files from other stages -gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/0-globals.auto.tfvars.json ./ -gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/0-org-setup.auto.tfvars.json ./ -gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json ./ - -# conventional place for stage tfvars (manually created) -gcloud storage cp gs://xxx-prod-iac-core-outputs-0/2-secops.auto.tfvars ./ -``` - -### Impersonating the automation service account - -The preconfigured provider file uses impersonation to run with this stage's automation service account's credentials. The `gcp-devops` and `organization-admins` groups have the necessary IAM bindings in place to do that, so make sure the current user is a member of one of those groups. - -### Variable configuration - -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 stages, which are prepopulated here via the `0-org-setup.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. - -Note that the `outputs_location` variable is disabled by default, you need to explicitly set it in your `terraform.tfvars` file if you want output files to be generated by this stage. This is a sample `terraform.tfvars` that configures it, refer to the [bootstrap stage documentation](../0-org-setup/README.md#output-files-and-cross-stage-variables) for more details: - -```tfvars -outputs_location = "~/fast-config" -``` - -### Using delayed billing association for projects - -This configuration is possible but unsupported and only exists for development purposes, use at your own risk: - -- temporarily switch `billing_account.id` to `null` in `0-globals.auto.tfvars.json` -- for each project resources in the project modules used in this stage (`dev`, `prod`) - - apply using `-target`, for example - `terraform apply -target 'module.project["dev"].google_project.project[0]'` - - untaint the project resource after applying, for example - `terraform untaint 'module.project["dev"].google_project.project[0]'` -- go through the process to associate the billing account with the two projects -- switch `billing_account.id` back to the real billing account id -- resume applying normally - -### Running the stage - -Once provider and variable values are in place and the correct user is configured, the stage can be run: - -```bash -terraform init -terraform apply -``` - -## Customizations - -### Workforce Identity Federation - -This is a minimal configuration that creates a Workforce Identity pool at organization level. - -```tfvars -workforce_identity_providers = { - test = { - issuer = "azuread" - display_name = "wif-provider" - description = "Workforce Identity pool" - saml = { - idp_metadata_xml = "..." - } - } -} -``` - - - -## Files - -| name | description | modules | resources | -|---|---|---|---| -| [identity-providers-defs.tf](./identity-providers-defs.tf) | Workforce Identity provider definitions. | | | -| [identity-providers.tf](./identity-providers.tf) | Workforce Identity Federation provider definitions. | | google_iam_workforce_pool · google_iam_workforce_pool_provider | -| [main.tf](./main.tf) | Module-level locals and resources. | folder · project | | -| [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | -| [variables-fast.tf](./variables-fast.tf) | None | | | -| [variables.tf](./variables.tf) | Module variables. | | | - -## Variables - -| name | description | type | required | default | producer | -|---|---|:---:|:---:|:---:|:---:| -| [automation](variables-fast.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-org-setup | -| [billing_account](variables-fast.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-org-setup | -| [environments](variables-fast.tf#L47) | Environment names. | map(object({…})) | ✓ | | 0-globals | -| [folder_ids](variables-fast.tf#L65) | Folder name => id mappings, the 'security' folder name must exist. | object({…}) | ✓ | | 1-resman | -| [organization](variables-fast.tf#L75) | Organization details. | object({…}) | ✓ | | 0-org-setup | -| [prefix](variables-fast.tf#L86) | Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants. | string | ✓ | | 0-org-setup | -| [custom_roles](variables-fast.tf#L38) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-org-setup | -| [essential_contacts](variables.tf#L17) | Email used for essential contacts, unset if null. | string | | null | | -| [outputs_location](variables.tf#L23) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | | -| [stage_config](variables-fast.tf#L96) | FAST stage configuration. | object({…}) | | {} | 1-resman | -| [tag_values](variables-fast.tf#L110) | Root-level tag values. | map(string) | | {} | 1-resman | -| [workforce_identity_providers](variables.tf#L29) | Workforce Identity Federation pools. | map(object({…})) | | {} | | - -## Outputs - -| name | description | sensitive | consumers | -|---|---|:---:|---| -| [federated_identity_pool](outputs.tf#L48) | Workforce Identity Federation pool. | | | -| [secops_project_ids](outputs.tf#L53) | SecOps project IDs. | | | -| [tfvars](outputs.tf#L58) | Terraform variable files for the following stages. | ✓ | | - diff --git a/fast/stages/2-secops/diagram.png b/fast/stages/2-secops/diagram.png deleted file mode 100644 index 8eb9fd7de4da6bc3f5da77c22c5b5676c2f049b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44855 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 YSC0F8tX9v*|6cfx@x5E+H{DSG1MDEiq5uE@ diff --git a/fast/stages/2-secops/fast_version.txt b/fast/stages/2-secops/fast_version.txt deleted file mode 100644 index c7701e1d1..000000000 --- a/fast/stages/2-secops/fast_version.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2025 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 -# -# https://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. - -# FAST release: v48.0.0 \ No newline at end of file diff --git a/fast/stages/2-secops/identity-providers-defs.tf b/fast/stages/2-secops/identity-providers-defs.tf deleted file mode 100644 index f4d6b3029..000000000 --- a/fast/stages/2-secops/identity-providers-defs.tf +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2025 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. - */ - -# tfdoc:file:description Workforce Identity provider definitions. - -locals { - workforce_identity_providers_defs = { - azuread = { - attribute_mapping = { - "google.subject" = "assertion.subject" - "google.display_name" = "assertion.attributes.userprincipalname[0]" - "google.groups" = "assertion.attributes.groups" - "attribute.first_name" = "assertion.attributes.givenname[0]" - "attribute.last_name" = "assertion.attributes.surname[0]" - "attribute.user_email" = "assertion.attributes.mail[0]" - } - } - okta = { - attribute_mapping = { - "google.subject" = "assertion.subject" - "google.display_name" = "assertion.subject" - "google.groups" = "assertion.attributes.groups" - "attribute.first_name" = "assertion.attributes.firstName[0]" - "attribute.last_name" = "assertion.attributes.lastName[0]" - "attribute.user_email" = "assertion.attributes.email[0]" - } - } - } -} diff --git a/fast/stages/2-secops/identity-providers.tf b/fast/stages/2-secops/identity-providers.tf deleted file mode 100644 index 6c22d6349..000000000 --- a/fast/stages/2-secops/identity-providers.tf +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2025 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. - */ - -# tfdoc:file:description Workforce Identity Federation provider definitions. - -locals { - workforce_identity_providers = { - for k, v in var.workforce_identity_providers : k => merge( - v, - lookup(local.workforce_identity_providers_defs, v.issuer, {}) - ) - } -} - -resource "google_iam_workforce_pool" "default" { - count = length(local.workforce_identity_providers) > 0 ? 1 : 0 - parent = "organizations/${var.organization.id}" - location = "global" - workforce_pool_id = "secops" -} - -resource "google_iam_workforce_pool_provider" "default" { - for_each = local.workforce_identity_providers - attribute_condition = each.value.attribute_condition - attribute_mapping = each.value.attribute_mapping - description = each.value.description - disabled = each.value.disabled - display_name = each.value.display_name - location = google_iam_workforce_pool.default[0].location - provider_id = each.key - workforce_pool_id = google_iam_workforce_pool.default[0].workforce_pool_id - saml { - idp_metadata_xml = each.value.saml.idp_metadata_xml - } -} diff --git a/fast/stages/2-secops/main.tf b/fast/stages/2-secops/main.tf deleted file mode 100644 index 7b643ee3d..000000000 --- a/fast/stages/2-secops/main.tf +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2025 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 { - has_env_folders = var.folder_ids.secops-dev != null - iam_delegated = join(",", formatlist("'%s'", [ - "roles/cloudkms.cryptoKeyEncrypterDecrypter" - ])) - iam_admin_delegated = try( - var.stage_config["secops"].iam_admin_delegated, {} - ) - iam_viewer = try( - var.stage_config["secops"].iam_viewer, {} - ) - project_services = [ - "chronicle.googleapis.com", - "stackdriver.googleapis.com" - ] -} - -module "folder" { - source = "../../../modules/folder" - folder_create = false - id = var.folder_ids.secops - contacts = ( - var.essential_contacts == null - ? {} - : { (var.essential_contacts) = ["ALL"] } - ) -} - -module "project" { - source = "../../../modules/project" - for_each = var.environments - name = "${each.value.short_name}-secops-0" - parent = coalesce( - var.folder_ids["secops-${each.key}"], var.folder_ids.secops - ) - prefix = var.prefix - billing_account = var.billing_account.id - labels = { environment = each.key } - services = local.project_services - tag_bindings = local.has_env_folders ? {} : { - environment = var.tag_values["environment/${each.value.tag_name}"] - } - # optionally delegate a fixed set of IAM roles to selected principals - iam = { - (var.custom_roles.project_iam_viewer) = try( - local.iam_viewer[each.key], [] - ) - } - iam_bindings = ( - lookup(local.iam_admin_delegated, each.key, null) == null ? {} : { - sa_delegated_grants = { - role = "roles/resourcemanager.projectIamAdmin" - members = try(local.iam_admin_delegated[each.key], []) - condition = { - title = "${each.key}_stage3_sa_delegated_grants" - description = "${var.environments[each.key].name} project delegated grants." - expression = format( - "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])", - local.iam_delegated - ) - } - } - } - ) -} diff --git a/fast/stages/2-secops/moved/.gitkeep b/fast/stages/2-secops/moved/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/fast/stages/2-secops/outputs.tf b/fast/stages/2-secops/outputs.tf deleted file mode 100644 index 90805e726..000000000 --- a/fast/stages/2-secops/outputs.tf +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2025 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 { - tfvars = { - federated_identity_pool = try( - google_iam_workforce_pool.default[0].name, null - ) - secops_project_ids = { - for k, v in module.project : k => v.id - } - } -} - -resource "local_file" "tfvars" { - for_each = var.outputs_location == null ? {} : { 1 = 1 } - file_permission = "0644" - filename = "${pathexpand(var.outputs_location)}/tfvars/2-secops.auto.tfvars.json" - content = jsonencode(local.tfvars) -} - -resource "google_storage_bucket_object" "tfvars" { - bucket = var.automation.outputs_bucket - name = "tfvars/2-security.auto.tfvars.json" - content = jsonencode(local.tfvars) -} - -resource "google_storage_bucket_object" "version" { - count = fileexists("fast_version.txt") ? 1 : 0 - bucket = var.automation.outputs_bucket - name = "versions/2-secops-version.txt" - source = "fast_version.txt" -} - -output "federated_identity_pool" { - description = "Workforce Identity Federation pool." - value = local.tfvars.federated_identity_pool -} - -output "secops_project_ids" { - description = "SecOps project IDs." - value = local.tfvars.secops_project_ids -} - -output "tfvars" { - description = "Terraform variable files for the following stages." - sensitive = true - value = local.tfvars -} diff --git a/fast/stages/2-secops/variables-fast.tf b/fast/stages/2-secops/variables-fast.tf deleted file mode 100644 index c7ff6ca2d..000000000 --- a/fast/stages/2-secops/variables-fast.tf +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright 2025 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 "automation" { - # tfdoc:variable:source 0-org-setup - description = "Automation resources created by the bootstrap stage." - type = object({ - outputs_bucket = string - }) -} - -variable "billing_account" { - # tfdoc:variable:source 0-org-setup - description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false." - type = object({ - id = string - is_org_level = optional(bool, true) - }) - validation { - condition = var.billing_account.is_org_level != null - error_message = "Invalid `null` value for `billing_account.is_org_level`." - } -} - -variable "custom_roles" { - # tfdoc:variable:source 0-org-setup - description = "Custom roles defined at the org level, in key => id format." - type = object({ - project_iam_viewer = string - }) - default = null -} - -variable "environments" { - # tfdoc:variable:source 0-globals - description = "Environment names." - type = map(object({ - name = string - short_name = string - tag_name = string - is_default = optional(bool, false) - })) - nullable = false - validation { - condition = anytrue([ - for k, v in var.environments : v.is_default == true - ]) - error_message = "At least one environment should be marked as default." - } -} - -variable "folder_ids" { - # tfdoc:variable:source 1-resman - description = "Folder name => id mappings, the 'security' folder name must exist." - type = object({ - secops = string - secops-dev = optional(string) - secops-prod = optional(string) - }) -} - -variable "organization" { - # tfdoc:variable:source 0-org-setup - description = "Organization details." - type = object({ - domain = string - id = number - customer_id = string - }) - nullable = false -} - -variable "prefix" { - # tfdoc:variable:source 0-org-setup - description = "Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants." - type = string - validation { - condition = try(length(var.prefix), 0) < 12 - error_message = "Use a maximum of 9 chars for organizations, and 11 chars for tenants." - } -} - -variable "stage_config" { - # tfdoc:variable:source 1-resman - description = "FAST stage configuration." - type = object({ - security = optional(object({ - short_name = optional(string) - iam_admin_delegated = optional(map(list(string)), {}) - iam_viewer = optional(map(list(string)), {}) - }), {}) - }) - default = {} - nullable = false -} - -variable "tag_values" { - # tfdoc:variable:source 1-resman - description = "Root-level tag values." - type = map(string) - default = {} -} diff --git a/fast/stages/2-secops/variables.tf b/fast/stages/2-secops/variables.tf deleted file mode 100644 index da3468210..000000000 --- a/fast/stages/2-secops/variables.tf +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2025 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 "essential_contacts" { - description = "Email used for essential contacts, unset if null." - type = string - default = null -} - -variable "outputs_location" { - description = "Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable." - type = string - default = null -} - -variable "workforce_identity_providers" { - description = "Workforce Identity Federation pools." - type = map(object({ - attribute_condition = optional(string) - issuer = string - display_name = string - description = string - disabled = optional(bool, false) - saml = optional(object({ - idp_metadata_xml = string - }), null) - })) - default = {} - nullable = false -} diff --git a/fast/stages/3-secops-dev/README.md b/fast/stages/3-secops-dev/README.md index 71d133eaa..8a54b0f29 100644 --- a/fast/stages/3-secops-dev/README.md +++ b/fast/stages/3-secops-dev/README.md @@ -1,8 +1,6 @@ # SecOps Stage -This stage allows automated configuration of SecOps instance at both infrastructure and application level. - -The following diagram illustrates the high-level design of SecOps instance configuration in both GCP and SecOps instance, which can be adapted to specific requirements via variables. +This stage allows automated configuration of a SecOps instance at both infrastructure and application level. The following diagram illustrates the high-level design.

SecOPs stage @@ -23,20 +21,20 @@ The following diagram illustrates the high-level design of SecOps instance confi ## Design overview and choices -The general idea behind this stage is to configure a single SecOps instance for a specific environment with configurations both on SecOps leveraging terraform resources (where available) and `restful_resource` for interacting with the new [SecOps APIs](https://cloud.google.com/chronicle/docs/reference/rest). +The general idea behind this stage is to configure a single SecOps instance for a specific environment with configurations for SecOps leveraging native Terraform resources (where available) and the `restful_resource` for interacting with the new [SecOps APIs](https://cloud.google.com/chronicle/docs/reference/rest). -Some high level features of the current version of the stage are: +Some high level features of this stage are: - API/Services enablement - Data RBAC configuration with labels and scopes -- IAM setup for the SecOps instance based on groups from Cloud Identity or WIF (with supports for Data RBAC) -- Detection Rules and reference lists management via terraform (leveraging [secops-rules](../../../modules/secops-rules) module) +- IAM setup for the SecOps instance based on Cloud Identity groups or WIF (with support for Data RBAC) +- Detection Rules and reference lists management via Terraform (leveraging the [secops-rules](../../../modules/secops-rules) module) - API Key setup for Webhook feeds -- Integration with Workspace for alerts and logs ingestion via SecOps Feeds +- Integration with Workspace for alert and log ingestion via SecOps Feeds ## How to run this stage -If this stage is deployed within a FAST-based GCP organization, we recommend executing it after foundational FAST `stage-2` components like `networking` and `security`. This is the recommended flow as specific data platform features in this stage might depend on configurations from these earlier stages. Although this stage can be run independently, instructions for such a standalone setup are beyond the scope of this document. +If this stage is deployed within a FAST-based GCP organization, we recommend executing it after foundational FAST `stage-2` components like `networking` and `security`. This is the recommended flow as specific features in this stage might depend on configurations from these earlier stages. Although this stage can be run independently, instructions for such a standalone setup are beyond the scope of this document. ### FAST prerequisites @@ -46,8 +44,6 @@ Network permissions are needed to associate data domain or product projects to S Security permissions are only needed when using CMEK encryption, to grant the relevant IAM roles to data platform service agents on the encryption keys used. -The ["Classic FAST" dataset](../0-org-setup/README.md#classic-fast-dataset) in the bootstrap stage contains the configuration for a development Data Platform that can be easily adapted to serve for this stage. - ## Customizations This stage is designed with few basic integrations provided out of the box which can be customized as per the following sections. @@ -136,19 +132,25 @@ Please be aware the Service Account Client ID needed during domain wide delegati | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [automation](variables-fast.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-org-setup | -| [tenant_config](variables.tf#L118) | SecOps Tenant configuration. | object({…}) | ✓ | | | +| [prefix](variables-fast.tf#L67) | Prefix for organization projects. | string | ✓ | | 0-org-setup | +| [tenant_config](variables.tf#L139) | SecOps Tenant configuration. | object({…}) | ✓ | | | | [billing_account](variables-fast.tf#L26) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | | {} | 0-org-setup | -| [data_rbac_config](variables.tf#L17) | SecOps Data RBAC scope and labels config. | object({…}) | | {} | | -| [factories_config](variables.tf#L51) | Paths to YAML config expected in 'rules' and 'reference_lists'. Path to folders containing rules definitions (yaral files) and reference lists content (txt files) for the corresponding _defs keys. | object({…}) | | {…} | | -| [folder_ids](variables-fast.tf#L35) | Folder name => id mappings. | map(string) | | {} | 1-resman | -| [iam](variables.tf#L68) | SecOps IAM configuration in {PRINCIPAL => {roles => [ROLES], scopes => [SCOPES]}} format. | map(object({…})) | | {} | | -| [iam_default](variables.tf#L78) | Groups ID in IdP assigned to SecOps admins, editors, viewers roles. | object({…}) | | {} | | -| [project_id](variables.tf#L88) | Project id that references existing SecOps project. Use this variable when running this stage in isolation. | string | | null | | -| [project_reuse](variables.tf#L94) | Whether to use an existing project, leave default for FAST deployment. | map(string) | | {} | | -| [region](variables.tf#L100) | Google Cloud region definition for resources. | string | | "europe-west8" | | -| [secops_project_ids](variables-fast.tf#L43) | SecOps Project IDs for each environment. | map(string) | | null | 2-secops | -| [stage_config](variables.tf#L106) | FAST stage configuration used to find resource ids. Must match name defined for the stage in resource management. | object({…}) | | {…} | | -| [workspace_integration_config](variables.tf#L126) | SecOps Feeds configuration for Workspace logs and entities ingestion. | object({…}) | | null | | +| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} | | +| [custom_roles](variables-fast.tf#L35) | Custom roles defined at the org level, in key => id format. | map(string) | | {} | 0-org-setup | +| [data_rbac_config](variables.tf#L30) | SecOps Data RBAC scope and labels config. | object({…}) | | {} | | +| [factories_config](variables.tf#L64) | Paths to YAML config expected in 'rules' and 'reference_lists'. Path to folders containing rules definitions (yaral files) and reference lists content (txt files) for the corresponding _defs keys. | object({…}) | | {…} | | +| [folder_ids](variables-fast.tf#L43) | Folders created in the bootstrap stage. | map(string) | | {} | 0-org-setup | +| [iam](variables.tf#L81) | SecOps IAM configuration in {PRINCIPAL => {roles => [ROLES], scopes => [SCOPES]}} format. | map(object({…})) | | {} | | +| [iam_default](variables.tf#L91) | Groups ID in IdP assigned to SecOps admins, editors, viewers roles. | object({…}) | | {} | | +| [iam_principals](variables-fast.tf#L51) | IAM-format principals. | map(string) | | {} | 0-org-setup | +| [kms_keys](variables-fast.tf#L59) | KMS key ids. | map(string) | | {} | 2-security | +| [parent_folder](variables.tf#L101) | Folder to use for created project. | string | | "$folder_ids:secops/dev" | | +| [project_id](variables.tf#L108) | Project id for newly created project, or id of existing project if project_create is false. | string | | "dev-secops-core-0" | | +| [project_ids](variables-fast.tf#L74) | Projects created in the bootstrap stage. | map(string) | | {} | 0-org-setup | +| [project_reuse](variables.tf#L115) | Whether to use an existing project. | map(string) | | null | | +| [region](variables.tf#L121) | Google Cloud region definition for resources. | string | | "europe-west8" | | +| [stage_config](variables.tf#L127) | FAST stage configuration used to find resource ids. Must match name defined for the stage in resource management. | object({…}) | | {…} | | +| [workspace_integration_config](variables.tf#L147) | SecOps Feeds configuration for Workspace logs and entities ingestion. | object({…}) | | null | | ## Outputs diff --git a/fast/stages/3-secops-dev/main.tf b/fast/stages/3-secops-dev/main.tf index 9bdf90017..483e3147f 100644 --- a/fast/stages/3-secops-dev/main.tf +++ b/fast/stages/3-secops-dev/main.tf @@ -17,36 +17,46 @@ locals { secops_api_key_secret_key = "secops-feeds-api-key" secops_workspace_int_sa_key = "secops-workspace-ing-sa-key" - secops_project_id = coalesce(try(var.secops_project_ids[var.stage_config.environment], null), var.project_id) - secops_feeds_api_path = "projects/${module.project.project_id}/locations/${var.tenant_config.region}/instances/${var.tenant_config.customer_id}/feeds" - workspace_log_ingestion = var.workspace_integration_config != null + secops_feeds_api_path = ( + "projects/${module.project.project_id}/locations/${var.tenant_config.region}/instances/${var.tenant_config.customer_id}/feeds" + ) + workspace_log_ingestion = var.workspace_integration_config != null } module "project" { source = "../../../modules/project" billing_account = var.project_reuse == null ? var.billing_account.id : null - name = local.secops_project_id - parent = var.folder_ids[var.stage_config.name] + name = var.project_id + parent = var.parent_folder + prefix = var.prefix project_reuse = var.project_reuse org_policies = var.workspace_integration_config != null ? { "iam.disableServiceAccountKeyCreation" = { rules = [{ enforce = false }] } } : {} - services = concat([ - "apikeys.googleapis.com", - "compute.googleapis.com", - "iap.googleapis.com", - "secretmanager.googleapis.com", - "stackdriver.googleapis.com", - "pubsub.googleapis.com", - "cloudfunctions.googleapis.com", + services = concat( + [ + "apikeys.googleapis.com", + "compute.googleapis.com", + "iap.googleapis.com", + "secretmanager.googleapis.com", + "stackdriver.googleapis.com", + "pubsub.googleapis.com", + "cloudfunctions.googleapis.com", ], var.workspace_integration_config != null ? [ "admin.googleapis.com", "alertcenter.googleapis.com" ] : [], ) + context = { + custom_roles = merge(var.custom_roles, var.context.custom_roles) + folder_ids = merge(var.folder_ids, var.context.folder_ids) + iam_principals = merge(var.iam_principals, var.context.iam_principals) + kms_keys = merge(var.kms_keys, var.context.kms_keys) + project_ids = merge(var.project_ids, var.context.project_ids) + } custom_roles = { "secopsDashboardViewer" = [ "chronicle.dashboardCharts.get", @@ -69,14 +79,26 @@ module "project" { } iam = {} iam_bindings_additive = merge( - { for group in var.iam_default.admins : - "${group}-admins" => { member = "group:${group}", role = "roles/chronicle.admin" } }, - { for group in var.iam_default.editors : - "${group}-editors" => { member = "group:${group}", role = "roles/chronicle.editor" } }, - { for group in var.iam_default.editors : - "${group}-viewers" => { member = "group:${group}", role = "roles/chronicle.viewer" } }, - { for k, v in var.iam : - k => { + { + for group in var.iam_default.admins : "${group}-admins" => { + member = "group:${group}" + role = "roles/chronicle.admin" + } + }, + { + for group in var.iam_default.editors : "${group}-editors" => { + member = "group:${group}" + role = "roles/chronicle.editor" + } + }, + { + for group in var.iam_default.editors : "${group}-viewers" => { + member = "group:${group}" + role = "roles/chronicle.viewer" + } + }, + { + for k, v in var.iam : k => { member = k role = "roles/chronicle.restrictedDataAccess" condition = { @@ -85,7 +107,8 @@ module "project" { description = "datarbac" } } - }) + } + ) iam_by_principals_additive = { for k, v in var.iam : k => v.roles } } @@ -93,7 +116,6 @@ resource "google_apikeys_key" "feed_api_key" { project = module.project.project_id name = "secops-feed-key" display_name = "SecOps Feeds API Key" - restrictions { api_targets { service = "chronicle.googleapis.com" @@ -103,7 +125,7 @@ resource "google_apikeys_key" "feed_api_key" { module "secops-rules" { source = "../../../modules/secops-rules" - project_id = local.secops_project_id + project_id = var.project_id tenant_config = { region = var.tenant_config.region customer_id = var.tenant_config.customer_id diff --git a/fast/stages/3-secops-dev/variables-fast.tf b/fast/stages/3-secops-dev/variables-fast.tf index ccbf95a5d..cb9261050 100644 --- a/fast/stages/3-secops-dev/variables-fast.tf +++ b/fast/stages/3-secops-dev/variables-fast.tf @@ -32,17 +32,49 @@ variable "billing_account" { default = {} } -variable "folder_ids" { - # tfdoc:variable:source 1-resman - description = "Folder name => id mappings." +variable "custom_roles" { + # tfdoc:variable:source 0-org-setup + description = "Custom roles defined at the org level, in key => id format." type = map(string) nullable = false default = {} } -variable "secops_project_ids" { - # tfdoc:variable:source 2-secops - description = "SecOps Project IDs for each environment." +variable "folder_ids" { + # tfdoc:variable:source 0-org-setup + description = "Folders created in the bootstrap stage." type = map(string) - default = null + nullable = false + default = {} +} + +variable "iam_principals" { + # tfdoc:variable:source 0-org-setup + description = "IAM-format principals." + type = map(string) + nullable = false + default = {} +} + +variable "kms_keys" { + # tfdoc:variable:source 2-security + description = "KMS key ids." + type = map(string) + nullable = false + default = {} +} + +variable "prefix" { + # tfdoc:variable:source 0-org-setup + description = "Prefix for organization projects." + type = string + nullable = false +} + +variable "project_ids" { + # tfdoc:variable:source 0-org-setup + description = "Projects created in the bootstrap stage." + type = map(string) + nullable = false + default = {} } diff --git a/fast/stages/3-secops-dev/variables.tf b/fast/stages/3-secops-dev/variables.tf index 2c09caf80..1a5c978a8 100644 --- a/fast/stages/3-secops-dev/variables.tf +++ b/fast/stages/3-secops-dev/variables.tf @@ -14,6 +14,19 @@ * limitations under the License. */ +variable "context" { + description = "Context-specific interpolations." + type = object({ + custom_roles = optional(map(string), {}) + folder_ids = optional(map(string), {}) + iam_principals = optional(map(string), {}) + kms_keys = optional(map(string), {}) + project_ids = optional(map(string), {}) + }) + default = {} + nullable = false +} + variable "data_rbac_config" { description = "SecOps Data RBAC scope and labels config." type = object({ @@ -85,16 +98,24 @@ variable "iam_default" { default = {} } -variable "project_id" { - description = "Project id that references existing SecOps project. Use this variable when running this stage in isolation." +variable "parent_folder" { + description = "Folder to use for created project." type = string - default = null + nullable = false + default = "$folder_ids:secops/dev" +} + +variable "project_id" { + description = "Project id for newly created project, or id of existing project if project_create is false." + type = string + nullable = false + default = "dev-secops-core-0" } variable "project_reuse" { - description = "Whether to use an existing project, leave default for FAST deployment." + description = "Whether to use an existing project." type = map(string) - default = {} + default = null } variable "region" { diff --git a/fast/stages/3-secops-dev/workspace.tf b/fast/stages/3-secops-dev/workspace.tf index 6904575a5..d711234b5 100644 --- a/fast/stages/3-secops-dev/workspace.tf +++ b/fast/stages/3-secops-dev/workspace.tf @@ -68,25 +68,36 @@ resource "restful_resource" "workspace_feeds" { "display_name" : each.key, "details" : { "feed_source_type" : "API", - "log_type" : "projects/${module.project.project_id}/locations/${var.tenant_config.region}/instances/${var.tenant_config.customer_id}/logTypes/${each.value.log_type}", + "log_type" : ( + "projects/${module.project.project_id}/locations/${var.tenant_config.region}/instances/${var.tenant_config.customer_id}/logTypes/${each.value.log_type}" + ), "asset_namespace" : "", "labels" : {}, - (each.value.feed_type) : merge({ - "authentication" : { - "token_endpoint" : "https://oauth2.googleapis.com/token", - "claims" : { - "issuer" : module.workspace-integration-sa[0].email, - "subject" : var.workspace_integration_config.delegated_user, - "audience" : "https://oauth2.googleapis.com/token" + (each.value.feed_type) : merge( + { + "authentication" : { + "token_endpoint" : "https://oauth2.googleapis.com/token", + "claims" : { + "issuer" : module.workspace-integration-sa[0].email, + "subject" : var.workspace_integration_config.delegated_user, + "audience" : "https://oauth2.googleapis.com/token" + }, + rs_credentials : { + private_key : jsondecode(base64decode( + google_service_account_key.workspace_integration_key[0].private_key + )).private_key + } }, - rs_credentials : { - private_key : jsondecode(base64decode(google_service_account_key.workspace_integration_key[0].private_key)).private_key - } + workspace_customer_id : ( + each.key == "ws-alerts" + ? trimprefix(var.workspace_integration_config.workspace_customer_id, "C") + : var.workspace_integration_config.workspace_customer_id + ) }, - workspace_customer_id : each.key == "ws-alerts" ? trimprefix(var.workspace_integration_config.workspace_customer_id, "C") : var.workspace_integration_config.workspace_customer_id - }, each.key == "ws-activity" ? { - applications : var.workspace_integration_config.applications - } : {}) + each.key != "ws-activity" ? {} : { + applications : var.workspace_integration_config.applications + } + ) } } write_only_attrs = ["details"] diff --git a/tests/fast/stages/s2_secops/simple.tfvars b/tests/fast/stages/s2_secops/simple.tfvars deleted file mode 100644 index 3d116dd40..000000000 --- a/tests/fast/stages/s2_secops/simple.tfvars +++ /dev/null @@ -1,32 +0,0 @@ -automation = { - outputs_bucket = "test" -} -billing_account = { - id = "000000-111111-222222" -} -custom_roles = { - project_iam_viewer = "organizations/123456789012/roles/bar" -} -environments = { - "dev" : { - "is_default" : true, - "key" : "dev", - "name" : "Development", - "short_name" : "dev", - "tag_name" : "development" - } -} -essential_contacts = "gcp-secops-admins@fast.example.com" -folder_ids = { - secops = "folders/12345678" -} -organization = { - domain = "fast.example.com" - id = 123456789012 - customer_id = "C00000000" -} -prefix = "fast" -tag_values = { - "environment/development" = "tagValues/12345" - "environment/production" = "tagValues/12346" -} diff --git a/tests/fast/stages/s2_secops/simple.yaml b/tests/fast/stages/s2_secops/simple.yaml deleted file mode 100644 index e39fb4679..000000000 --- a/tests/fast/stages/s2_secops/simple.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2025 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. - -counts: - google_essential_contacts_contact: 1 - google_project: 1 - google_project_iam_binding: 1 - google_project_iam_member: 1 - google_project_service: 2 - google_project_service_identity: 1 - google_storage_bucket_object: 2 - google_tags_tag_binding: 1 - modules: 2 - resources: 10 - -outputs: - federated_identity_pool: null - secops_project_ids: - dev: fast-dev-secops-0 - tfvars: - federated_identity_pool: null - secops_project_ids: - dev: fast-dev-secops-0 diff --git a/tests/fast/stages/s2_secops/tftest.yaml b/tests/fast/stages/s2_secops/tftest.yaml deleted file mode 100644 index 805383079..000000000 --- a/tests/fast/stages/s2_secops/tftest.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2025 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. - -module: fast/stages/2-secops - -tests: - simple: diff --git a/tests/fast/stages/s3_secops_dev/simple.tfvars b/tests/fast/stages/s3_secops_dev/simple.tfvars index bcd987eef..9c033f441 100644 --- a/tests/fast/stages/s3_secops_dev/simple.tfvars +++ b/tests/fast/stages/s3_secops_dev/simple.tfvars @@ -4,17 +4,13 @@ automation = { billing_account = { id = "012345-67890A-BCDEF0", } -project_reuse = null folder_ids = { - "secops-dev" = "folders/123456789" + "secops/dev" = "folders/123456789" } tenant_config = { customer_id = "xxxxxx-xxxxxx-xxxxxx" region = "europe" } -secops_project_ids = { - dev = "fast-dev-secops-0" -} iam_default = { viewers = ["gcp-secops-admins@fast.example.com"] } @@ -24,6 +20,7 @@ iam = { scopes = ["gscope"] } } +prefix = "fast" workspace_integration_config = { delegated_user = "secops-feed@fast.example.com" workspace_customer_id = "C121212" diff --git a/tests/fast/stages/s3_secops_dev/simple.yaml b/tests/fast/stages/s3_secops_dev/simple.yaml index 066ba34eb..a41f6b470 100644 --- a/tests/fast/stages/s3_secops_dev/simple.yaml +++ b/tests/fast/stages/s3_secops_dev/simple.yaml @@ -35,4 +35,4 @@ counts: restful_resource: 6 outputs: - project_id: fast-dev-secops-0 + project_id: fast-dev-secops-core-0