diff --git a/.github/workflows/ci-pull-request.yaml b/.github/workflows/ci-pull-request.yaml index 1639f72..9b5f78d 100644 --- a/.github/workflows/ci-pull-request.yaml +++ b/.github/workflows/ci-pull-request.yaml @@ -40,6 +40,7 @@ jobs: - "secure_config_posture_identity_access/organization/main.tf" - "secure_threat_detection/single/main.tf" - "secure_threat_detection/organization/main.tf" + - "agentless-scan/organization/main.tf" steps: - name: Set up Go uses: actions/setup-go@v2 diff --git a/Makefile b/Makefile index 98b7924..946d1ac 100644 --- a/Makefile +++ b/Makefile @@ -18,8 +18,11 @@ deps: $(TFLINT) lint: $(TFLINT) $(MAKE) -C modules lint +fmt-check: fmt fmt: terraform fmt -check -recursive modules +fmt-fix: + terraform fmt -recursive modules clean: find -name ".terraform" -type d | xargs rm -rf diff --git a/modules/services/agentless-scan/README.md b/modules/services/agentless-scan/README.md index d8a3a91..efe23cf 100644 --- a/modules/services/agentless-scan/README.md +++ b/modules/services/agentless-scan/README.md @@ -12,9 +12,16 @@ The following resources will be created on each instrumented project: - 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. -![permission-diagram.png](permission-diagram.png) -Organizational support will be added later on. +## Single Project Setup +![permission_diagram_single](./permissions_diagram_single.png) + +## Organizational Setup + +Set `is_organizatinal=true` together with the `organization_domain=YOUR_DOMAIN` + +![permission_diagram_org](./permissions_diagram_org.png) +

