diff --git a/examples/simple_zonal_with_acm/acm.tf b/examples/simple_zonal_with_acm/acm.tf index 7134381a5d..c72df471e0 100644 --- a/examples/simple_zonal_with_acm/acm.tf +++ b/examples/simple_zonal_with_acm/acm.tf @@ -26,5 +26,7 @@ module "acm" { secret_type = "ssh" - policy_bundles = "https://github.com/GoogleCloudPlatform/acm-policy-controller-library/bundles/policy-essentials-v2022#e4094aacb91a35b0219f6f4cf6a31580e85b3c28" + policy_bundles = ["https://github.com/GoogleCloudPlatform/acm-policy-controller-library/bundles/policy-essentials-v2022#e4094aacb91a35b0219f6f4cf6a31580e85b3c28"] + + create_metrics_gcp_sa = true } diff --git a/modules/acm/README.md b/modules/acm/README.md index cd2a0d54c7..228d3e3631 100644 --- a/modules/acm/README.md +++ b/modules/acm/README.md @@ -67,6 +67,7 @@ data "google_client_config" "default" {} | cluster\_membership\_id | The cluster membership ID. If unset, one will be autogenerated. | `string` | `""` | no | | cluster\_name | GCP cluster Name used to reach cluster and which becomes the cluster name in the Config Sync kubernetes custom resource. | `string` | n/a | yes | | configmanagement\_version | Version of ACM. | `string` | `""` | no | +| create\_metrics\_gcp\_sa | Create a Google service account for ACM metrics writing | `bool` | `false` | no | | create\_ssh\_key | Controls whether a key will be generated for Git authentication | `bool` | `true` | no | | enable\_config\_sync | Whether to enable the ACM Config Sync on the cluster | `bool` | `true` | no | | enable\_fleet\_feature | Whether to enable the ACM feature on the fleet. | `bool` | `true` | no | @@ -78,7 +79,8 @@ data "google_client_config" "default" {} | https\_proxy | URL for the HTTPS proxy to be used when communicating with the Git repo. | `string` | `null` | no | | install\_template\_library | Whether to install the default Policy Controller template library | `bool` | `true` | no | | location | GCP location used to reach cluster. | `string` | n/a | yes | -| policy\_bundles | A space separated list of Policy Controller policy bundles git urls (example: https://github.com/GoogleCloudPlatform/acm-policy-controller-library.git/bundles/policy-essentials-v2022) to install on the cluster. | `string` | `null` | no | +| metrics\_gcp\_sa\_name | The name of the Google service account for ACM metrics writing | `string` | `"acm-metrics-writer"` | no | +| policy\_bundles | A list of Policy Controller policy bundles git urls (example: https://github.com/GoogleCloudPlatform/acm-policy-controller-library.git/bundles/policy-essentials-v2022) to install on the cluster. | `list(string)` | `[]` | no | | policy\_dir | Subfolder containing configs in ACM Git repo. If un-set, uses Config Management default. | `string` | `""` | no | | project\_id | GCP project\_id used to reach cluster. | `string` | n/a | yes | | secret\_type | git authentication secret type, is passed through to ConfigManagement spec.git.secretType. Overriden to value 'ssh' if `create_ssh_key` is true | `string` | `"ssh"` | no | @@ -92,6 +94,7 @@ data "google_client_config" "default" {} | Name | Description | |------|-------------| +| acm\_metrics\_writer\_sa | The ACM metrics writer Service Account | | configmanagement\_version | Version of ACM installed. | | git\_creds\_public | Public key of SSH keypair to allow the Anthos Config Management Operator to authenticate to your Git repository. | | wait | An output to use when you want to depend on cmd finishing | diff --git a/modules/acm/creds.tf b/modules/acm/creds.tf index 0e0ec5b0f2..412ce9c226 100644 --- a/modules/acm/creds.tf +++ b/modules/acm/creds.tf @@ -14,6 +14,11 @@ * limitations under the License. */ +locals { + # GCP service account ids must be <= 30 chars matching regex ^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$ + service_account_name = trimsuffix(substr(var.metrics_gcp_sa_name, 0, 30), "-") +} + resource "tls_private_key" "k8sop_creds" { count = var.create_ssh_key ? 1 : 0 algorithm = "RSA" @@ -22,12 +27,94 @@ resource "tls_private_key" "k8sop_creds" { # Wait for the ACM operator to create the namespace resource "time_sleep" "wait_acm" { - count = (var.create_ssh_key == true || var.ssh_auth_key != null) ? 1 : 0 + count = (var.create_ssh_key == true || var.ssh_auth_key != null || var.enable_policy_controller || var.enable_config_sync) ? 1 : 0 depends_on = [google_gke_hub_feature_membership.main] create_duration = "300s" } +resource "google_service_account_iam_binding" "config-management-monitoring-iam" { + count = var.enable_config_sync && var.create_metrics_gcp_sa ? 1 : 0 + service_account_id = google_service_account.acm_metrics_writer_sa[0].name + role = "roles/iam.workloadIdentityUser" + + members = ["serviceAccount:${var.project_id}.svc.id.goog[config-management-monitoring/default]"] + + depends_on = [google_gke_hub_feature_membership.main] +} + +resource "google_service_account_iam_binding" "gatekeeper-system-iam" { + count = var.enable_policy_controller && var.create_metrics_gcp_sa ? 1 : 0 + service_account_id = google_service_account.acm_metrics_writer_sa[0].name + role = "roles/iam.workloadIdentityUser" + + members = ["serviceAccount:${var.project_id}.svc.id.goog[gatekeeper-system/gatekeeper-admin]"] + + depends_on = [google_gke_hub_feature_membership.main] +} + +module "annotate-sa-config-management-monitoring" { + source = "terraform-google-modules/gcloud/google//modules/kubectl-wrapper" + version = "~> 3.1" + + count = var.enable_config_sync && var.create_metrics_gcp_sa ? 1 : 0 + skip_download = true + cluster_name = var.cluster_name + cluster_location = var.location + project_id = var.project_id + + kubectl_create_command = "kubectl annotate --overwrite sa -n config-management-monitoring default iam.gke.io/gcp-service-account=${google_service_account.acm_metrics_writer_sa[0].email}" + kubectl_destroy_command = "kubectl annotate sa -n config-management-monitoring default iam.gke.io/gcp-service-account-" + + module_depends_on = time_sleep.wait_acm +} + +module "annotate-sa-gatekeeper-system" { + source = "terraform-google-modules/gcloud/google//modules/kubectl-wrapper" + version = "~> 3.1" + + count = var.enable_policy_controller && var.create_metrics_gcp_sa ? 1 : 0 + skip_download = true + cluster_name = var.cluster_name + cluster_location = var.location + project_id = var.project_id + + kubectl_create_command = "kubectl annotate --overwrite sa -n gatekeeper-system gatekeeper-admin iam.gke.io/gcp-service-account=${google_service_account.acm_metrics_writer_sa[0].email}; kubectl rollout restart deployment gatekeeper-controller-manager -n gatekeeper-system" + kubectl_destroy_command = "kubectl annotate sa -n gatekeeper-system gatekeeper-admin iam.gke.io/gcp-service-account-" + + module_depends_on = time_sleep.wait_acm +} + +module "annotate-sa-gatekeeper-system-restart" { + source = "terraform-google-modules/gcloud/google//modules/kubectl-wrapper" + version = "~> 3.1" + + count = var.enable_policy_controller && var.create_metrics_gcp_sa ? 1 : 0 + skip_download = true + cluster_name = var.cluster_name + cluster_location = var.location + project_id = var.project_id + + kubectl_create_command = "kubectl rollout restart deployment gatekeeper-controller-manager -n gatekeeper-system" + kubectl_destroy_command = "" + + module_depends_on = module.annotate-sa-gatekeeper-system +} + +resource "google_service_account" "acm_metrics_writer_sa" { + count = var.create_metrics_gcp_sa ? 1 : 0 + + display_name = "ACM Metrics Writer SA" + account_id = local.service_account_name + project = var.project_id +} + +resource "google_project_iam_member" "acm_metrics_writer_sa_role" { + project = var.project_id + role = "roles/monitoring.metricWriter" + member = "serviceAccount:${google_service_account.acm_metrics_writer_sa[0].email}" +} + resource "kubernetes_secret_v1" "creds" { count = (var.create_ssh_key == true || var.ssh_auth_key != null) ? 1 : 0 depends_on = [time_sleep.wait_acm] @@ -38,6 +125,6 @@ resource "kubernetes_secret_v1" "creds" { } data = { - "local.k8sop_creds_secret_key" = local.private_key + (local.k8sop_creds_secret_key) = local.private_key } } diff --git a/modules/acm/outputs.tf b/modules/acm/outputs.tf index 147908cb30..3e9594cee5 100644 --- a/modules/acm/outputs.tf +++ b/modules/acm/outputs.tf @@ -31,3 +31,8 @@ output "wait" { google_gke_hub_feature_membership.main ] } + +output "acm_metrics_writer_sa" { + description = "The ACM metrics writer Service Account" + value = google_service_account.acm_metrics_writer_sa[0].email +} diff --git a/modules/acm/policy_bundles.tf b/modules/acm/policy_bundles.tf index 17df12a694..b03987e867 100644 --- a/modules/acm/policy_bundles.tf +++ b/modules/acm/policy_bundles.tf @@ -14,20 +14,16 @@ * limitations under the License. */ -locals { - policy_bundles = var.policy_bundles != null ? var.policy_bundles : "" -} - module "policy_bundles" { source = "terraform-google-modules/gcloud/google//modules/kubectl-wrapper" version = "~> 3.1" + for_each = toset(var.policy_bundles) project_id = var.project_id cluster_name = var.cluster_name cluster_location = var.location - enabled = (var.policy_bundles != null) && var.enable_policy_controller ? true : false - kubectl_create_command = "kubectl apply -k ${local.policy_bundles}" - kubectl_destroy_command = "kubectl delete -k ${local.policy_bundles}" + kubectl_create_command = "kubectl apply -k ${each.key}" + kubectl_destroy_command = "kubectl delete -k ${each.key}" module_depends_on = [time_sleep.wait_acm] } diff --git a/modules/acm/variables.tf b/modules/acm/variables.tf index 595ee6047b..dfb4f6d2fd 100644 --- a/modules/acm/variables.tf +++ b/modules/acm/variables.tf @@ -148,7 +148,19 @@ variable "enable_referential_rules" { } variable "policy_bundles" { - description = "A space separated list of Policy Controller policy bundles git urls (example: https://github.com/GoogleCloudPlatform/acm-policy-controller-library.git/bundles/policy-essentials-v2022) to install on the cluster." + description = "A list of Policy Controller policy bundles git urls (example: https://github.com/GoogleCloudPlatform/acm-policy-controller-library.git/bundles/policy-essentials-v2022) to install on the cluster." + type = list(string) + default = [] +} + +variable "create_metrics_gcp_sa" { + description = "Create a Google service account for ACM metrics writing" + type = bool + default = false +} + +variable "metrics_gcp_sa_name" { + description = "The name of the Google service account for ACM metrics writing" type = string - default = null + default = "acm-metrics-writer" } diff --git a/test/integration/simple_zonal/controls/acm.rb b/test/integration/simple_zonal/controls/acm.rb index 45663f176d..225f2bf963 100644 --- a/test/integration/simple_zonal/controls/acm.rb +++ b/test/integration/simple_zonal/controls/acm.rb @@ -1,4 +1,4 @@ -# Copyright 2019 Google LLC +# Copyright 2019-2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -48,5 +48,12 @@ expect(namespace).not_to be nil end end + + describe "gatekeeper-system namespace" do + let(:namespace) { client.get_namespace("gatekeeper-system") } + it "should exist" do + expect(namespace).not_to be nil + end + end end end