From d8e8da8724d509897f5cca3d70d724f3727b7afd Mon Sep 17 00:00:00 2001 From: jose-pablo-camacho Date: Tue, 17 Sep 2024 15:51:14 -0600 Subject: [PATCH] feat(vm): add vm modular support (#39) * feat(modular): add modular support for cdr/ciem * feat(modular): add modular support for cdr/ciem * feat(modular): add modular support for cdr/ciem * feat(modular): add modular support for cdr/ciem * feat(modular): add modular support for cdr/ciem * feat(modular): address feedback for modular support for cdr/ciem * adding modular onboarding module * fix var refns * adding modular onboarding example * adding config posture module for modular onboarding * updating README * fix role naming & version metadata * updating examples for onboarding & cspm org * cleanup foundational READMEs * use external_id datasource * update README * remove mgmt_group_ids in cspm module * bump sysdig provider version to be consistent & have latest datasources * updating examples * update example * feat(modular): address feedback for modular support for cdr/ciem, rebase and cleanup * feat(modular): address feedback for modular support for cdr/ciem, rebase and cleanup * add explicit dependency * feat(modular): address feedback for modular support for cdr/ciem, rebase and cleanup * feat(modular): address feedback for modular support for cdr/ciem, rebase and cleanup * feat(modular): address feedback for modular support for cdr/ciem, rebase and cleanup * feat(vm): add vm modular support * feat(vm): add vm modular support * feat(modular): add vm modular support * feat(vm): add vm modular support * feat(vm): add vm modular support * delete some test files * feat(vm): modular vm feedback * feat(vm): modular vm feedback --------- Co-authored-by: Haresh Suresh --- modules/agentless-scan/README.md | 95 ++++++++ modules/agentless-scan/main.tf | 208 ++++++++++++++++++ modules/agentless-scan/organizational.tf | 50 +++++ modules/agentless-scan/outputs.tf | 5 + modules/agentless-scan/variables.tf | 33 +++ modules/agentless-scan/versions.tf | 18 ++ .../modular_organization/agentless-scan.tf | 20 ++ .../modular_single_project/agentless-scan.tf | 18 ++ 8 files changed, 447 insertions(+) create mode 100644 modules/agentless-scan/README.md create mode 100644 modules/agentless-scan/main.tf create mode 100644 modules/agentless-scan/organizational.tf create mode 100644 modules/agentless-scan/outputs.tf create mode 100644 modules/agentless-scan/variables.tf create mode 100644 modules/agentless-scan/versions.tf create mode 100644 test/examples/modular_organization/agentless-scan.tf create mode 100644 test/examples/modular_single_project/agentless-scan.tf diff --git a/modules/agentless-scan/README.md b/modules/agentless-scan/README.md new file mode 100644 index 0000000..3c3cf22 --- /dev/null +++ b/modules/agentless-scan/README.md @@ -0,0 +1,95 @@ +# GCP Agentless Scanning Module + +This Module creates the resources required to scan hosts on Google Cloud Projects. Before applying the changes defined +in this module, the following operations need to be performed on the target GCP environment: + +- The APIs needed for the VM feature are listed below: + - Compute Engine API + +- The following resources will be created in each instrumented project: + - For the **Resource Discovery**: Enable Sysdig to authenticate through a Workload Identity Pool (requires provider, + service account, role, and related bindings) in order to be able to discover the VPC/Instance/Volumes. + - For the **Host Data Extraction**: Enable Sysdig to create a disk copy on our SaaS platform, to be able to extract + the data required for security assessment. + +This module will also deploy a Service Principal Component in Sysdig Backend for onboarded Sysdig Cloud Account. + +## Single Project Setup + +![permission_diagram_single](./permissions_diagram_single.png) + +## Organizational Setup + +Set `is_organizatinal=true` together with the `organization_domain=`. +![permission_diagram_org](./permissions_diagram_org.png) + +## Requirements + +| Name | Version | +|---------------------------------------------------------------------------|-----------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [google](#requirement\_google) | >= 4.21.0 | +| [sysdig](#requirement\_sysdig) | >= 1.34.0 | +| [random](#requirement\_random) | >= 3.1 | + +## Providers + +| Name | Version | +|---------------------------------------------------------------------------|-----------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [google](#requirement\_google) | >= 4.21.0 | +| [sysdig](#requirement\_sysdig) | >= 1.34.0 | +| [random](#requirement\_random) | >= 3.1 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| +| [random_id.suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | +| [google_iam_workload_identity_pool.agentless](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool) | resource | +| [google_iam_workload_identity_pool_provider.agentless](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider) | resource | +| [google_iam_workload_identity_pool_provider.agentless_gcp](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider) | resource | +| [google_organization_iam_binding.admin_account_iam](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/organization_iam_binding) | resource | +| [google_organization_iam_binding.controller_custom](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/organization_iam_binding) | resource | +| [google_organization_iam_custom_role.controller](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/organization_iam_custom_role) | resource | +| [google_organization_iam_custom_role.worker_role](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/organization_iam_custom_role) | resource | +| [google_project_iam_binding.admin_account_iam](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | +| [google_project_iam_binding.controller_custom](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | +| [google_project_iam_custom_role.controller](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_custom_role) | resource | +| [google_project_iam_custom_role.worker_role](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_custom_role) | resource | +| [google_service_account.controller](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource | +| [google_service_account_iam_member.controller_custom](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_member) | resource | +| [google_service_account_iam_member.controller_custom_gcp](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_member) | resource | +| [google_organization.org](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/organization) | data source | +| [google_project.project](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/project) | data source | +| [sysdig_secure_trusted_cloud_identity.trusted_identity](https://registry.terraform.io/providers/sysdiglabs/sysdig/latest/docs/data-sources/secure_trusted_cloud_identity) | data source | +| [sysdig_secure_cloud_auth_account_component.gcp_agentless_scan](https://registry.terraform.io/providers/sysdiglabs/sysdig/latest/docs/resources/secure_cloud_auth_account_component) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|----------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|-----------------------------|:--------:| +| [project\_id](#input\_project\_id) | GCP Project ID | `string` | n/a | yes | +| [is\_organizational](#input\_is\_organizational) | Optional. Determines whether module must scope whole organization. Otherwise single project will be scoped | `bool` | `false` | no | +| [organization\_domain](#input\_organization\_domain) | Optional. If `is_organizational=true` is set, its mandatory to specify this value, with the GCP Organization domain. e.g. sysdig.com | `string` | `null` | no | +| [sysdig\_account\_id](#input\_sysdig\_account\_id) | Sysdig provided GCP Account designated for the host scan.
One of `sysdig_backend` or `sysdig_account_id`must be provided | `string` | `null` | no | +| [sysdig\_secure\_account\_id](#input\_sysdig\_secure\_account\_id) | ID of the Sysdig Cloud Account to enable Agentless Scanning integration for (in case of organization, ID of the Sysdig management account) | `string` | `null` | no | +| [suffix](#input\_suffix) | Optional. Suffix word to enable multiple deployments with different naming
(Workload Identity Pool and Providers have a soft deletion on Google Platform that will disallow name re-utilization)
By default a random value will be autogenerated. | `string` | `null` | no | + +## Outputs + +| Name | Description | +|--------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| +| [agentless\_scan\_component\_id](#agentless\_scan\_component\_id) | Component identifier of Agentless Scan integration created in Sysdig Backend for VM | + +## Authors + +Module is maintained by [Sysdig](https://sysdig.com). + +## License + +Apache 2 Licensed. See LICENSE for full details. diff --git a/modules/agentless-scan/main.tf b/modules/agentless-scan/main.tf new file mode 100644 index 0000000..b63511a --- /dev/null +++ b/modules/agentless-scan/main.tf @@ -0,0 +1,208 @@ +#----------------------------------------------------------------------------------------- +# Fetch the data sources +#----------------------------------------------------------------------------------------- +data "sysdig_secure_agentless_scanning_assets" "assets" {} + +#----------------------------------------------------------------------------------------- +# These locals indicate the suffix to create unique name for resources and permissions +#----------------------------------------------------------------------------------------- +locals { + suffix = var.suffix == null ? random_id.suffix[0].hex : var.suffix + host_discovery_permissions = [ + # networks + "compute.networks.list", + "compute.networks.get", + # instances + "compute.instances.list", + "compute.instances.get", + # disks + "compute.disks.list", + "compute.disks.get", + # workload identity federation + "iam.serviceAccounts.getAccessToken", + ] + host_scan_permissions = [ + # general stuff + "compute.zoneOperations.get", + # disks + "compute.disks.get", + "compute.disks.useReadOnly", + ] +} + +#----------------------------------------------------------------------------------------------------------------------- +# A random resource is used to generate unique Agentless Scan name suffix for resources. +# This prevents conflicts when recreating an Agentless Scan resources with the same name. +#----------------------------------------------------------------------------------------------------------------------- +resource "random_id" "suffix" { + count = var.suffix == null ? 1 : 0 + byte_length = 3 +} + + +resource "google_service_account" "controller" { + project = var.project_id + account_id = "sysdig-ahs-${local.suffix}" + display_name = "Sysdig Agentless Host Scanning" +} + +#----------------------------------------------------------------------------------------------------------------------- +# Configure Workload Identity Federation for auth +# See https://cloud.google.com/iam/docs/access-resources-aws +#----------------------------------------------------------------------------------------------------------------------- + +resource "google_iam_workload_identity_pool" "agentless" { + workload_identity_pool_id = "sysdig-ahs-${local.suffix}" +} + +resource "google_iam_workload_identity_pool_provider" "agentless" { + count = data.sysdig_secure_agentless_scanning_assets.assets.backend.cloud_id != null ? 1 : 0 + + lifecycle { + precondition { + condition = (data.sysdig_secure_agentless_scanning_assets.assets.backend.cloud_id != null && var.sysdig_account_id == null) + error_message = "Cannot provide both sysdig_backend or sysdig_account_id" + } + } + + workload_identity_pool_id = google_iam_workload_identity_pool.agentless.workload_identity_pool_id + workload_identity_pool_provider_id = "sysdig-ahs-${local.suffix}" + display_name = "Sysdig Agentless Controller" + description = "AWS identity pool provider for Sysdig Secure Agentless Host Scanning" + disabled = false + + attribute_condition = "attribute.aws_account==\"${data.sysdig_secure_agentless_scanning_assets.assets.backend.cloud_id}\"" + + attribute_mapping = { + "google.subject" = "assertion.arn" + "attribute.aws_account" = "assertion.account" + "attribute.role" = "assertion.arn.extract(\"/assumed-role/{role}/\")" + "attribute.session" = "assertion.arn.extract(\"/assumed-role/{role_and_session}/\").extract(\"/{session}\")" + } + + aws { + account_id = data.sysdig_secure_agentless_scanning_assets.assets.backend.cloud_id + } +} + +resource "google_service_account_iam_member" "controller_custom" { + count = data.sysdig_secure_agentless_scanning_assets.assets.backend.cloud_id != null ? 1 : 0 + + lifecycle { + precondition { + condition = (data.sysdig_secure_agentless_scanning_assets.assets.backend.cloud_id != null && var.sysdig_account_id == null) + error_message = "Cannot provide both sysdig_backend or sysdig_account_id" + } + } + + service_account_id = google_service_account.controller.name + role = "roles/iam.workloadIdentityUser" + member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.agentless.name}/attribute.aws_account/${data.sysdig_secure_agentless_scanning_assets.assets.backend.cloud_id}" +} + +resource "google_iam_workload_identity_pool_provider" "agentless_gcp" { + count = var.sysdig_account_id != null ? 1 : 0 + + lifecycle { + precondition { + condition = (data.sysdig_secure_agentless_scanning_assets.assets.backend.cloud_id == null && var.sysdig_account_id != null) + error_message = "Cannot provide both sysdig_backend or sysdig_account_id" + } + } + + workload_identity_pool_id = google_iam_workload_identity_pool.agentless.workload_identity_pool_id + workload_identity_pool_provider_id = "sysdig-ahs-${local.suffix}-gcp" + display_name = "Sysdig Agentless Controller" + description = "GCP identity pool provider for Sysdig Secure Agentless Host Scanning" + disabled = false + + attribute_condition = "google.subject == \"${var.sysdig_account_id}\"" + + attribute_mapping = { + "google.subject" = "assertion.sub" + "attribute.sa_id" = "assertion.sub" + } + + oidc { + issuer_uri = "https://accounts.google.com" + } +} + +resource "google_service_account_iam_member" "controller_custom_gcp" { + count = var.sysdig_account_id != null ? 1 : 0 + + lifecycle { + precondition { + condition = (data.sysdig_secure_agentless_scanning_assets.assets.backend.cloud_id == null && var.sysdig_account_id != null) + error_message = "Cannot provide both sysdig_backend or sysdig_account_id" + } + } + + service_account_id = google_service_account.controller.name + role = "roles/iam.workloadIdentityUser" + member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.agentless.name}/attribute.sa_id/${var.sysdig_account_id}" +} + +#----------------------------------------------------------------------------------------- +# Custom IAM roles and bindings +#----------------------------------------------------------------------------------------- + +resource "google_project_iam_custom_role" "controller_role" { + count = var.is_organizational ? 0 : 1 + + project = var.project_id + role_id = "SysdigCloudVMDiscovery${local.suffix}" + title = "SysdigCloudVM, for Host Discovery" + permissions = local.host_discovery_permissions +} + +resource "google_project_iam_binding" "controller_binding" { + count = var.is_organizational ? 0 : 1 + + project = var.project_id + role = google_project_iam_custom_role.controller_role[0].id + members = [ + "serviceAccount:${google_service_account.controller.email}", + ] +} + +resource "google_project_iam_custom_role" "worker_role" { + count = var.is_organizational ? 0 : 1 + + project = var.project_id + role_id = "SysdigCloudVMScan${local.suffix}" + title = "SysdigCloudVM, for Host Scan" + permissions = local.host_scan_permissions +} + +resource "google_project_iam_binding" "admin_account_iam" { + count = var.is_organizational ? 0 : 1 + + project = var.project_id + role = google_project_iam_custom_role.worker_role[0].id + members = [ + "serviceAccount:${data.sysdig_secure_agentless_scanning_assets.assets.gcp.worker_identity}", + ] +} + +#----------------------------------------------------------------------------------------------------------------------------------------- +# Call Sysdig Backend to add the agentless-scan integration to the Sysdig Cloud Account +# +# Note (optional): To ensure this gets called after all cloud resources are created, add +# explicit dependency using depends_on +#----------------------------------------------------------------------------------------------------------------------------------------- + +resource "sysdig_secure_cloud_auth_account_component" "gcp_agentless_scan" { + account_id = var.sysdig_secure_account_id + type = "COMPONENT_SERVICE_PRINCIPAL" + instance = "secure-scanning" + version = "v0.1.0" + service_principal_metadata = jsonencode({ + gcp = { + workload_identity_federation = { + pool_provider_id = data.sysdig_secure_agentless_scanning_assets.assets.gcp.worker_identity != null ? google_iam_workload_identity_pool_provider.agentless[0].name : var.sysdig_account_id != null ? google_iam_workload_identity_pool_provider.agentless_gcp[0].name : null + } + email = google_service_account.controller.email + } + }) +} diff --git a/modules/agentless-scan/organizational.tf b/modules/agentless-scan/organizational.tf new file mode 100644 index 0000000..60ca1a6 --- /dev/null +++ b/modules/agentless-scan/organizational.tf @@ -0,0 +1,50 @@ +#----------------------------------------------------------------------------------------- +# Fetch the data sources +#----------------------------------------------------------------------------------------- + +data "google_organization" "org" { + count = var.is_organizational ? 1 : 0 + domain = var.organization_domain +} + +#----------------------------------------------------------------------------------------- +# Custom IAM roles and bindings +#----------------------------------------------------------------------------------------- + +resource "google_organization_iam_custom_role" "controller_role" { + count = var.is_organizational ? 1 : 0 + + org_id = data.google_organization.org[0].org_id + role_id = "SysdigCloudVMDiscovery${local.suffix}" + title = "SysdigCloudVM, for Host Discovery" + permissions = local.host_discovery_permissions +} + +resource "google_organization_iam_binding" "controller_custom" { + count = var.is_organizational ? 1 : 0 + + org_id = data.google_organization.org[0].org_id + role = google_organization_iam_custom_role.controller_role[0].id + members = [ + "serviceAccount:${google_service_account.controller.email}", + ] +} + +resource "google_organization_iam_custom_role" "worker_role" { + count = var.is_organizational ? 1 : 0 + + org_id = data.google_organization.org[0].org_id + role_id = "SysdigCloudVMScan${local.suffix}" + title = "SysdigCloudVM, for Host Scan" + permissions = local.host_scan_permissions +} + +resource "google_organization_iam_binding" "admin_account_iam" { + count = var.is_organizational ? 1 : 0 + + org_id = data.google_organization.org[0].org_id + role = google_organization_iam_custom_role.worker_role[0].id + members = [ + "serviceAccount:${data.sysdig_secure_agentless_scanning_assets.assets.gcp.worker_identity}", + ] +} diff --git a/modules/agentless-scan/outputs.tf b/modules/agentless-scan/outputs.tf new file mode 100644 index 0000000..36b8b1c --- /dev/null +++ b/modules/agentless-scan/outputs.tf @@ -0,0 +1,5 @@ +output "agentless_scan_component_id" { + value = "${sysdig_secure_cloud_auth_account_component.gcp_agentless_scan.type}/${sysdig_secure_cloud_auth_account_component.gcp_agentless_scan.instance}" + description = "Component identifier of Agentless Scan integration created in Sysdig Backend for VM" + depends_on = [sysdig_secure_cloud_auth_account_component.gcp_agentless_scan] +} \ No newline at end of file diff --git a/modules/agentless-scan/variables.tf b/modules/agentless-scan/variables.tf new file mode 100644 index 0000000..8b2fb24 --- /dev/null +++ b/modules/agentless-scan/variables.tf @@ -0,0 +1,33 @@ +variable "project_id" { + type = string + description = "(Required) Target Project identifier provided by the customer" +} + +variable "is_organizational" { + description = "(Optional) Set this field to true to deploy to a GCP Organization." + type = bool + default = false +} + +variable "organization_domain" { + type = string + description = "(Optional) Organization domain. e.g. sysdig.com" + default = "" +} + +variable "sysdig_account_id" { + type = string + description = "Sysdig provided GCP Account designated for the host scan. One of sysdig_backend or sysdig_account_id must be provided" + default = null +} + +variable "sysdig_secure_account_id" { + type = string + description = "ID of the Sysdig Cloud Account to enable Agentless Scanning integration for (in case of organization, ID of the Sysdig management account)" +} + +variable "suffix" { + type = string + description = "Suffix to uniquely identify resources during multiple installs. If not provided, random value is autogenerated." + default = null +} diff --git a/modules/agentless-scan/versions.tf b/modules/agentless-scan/versions.tf new file mode 100644 index 0000000..adb6e1a --- /dev/null +++ b/modules/agentless-scan/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.0.0" + + required_providers { + google = { + source = "hashicorp/google" + version = ">= 4.21.0" + } + sysdig = { + source = "sysdiglabs/sysdig" + version = ">= 1.34.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.1" + } + } +} \ No newline at end of file diff --git a/test/examples/modular_organization/agentless-scan.tf b/test/examples/modular_organization/agentless-scan.tf new file mode 100644 index 0000000..3f7cca8 --- /dev/null +++ b/test/examples/modular_organization/agentless-scan.tf @@ -0,0 +1,20 @@ +#--------------------------------------------------------------------------------------------- +# Ensure installation flow for foundational onboarding has been completed before +# installing additional Sysdig features. +#--------------------------------------------------------------------------------------------- + +module "agentless-scan" { + source = "../../../modules/agentless-scan" + project_id = module.onboarding.project_id + is_organizational = module.onboarding.is_organizational + organization_domain = module.onboarding.organization_domain + sysdig_secure_account_id = module.onboarding.sysdig_secure_account_id +} + +resource "sysdig_secure_cloud_auth_account_feature" "agentless_scanning" { + account_id = module.onboarding.sysdig_secure_account_id + type = "FEATURE_SECURE_AGENTLESS_SCANNING" + enabled = true + components = [module.agentless-scan.agentless_scan_component_id] + depends_on = [module.agentless-scan] +} \ No newline at end of file diff --git a/test/examples/modular_single_project/agentless-scan.tf b/test/examples/modular_single_project/agentless-scan.tf new file mode 100644 index 0000000..1f082f8 --- /dev/null +++ b/test/examples/modular_single_project/agentless-scan.tf @@ -0,0 +1,18 @@ +#--------------------------------------------------------------------------------------------- +# Ensure installation flow for foundational onboarding has been completed before +# installing additional Sysdig features. +#--------------------------------------------------------------------------------------------- + +module "agentless-scan" { + source = "../../../modules/agentless-scan" + project_id = module.onboarding.project_id + sysdig_secure_account_id = module.onboarding.sysdig_secure_account_id +} + +resource "sysdig_secure_cloud_auth_account_feature" "agentless_scanning" { + account_id = module.onboarding.sysdig_secure_account_id + type = "FEATURE_SECURE_AGENTLESS_SCANNING" + enabled = true + components = [module.agentless-scan.agentless_scan_component_id] + depends_on = [module.agentless-scan] +} \ No newline at end of file