@@ -37,8 +44,8 @@ While on Controlled Availability check with your Sysdig representative. | Name | Version | |------|---------| -| [google](#provider\_google) | >= 4.1, < 5.0 | -| [random](#provider\_random) | >= 3.1, < 4.0 | +| [google](#provider\_google) | 4.84.0 | +| [random](#provider\_random) | 3.6.0 | ## Modules @@ -51,7 +58,11 @@ No modules. | [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_project_iam_binding.admin-account-iam](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | 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 | @@ -59,18 +70,21 @@ No modules. | [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 | | [random_id.suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | 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 | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [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 | | [project\_id](#input\_project\_id) | GCP Project ID | `string` | n/a | yes | -| [worker\_identity](#input\_worker\_identity) | Sysdig provided Identity for the Service Account in charge of performing the host disk analysis | `string` | n/a | yes | -| [role\_name](#input\_role\_name) | Name for the Worker Role on the Customer infrastructure | `string` | `"SysdigAgentlessHostRole"` | no | -| [suffix](#input\_suffix) | By default a random value will be autogenerated.
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) | `string` | `null` | no | +| [role\_name](#input\_role\_name) | Optional. Name for the Worker Role on the Customer infrastructure | `string` | `"SysdigAgentlessHostRole"` | 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 | | [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\_backend](#input\_sysdig\_backend) | Sysdig provided AWS Account designated for the host scan.
One of `sysdig_backend` or `sysdig_account_id`must be provided | `string` | `null` | no | +| [worker\_identity](#input\_worker\_identity) | Sysdig provided Identity for the Service Account in charge of performing the host disk analysis | `string` | n/a | yes | ## Outputs diff --git a/modules/services/agentless-scan/controller.tf b/modules/services/agentless-scan/controller.tf index 18e41ed..492ec65 100644 --- a/modules/services/agentless-scan/controller.tf +++ b/modules/services/agentless-scan/controller.tf @@ -4,34 +4,6 @@ resource "google_service_account" "controller" { display_name = "Sysdig Agentless Host Scanning" } -resource "google_project_iam_custom_role" "controller" { - project = var.project_id - role_id = "${var.role_name}Controller${title(local.suffix)}" - title = "Role for Sysdig Agentless Host Workers" - 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", - ] -} - -resource "google_project_iam_binding" "controller_custom" { - project = var.project_id - role = google_project_iam_custom_role.controller.id - - members = [ - "serviceAccount:${google_service_account.controller.email}", - ] -} - resource "google_iam_workload_identity_pool" "agentless" { workload_identity_pool_id = "sysdig-ahs-${local.suffix}" } diff --git a/modules/services/agentless-scan/controller_org.tf b/modules/services/agentless-scan/controller_org.tf new file mode 100644 index 0000000..e8c6336 --- /dev/null +++ b/modules/services/agentless-scan/controller_org.tf @@ -0,0 +1,18 @@ +resource "google_organization_iam_custom_role" "controller" { + count = local.is_organizational ? 1 : 0 + + org_id = data.google_organization.org[0].org_id + role_id = "${var.role_name}Discovery${title(local.suffix)}" + title = "${var.role_name}, for Host Discovery" + permissions = local.host_discovery_permissions +} + +resource "google_organization_iam_binding" "controller_custom" { + count = local.is_organizational ? 1 : 0 + + org_id = data.google_organization.org[0].org_id + role = google_organization_iam_custom_role.controller[0].id + members = [ + "serviceAccount:${google_service_account.controller.email}", + ] +} \ No newline at end of file diff --git a/modules/services/agentless-scan/controller_single.tf b/modules/services/agentless-scan/controller_single.tf new file mode 100644 index 0000000..b91eb16 --- /dev/null +++ b/modules/services/agentless-scan/controller_single.tf @@ -0,0 +1,18 @@ +resource "google_project_iam_custom_role" "controller" { + count = local.is_organizational ? 0 : 1 + + project = var.project_id + role_id = "${var.role_name}Discovery${title(local.suffix)}" + title = "${var.role_name}, for Host Discovery" + permissions = local.host_discovery_permissions +} + +resource "google_project_iam_binding" "controller_custom" { + count = local.is_organizational ? 0 : 1 + + project = var.project_id + role = google_project_iam_custom_role.controller[0].id + members = [ + "serviceAccount:${google_service_account.controller.email}", + ] +} \ No newline at end of file diff --git a/modules/services/agentless-scan/data.tf b/modules/services/agentless-scan/data.tf index c2d738c..de45ac7 100644 --- a/modules/services/agentless-scan/data.tf +++ b/modules/services/agentless-scan/data.tf @@ -1,3 +1,8 @@ data "google_project" "project" { project_id = var.project_id +} + +data "google_organization" "org" { + count = local.is_organizational ? 1 : 0 + domain = var.organization_domain } \ No newline at end of file diff --git a/modules/services/agentless-scan/locals.tf b/modules/services/agentless-scan/locals.tf index 8a77ae2..366172b 100644 --- a/modules/services/agentless-scan/locals.tf +++ b/modules/services/agentless-scan/locals.tf @@ -1,5 +1,29 @@ locals { suffix = var.suffix == null ? random_id.suffix[0].hex : var.suffix + + is_organizational = var.is_organizational && var.organization_domain != null ? true : false + + 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", + ] } diff --git a/modules/services/agentless-scan/permission-diagram.png b/modules/services/agentless-scan/permission-diagram.png deleted file mode 100644 index a50c2e0..0000000 Binary files a/modules/services/agentless-scan/permission-diagram.png and /dev/null differ diff --git a/modules/services/agentless-scan/permissions_diagram_org.png b/modules/services/agentless-scan/permissions_diagram_org.png new file mode 100644 index 0000000..75a6bfb Binary files /dev/null and b/modules/services/agentless-scan/permissions_diagram_org.png differ diff --git a/modules/services/agentless-scan/permissions_diagram_single.png b/modules/services/agentless-scan/permissions_diagram_single.png new file mode 100644 index 0000000..1f3d87f Binary files /dev/null and b/modules/services/agentless-scan/permissions_diagram_single.png differ diff --git a/modules/services/agentless-scan/variables.tf b/modules/services/agentless-scan/variables.tf index 7485be6..081bfc6 100644 --- a/modules/services/agentless-scan/variables.tf +++ b/modules/services/agentless-scan/variables.tf @@ -1,3 +1,4 @@ +# mandatory variable "project_id" { type = string description = "GCP Project ID" @@ -20,18 +21,28 @@ variable "sysdig_account_id" { default = null } - -# optionals +# optional variable "role_name" { type = string - description = "Name for the Worker Role on the Customer infrastructure" - default = "SysdigAgentlessHostRole" + description = "Optional. Name for Sysdig operations on discovery and scan" + default = "SysdigCloudVM" } - variable "suffix" { type = string - description = "By default a random value will be autogenerated.
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)" + description = "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." default = null -} \ No newline at end of file +} + +variable "is_organizational" { + description = "Optional. Determines whether module must scope whole organization. Otherwise single project will be scoped" + type = bool + default = false +} + +variable "organization_domain" { + type = string + description = "Optional. If `is_organizational=true` is set, its mandatory to specify this value, with the GCP Organization domain. e.g. sysdig.com" + default = null +} diff --git a/modules/services/agentless-scan/worker.tf b/modules/services/agentless-scan/worker.tf deleted file mode 100644 index c958b5d..0000000 --- a/modules/services/agentless-scan/worker.tf +++ /dev/null @@ -1,21 +0,0 @@ -resource "google_project_iam_custom_role" "worker_role" { - project = var.project_id - role_id = "${var.role_name}Worker${title(local.suffix)}" - title = "${var.role_name} - Sysdig Agentless" - permissions = [ - # general stuff - "compute.zoneOperations.get", - # disks - "compute.disks.get", - "compute.disks.useReadOnly", - ] -} - -resource "google_project_iam_binding" "admin-account-iam" { - project = var.project_id - role = google_project_iam_custom_role.worker_role.id - - members = [ - "serviceAccount:${var.worker_identity}", - ] -} \ No newline at end of file diff --git a/modules/services/agentless-scan/worker_org.tf b/modules/services/agentless-scan/worker_org.tf new file mode 100644 index 0000000..0ff5351 --- /dev/null +++ b/modules/services/agentless-scan/worker_org.tf @@ -0,0 +1,18 @@ +resource "google_organization_iam_custom_role" "worker_role" { + count = local.is_organizational ? 1 : 0 + + org_id = data.google_organization.org[0].org_id + role_id = "${var.role_name}Scan${title(local.suffix)}" + title = "${var.role_name}, for Host Scan" + permissions = local.host_scan_permissions +} + +resource "google_organization_iam_binding" "admin_account_iam" { + count = local.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:${var.worker_identity}", + ] +} diff --git a/modules/services/agentless-scan/worker_single.tf b/modules/services/agentless-scan/worker_single.tf new file mode 100644 index 0000000..5b2ffd0 --- /dev/null +++ b/modules/services/agentless-scan/worker_single.tf @@ -0,0 +1,18 @@ +resource "google_project_iam_custom_role" "worker_role" { + count = local.is_organizational ? 0 : 1 + + project = var.project_id + role_id = "${var.role_name}Scan${title(local.suffix)}" + title = "${var.role_name}, for Host Scan" + permissions = local.host_scan_permissions +} + +resource "google_project_iam_binding" "admin_account_iam" { + count = local.is_organizational ? 0 : 1 + + project = var.project_id + role = google_project_iam_custom_role.worker_role[0].id + members = [ + "serviceAccount:${var.worker_identity}", + ] +} diff --git a/test/examples/agentless-scan/README b/test/examples/agentless-scan/README new file mode 100644 index 0000000..30a151a --- /dev/null +++ b/test/examples/agentless-scan/README @@ -0,0 +1,6 @@ + +note; + +- we test the cloud-scan module together with its dependencies on the minimal use-case + - cspm; for discovery + organizational setup (`secure-onboarding` component) + - sysdig provider `sysdig_secure_cloud_auth_account`; for authentication \ No newline at end of file diff --git a/test/examples/agentless-scan/organization/deps_scanning_org.tf b/test/examples/agentless-scan/organization/deps_scanning_org.tf new file mode 100644 index 0000000..b7419cc --- /dev/null +++ b/test/examples/agentless-scan/organization/deps_scanning_org.tf @@ -0,0 +1,9 @@ +# this is required for organizational setup (+cloud-host vm) + +module "organization-posture" { + source = "sysdiglabs/secure/google//modules/services/service-principal" + project_id = "org-child-project-1" + service_account_name = "sysdig-secure-igm6" + is_organizational = true + organization_domain = "draios.com" +} \ No newline at end of file diff --git a/test/examples/agentless-scan/organization/main.tf b/test/examples/agentless-scan/organization/main.tf new file mode 100644 index 0000000..a580f46 --- /dev/null +++ b/test/examples/agentless-scan/organization/main.tf @@ -0,0 +1,14 @@ +provider "google"{ + project="mytestproject" +} + + +module "cloud_host" { + source = "../../../..//modules/services/agentless-scan" + project_id = "mytestproject" + sysdig_account_id = "012345678" + worker_identity = "foo@bar.com" + + is_organizational = true + organization_domain = "myorg.com" +} \ No newline at end of file diff --git a/test/examples/agentless-scan/organization/provider.tf b/test/examples/agentless-scan/organization/provider.tf new file mode 100644 index 0000000..951e8f0 --- /dev/null +++ b/test/examples/agentless-scan/organization/provider.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">=1.0" + + required_providers { + google = { + source = "hashicorp/google" + version = ">= 4.1, < 5.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.1, < 4.0" + } + sysdig = { + source = "sysdiglabs/sysdig" + version = ">= 1.23.1" + } + } +} diff --git a/test/examples/agentless-scan/organization/sysdig_provider.tf b/test/examples/agentless-scan/organization/sysdig_provider.tf new file mode 100644 index 0000000..be3ecd9 --- /dev/null +++ b/test/examples/agentless-scan/organization/sysdig_provider.tf @@ -0,0 +1,49 @@ +provider "sysdig" { + sysdig_secure_url = "https://secure-staging.sysdig.com" + sysdig_secure_api_token = "12124235" +} + +resource "sysdig_secure_cloud_auth_account" "gcp_project" { + enabled = true + provider_id = "org-child-project-1" + provider_type = "PROVIDER_GCP" + + feature { + secure_agentless_scanning { + enabled = true + components = ["COMPONENT_SERVICE_PRINCIPAL/secure-scanning"] + } + } + + + component { + type = "COMPONENT_SERVICE_PRINCIPAL" + instance = "secure-onboarding" + service_principal_metadata = jsonencode({ + gcp = { + key = module.organization-posture.service_account_key + } + }) + } + + + component { + type = "COMPONENT_SERVICE_PRINCIPAL" + instance = "secure-scanning" + service_principal_metadata = jsonencode({ + gcp = { + workload_identity_federation = { + pool_provider_id = module.cloud_host.workload_identity_pool_provider + } + email = module.cloud_host.controller_service_account + } + }) + } + + depends_on = [module.cloud_host, module.organization-posture] +} + +resource "sysdig_secure_organization" "gcp_organization_myproject" { + management_account_id = sysdig_secure_cloud_auth_account.gcp_project.id + depends_on = [module.organization-posture] +} \ No newline at end of file