From 797ef3a8f484f515329ed5acc5c85767f98075f1 Mon Sep 17 00:00:00 2001 From: Austin Mitchell Date: Tue, 3 Dec 2024 11:18:35 -0800 Subject: [PATCH 01/14] add oidc_connector module --- aws_gke_oidc_connector/.terraform.lock.hcl | 63 ++++++++++++++++++ aws_gke_oidc_connector/README.md | 77 ++++++++++++++++++++++ aws_gke_oidc_connector/main.tf | 61 +++++++++++++++++ aws_gke_oidc_connector/outputs.tf | 4 ++ aws_gke_oidc_connector/providers.tf | 3 + aws_gke_oidc_connector/variables.tf | 49 ++++++++++++++ 6 files changed, 257 insertions(+) create mode 100644 aws_gke_oidc_connector/.terraform.lock.hcl create mode 100644 aws_gke_oidc_connector/README.md create mode 100644 aws_gke_oidc_connector/main.tf create mode 100644 aws_gke_oidc_connector/outputs.tf create mode 100644 aws_gke_oidc_connector/providers.tf create mode 100644 aws_gke_oidc_connector/variables.tf diff --git a/aws_gke_oidc_connector/.terraform.lock.hcl b/aws_gke_oidc_connector/.terraform.lock.hcl new file mode 100644 index 00000000..6d6f71ab --- /dev/null +++ b/aws_gke_oidc_connector/.terraform.lock.hcl @@ -0,0 +1,63 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.76.0" + constraints = ">= 4.0.0" + hashes = [ + "h1:JSLR3JP9naVcnH0PHcDwwHr3aQB9vlW0+b8HQma1GpU=", + "zh:05b2a0d25fc07576f6698d4840d0d2ae2599484c49f1b911ea1154584557bc13", + "zh:1b22dd1d9c482739e133adb996a9c8b285ca7d978d0fe04deaa5588eba5d254c", + "zh:216088c8800e7b8d7eff7b1a822317bc6faec64f27946ffd22bb3494ac4175cb", + "zh:43e994112b1484bf49945c4885aa2fee32486c9a5d64b9146bbd6f309f24e332", + "zh:46a28ba800f176eef500f998217bccc331605ef05f11abb1728f727a81f3a8b0", + "zh:4fad2743174a600da76a0cceeec2fef8399a18d880ba8929d811cd5cea1b5dee", + "zh:5c42a2c1438cd7533456026f52b562715664490711fdea809f44610a7565c145", + "zh:792d4fd4be434682e4540d2579505c7f11f39d0efe1d12ee2761ed0d46c8cd51", + "zh:7bb5f9f87c9da6d62d6f89504f01a9d6d2f19dcaa0efc46ea51ebdc4bb6fd536", + "zh:81cdbd97f81b1110fce793944d5668a4389904979eb7d178d3142a6b0e175e5e", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:ab4b881eb0f3812b702aaecf921c5c16bbcc33d61d668be4d72d6da9c57ded85", + "zh:c1d9d1166fd948845614deef81f3197568d0d3c2a03b8b97fff308ebc59043f9", + "zh:cda7530f2c01434e483d3faf62fc0685295e7f844176aa38df1ba65fa6a4407a", + "zh:fdad558b1c41aa68123d0da82cc0d65bc86d09eaa1ab1d3a167ec3bce0fc0c66", + ] +} + +provider "registry.terraform.io/hashicorp/google" { + version = "6.11.2" + hashes = [ + "h1:H71tUF5icQMV//ccF7TGKdYA39mqiu2neAxB7YLHW3o=", + "zh:00dc7de86e134064eebaa8a9a792de466bbbea090c0f83e5276aa421bd820740", + "zh:07efe84ab4557c2654e275ad0526a156aa8afbab245a1b6c0b25807d36b529ba", + "zh:17c029263afaf24e09078a1e65df4e12562304cc05f2f728b141e211f8959dc1", + "zh:381c13a92e059a8167d08ade6ed3248e61e192214ed49d401a9efb46ff707d2d", + "zh:761a1ba29e56245a553eef936eb4b16a10c1ec596b0eadd6c9d5951152bc27ee", + "zh:7956a1793ead6071c5787e25611f21f8272681ade94a47199483df03671a9b3a", + "zh:83fd6d976253ab911c947a79b9fa9450179605660727c0765c85b00ef054ec9b", + "zh:94a6566cc7db6a6f19cc6e05a30a7d4b6a8c4c668c8b9e14db17d2ee34b0678e", + "zh:a1a44bf143c8ebdcb7994f19897591fcce4d7480087daddaecaf0c587ba28d69", + "zh:d7f0e21c61638268267bd75160c01ec9f2339e357696159c9c7e2787310a0400", + "zh:e5b8b1f822549b0c1eb2c999e90548465774ee3bff15d284020a3d84c35f3af6", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.0.6" + hashes = [ + "h1:dYSb3V94K5dDMtrBRLPzBpkMTPn+3cXZ/kIJdtFL+2M=", + "zh:10de0d8af02f2e578101688fd334da3849f56ea91b0d9bd5b1f7a243417fdda8", + "zh:37fc01f8b2bc9d5b055dc3e78bfd1beb7c42cfb776a4c81106e19c8911366297", + "zh:4578ca03d1dd0b7f572d96bd03f744be24c726bfd282173d54b100fd221608bb", + "zh:6c475491d1250050765a91a493ef330adc24689e8837a0f07da5a0e1269e11c1", + "zh:81bde94d53cdababa5b376bbc6947668be4c45ab655de7aa2e8e4736dfd52509", + "zh:abdce260840b7b050c4e401d4f75c7a199fafe58a8b213947a258f75ac18b3e8", + "zh:b754cebfc5184873840f16a642a7c9ef78c34dc246a8ae29e056c79939963c7a", + "zh:c928b66086078f9917aef0eec15982f2e337914c5c4dbc31dd4741403db7eb18", + "zh:cded27bee5f24de6f2ee0cfd1df46a7f88e84aaffc2ecbf3ff7094160f193d50", + "zh:d65eb3867e8f69aaf1b8bb53bd637c99c6b649ba3db16ded50fa9a01076d1a27", + "zh:ecb0c8b528c7a619fa71852bb3fb5c151d47576c5aab2bf3af4db52588722eeb", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/aws_gke_oidc_connector/README.md b/aws_gke_oidc_connector/README.md new file mode 100644 index 00000000..175c1c21 --- /dev/null +++ b/aws_gke_oidc_connector/README.md @@ -0,0 +1,77 @@ + +# AWS-GKE OIDC Connector +This module will create an AWS role + an OIDC configuration that will allow a specified GKE service account to assume it. + +After creating these resources, add the following environment variables, volumes, and volume mounts to your pod definition: +* env: +``` +- name: AWS_REGION + value: +- name: AWS_ROLE_ARN + value: +- name: AWS_WEB_IDENTITY_TOKEN_FILE + value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token +- name: AWS_STS_REGIONAL_ENDPOINTS + value: regional +``` +* volumes: +``` +- name: aws-token + projected: + defaultMode: 420 + sources: + - serviceAccountToken: + audience: sts.amazonaws.com + expirationSeconds: 86400 + path: token +``` +* volumeMounts: +``` +- mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount/ + name: aws-token +``` + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.76.0 | +| [tls](#provider\_tls) | 4.0.6 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [iam\_assumable\_role\_for\_oidc](#module\_iam\_assumable\_role\_for\_oidc) | terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc | ~> v5.9 | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_openid_connect_provider.gke_oidc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource | +| [tls_certificate.gke_oidc](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/certificate) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aws\_region](#input\_aws\_region) | AWS region | `string` | n/a | yes | +| [gcp\_project\_id](#input\_gcp\_project\_id) | ID of the GKE cluster's project | `string` | n/a | yes | +| [gcp\_region](#input\_gcp\_region) | GKE cluster's GCP region | `string` | n/a | yes | +| [gke\_cluster\_name](#input\_gke\_cluster\_name) | GKE cluster name | `string` | n/a | yes | +| [gke\_namespace](#input\_gke\_namespace) | Namespace for GKE workload | `string` | n/a | yes | +| [gke\_service\_account](#input\_gke\_service\_account) | GKE service account to grant role assumption privilleges | `string` | n/a | yes | +| [iam\_policy\_arns](#input\_iam\_policy\_arns) | One or more policy arns to attach to created AWS role | `list(string)` | n/a | yes | +| [role\_name](#input\_role\_name) | Name to give the AWS role | `string` | n/a | yes | +| [tags](#input\_tags) | Tags to apply to all AWS resources | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [role\_arn](#output\_role\_arn) | ARN for the GKE-AWS connector role | + diff --git a/aws_gke_oidc_connector/main.tf b/aws_gke_oidc_connector/main.tf new file mode 100644 index 00000000..a6b72114 --- /dev/null +++ b/aws_gke_oidc_connector/main.tf @@ -0,0 +1,61 @@ +/* + * # AWS-GKE OIDC Connector + * This module will create an AWS role + an OIDC configuration that will allow a specified GKE service account to assume it. + * + * After creating these resources, add the following environment variables, volumes, and volume mounts to your pod definition: + * * env: + * ``` + * - name: AWS_REGION + * value: + * - name: AWS_ROLE_ARN + * value: + * - name: AWS_WEB_IDENTITY_TOKEN_FILE + * value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token + * - name: AWS_STS_REGIONAL_ENDPOINTS + * value: regional + * ``` + * * volumes: + * ``` + * - name: aws-token + * projected: + * defaultMode: 420 + * sources: + * - serviceAccountToken: + * audience: sts.amazonaws.com + * expirationSeconds: 86400 + * path: token + * ``` + * * volumeMounts: + * ``` + * - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount/ + * name: aws-token + * ``` +*/ +module "iam_assumable_role_for_oidc" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "~> v5.9" + create_role = true + role_name = var.role_name + role_description = "Role for ${var.gke_cluster_name}/${var.gke_namespace}/${var.gke_service_account} to assume" + provider_url = replace(aws_iam_openid_connect_provider.gke_oidc.url, "https://", "") + role_policy_arns = var.iam_policy_arns + oidc_fully_qualified_subjects = ["system:serviceaccount:${var.gke_namespace}:${var.gke_service_account}"] + tags = var.tags +} + +resource "aws_iam_openid_connect_provider" "gke_oidc" { + url = "https://container.googleapis.com/v1/projects/${var.gcp_project_id}/locations/${var.gcp_region}/clusters/${var.gke_cluster_name}" + + client_id_list = [ + "sts.amazonaws.com" + ] + + thumbprint_list = [ + data.tls_certificate.gke_oidc.certificates.0.sha1_fingerprint + ] +} + +data "tls_certificate" "gke_oidc" { + url = "https://container.googleapis.com/v1/projects/${var.gcp_project_id}/locations/${var.gcp_region}/clusters/${var.gke_cluster_name}" +} + diff --git a/aws_gke_oidc_connector/outputs.tf b/aws_gke_oidc_connector/outputs.tf new file mode 100644 index 00000000..da87ea5a --- /dev/null +++ b/aws_gke_oidc_connector/outputs.tf @@ -0,0 +1,4 @@ +output "role_arn" { + description = "ARN for the GKE-AWS connector role" + value = module.iam_assumable_role_for_oidc.iam_role_name +} diff --git a/aws_gke_oidc_connector/providers.tf b/aws_gke_oidc_connector/providers.tf new file mode 100644 index 00000000..c9d7ccbd --- /dev/null +++ b/aws_gke_oidc_connector/providers.tf @@ -0,0 +1,3 @@ +provider "aws" { + region = var.aws_region +} diff --git a/aws_gke_oidc_connector/variables.tf b/aws_gke_oidc_connector/variables.tf new file mode 100644 index 00000000..72586ed9 --- /dev/null +++ b/aws_gke_oidc_connector/variables.tf @@ -0,0 +1,49 @@ +### Required + +variable "iam_policy_arns" { + description = "One or more policy arns to attach to created AWS role" + type = list(string) +} + +variable "role_name" { + description = "Name to give the AWS role" + type = string +} + +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "gcp_region" { + description = "GKE cluster's GCP region" + type = string +} + +variable "gcp_project_id" { + description = "ID of the GKE cluster's project" + type = string +} + +variable "gke_cluster_name" { + description = "GKE cluster name" + type = string +} + +variable "gke_namespace" { + description = "Namespace for GKE workload" + type = string +} + +variable "gke_service_account" { + description = "GKE service account to grant role assumption privilleges" + type = string +} + +### Optional + +variable "tags" { + description = "Tags to apply to all AWS resources" + type = map(string) + default = {} +} From 79395328b74c6e88fa8e546841667f1e0e4cd039 Mon Sep 17 00:00:00 2001 From: Austin Mitchell Date: Thu, 5 Dec 2024 12:58:46 -0800 Subject: [PATCH 02/14] split into 2 modules --- .../.terraform.lock.hcl | 19 -------- aws_gke_oidc_config/README.md | 43 ++++++++++++++++++ aws_gke_oidc_config/examples/main.tf | 6 +++ aws_gke_oidc_config/main.tf | 25 +++++++++++ aws_gke_oidc_config/variables.tf | 16 +++++++ aws_gke_oidc_connector/providers.tf | 3 -- .../README.md | 19 ++++---- .../examples/.terraform.lock.hcl | 44 +++++++++++++++++++ .../examples/role_and_config/main.tf | 23 ++++++++++ .../examples/role_and_config/providers.tf | 13 ++++++ .../examples/role_with_policy/main.tf | 43 ++++++++++++++++++ .../examples/role_with_policy/providers.tf | 13 ++++++ .../main.tf | 25 ++++------- .../outputs.tf | 0 .../variables.tf | 14 +++--- 15 files changed, 251 insertions(+), 55 deletions(-) rename {aws_gke_oidc_connector => aws_gke_oidc_config}/.terraform.lock.hcl (70%) create mode 100644 aws_gke_oidc_config/README.md create mode 100644 aws_gke_oidc_config/examples/main.tf create mode 100644 aws_gke_oidc_config/main.tf create mode 100644 aws_gke_oidc_config/variables.tf delete mode 100644 aws_gke_oidc_connector/providers.tf rename {aws_gke_oidc_connector => aws_gke_oidc_role}/README.md (79%) create mode 100644 aws_gke_oidc_role/examples/.terraform.lock.hcl create mode 100644 aws_gke_oidc_role/examples/role_and_config/main.tf create mode 100644 aws_gke_oidc_role/examples/role_and_config/providers.tf create mode 100644 aws_gke_oidc_role/examples/role_with_policy/main.tf create mode 100644 aws_gke_oidc_role/examples/role_with_policy/providers.tf rename {aws_gke_oidc_connector => aws_gke_oidc_role}/main.tf (71%) rename {aws_gke_oidc_connector => aws_gke_oidc_role}/outputs.tf (100%) rename {aws_gke_oidc_connector => aws_gke_oidc_role}/variables.tf (87%) diff --git a/aws_gke_oidc_connector/.terraform.lock.hcl b/aws_gke_oidc_config/.terraform.lock.hcl similarity index 70% rename from aws_gke_oidc_connector/.terraform.lock.hcl rename to aws_gke_oidc_config/.terraform.lock.hcl index 6d6f71ab..3465dbca 100644 --- a/aws_gke_oidc_connector/.terraform.lock.hcl +++ b/aws_gke_oidc_config/.terraform.lock.hcl @@ -24,25 +24,6 @@ provider "registry.terraform.io/hashicorp/aws" { ] } -provider "registry.terraform.io/hashicorp/google" { - version = "6.11.2" - hashes = [ - "h1:H71tUF5icQMV//ccF7TGKdYA39mqiu2neAxB7YLHW3o=", - "zh:00dc7de86e134064eebaa8a9a792de466bbbea090c0f83e5276aa421bd820740", - "zh:07efe84ab4557c2654e275ad0526a156aa8afbab245a1b6c0b25807d36b529ba", - "zh:17c029263afaf24e09078a1e65df4e12562304cc05f2f728b141e211f8959dc1", - "zh:381c13a92e059a8167d08ade6ed3248e61e192214ed49d401a9efb46ff707d2d", - "zh:761a1ba29e56245a553eef936eb4b16a10c1ec596b0eadd6c9d5951152bc27ee", - "zh:7956a1793ead6071c5787e25611f21f8272681ade94a47199483df03671a9b3a", - "zh:83fd6d976253ab911c947a79b9fa9450179605660727c0765c85b00ef054ec9b", - "zh:94a6566cc7db6a6f19cc6e05a30a7d4b6a8c4c668c8b9e14db17d2ee34b0678e", - "zh:a1a44bf143c8ebdcb7994f19897591fcce4d7480087daddaecaf0c587ba28d69", - "zh:d7f0e21c61638268267bd75160c01ec9f2339e357696159c9c7e2787310a0400", - "zh:e5b8b1f822549b0c1eb2c999e90548465774ee3bff15d284020a3d84c35f3af6", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} - provider "registry.terraform.io/hashicorp/tls" { version = "4.0.6" hashes = [ diff --git a/aws_gke_oidc_config/README.md b/aws_gke_oidc_config/README.md new file mode 100644 index 00000000..a8e65821 --- /dev/null +++ b/aws_gke_oidc_config/README.md @@ -0,0 +1,43 @@ + +# AWS-GKE OIDC Config +This module will create an AWS OIDC config that creates a trust relationship between a GKE cluster & AWS account. + +Once this module has been invoked for a given account + GKE cluster, the `aws_gke_oidc_role` module can be used +to create any number of roles to be used by GKE workloads. + +See the `aws_gke_oidc_role` for complete usage instructions + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.76.0 | +| [tls](#provider\_tls) | 4.0.6 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_openid_connect_provider.gke_oidc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource | +| [tls_certificate.gke_oidc](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/certificate) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [gcp\_project\_id](#input\_gcp\_project\_id) | ID of the GKE cluster's project | `string` | n/a | yes | +| [gcp\_region](#input\_gcp\_region) | GKE cluster's GCP region | `string` | n/a | yes | +| [gke\_cluster\_name](#input\_gke\_cluster\_name) | GKE cluster name | `string` | n/a | yes | + +## Outputs + +No outputs. + \ No newline at end of file diff --git a/aws_gke_oidc_config/examples/main.tf b/aws_gke_oidc_config/examples/main.tf new file mode 100644 index 00000000..33508d4a --- /dev/null +++ b/aws_gke_oidc_config/examples/main.tf @@ -0,0 +1,6 @@ +module "oidc_config" { + source = "../." + gcp_region = "us-west1" + gcp_project_id = "moz-fx-platform-mgmt-global" + gke_cluster_name = "global-platform-admin-mgmt" +} diff --git a/aws_gke_oidc_config/main.tf b/aws_gke_oidc_config/main.tf new file mode 100644 index 00000000..41d69e1f --- /dev/null +++ b/aws_gke_oidc_config/main.tf @@ -0,0 +1,25 @@ +/* + * # AWS-GKE OIDC Config + * This module will create an AWS OIDC config that creates a trust relationship between a GKE cluster & AWS account. + * + * Once this module has been invoked for a given account + GKE cluster, the `aws_gke_oidc_role` module can be used + * to create any number of roles to be used by GKE workloads. + * + * See the `aws_gke_oidc_role` for complete usage instructions +*/ + +resource "aws_iam_openid_connect_provider" "gke_oidc" { + url = "https://container.googleapis.com/v1/projects/${var.gcp_project_id}/locations/${var.gcp_region}/clusters/${var.gke_cluster_name}" + + client_id_list = [ + "sts.amazonaws.com" + ] + + thumbprint_list = [ + data.tls_certificate.gke_oidc.certificates.0.sha1_fingerprint + ] +} + +data "tls_certificate" "gke_oidc" { + url = "https://container.googleapis.com/v1/projects/${var.gcp_project_id}/locations/${var.gcp_region}/clusters/${var.gke_cluster_name}" +} diff --git a/aws_gke_oidc_config/variables.tf b/aws_gke_oidc_config/variables.tf new file mode 100644 index 00000000..0a24bd16 --- /dev/null +++ b/aws_gke_oidc_config/variables.tf @@ -0,0 +1,16 @@ +### Required + +variable "gcp_region" { + description = "GKE cluster's GCP region" + type = string +} + +variable "gcp_project_id" { + description = "ID of the GKE cluster's project" + type = string +} + +variable "gke_cluster_name" { + description = "GKE cluster name" + type = string +} diff --git a/aws_gke_oidc_connector/providers.tf b/aws_gke_oidc_connector/providers.tf deleted file mode 100644 index c9d7ccbd..00000000 --- a/aws_gke_oidc_connector/providers.tf +++ /dev/null @@ -1,3 +0,0 @@ -provider "aws" { - region = var.aws_region -} diff --git a/aws_gke_oidc_connector/README.md b/aws_gke_oidc_role/README.md similarity index 79% rename from aws_gke_oidc_connector/README.md rename to aws_gke_oidc_role/README.md index 175c1c21..c9f5541a 100644 --- a/aws_gke_oidc_connector/README.md +++ b/aws_gke_oidc_role/README.md @@ -1,6 +1,9 @@ -# AWS-GKE OIDC Connector -This module will create an AWS role + an OIDC configuration that will allow a specified GKE service account to assume it. +# AWS-GKE OIDC Role +This module will create an AWS role that will allow a specified GKE service account to assume it. + +Requires that `../aws_gke_oidc_config` has been applied for a given AWS account + GKE cluster combination +if you get an error about the `aws_iam_openid_connect_provider` data source being missing, apply that module. After creating these resources, add the following environment variables, volumes, and volume mounts to your pod definition: * env: @@ -39,8 +42,7 @@ No requirements. | Name | Version | |------|---------| -| [aws](#provider\_aws) | 5.76.0 | -| [tls](#provider\_tls) | 4.0.6 | +| [aws](#provider\_aws) | n/a | ## Modules @@ -52,26 +54,25 @@ No requirements. | Name | Type | |------|------| -| [aws_iam_openid_connect_provider.gke_oidc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource | -| [tls_certificate.gke_oidc](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/certificate) | data source | +| [aws_iam_openid_connect_provider.gke_oidc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_openid_connect_provider) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [aws\_region](#input\_aws\_region) | AWS region | `string` | n/a | yes | -| [gcp\_project\_id](#input\_gcp\_project\_id) | ID of the GKE cluster's project | `string` | n/a | yes | +| [gcp\_project\_id](#input\_gcp\_project\_id) | GKE cluster's project ID | `string` | n/a | yes | | [gcp\_region](#input\_gcp\_region) | GKE cluster's GCP region | `string` | n/a | yes | | [gke\_cluster\_name](#input\_gke\_cluster\_name) | GKE cluster name | `string` | n/a | yes | | [gke\_namespace](#input\_gke\_namespace) | Namespace for GKE workload | `string` | n/a | yes | | [gke\_service\_account](#input\_gke\_service\_account) | GKE service account to grant role assumption privilleges | `string` | n/a | yes | | [iam\_policy\_arns](#input\_iam\_policy\_arns) | One or more policy arns to attach to created AWS role | `list(string)` | n/a | yes | | [role\_name](#input\_role\_name) | Name to give the AWS role | `string` | n/a | yes | -| [tags](#input\_tags) | Tags to apply to all AWS resources | `map(string)` | `{}` | no | +| [tags](#input\_tags) | Tags to apply to the AWS role | `map(string)` | `{}` | no | ## Outputs | Name | Description | |------|-------------| | [role\_arn](#output\_role\_arn) | ARN for the GKE-AWS connector role | - + \ No newline at end of file diff --git a/aws_gke_oidc_role/examples/.terraform.lock.hcl b/aws_gke_oidc_role/examples/.terraform.lock.hcl new file mode 100644 index 00000000..64dc9159 --- /dev/null +++ b/aws_gke_oidc_role/examples/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.80.0" + constraints = ">= 4.0.0" + hashes = [ + "h1:N5Wfsf4xe5DJfSeo0G/ulkIxzyfmUIoSj/hAiZ2DaKU=", + "zh:0b1655e39639d60f2de2860a5df8642f9556ba0ca04529c1b861fde4935cb0df", + "zh:13dc0155e0a11edceee29ce687fc04c5a5a85f3324c67556472713cfd52e5807", + "zh:180f6cb2be44be14cfe329e0649121b774319f083b6e4e8fb749f85090d73121", + "zh:3158d44b74c67465f7f19f22c42b643840c8d18ce833e2ec86e8d93085b06926", + "zh:6351b5bf7cde5dc83e926944891570636069e05ca43341f4d1feda67773469bf", + "zh:6fa9db1532096ba50e842d369b6688979306d2295c7ead49b8a266b0d60962cc", + "zh:85d2fe75def7619ff2cc29102048875039cad088fafb62ecc14c3763e7b1e9d9", + "zh:9028d653f1d7341c6dfe2afe961b6541581e9043a474eac2faf90e6426a24f6d", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9c4e248c442bc60f07f9f089e5361f19936833370dc3c04b27916672b765f0e1", + "zh:a710a3979596e3f3938c3ec6bb748e604724d3a4afa96ed2c14f0a245cc41a11", + "zh:c27936bdf447779d0c0833bf52a9ef618985f5ea8e3e243d6266513520ca31c4", + "zh:c7681134a123486e72eaedc3f8d2d75e267dbbfd45fa7de5aea8f757af57f89b", + "zh:ea717ebad3561fd02591f9eecf30f3df5635405556fba2bdbf29fd42691bebac", + "zh:f4e1e8f23c58c3e8f4371f9c3379a723ab4155246e6b6daad8eb99e16666b2cb", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.0.6" + hashes = [ + "h1:dYSb3V94K5dDMtrBRLPzBpkMTPn+3cXZ/kIJdtFL+2M=", + "zh:10de0d8af02f2e578101688fd334da3849f56ea91b0d9bd5b1f7a243417fdda8", + "zh:37fc01f8b2bc9d5b055dc3e78bfd1beb7c42cfb776a4c81106e19c8911366297", + "zh:4578ca03d1dd0b7f572d96bd03f744be24c726bfd282173d54b100fd221608bb", + "zh:6c475491d1250050765a91a493ef330adc24689e8837a0f07da5a0e1269e11c1", + "zh:81bde94d53cdababa5b376bbc6947668be4c45ab655de7aa2e8e4736dfd52509", + "zh:abdce260840b7b050c4e401d4f75c7a199fafe58a8b213947a258f75ac18b3e8", + "zh:b754cebfc5184873840f16a642a7c9ef78c34dc246a8ae29e056c79939963c7a", + "zh:c928b66086078f9917aef0eec15982f2e337914c5c4dbc31dd4741403db7eb18", + "zh:cded27bee5f24de6f2ee0cfd1df46a7f88e84aaffc2ecbf3ff7094160f193d50", + "zh:d65eb3867e8f69aaf1b8bb53bd637c99c6b649ba3db16ded50fa9a01076d1a27", + "zh:ecb0c8b528c7a619fa71852bb3fb5c151d47576c5aab2bf3af4db52588722eeb", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/aws_gke_oidc_role/examples/role_and_config/main.tf b/aws_gke_oidc_role/examples/role_and_config/main.tf new file mode 100644 index 00000000..aba0e8cf --- /dev/null +++ b/aws_gke_oidc_role/examples/role_and_config/main.tf @@ -0,0 +1,23 @@ +/* +* Example of creating both an OIDC config & role to utilize it +*/ + +module "oidc_config" { + source = "../../aws_gke_oidc_config/" + gcp_region = "us-west1" + gcp_project_id = "moz-fx-platform-mgmt-global" + gke_cluster_name = "global-platform-admin-mgmt" +} + +module "oidc_role" { + depends_on = [module.oidc_config] + source = ".././" + role_name = "opst-1509-oidc-test" + aws_region = "us-west-1" + gcp_region = "us-west1" + gke_cluster_name = "global-platform-admin-mgmt" + gcp_project_id = "moz-fx-platform-mgmt-global" + gke_namespace = "atlantis-sandbox" + gke_service_account = "atlantis-sandbox" + iam_policy_arns = [] +} diff --git a/aws_gke_oidc_role/examples/role_and_config/providers.tf b/aws_gke_oidc_role/examples/role_and_config/providers.tf new file mode 100644 index 00000000..b2c3630a --- /dev/null +++ b/aws_gke_oidc_role/examples/role_and_config/providers.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +# Configure the AWS Provider +provider "aws" { + region = "us-west-2" +} diff --git a/aws_gke_oidc_role/examples/role_with_policy/main.tf b/aws_gke_oidc_role/examples/role_with_policy/main.tf new file mode 100644 index 00000000..e5aa27be --- /dev/null +++ b/aws_gke_oidc_role/examples/role_with_policy/main.tf @@ -0,0 +1,43 @@ +/* +* Example of creating just the role. The module will infer the OIDC url from vars. +* Attaches an inline ec2 policy & managed AWS ViewOnly policy. +* Resulting role will be assumable by the "foo" service account in the "bar" namespace of the "baz" cluster +*/ + +module "oidc_role" { + depends_on = [module.oidc_config] + source = ".././" + role_name = "oidc-example-role" + aws_region = "us-west-1" + gcp_region = "us-west1" + gke_cluster_name = "baz" + gcp_project_id = "example-project" + gke_namespace = "bar" + gke_service_account = "foo" + iam_policy_arns = [aws_iam_policy.example_policy, data.aws_iam_policy.view_only] +} + +resource "aws_iam_policy" "example_policy" { + name = "example_policy" + path = "/" + description = "My example policy" + + # Terraform's "jsonencode" function converts a + # Terraform expression result to valid JSON syntax. + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "ec2:Describe*", + ] + Effect = "Allow" + Resource = "*" + }, + ] + }) +} + +data "aws_iam_policy" "view_only" { + arn = "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess" +} diff --git a/aws_gke_oidc_role/examples/role_with_policy/providers.tf b/aws_gke_oidc_role/examples/role_with_policy/providers.tf new file mode 100644 index 00000000..b2c3630a --- /dev/null +++ b/aws_gke_oidc_role/examples/role_with_policy/providers.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +# Configure the AWS Provider +provider "aws" { + region = "us-west-2" +} diff --git a/aws_gke_oidc_connector/main.tf b/aws_gke_oidc_role/main.tf similarity index 71% rename from aws_gke_oidc_connector/main.tf rename to aws_gke_oidc_role/main.tf index a6b72114..5ee26582 100644 --- a/aws_gke_oidc_connector/main.tf +++ b/aws_gke_oidc_role/main.tf @@ -1,6 +1,9 @@ /* - * # AWS-GKE OIDC Connector - * This module will create an AWS role + an OIDC configuration that will allow a specified GKE service account to assume it. + * # AWS-GKE OIDC Role + * This module will create an AWS role that will allow a specified GKE service account to assume it. + * + * Requires that `../aws_gke_oidc_config` has been applied for a given AWS account + GKE cluster combination + * if you get an error about the `aws_iam_openid_connect_provider` data source being missing, apply that module. * * After creating these resources, add the following environment variables, volumes, and volume mounts to your pod definition: * * env: @@ -31,31 +34,19 @@ * name: aws-token * ``` */ + module "iam_assumable_role_for_oidc" { source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" version = "~> v5.9" create_role = true role_name = var.role_name role_description = "Role for ${var.gke_cluster_name}/${var.gke_namespace}/${var.gke_service_account} to assume" - provider_url = replace(aws_iam_openid_connect_provider.gke_oidc.url, "https://", "") + provider_url = replace(data.aws_iam_openid_connect_provider.gke_oidc.url, "https://", "") role_policy_arns = var.iam_policy_arns oidc_fully_qualified_subjects = ["system:serviceaccount:${var.gke_namespace}:${var.gke_service_account}"] tags = var.tags } -resource "aws_iam_openid_connect_provider" "gke_oidc" { - url = "https://container.googleapis.com/v1/projects/${var.gcp_project_id}/locations/${var.gcp_region}/clusters/${var.gke_cluster_name}" - - client_id_list = [ - "sts.amazonaws.com" - ] - - thumbprint_list = [ - data.tls_certificate.gke_oidc.certificates.0.sha1_fingerprint - ] -} - -data "tls_certificate" "gke_oidc" { +data "aws_iam_openid_connect_provider" "gke_oidc" { url = "https://container.googleapis.com/v1/projects/${var.gcp_project_id}/locations/${var.gcp_region}/clusters/${var.gke_cluster_name}" } - diff --git a/aws_gke_oidc_connector/outputs.tf b/aws_gke_oidc_role/outputs.tf similarity index 100% rename from aws_gke_oidc_connector/outputs.tf rename to aws_gke_oidc_role/outputs.tf diff --git a/aws_gke_oidc_connector/variables.tf b/aws_gke_oidc_role/variables.tf similarity index 87% rename from aws_gke_oidc_connector/variables.tf rename to aws_gke_oidc_role/variables.tf index 72586ed9..3b8a9c64 100644 --- a/aws_gke_oidc_connector/variables.tf +++ b/aws_gke_oidc_role/variables.tf @@ -1,7 +1,7 @@ ### Required variable "iam_policy_arns" { - description = "One or more policy arns to attach to created AWS role" + description = "One or more policy arns to attach to created AWS role" type = list(string) } @@ -15,13 +15,13 @@ variable "aws_region" { type = string } -variable "gcp_region" { - description = "GKE cluster's GCP region" +variable "gcp_project_id" { + description = "GKE cluster's project ID" type = string } -variable "gcp_project_id" { - description = "ID of the GKE cluster's project" +variable "gcp_region" { + description = "GKE cluster's GCP region" type = string } @@ -36,14 +36,14 @@ variable "gke_namespace" { } variable "gke_service_account" { - description = "GKE service account to grant role assumption privilleges" + description = "GKE service account to grant role assumption privilleges" type = string } ### Optional variable "tags" { - description = "Tags to apply to all AWS resources" + description = "Tags to apply to the AWS role" type = map(string) default = {} } From 58a570015683cd2c1e323f6b3e3953b0de3a3f80 Mon Sep 17 00:00:00 2001 From: Austin Mitchell Date: Thu, 5 Dec 2024 13:02:07 -0800 Subject: [PATCH 03/14] feat: add versions.tf --- aws_gke_oidc_config/versions.tf | 8 ++++++++ aws_gke_oidc_role/versions.tf | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 aws_gke_oidc_config/versions.tf create mode 100644 aws_gke_oidc_role/versions.tf diff --git a/aws_gke_oidc_config/versions.tf b/aws_gke_oidc_config/versions.tf new file mode 100644 index 00000000..f6c2dd66 --- /dev/null +++ b/aws_gke_oidc_config/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.37" + } + } +} diff --git a/aws_gke_oidc_role/versions.tf b/aws_gke_oidc_role/versions.tf new file mode 100644 index 00000000..f6c2dd66 --- /dev/null +++ b/aws_gke_oidc_role/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.37" + } + } +} From 3cba422491dfa33be1bd2a05a06ec895f055f17f Mon Sep 17 00:00:00 2001 From: Austin Mitchell Date: Thu, 5 Dec 2024 13:14:42 -0800 Subject: [PATCH 04/14] add tf locks, fix up examples --- aws_gke_oidc_config/README.md | 4 +- aws_gke_oidc_config/examples/main.tf | 4 ++ aws_gke_oidc_role/README.md | 6 ++- .../role_and_config/.terraform.lock.hcl | 44 +++++++++++++++++++ .../examples/role_and_config/main.tf | 4 +- .../role_with_policy/.terraform.lock.hcl | 25 +++++++++++ .../examples/role_with_policy/main.tf | 5 +-- 7 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 aws_gke_oidc_role/examples/role_and_config/.terraform.lock.hcl create mode 100644 aws_gke_oidc_role/examples/role_with_policy/.terraform.lock.hcl diff --git a/aws_gke_oidc_config/README.md b/aws_gke_oidc_config/README.md index a8e65821..7a6b607d 100644 --- a/aws_gke_oidc_config/README.md +++ b/aws_gke_oidc_config/README.md @@ -9,7 +9,9 @@ See the `aws_gke_oidc_role` for complete usage instructions ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [aws](#requirement\_aws) | >= 4.37 | ## Providers diff --git a/aws_gke_oidc_config/examples/main.tf b/aws_gke_oidc_config/examples/main.tf index 33508d4a..40f8e5d2 100644 --- a/aws_gke_oidc_config/examples/main.tf +++ b/aws_gke_oidc_config/examples/main.tf @@ -1,3 +1,7 @@ +/* + * Creates an OIDC trust relationship between the global-platform-admin-mgmt cluster & the authenticated AWS account. + */ + module "oidc_config" { source = "../." gcp_region = "us-west1" diff --git a/aws_gke_oidc_role/README.md b/aws_gke_oidc_role/README.md index c9f5541a..41c32849 100644 --- a/aws_gke_oidc_role/README.md +++ b/aws_gke_oidc_role/README.md @@ -36,13 +36,15 @@ After creating these resources, add the following environment variables, volumes ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [aws](#requirement\_aws) | >= 4.37 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | n/a | +| [aws](#provider\_aws) | >= 4.37 | ## Modules diff --git a/aws_gke_oidc_role/examples/role_and_config/.terraform.lock.hcl b/aws_gke_oidc_role/examples/role_and_config/.terraform.lock.hcl new file mode 100644 index 00000000..5b723121 --- /dev/null +++ b/aws_gke_oidc_role/examples/role_and_config/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.80.0" + constraints = ">= 4.0.0, >= 4.37.0, ~> 5.0" + hashes = [ + "h1:N5Wfsf4xe5DJfSeo0G/ulkIxzyfmUIoSj/hAiZ2DaKU=", + "zh:0b1655e39639d60f2de2860a5df8642f9556ba0ca04529c1b861fde4935cb0df", + "zh:13dc0155e0a11edceee29ce687fc04c5a5a85f3324c67556472713cfd52e5807", + "zh:180f6cb2be44be14cfe329e0649121b774319f083b6e4e8fb749f85090d73121", + "zh:3158d44b74c67465f7f19f22c42b643840c8d18ce833e2ec86e8d93085b06926", + "zh:6351b5bf7cde5dc83e926944891570636069e05ca43341f4d1feda67773469bf", + "zh:6fa9db1532096ba50e842d369b6688979306d2295c7ead49b8a266b0d60962cc", + "zh:85d2fe75def7619ff2cc29102048875039cad088fafb62ecc14c3763e7b1e9d9", + "zh:9028d653f1d7341c6dfe2afe961b6541581e9043a474eac2faf90e6426a24f6d", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9c4e248c442bc60f07f9f089e5361f19936833370dc3c04b27916672b765f0e1", + "zh:a710a3979596e3f3938c3ec6bb748e604724d3a4afa96ed2c14f0a245cc41a11", + "zh:c27936bdf447779d0c0833bf52a9ef618985f5ea8e3e243d6266513520ca31c4", + "zh:c7681134a123486e72eaedc3f8d2d75e267dbbfd45fa7de5aea8f757af57f89b", + "zh:ea717ebad3561fd02591f9eecf30f3df5635405556fba2bdbf29fd42691bebac", + "zh:f4e1e8f23c58c3e8f4371f9c3379a723ab4155246e6b6daad8eb99e16666b2cb", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.0.6" + hashes = [ + "h1:dYSb3V94K5dDMtrBRLPzBpkMTPn+3cXZ/kIJdtFL+2M=", + "zh:10de0d8af02f2e578101688fd334da3849f56ea91b0d9bd5b1f7a243417fdda8", + "zh:37fc01f8b2bc9d5b055dc3e78bfd1beb7c42cfb776a4c81106e19c8911366297", + "zh:4578ca03d1dd0b7f572d96bd03f744be24c726bfd282173d54b100fd221608bb", + "zh:6c475491d1250050765a91a493ef330adc24689e8837a0f07da5a0e1269e11c1", + "zh:81bde94d53cdababa5b376bbc6947668be4c45ab655de7aa2e8e4736dfd52509", + "zh:abdce260840b7b050c4e401d4f75c7a199fafe58a8b213947a258f75ac18b3e8", + "zh:b754cebfc5184873840f16a642a7c9ef78c34dc246a8ae29e056c79939963c7a", + "zh:c928b66086078f9917aef0eec15982f2e337914c5c4dbc31dd4741403db7eb18", + "zh:cded27bee5f24de6f2ee0cfd1df46a7f88e84aaffc2ecbf3ff7094160f193d50", + "zh:d65eb3867e8f69aaf1b8bb53bd637c99c6b649ba3db16ded50fa9a01076d1a27", + "zh:ecb0c8b528c7a619fa71852bb3fb5c151d47576c5aab2bf3af4db52588722eeb", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/aws_gke_oidc_role/examples/role_and_config/main.tf b/aws_gke_oidc_role/examples/role_and_config/main.tf index aba0e8cf..1ac367bc 100644 --- a/aws_gke_oidc_role/examples/role_and_config/main.tf +++ b/aws_gke_oidc_role/examples/role_and_config/main.tf @@ -3,7 +3,7 @@ */ module "oidc_config" { - source = "../../aws_gke_oidc_config/" + source = "../../../aws_gke_oidc_config/" gcp_region = "us-west1" gcp_project_id = "moz-fx-platform-mgmt-global" gke_cluster_name = "global-platform-admin-mgmt" @@ -11,7 +11,7 @@ module "oidc_config" { module "oidc_role" { depends_on = [module.oidc_config] - source = ".././" + source = "../.././" role_name = "opst-1509-oidc-test" aws_region = "us-west-1" gcp_region = "us-west1" diff --git a/aws_gke_oidc_role/examples/role_with_policy/.terraform.lock.hcl b/aws_gke_oidc_role/examples/role_with_policy/.terraform.lock.hcl new file mode 100644 index 00000000..5d6683db --- /dev/null +++ b/aws_gke_oidc_role/examples/role_with_policy/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.80.0" + constraints = ">= 4.0.0, >= 4.37.0, ~> 5.0" + hashes = [ + "h1:N5Wfsf4xe5DJfSeo0G/ulkIxzyfmUIoSj/hAiZ2DaKU=", + "zh:0b1655e39639d60f2de2860a5df8642f9556ba0ca04529c1b861fde4935cb0df", + "zh:13dc0155e0a11edceee29ce687fc04c5a5a85f3324c67556472713cfd52e5807", + "zh:180f6cb2be44be14cfe329e0649121b774319f083b6e4e8fb749f85090d73121", + "zh:3158d44b74c67465f7f19f22c42b643840c8d18ce833e2ec86e8d93085b06926", + "zh:6351b5bf7cde5dc83e926944891570636069e05ca43341f4d1feda67773469bf", + "zh:6fa9db1532096ba50e842d369b6688979306d2295c7ead49b8a266b0d60962cc", + "zh:85d2fe75def7619ff2cc29102048875039cad088fafb62ecc14c3763e7b1e9d9", + "zh:9028d653f1d7341c6dfe2afe961b6541581e9043a474eac2faf90e6426a24f6d", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9c4e248c442bc60f07f9f089e5361f19936833370dc3c04b27916672b765f0e1", + "zh:a710a3979596e3f3938c3ec6bb748e604724d3a4afa96ed2c14f0a245cc41a11", + "zh:c27936bdf447779d0c0833bf52a9ef618985f5ea8e3e243d6266513520ca31c4", + "zh:c7681134a123486e72eaedc3f8d2d75e267dbbfd45fa7de5aea8f757af57f89b", + "zh:ea717ebad3561fd02591f9eecf30f3df5635405556fba2bdbf29fd42691bebac", + "zh:f4e1e8f23c58c3e8f4371f9c3379a723ab4155246e6b6daad8eb99e16666b2cb", + ] +} diff --git a/aws_gke_oidc_role/examples/role_with_policy/main.tf b/aws_gke_oidc_role/examples/role_with_policy/main.tf index e5aa27be..fe3747f2 100644 --- a/aws_gke_oidc_role/examples/role_with_policy/main.tf +++ b/aws_gke_oidc_role/examples/role_with_policy/main.tf @@ -5,8 +5,7 @@ */ module "oidc_role" { - depends_on = [module.oidc_config] - source = ".././" + source = "../.././" role_name = "oidc-example-role" aws_region = "us-west-1" gcp_region = "us-west1" @@ -14,7 +13,7 @@ module "oidc_role" { gcp_project_id = "example-project" gke_namespace = "bar" gke_service_account = "foo" - iam_policy_arns = [aws_iam_policy.example_policy, data.aws_iam_policy.view_only] + iam_policy_arns = [aws_iam_policy.example_policy.arn, data.aws_iam_policy.view_only.arn] } resource "aws_iam_policy" "example_policy" { From 34d4f40c832c1aeff3e85faa08b205fdb0133c82 Mon Sep 17 00:00:00 2001 From: Austin Mitchell Date: Thu, 5 Dec 2024 13:18:48 -0800 Subject: [PATCH 05/14] add tf version to version.tf --- aws_gke_oidc_config/versions.tf | 1 + aws_gke_oidc_role/versions.tf | 1 + 2 files changed, 2 insertions(+) diff --git a/aws_gke_oidc_config/versions.tf b/aws_gke_oidc_config/versions.tf index f6c2dd66..36a9084e 100644 --- a/aws_gke_oidc_config/versions.tf +++ b/aws_gke_oidc_config/versions.tf @@ -5,4 +5,5 @@ terraform { version = ">= 4.37" } } + required_version = "~> 1.0" } diff --git a/aws_gke_oidc_role/versions.tf b/aws_gke_oidc_role/versions.tf index f6c2dd66..36a9084e 100644 --- a/aws_gke_oidc_role/versions.tf +++ b/aws_gke_oidc_role/versions.tf @@ -5,4 +5,5 @@ terraform { version = ">= 4.37" } } + required_version = "~> 1.0" } From fd877ac38afa0a450ab04fa18b161736e0b2c43e Mon Sep 17 00:00:00 2001 From: Austin Mitchell Date: Thu, 5 Dec 2024 13:21:54 -0800 Subject: [PATCH 06/14] tf fmt --- aws_gke_oidc_config/examples/main.tf | 6 ++--- .../examples/role_and_config/main.tf | 24 +++++++++---------- .../examples/role_with_policy/main.tf | 16 ++++++------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/aws_gke_oidc_config/examples/main.tf b/aws_gke_oidc_config/examples/main.tf index 40f8e5d2..5297c14c 100644 --- a/aws_gke_oidc_config/examples/main.tf +++ b/aws_gke_oidc_config/examples/main.tf @@ -3,8 +3,8 @@ */ module "oidc_config" { - source = "../." - gcp_region = "us-west1" - gcp_project_id = "moz-fx-platform-mgmt-global" + source = "../." + gcp_region = "us-west1" + gcp_project_id = "moz-fx-platform-mgmt-global" gke_cluster_name = "global-platform-admin-mgmt" } diff --git a/aws_gke_oidc_role/examples/role_and_config/main.tf b/aws_gke_oidc_role/examples/role_and_config/main.tf index 1ac367bc..1361d165 100644 --- a/aws_gke_oidc_role/examples/role_and_config/main.tf +++ b/aws_gke_oidc_role/examples/role_and_config/main.tf @@ -3,21 +3,21 @@ */ module "oidc_config" { - source = "../../../aws_gke_oidc_config/" - gcp_region = "us-west1" - gcp_project_id = "moz-fx-platform-mgmt-global" + source = "../../../aws_gke_oidc_config/" + gcp_region = "us-west1" + gcp_project_id = "moz-fx-platform-mgmt-global" gke_cluster_name = "global-platform-admin-mgmt" } module "oidc_role" { - depends_on = [module.oidc_config] - source = "../.././" - role_name = "opst-1509-oidc-test" - aws_region = "us-west-1" - gcp_region = "us-west1" - gke_cluster_name = "global-platform-admin-mgmt" - gcp_project_id = "moz-fx-platform-mgmt-global" - gke_namespace = "atlantis-sandbox" + depends_on = [module.oidc_config] + source = "../.././" + role_name = "opst-1509-oidc-test" + aws_region = "us-west-1" + gcp_region = "us-west1" + gke_cluster_name = "global-platform-admin-mgmt" + gcp_project_id = "moz-fx-platform-mgmt-global" + gke_namespace = "atlantis-sandbox" gke_service_account = "atlantis-sandbox" - iam_policy_arns = [] + iam_policy_arns = [] } diff --git a/aws_gke_oidc_role/examples/role_with_policy/main.tf b/aws_gke_oidc_role/examples/role_with_policy/main.tf index fe3747f2..b8388452 100644 --- a/aws_gke_oidc_role/examples/role_with_policy/main.tf +++ b/aws_gke_oidc_role/examples/role_with_policy/main.tf @@ -5,15 +5,15 @@ */ module "oidc_role" { - source = "../.././" - role_name = "oidc-example-role" - aws_region = "us-west-1" - gcp_region = "us-west1" - gke_cluster_name = "baz" - gcp_project_id = "example-project" - gke_namespace = "bar" + source = "../.././" + role_name = "oidc-example-role" + aws_region = "us-west-1" + gcp_region = "us-west1" + gke_cluster_name = "baz" + gcp_project_id = "example-project" + gke_namespace = "bar" gke_service_account = "foo" - iam_policy_arns = [aws_iam_policy.example_policy.arn, data.aws_iam_policy.view_only.arn] + iam_policy_arns = [aws_iam_policy.example_policy.arn, data.aws_iam_policy.view_only.arn] } resource "aws_iam_policy" "example_policy" { From 4216e4d6fa4f50ce891d79429f39ef87b79d17f2 Mon Sep 17 00:00:00 2001 From: Austin Mitchell Date: Wed, 18 Dec 2024 12:53:05 -0800 Subject: [PATCH 07/14] drop tags var in lieu of provider default_tags; rm lock files --- aws_gke_oidc_config/.terraform.lock.hcl | 44 ------------------- .../examples/.terraform.lock.hcl | 44 ------------------- .../role_and_config/.terraform.lock.hcl | 44 ------------------- .../role_with_policy/.terraform.lock.hcl | 25 ----------- aws_gke_oidc_role/main.tf | 1 - aws_gke_oidc_role/variables.tf | 8 ---- 6 files changed, 166 deletions(-) delete mode 100644 aws_gke_oidc_config/.terraform.lock.hcl delete mode 100644 aws_gke_oidc_role/examples/.terraform.lock.hcl delete mode 100644 aws_gke_oidc_role/examples/role_and_config/.terraform.lock.hcl delete mode 100644 aws_gke_oidc_role/examples/role_with_policy/.terraform.lock.hcl diff --git a/aws_gke_oidc_config/.terraform.lock.hcl b/aws_gke_oidc_config/.terraform.lock.hcl deleted file mode 100644 index 3465dbca..00000000 --- a/aws_gke_oidc_config/.terraform.lock.hcl +++ /dev/null @@ -1,44 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "5.76.0" - constraints = ">= 4.0.0" - hashes = [ - "h1:JSLR3JP9naVcnH0PHcDwwHr3aQB9vlW0+b8HQma1GpU=", - "zh:05b2a0d25fc07576f6698d4840d0d2ae2599484c49f1b911ea1154584557bc13", - "zh:1b22dd1d9c482739e133adb996a9c8b285ca7d978d0fe04deaa5588eba5d254c", - "zh:216088c8800e7b8d7eff7b1a822317bc6faec64f27946ffd22bb3494ac4175cb", - "zh:43e994112b1484bf49945c4885aa2fee32486c9a5d64b9146bbd6f309f24e332", - "zh:46a28ba800f176eef500f998217bccc331605ef05f11abb1728f727a81f3a8b0", - "zh:4fad2743174a600da76a0cceeec2fef8399a18d880ba8929d811cd5cea1b5dee", - "zh:5c42a2c1438cd7533456026f52b562715664490711fdea809f44610a7565c145", - "zh:792d4fd4be434682e4540d2579505c7f11f39d0efe1d12ee2761ed0d46c8cd51", - "zh:7bb5f9f87c9da6d62d6f89504f01a9d6d2f19dcaa0efc46ea51ebdc4bb6fd536", - "zh:81cdbd97f81b1110fce793944d5668a4389904979eb7d178d3142a6b0e175e5e", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:ab4b881eb0f3812b702aaecf921c5c16bbcc33d61d668be4d72d6da9c57ded85", - "zh:c1d9d1166fd948845614deef81f3197568d0d3c2a03b8b97fff308ebc59043f9", - "zh:cda7530f2c01434e483d3faf62fc0685295e7f844176aa38df1ba65fa6a4407a", - "zh:fdad558b1c41aa68123d0da82cc0d65bc86d09eaa1ab1d3a167ec3bce0fc0c66", - ] -} - -provider "registry.terraform.io/hashicorp/tls" { - version = "4.0.6" - hashes = [ - "h1:dYSb3V94K5dDMtrBRLPzBpkMTPn+3cXZ/kIJdtFL+2M=", - "zh:10de0d8af02f2e578101688fd334da3849f56ea91b0d9bd5b1f7a243417fdda8", - "zh:37fc01f8b2bc9d5b055dc3e78bfd1beb7c42cfb776a4c81106e19c8911366297", - "zh:4578ca03d1dd0b7f572d96bd03f744be24c726bfd282173d54b100fd221608bb", - "zh:6c475491d1250050765a91a493ef330adc24689e8837a0f07da5a0e1269e11c1", - "zh:81bde94d53cdababa5b376bbc6947668be4c45ab655de7aa2e8e4736dfd52509", - "zh:abdce260840b7b050c4e401d4f75c7a199fafe58a8b213947a258f75ac18b3e8", - "zh:b754cebfc5184873840f16a642a7c9ef78c34dc246a8ae29e056c79939963c7a", - "zh:c928b66086078f9917aef0eec15982f2e337914c5c4dbc31dd4741403db7eb18", - "zh:cded27bee5f24de6f2ee0cfd1df46a7f88e84aaffc2ecbf3ff7094160f193d50", - "zh:d65eb3867e8f69aaf1b8bb53bd637c99c6b649ba3db16ded50fa9a01076d1a27", - "zh:ecb0c8b528c7a619fa71852bb3fb5c151d47576c5aab2bf3af4db52588722eeb", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} diff --git a/aws_gke_oidc_role/examples/.terraform.lock.hcl b/aws_gke_oidc_role/examples/.terraform.lock.hcl deleted file mode 100644 index 64dc9159..00000000 --- a/aws_gke_oidc_role/examples/.terraform.lock.hcl +++ /dev/null @@ -1,44 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "5.80.0" - constraints = ">= 4.0.0" - hashes = [ - "h1:N5Wfsf4xe5DJfSeo0G/ulkIxzyfmUIoSj/hAiZ2DaKU=", - "zh:0b1655e39639d60f2de2860a5df8642f9556ba0ca04529c1b861fde4935cb0df", - "zh:13dc0155e0a11edceee29ce687fc04c5a5a85f3324c67556472713cfd52e5807", - "zh:180f6cb2be44be14cfe329e0649121b774319f083b6e4e8fb749f85090d73121", - "zh:3158d44b74c67465f7f19f22c42b643840c8d18ce833e2ec86e8d93085b06926", - "zh:6351b5bf7cde5dc83e926944891570636069e05ca43341f4d1feda67773469bf", - "zh:6fa9db1532096ba50e842d369b6688979306d2295c7ead49b8a266b0d60962cc", - "zh:85d2fe75def7619ff2cc29102048875039cad088fafb62ecc14c3763e7b1e9d9", - "zh:9028d653f1d7341c6dfe2afe961b6541581e9043a474eac2faf90e6426a24f6d", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9c4e248c442bc60f07f9f089e5361f19936833370dc3c04b27916672b765f0e1", - "zh:a710a3979596e3f3938c3ec6bb748e604724d3a4afa96ed2c14f0a245cc41a11", - "zh:c27936bdf447779d0c0833bf52a9ef618985f5ea8e3e243d6266513520ca31c4", - "zh:c7681134a123486e72eaedc3f8d2d75e267dbbfd45fa7de5aea8f757af57f89b", - "zh:ea717ebad3561fd02591f9eecf30f3df5635405556fba2bdbf29fd42691bebac", - "zh:f4e1e8f23c58c3e8f4371f9c3379a723ab4155246e6b6daad8eb99e16666b2cb", - ] -} - -provider "registry.terraform.io/hashicorp/tls" { - version = "4.0.6" - hashes = [ - "h1:dYSb3V94K5dDMtrBRLPzBpkMTPn+3cXZ/kIJdtFL+2M=", - "zh:10de0d8af02f2e578101688fd334da3849f56ea91b0d9bd5b1f7a243417fdda8", - "zh:37fc01f8b2bc9d5b055dc3e78bfd1beb7c42cfb776a4c81106e19c8911366297", - "zh:4578ca03d1dd0b7f572d96bd03f744be24c726bfd282173d54b100fd221608bb", - "zh:6c475491d1250050765a91a493ef330adc24689e8837a0f07da5a0e1269e11c1", - "zh:81bde94d53cdababa5b376bbc6947668be4c45ab655de7aa2e8e4736dfd52509", - "zh:abdce260840b7b050c4e401d4f75c7a199fafe58a8b213947a258f75ac18b3e8", - "zh:b754cebfc5184873840f16a642a7c9ef78c34dc246a8ae29e056c79939963c7a", - "zh:c928b66086078f9917aef0eec15982f2e337914c5c4dbc31dd4741403db7eb18", - "zh:cded27bee5f24de6f2ee0cfd1df46a7f88e84aaffc2ecbf3ff7094160f193d50", - "zh:d65eb3867e8f69aaf1b8bb53bd637c99c6b649ba3db16ded50fa9a01076d1a27", - "zh:ecb0c8b528c7a619fa71852bb3fb5c151d47576c5aab2bf3af4db52588722eeb", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} diff --git a/aws_gke_oidc_role/examples/role_and_config/.terraform.lock.hcl b/aws_gke_oidc_role/examples/role_and_config/.terraform.lock.hcl deleted file mode 100644 index 5b723121..00000000 --- a/aws_gke_oidc_role/examples/role_and_config/.terraform.lock.hcl +++ /dev/null @@ -1,44 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "5.80.0" - constraints = ">= 4.0.0, >= 4.37.0, ~> 5.0" - hashes = [ - "h1:N5Wfsf4xe5DJfSeo0G/ulkIxzyfmUIoSj/hAiZ2DaKU=", - "zh:0b1655e39639d60f2de2860a5df8642f9556ba0ca04529c1b861fde4935cb0df", - "zh:13dc0155e0a11edceee29ce687fc04c5a5a85f3324c67556472713cfd52e5807", - "zh:180f6cb2be44be14cfe329e0649121b774319f083b6e4e8fb749f85090d73121", - "zh:3158d44b74c67465f7f19f22c42b643840c8d18ce833e2ec86e8d93085b06926", - "zh:6351b5bf7cde5dc83e926944891570636069e05ca43341f4d1feda67773469bf", - "zh:6fa9db1532096ba50e842d369b6688979306d2295c7ead49b8a266b0d60962cc", - "zh:85d2fe75def7619ff2cc29102048875039cad088fafb62ecc14c3763e7b1e9d9", - "zh:9028d653f1d7341c6dfe2afe961b6541581e9043a474eac2faf90e6426a24f6d", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9c4e248c442bc60f07f9f089e5361f19936833370dc3c04b27916672b765f0e1", - "zh:a710a3979596e3f3938c3ec6bb748e604724d3a4afa96ed2c14f0a245cc41a11", - "zh:c27936bdf447779d0c0833bf52a9ef618985f5ea8e3e243d6266513520ca31c4", - "zh:c7681134a123486e72eaedc3f8d2d75e267dbbfd45fa7de5aea8f757af57f89b", - "zh:ea717ebad3561fd02591f9eecf30f3df5635405556fba2bdbf29fd42691bebac", - "zh:f4e1e8f23c58c3e8f4371f9c3379a723ab4155246e6b6daad8eb99e16666b2cb", - ] -} - -provider "registry.terraform.io/hashicorp/tls" { - version = "4.0.6" - hashes = [ - "h1:dYSb3V94K5dDMtrBRLPzBpkMTPn+3cXZ/kIJdtFL+2M=", - "zh:10de0d8af02f2e578101688fd334da3849f56ea91b0d9bd5b1f7a243417fdda8", - "zh:37fc01f8b2bc9d5b055dc3e78bfd1beb7c42cfb776a4c81106e19c8911366297", - "zh:4578ca03d1dd0b7f572d96bd03f744be24c726bfd282173d54b100fd221608bb", - "zh:6c475491d1250050765a91a493ef330adc24689e8837a0f07da5a0e1269e11c1", - "zh:81bde94d53cdababa5b376bbc6947668be4c45ab655de7aa2e8e4736dfd52509", - "zh:abdce260840b7b050c4e401d4f75c7a199fafe58a8b213947a258f75ac18b3e8", - "zh:b754cebfc5184873840f16a642a7c9ef78c34dc246a8ae29e056c79939963c7a", - "zh:c928b66086078f9917aef0eec15982f2e337914c5c4dbc31dd4741403db7eb18", - "zh:cded27bee5f24de6f2ee0cfd1df46a7f88e84aaffc2ecbf3ff7094160f193d50", - "zh:d65eb3867e8f69aaf1b8bb53bd637c99c6b649ba3db16ded50fa9a01076d1a27", - "zh:ecb0c8b528c7a619fa71852bb3fb5c151d47576c5aab2bf3af4db52588722eeb", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} diff --git a/aws_gke_oidc_role/examples/role_with_policy/.terraform.lock.hcl b/aws_gke_oidc_role/examples/role_with_policy/.terraform.lock.hcl deleted file mode 100644 index 5d6683db..00000000 --- a/aws_gke_oidc_role/examples/role_with_policy/.terraform.lock.hcl +++ /dev/null @@ -1,25 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "5.80.0" - constraints = ">= 4.0.0, >= 4.37.0, ~> 5.0" - hashes = [ - "h1:N5Wfsf4xe5DJfSeo0G/ulkIxzyfmUIoSj/hAiZ2DaKU=", - "zh:0b1655e39639d60f2de2860a5df8642f9556ba0ca04529c1b861fde4935cb0df", - "zh:13dc0155e0a11edceee29ce687fc04c5a5a85f3324c67556472713cfd52e5807", - "zh:180f6cb2be44be14cfe329e0649121b774319f083b6e4e8fb749f85090d73121", - "zh:3158d44b74c67465f7f19f22c42b643840c8d18ce833e2ec86e8d93085b06926", - "zh:6351b5bf7cde5dc83e926944891570636069e05ca43341f4d1feda67773469bf", - "zh:6fa9db1532096ba50e842d369b6688979306d2295c7ead49b8a266b0d60962cc", - "zh:85d2fe75def7619ff2cc29102048875039cad088fafb62ecc14c3763e7b1e9d9", - "zh:9028d653f1d7341c6dfe2afe961b6541581e9043a474eac2faf90e6426a24f6d", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9c4e248c442bc60f07f9f089e5361f19936833370dc3c04b27916672b765f0e1", - "zh:a710a3979596e3f3938c3ec6bb748e604724d3a4afa96ed2c14f0a245cc41a11", - "zh:c27936bdf447779d0c0833bf52a9ef618985f5ea8e3e243d6266513520ca31c4", - "zh:c7681134a123486e72eaedc3f8d2d75e267dbbfd45fa7de5aea8f757af57f89b", - "zh:ea717ebad3561fd02591f9eecf30f3df5635405556fba2bdbf29fd42691bebac", - "zh:f4e1e8f23c58c3e8f4371f9c3379a723ab4155246e6b6daad8eb99e16666b2cb", - ] -} diff --git a/aws_gke_oidc_role/main.tf b/aws_gke_oidc_role/main.tf index 5ee26582..f2a5f296 100644 --- a/aws_gke_oidc_role/main.tf +++ b/aws_gke_oidc_role/main.tf @@ -44,7 +44,6 @@ module "iam_assumable_role_for_oidc" { provider_url = replace(data.aws_iam_openid_connect_provider.gke_oidc.url, "https://", "") role_policy_arns = var.iam_policy_arns oidc_fully_qualified_subjects = ["system:serviceaccount:${var.gke_namespace}:${var.gke_service_account}"] - tags = var.tags } data "aws_iam_openid_connect_provider" "gke_oidc" { diff --git a/aws_gke_oidc_role/variables.tf b/aws_gke_oidc_role/variables.tf index 3b8a9c64..3c58917b 100644 --- a/aws_gke_oidc_role/variables.tf +++ b/aws_gke_oidc_role/variables.tf @@ -39,11 +39,3 @@ variable "gke_service_account" { description = "GKE service account to grant role assumption privilleges" type = string } - -### Optional - -variable "tags" { - description = "Tags to apply to the AWS role" - type = map(string) - default = {} -} From e5acaa6b323c0f62a1077394c35bcb5020dcbd1a Mon Sep 17 00:00:00 2001 From: James Francis <931363+tcotav@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:31:58 -0800 Subject: [PATCH 08/14] feat(google_project): added pam to default project services (#227) --- google_project/locals.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/google_project/locals.tf b/google_project/locals.tf index c07a07d8..af17039e 100644 --- a/google_project/locals.tf +++ b/google_project/locals.tf @@ -30,6 +30,7 @@ locals { "servicehealth.googleapis.com", "servicenetworking.googleapis.com", "stackdriver.googleapis.com", + "privilegedaccessmanager.googleapis.com" ] all_project_services = setunion(local.default_project_services, var.project_services) From e7b5248d3cdc5c3fbb6492b28ff10c936e01677f Mon Sep 17 00:00:00 2001 From: Basma A Date: Tue, 10 Dec 2024 12:12:00 -0500 Subject: [PATCH 09/14] chore(OPST-1385): renaming google workgroups module to mozilla workgroup (#234) * renaming google workgroups module to mozilla workgroup Signed-off-by: Basma1912 * terraform fmt Signed-off-by: Basma1912 * deleted mozilla workgroup Signed-off-by: Basma1912 * Update mozilla_workgroup/README.md Co-authored-by: Jason Thomas * Update mozilla_workgroup/README.md Co-authored-by: Jason Thomas * change roles default value and add a comment Signed-off-by: Basma1912 * chore(mozilla_workgroup): update the README file Signed-off-by: Basma1912 --------- Signed-off-by: Basma1912 Co-authored-by: Jason Thomas --- google_workgroup/README.md | 50 ---------- google_workgroup/main.tf | 93 ----------------- google_workgroup/outputs.tf | 24 ----- google_workgroup/variables.tf | 35 ------- google_workgroup/versions.tf | 11 --- .../.terraform-docs.yml | 0 mozilla_workgroup/README.md | 97 +++++++++++------- .../examples/example1.tf | 2 +- mozilla_workgroup/main.tf | 99 +++++++++++++++++-- mozilla_workgroup/outputs.tf | 19 +++- mozilla_workgroup/variables.tf | 34 +++++++ mozilla_workgroup/versions.tf | 8 +- 12 files changed, 209 insertions(+), 263 deletions(-) delete mode 100644 google_workgroup/README.md delete mode 100644 google_workgroup/main.tf delete mode 100644 google_workgroup/outputs.tf delete mode 100644 google_workgroup/variables.tf delete mode 100644 google_workgroup/versions.tf rename {google_workgroup => mozilla_workgroup}/.terraform-docs.yml (100%) rename {google_workgroup => mozilla_workgroup}/examples/example1.tf (78%) diff --git a/google_workgroup/README.md b/google_workgroup/README.md deleted file mode 100644 index c04cb9f3..00000000 --- a/google_workgroup/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# workgroup -Retrieve workgroup ACL lists associated with data and gcp access workgroups. - -Workgroup identifiers should be of the form: - -``` -workgroup:WORKGROUP_NAME[/SUBGROUP] -``` - -where `SUBGROUP` defaults to `default`. For example: `workgroup:app`, `workgroup:app/admin`. - -For subgroup queries across all workgroups, an additional identifier format: - -``` -subgroup:SUBGROUP -``` - -is supported, which will return all workgroups that contain a particular subgroup. - -This module is cloned from https://github.com/mozilla-services/cloudops-infra-terraform-modules/tree/master/data-workgroup. -## Example -```hcl -module "workgroup" { - source = "github.com/mozilla/terraform-modules//google_workgroup?ref=main" - - ids = ["workgroup:app/admins"] - roles = {} - workgroup_outputs = ["members", "google_groups"] - terraform_remote_state_bucket = "moz-bucket" - terraform_remote_state_prefix = "projects/workgroups" -} -``` -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [ids](#input\_ids) | List of workgroup identifiers to look up access for | `set(string)` | n/a | yes | -| [roles](#input\_roles) | List of roles to generate bigquery acls for | `map(string)` |
{
"metadata_viewer": "roles/bigquery.metadataViewer",
"read": "READER",
"write": "WRITER"
}
| no | -| [terraform\_remote\_state\_bucket](#input\_terraform\_remote\_state\_bucket) | The GCS bucket used for terraform state that contains the expected workgroups output | `string` | n/a | yes | -| [terraform\_remote\_state\_prefix](#input\_terraform\_remote\_state\_prefix) | The path prefix where the terraform state file is located | `string` | n/a | yes | -| [workgroup\_outputs](#input\_workgroup\_outputs) | Expected outputs from workgroup output definition | `list(any)` |
[
"bigquery_acls",
"members",
"service_accounts",
"google_groups"
]
| no | -## Outputs - -| Name | Description | -|------|-------------| -| [bigquery](#output\_bigquery) | bigquery acls for members associated with the input workgroups | -| [google\_groups](#output\_google\_groups) | google groups associated with the input workgroups, unqualified | -| [ids](#output\_ids) | pass input ids as output | -| [members](#output\_members) | authoritative, fully-qualified list of members associated with the input workgroups | -| [service\_accounts](#output\_service\_accounts) | service accounts associated with the input workgroups, unqualified | diff --git a/google_workgroup/main.tf b/google_workgroup/main.tf deleted file mode 100644 index c3b3c711..00000000 --- a/google_workgroup/main.tf +++ /dev/null @@ -1,93 +0,0 @@ -/** - * # workgroup - * Retrieve workgroup ACL lists associated with data and gcp access workgroups. - * - * Workgroup identifiers should be of the form: - * - * ``` - * workgroup:WORKGROUP_NAME[/SUBGROUP] - * ``` - * - * where `SUBGROUP` defaults to `default`. For example: `workgroup:app`, `workgroup:app/admin`. - * - * For subgroup queries across all workgroups, an additional identifier format: - * - * ``` - * subgroup:SUBGROUP - * ``` - * - * is supported, which will return all workgroups that contain a particular subgroup. - * - * This module is cloned from https://github.com/mozilla-services/cloudops-infra-terraform-modules/tree/master/data-workgroup. - * - */ - -data "terraform_remote_state" "workgroups" { - backend = "gcs" - - config = { - bucket = var.terraform_remote_state_bucket - prefix = var.terraform_remote_state_prefix - } -} - -locals { - workgroups = data.terraform_remote_state.workgroups.outputs.workgroups - # there isn't a good way of dynamically propagating outputs, so this wrapper - # module will need to be updated in the event more convenience outputs are - # added e.g. storage_read_acls - # the alternative would be to only expose members via this interface, which - # is always authoritative, and leave it up to the calling module to - # restructure the output as needed - outputs = var.workgroup_outputs - - # convert all workgroup identifiers into [workgroup, subgroup] format - workgroup_ids = [for workgroup in var.ids : - workgroup if length(regexall("^workgroup:", workgroup)) > 0 - ] - - normalized_workgroups = [for workgroup in local.workgroup_ids : - slice(compact(concat(split("/", trimprefix(workgroup, "workgroup:")), ["default"])), 0, 2) - ] - - # convert all subgroup identifiers into ["*", subgroup] format - subgroup_ids = [for subgroup in var.ids : - subgroup if length(regexall("^subgroup:", subgroup)) > 0 - ] - - normalized_subgroups = [for subgroup in local.subgroup_ids : - ["*", trimprefix(subgroup, "subgroup:")] - ] - - # combine the two reference types - normalized_ids = concat(local.normalized_workgroups, local.normalized_subgroups) - - # expand * if necessary to create a full list of workgroups - expanded_workgroups = distinct(concat([], [ - for workgroup in local.normalized_ids : workgroup[0] == "*" ? - [for key in keys(local.workgroups) : [key, workgroup[1]]] : [workgroup] - ]...)) - - # bespoke error checking designed to fail when an unknown subgroup is - # specified, to match behavior when an unknown workgroup is specified - subgroups_all = distinct(flatten([ - for workgroup in local.workgroups : [ - for output_type, output_value in workgroup : keys(output_value) if contains(local.outputs, output_type) - ] - ])) - - subgroups_test = [for subgroup in local.normalized_subgroups : index(local.subgroups_all, subgroup[1])] - - access = { for k in local.outputs : k => distinct(flatten(concat( - [for workgroup in local.expanded_workgroups : lookup(local.workgroups[workgroup[0]][k], workgroup[1], [])], - ))) } - - - bigquery_acls = { for output, role in var.roles : - "${output}_acls" => - toset([ - for k, v in local.access["bigquery_acls"] : - merge(v, { "role" : role }) - ]) - } -} diff --git a/google_workgroup/outputs.tf b/google_workgroup/outputs.tf deleted file mode 100644 index 60a49092..00000000 --- a/google_workgroup/outputs.tf +++ /dev/null @@ -1,24 +0,0 @@ -output "bigquery" { - description = "bigquery acls for members associated with the input workgroups" - value = local.bigquery_acls -} - -output "members" { - description = "authoritative, fully-qualified list of members associated with the input workgroups" - value = toset(lookup(local.access, "members", [])) -} - -output "google_groups" { - description = "google groups associated with the input workgroups, unqualified" - value = toset(lookup(local.access, "google_groups", [])) -} - -output "service_accounts" { - description = "service accounts associated with the input workgroups, unqualified" - value = toset(lookup(local.access, "service_accounts", [])) -} - -output "ids" { - description = "pass input ids as output" - value = var.ids -} diff --git a/google_workgroup/variables.tf b/google_workgroup/variables.tf deleted file mode 100644 index 0e7b518f..00000000 --- a/google_workgroup/variables.tf +++ /dev/null @@ -1,35 +0,0 @@ -variable "ids" { - type = set(string) - description = "List of workgroup identifiers to look up access for" - - validation { - condition = alltrue([for i in var.ids : length(regexall("^workgroup:[a-zA-Z0-9-]+(/[a-zA-Z0-9\\.-]+)?$|^subgroup:[a-zA-Z0-9\\.-]+$", i)) > 0]) - error_message = "Bad workgroup identifier format, must match workgroup:WORKGROUP[/SUBGROUP] or subgroup:SUBGROUP." - } -} - -variable "roles" { - type = map(string) - description = "List of roles to generate bigquery acls for" - default = { - metadata_viewer = "roles/bigquery.metadataViewer" - read = "READER" - write = "WRITER" - } -} - -variable "terraform_remote_state_bucket" { - type = string - description = "The GCS bucket used for terraform state that contains the expected workgroups output" -} - -variable "terraform_remote_state_prefix" { - type = string - description = "The path prefix where the terraform state file is located" -} - -variable "workgroup_outputs" { - default = ["bigquery_acls", "members", "service_accounts", "google_groups"] - type = list(any) - description = "Expected outputs from workgroup output definition" -} diff --git a/google_workgroup/versions.tf b/google_workgroup/versions.tf deleted file mode 100644 index 750fc7d0..00000000 --- a/google_workgroup/versions.tf +++ /dev/null @@ -1,11 +0,0 @@ -terraform { - required_providers { - - google = { - source = "hashicorp/google" - version = ">= 3.0" - } - } - - required_version = ">= 1.0" -} diff --git a/google_workgroup/.terraform-docs.yml b/mozilla_workgroup/.terraform-docs.yml similarity index 100% rename from google_workgroup/.terraform-docs.yml rename to mozilla_workgroup/.terraform-docs.yml diff --git a/mozilla_workgroup/README.md b/mozilla_workgroup/README.md index 64e5a024..bda9cc1c 100644 --- a/mozilla_workgroup/README.md +++ b/mozilla_workgroup/README.md @@ -1,76 +1,103 @@ -# Mozilla Workgroup +# Mozilla workgroup +Retrieve workgroup ACL lists associated with data and gcp access workgroups. -Builds on top of the [google workgroup module](../google_workgroup/) and contains constants for mozilla environments. This is the default to use for creating our internal tenants. +Workgroup identifiers should be of the form: - -## Requirements +``` +workgroup:WORKGROUP_NAME[/SUBGROUP] +``` -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | ~> 1.0 | -| [google](#requirement\_google) | >= 3.0 | -| [google-beta](#requirement\_google-beta) | >= 4.0 | +where `SUBGROUP` defaults to `default`. For example: `workgroup:app`, `workgroup:app/admin`. -## Providers +For subgroup queries across all workgroups, an additional identifier format: -No providers. +``` +subgroup:SUBGROUP +``` -## Modules +is supported, which will return all workgroups that contain a particular subgroup. -| Name | Source | Version | -|------|--------|---------| -| [workgroup](#module\_workgroup) | github.com/mozilla/terraform-modules//google_workgroup | OPST-682 | - -## Resources - -No resources. +This module is cloned from https://github.com/mozilla-services/cloudops-infra-terraform-modules/tree/master/data-workgroup. +## Example +```hcl +module "workgroup" { + source = "github.com/mozilla/terraform-modules//mozilla_workgroup?ref=main" + ids = ["workgroup:app/admins"] + roles = {} + workgroup_outputs = ["members", "google_groups"] + terraform_remote_state_bucket = "moz-bucket" + terraform_remote_state_prefix = "projects/workgroups" +} +``` ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [ids](#input\_ids) | List of workgroup identifiers to look up access for | `set(string)` | n/a | yes | - +| [roles](#input\_roles) | List of roles to generate bigquery acls for | `map(string)` |
{
"metadata_viewer": "roles/bigquery.metadataViewer",
"read": "READER",
"write": "WRITER"
}
| no | +| [terraform\_remote\_state\_bucket](#input\_terraform\_remote\_state\_bucket) | The GCS bucket used for terraform state that contains the expected workgroups output | `string` | n/a | yes | +| [terraform\_remote\_state\_prefix](#input\_terraform\_remote\_state\_prefix) | The path prefix where the terraform state file is located | `string` | n/a | yes | +| [workgroup\_outputs](#input\_workgroup\_outputs) | Expected outputs from workgroup output definition | `list(any)` |
[
"bigquery_acls",
"members",
"service_accounts",
"google_groups"
]
| no | ## Outputs | Name | Description | |------|-------------| +| [bigquery](#output\_bigquery) | bigquery acls for members associated with the input workgroups | | [google\_groups](#output\_google\_groups) | google groups associated with the input workgroups, unqualified | +| [ids](#output\_ids) | pass input ids as output | | [members](#output\_members) | authoritative, fully-qualified list of members associated with the input workgroups | +| [service\_accounts](#output\_service\_accounts) | service accounts associated with the input workgroups, unqualified | -## Requirements +# workgroup +Retrieve workgroup ACL lists associated with data and gcp access workgroups. -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | ~> 1.0 | -| [google](#requirement\_google) | >= 3.0 | -| [google-beta](#requirement\_google-beta) | >= 4.0 | +Workgroup identifiers should be of the form: -## Providers +``` +workgroup:WORKGROUP_NAME[/SUBGROUP] +``` -No providers. +where `SUBGROUP` defaults to `default`. For example: `workgroup:app`, `workgroup:app/admin`. -## Modules +For subgroup queries across all workgroups, an additional identifier format: -| Name | Source | Version | -|------|--------|---------| -| [workgroup](#module\_workgroup) | github.com/mozilla/terraform-modules//google_workgroup | main | +``` +subgroup:SUBGROUP +``` -## Resources +is supported, which will return all workgroups that contain a particular subgroup. -No resources. +This module is cloned from https://github.com/mozilla-services/cloudops-infra-terraform-modules/tree/master/data-workgroup. +## Example +```hcl +module "workgroup" { + source = "github.com/mozilla/terraform-modules//mozilla_workgroup?ref=main" + ids = ["workgroup:app/admins"] + roles = {} + workgroup_outputs = ["members", "google_groups"] + terraform_remote_state_bucket = "moz-bucket" + terraform_remote_state_prefix = "projects/workgroups" +} +``` ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [ids](#input\_ids) | List of workgroup identifiers to look up access for | `set(string)` | n/a | yes | - +| [roles](#input\_roles) | List of roles to generate bigquery acls for | `map(string)` | `{}` | no | +| [terraform\_remote\_state\_bucket](#input\_terraform\_remote\_state\_bucket) | The GCS bucket used for terraform state that contains the expected workgroups output | `string` | `"moz-fx-platform-mgmt-global-tf"` | no | +| [terraform\_remote\_state\_prefix](#input\_terraform\_remote\_state\_prefix) | The path prefix where the terraform state file is located | `string` | `"projects/google-workspace-management"` | no | +| [workgroup\_outputs](#input\_workgroup\_outputs) | Expected outputs from workgroup output definition | `list(any)` |
[
"members",
"google_groups"
]
| no | ## Outputs | Name | Description | |------|-------------| +| [bigquery](#output\_bigquery) | bigquery acls for members associated with the input workgroups | | [google\_groups](#output\_google\_groups) | google groups associated with the input workgroups, unqualified | +| [ids](#output\_ids) | pass input ids as output | | [members](#output\_members) | authoritative, fully-qualified list of members associated with the input workgroups | +| [service\_accounts](#output\_service\_accounts) | service accounts associated with the input workgroups, unqualified | \ No newline at end of file diff --git a/google_workgroup/examples/example1.tf b/mozilla_workgroup/examples/example1.tf similarity index 78% rename from google_workgroup/examples/example1.tf rename to mozilla_workgroup/examples/example1.tf index cbc4d0d0..e97d0bba 100644 --- a/google_workgroup/examples/example1.tf +++ b/mozilla_workgroup/examples/example1.tf @@ -1,5 +1,5 @@ module "workgroup" { - source = "github.com/mozilla/terraform-modules//google_workgroup?ref=main" + source = "github.com/mozilla/terraform-modules//mozilla_workgroup?ref=main" ids = ["workgroup:app/admins"] roles = {} diff --git a/mozilla_workgroup/main.tf b/mozilla_workgroup/main.tf index 051d15eb..c3b3c711 100644 --- a/mozilla_workgroup/main.tf +++ b/mozilla_workgroup/main.tf @@ -1,8 +1,93 @@ -module "workgroup" { - source = "../google_workgroup" - ids = var.ids - roles = {} - workgroup_outputs = ["members", "google_groups"] - terraform_remote_state_bucket = "moz-fx-platform-mgmt-global-tf" - terraform_remote_state_prefix = "projects/google-workspace-management" +/** + * # workgroup + * Retrieve workgroup ACL lists associated with data and gcp access workgroups. + * + * Workgroup identifiers should be of the form: + * + * ``` + * workgroup:WORKGROUP_NAME[/SUBGROUP] + * ``` + * + * where `SUBGROUP` defaults to `default`. For example: `workgroup:app`, `workgroup:app/admin`. + * + * For subgroup queries across all workgroups, an additional identifier format: + * + * ``` + * subgroup:SUBGROUP + * ``` + * + * is supported, which will return all workgroups that contain a particular subgroup. + * + * This module is cloned from https://github.com/mozilla-services/cloudops-infra-terraform-modules/tree/master/data-workgroup. + * + */ + +data "terraform_remote_state" "workgroups" { + backend = "gcs" + + config = { + bucket = var.terraform_remote_state_bucket + prefix = var.terraform_remote_state_prefix + } +} + +locals { + workgroups = data.terraform_remote_state.workgroups.outputs.workgroups + # there isn't a good way of dynamically propagating outputs, so this wrapper + # module will need to be updated in the event more convenience outputs are + # added e.g. storage_read_acls + # the alternative would be to only expose members via this interface, which + # is always authoritative, and leave it up to the calling module to + # restructure the output as needed + outputs = var.workgroup_outputs + + # convert all workgroup identifiers into [workgroup, subgroup] format + workgroup_ids = [for workgroup in var.ids : + workgroup if length(regexall("^workgroup:", workgroup)) > 0 + ] + + normalized_workgroups = [for workgroup in local.workgroup_ids : + slice(compact(concat(split("/", trimprefix(workgroup, "workgroup:")), ["default"])), 0, 2) + ] + + # convert all subgroup identifiers into ["*", subgroup] format + subgroup_ids = [for subgroup in var.ids : + subgroup if length(regexall("^subgroup:", subgroup)) > 0 + ] + + normalized_subgroups = [for subgroup in local.subgroup_ids : + ["*", trimprefix(subgroup, "subgroup:")] + ] + + # combine the two reference types + normalized_ids = concat(local.normalized_workgroups, local.normalized_subgroups) + + # expand * if necessary to create a full list of workgroups + expanded_workgroups = distinct(concat([], [ + for workgroup in local.normalized_ids : workgroup[0] == "*" ? + [for key in keys(local.workgroups) : [key, workgroup[1]]] : [workgroup] + ]...)) + + # bespoke error checking designed to fail when an unknown subgroup is + # specified, to match behavior when an unknown workgroup is specified + subgroups_all = distinct(flatten([ + for workgroup in local.workgroups : [ + for output_type, output_value in workgroup : keys(output_value) if contains(local.outputs, output_type) + ] + ])) + + subgroups_test = [for subgroup in local.normalized_subgroups : index(local.subgroups_all, subgroup[1])] + + access = { for k in local.outputs : k => distinct(flatten(concat( + [for workgroup in local.expanded_workgroups : lookup(local.workgroups[workgroup[0]][k], workgroup[1], [])], + ))) } + + + bigquery_acls = { for output, role in var.roles : + "${output}_acls" => + toset([ + for k, v in local.access["bigquery_acls"] : + merge(v, { "role" : role }) + ]) + } } diff --git a/mozilla_workgroup/outputs.tf b/mozilla_workgroup/outputs.tf index d3443e33..60a49092 100644 --- a/mozilla_workgroup/outputs.tf +++ b/mozilla_workgroup/outputs.tf @@ -1,9 +1,24 @@ +output "bigquery" { + description = "bigquery acls for members associated with the input workgroups" + value = local.bigquery_acls +} + output "members" { description = "authoritative, fully-qualified list of members associated with the input workgroups" - value = module.workgroup.members + value = toset(lookup(local.access, "members", [])) } output "google_groups" { description = "google groups associated with the input workgroups, unqualified" - value = module.workgroup.google_groups + value = toset(lookup(local.access, "google_groups", [])) +} + +output "service_accounts" { + description = "service accounts associated with the input workgroups, unqualified" + value = toset(lookup(local.access, "service_accounts", [])) +} + +output "ids" { + description = "pass input ids as output" + value = var.ids } diff --git a/mozilla_workgroup/variables.tf b/mozilla_workgroup/variables.tf index 92ddcf42..959c341c 100644 --- a/mozilla_workgroup/variables.tf +++ b/mozilla_workgroup/variables.tf @@ -7,3 +7,37 @@ variable "ids" { error_message = "Bad workgroup identifier format, must match workgroup:WORKGROUP[/SUBGROUP] or subgroup:SUBGROUP." } } + +/* roles can be BigQuery roles and/or basic roles for dataset" + https://cloud.google.com/bigquery/docs/access-control-basic-roles + https://cloud.google.com/bigquery/docs/access-control#bigquery + example + metadata_viewer = "roles/bigquery.metadataViewer" + read = "READER" + write = "WRITER" + */ + +variable "roles" { + type = map(string) + description = "List of roles to generate bigquery acls for" + default = {} +} + +variable "terraform_remote_state_bucket" { + type = string + description = "The GCS bucket used for terraform state that contains the expected workgroups output" + default = "moz-fx-platform-mgmt-global-tf" +} + +variable "terraform_remote_state_prefix" { + type = string + description = "The path prefix where the terraform state file is located" + default = "projects/google-workspace-management" +} + +variable "workgroup_outputs" { + default = ["members", "google_groups"] + type = list(any) + description = "Expected outputs from workgroup output definition" + # output can be ["bigquery_acls", "members", "service_accounts", "google_groups"] +} diff --git a/mozilla_workgroup/versions.tf b/mozilla_workgroup/versions.tf index 67d4b379..750fc7d0 100644 --- a/mozilla_workgroup/versions.tf +++ b/mozilla_workgroup/versions.tf @@ -1,13 +1,11 @@ terraform { required_providers { + google = { source = "hashicorp/google" version = ">= 3.0" } - google-beta = { - source = "hashicorp/google-beta" - version = ">= 4.0" - } } - required_version = "~> 1.0" + + required_version = ">= 1.0" } From ef7ff1089261201ac68200388bdd78b5822f6cc3 Mon Sep 17 00:00:00 2001 From: James Francis <931363+tcotav@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:24:08 -0800 Subject: [PATCH 10/14] feat: Pam integration to the google_permissions module (#229) * initial entitlement integration * check prod/non-prod vars * cleaning up pathing * missed the beta for pam * correct ent role list allowed * correct ent role list allowed - rm local * hardcode google-beta * removed validation (for now) * interpolate fail * forgot local - fixed * did + again on str, fixed * wrong service name enabled * wrong service name enabled - fixed * iam brought into tf, now borked * borked test, temp fix * removed all enabling of API * revert * added disable_on_destroy false for services * remove api on again * going to nuke dependent services * put service api in loop * hardcoded * one more try - iam fix * force rm'd iam from tf * added depends_on, separated prod, non * missed the instance key * more conditions to count * rm'd service enable, add folder entitle * enable svc * forgot comment out fol ent * fixed parent * number of resource changes * cp error - double resource * HACK: add my user to all installs * working hack - same as prev * temp remove nonprod entitlement * readd entitlement * added more perms for sa pam * added count on data project resources * forgot count on reference to resource counted * typo on c&p * roles/ needed * try again -- wrong proj * enable other resources * found roles - pam * hc org number, fix typo in role * adding back entitlement * mod to hardcode dev * cleanup after working * removing me as owner - hack * forgot to delete * added org id var * fixed err in desc of org id * removed PAM svc add + related * formatting tf * removed extra depends_on * tf fmt * moved from google-beta to GA version * adding req'd approval iam perms * var.var typo * tf fmt forgotten * tidying up - foreach used * integrate python func for slack * remove alert trigger - false alarm * tf fmt of new files * duh - set and each * fixed errors in tf * toset * TODO - remove my perms * adding back tghe hack to add me to owner * bad cp * bucket name fix * remove prod/nonprod from bucket name * moved bucket to nonprod * perms for builder * perms for builder - each'd * perms for builder - each'd * perms for builder - each'd * add run.invoker * pubsub perms * each.key. again * trying to find the right way to add perms * just going to leave off the perms for pubsub * add guards ensuring at least one project for slack * same as prev + tf fmt * adding pam_entitlement * tf plan works w/lookup... run next * fixed additional entitlements * slack fix+remove, merge mess entitlement fix * clean up legacy tf for new ent yaml * integrated publish to slack * removed extra iam sa account * chore: remove impersonate_service_account * removed data src * removed python code for slack * removed owner_jfrancis perms grant * fixed bool * fix bool problem owner create * CR fixes * fixed dupe project id envs * basic example add * missed var name change in prev * removed branch * app_code default empty string * caught empty app_code legacy * chore(google_permissions): update README --------- Co-authored-by: Jason Thomas --- google_permissions/README.md | 20 +- .../examples/entitlement_basic/data.tf | 19 ++ .../examples/entitlement_basic/locals.tf | 11 + .../examples/entitlement_basic/permissions.tf | 28 ++ .../examples/entitlement_basic/provider.tf | 9 + .../examples/entitlement_basic/terraform.tf | 7 + .../examples/entitlement_basic/versions.tf | 11 + google_permissions/main.tf | 22 +- google_permissions/other_roles.tf | 60 ++--- google_permissions/pam_entitlement.tf | 247 ++++++++++++++++++ google_permissions/variables.tf | 51 +++- google_permissions/versions.tf | 6 +- 12 files changed, 445 insertions(+), 46 deletions(-) create mode 100644 google_permissions/examples/entitlement_basic/data.tf create mode 100644 google_permissions/examples/entitlement_basic/locals.tf create mode 100644 google_permissions/examples/entitlement_basic/permissions.tf create mode 100644 google_permissions/examples/entitlement_basic/provider.tf create mode 100644 google_permissions/examples/entitlement_basic/terraform.tf create mode 100644 google_permissions/examples/entitlement_basic/versions.tf create mode 100644 google_permissions/pam_entitlement.tf diff --git a/google_permissions/README.md b/google_permissions/README.md index aeaa1a66..210ce144 100644 --- a/google_permissions/README.md +++ b/google_permissions/README.md @@ -9,26 +9,30 @@ For information on how to add new roles to the modules, please see [this documen | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | ~> 1.2 | -| [google](#requirement\_google) | >= 3.0 | +| [google](#requirement\_google) | >=6.7.0 | +| [google-beta](#requirement\_google-beta) | >=6.7.0 | ## Providers | Name | Version | |------|---------| -| [google](#provider\_google) | >= 3.0 | +| [google](#provider\_google) | >=6.7.0 | ## Modules | Name | Source | Version | |------|--------|---------| | [admins\_workgroup](#module\_admins\_workgroup) | ../mozilla_workgroup | n/a | +| [approvals\_workgroup](#module\_approvals\_workgroup) | ../mozilla_workgroup | n/a | | [developers\_workgroup](#module\_developers\_workgroup) | ../mozilla_workgroup | n/a | | [viewers\_workgroup](#module\_viewers\_workgroup) | ../mozilla_workgroup | n/a | +| [workgroup](#module\_workgroup) | ../mozilla_workgroup | n/a | ## Resources | Name | Type | |------|------| +| [google_cloud_asset_project_feed.project_feed](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_asset_project_feed) | resource | | [google_folder_iam_binding.bq_data_viewer](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/folder_iam_binding) | resource | | [google_folder_iam_binding.bq_job_user](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/folder_iam_binding) | resource | | [google_folder_iam_binding.developers_logging_admin](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/folder_iam_binding) | resource | @@ -40,6 +44,9 @@ For information on how to add new roles to the modules, please see [this documen | [google_folder_iam_binding.folder](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/folder_iam_binding) | resource | | [google_folder_iam_binding.owner](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/folder_iam_binding) | resource | | [google_folder_iam_binding.viewer](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/folder_iam_binding) | resource | +| [google_privileged_access_manager_entitlement.additional_entitlements](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/privileged_access_manager_entitlement) | resource | +| [google_privileged_access_manager_entitlement.default_nonprod_entitlement](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/privileged_access_manager_entitlement) | resource | +| [google_privileged_access_manager_entitlement.default_prod_entitlement](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/privileged_access_manager_entitlement) | resource | | [google_project_iam_binding.automl_editor_prod](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | | [google_project_iam_binding.bucket_admin](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | | [google_project_iam_binding.editor_nonprod](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | @@ -47,8 +54,8 @@ For information on how to add new roles to the modules, please see [this documen | [google_project_iam_binding.nonprod_developer_colabEnterpriseUser](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | | [google_project_iam_binding.nonprod_developer_db_admin](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | | [google_project_iam_binding.nonprod_developer_monitoring_uptimecheckconfigeditor](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | -| [google_project_iam_binding.nonprod_developer_objectUser](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | | [google_project_iam_binding.nonprod_developer_oath_config_editor](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | +| [google_project_iam_binding.nonprod_developer_objectUser](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | | [google_project_iam_binding.nonprod_developer_pubsub_editor](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | | [google_project_iam_binding.nonprod_developer_secretmanager_admin](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | | [google_project_iam_binding.prod_bucket_admin](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource | @@ -72,8 +79,13 @@ For information on how to add new roles to the modules, please see [this documen |------|-------------|------|---------|:--------:| | [admin\_ids](#input\_admin\_ids) | List of admin IDs to add to the project. | `list(string)` | `[]` | no | | [admin\_only](#input\_admin\_only) | Whether or not to create a project with admin-only role. | `bool` | `false` | no | +| [app\_code](#input\_app\_code) | The application code for the permissions. See https://github.com/mozilla-services/inventory/blob/master/application_component_registry.csv. | `string` | `""` | no | | [developer\_ids](#input\_developer\_ids) | List of developer IDs to add to the project. | `list(string)` | `[]` | no | -| [folder\_roles](#input\_folder\_roles) | List of roles to apply at the folder level. | `list(string)` | `[]` | no | +| [entitlement\_data](#input\_entitlement\_data) | The entitlement data for the project. |
object({
enabled = bool
additional_roles = list(string)
additional_entitlements = list(object({
name = string
roles = list(string)
principals = list(string)
approval_workflow = optional(object({
principals = list(string)
}))
}))
})
|
{
"additional_entitlements": [],
"additional_roles": [],
"enabled": false
}
| no | +| [entitlement\_enabled](#input\_entitlement\_enabled) | Whether or not to enable entitlements. | `bool` | `false` | no | +| [entitlement\_slack\_topic](#input\_entitlement\_slack\_topic) | The name of the pubsub topic to use for slack notifications. | `string` | `""` | no | +| [feed\_id](#input\_feed\_id) | The ID of the feed to be created | `string` | `"grant_feed"` | no | +| [folder\_roles](#input\_folder\_roles) | List of roles to apply at the folder level. Also used as the roles in the entitlement. | `list(string)` | `[]` | no | | [google\_folder\_id](#input\_google\_folder\_id) | The ID of the folder to create the project in. | `string` | n/a | yes | | [google\_nonprod\_project\_id](#input\_google\_nonprod\_project\_id) | The ID of the nonprod project. | `string` | `""` | no | | [google\_prod\_project\_id](#input\_google\_prod\_project\_id) | The ID of the prod project. | `string` | `""` | no | diff --git a/google_permissions/examples/entitlement_basic/data.tf b/google_permissions/examples/entitlement_basic/data.tf new file mode 100644 index 00000000..afe809a9 --- /dev/null +++ b/google_permissions/examples/entitlement_basic/data.tf @@ -0,0 +1,19 @@ +data "terraform_remote_state" "projects" { + backend = "gcs" + + config = { + bucket = "moz-fx-sandbox-terraform-state-global" + prefix = "projects/projects/global" + impersonate_service_account = "tf-sandbox@moz-fx-sandbox-terraform-admin.iam.gserviceaccount.com" + } +} + +data "terraform_remote_state" "platform_shared" { + backend = "gcs" + + config = { + prefix = "projects/platform-shared/global" + bucket = "moz-fx-platform-terraform-state-global" + impersonate_service_account = "tf-sandbox@moz-fx-sandbox-terraform-admin.iam.gserviceaccount.com" + } +} diff --git a/google_permissions/examples/entitlement_basic/locals.tf b/google_permissions/examples/entitlement_basic/locals.tf new file mode 100644 index 00000000..ce7592b6 --- /dev/null +++ b/google_permissions/examples/entitlement_basic/locals.tf @@ -0,0 +1,11 @@ +locals { + application = "testapp5" + project = data.terraform_remote_state.projects.outputs.projects[local.application] + + // entitlement related variables + tenant_entitlements = data.terraform_remote_state.platform_shared.outputs.tenant_entitlements[local.application] + google_prod_project_id = local.tenant_entitlements.prod + google_nonprod_project_id = local.tenant_entitlements.nonprod + entitlement_enabled = local.tenant_entitlements.entitlements.enabled + entitlement_data = local.tenant_entitlements.entitlements +} diff --git a/google_permissions/examples/entitlement_basic/permissions.tf b/google_permissions/examples/entitlement_basic/permissions.tf new file mode 100644 index 00000000..e1b4ecef --- /dev/null +++ b/google_permissions/examples/entitlement_basic/permissions.tf @@ -0,0 +1,28 @@ +module "permissions" { + source = "github.com/mozilla/terraform-modules//google_permissions" + //source = "../google_permissions" + + // the appcode (aka application or tenant name) is used to create the workgroups and entitlements if used + app_code = local.application + + entitlement_enabled = local.entitlement_enabled + entitlement_data = local.entitlement_data + + google_folder_id = local.project.folder.id + + // + // We pull these from the remote state data now + // which sources it as the realms from the tenant yaml + // + // google_prod_project_id = local.project["prod"].id -- this one doesn't have prod + // google_nonprod_project_id = local.project["nonprod"].id + + // if you want to send to slack + //entitlement_slack_topic = local.entitlement_slack_pubsub_topic + + // in this case -- all three of these workgroups will be given the basic user permissions in the folder + admin_ids = ["workgroup:${local.application}/admins"] + developer_ids = ["workgroup:${local.application}/developers"] + viewer_ids = ["workgroup:${local.application}/viewers"] + +} diff --git a/google_permissions/examples/entitlement_basic/provider.tf b/google_permissions/examples/entitlement_basic/provider.tf new file mode 100644 index 00000000..bf40a96b --- /dev/null +++ b/google_permissions/examples/entitlement_basic/provider.tf @@ -0,0 +1,9 @@ +provider "google" { + impersonate_service_account = "tf-sandbox@moz-fx-sandbox-terraform-admin.iam.gserviceaccount.com" + region = "us-west1" +} + +provider "google-beta" { + impersonate_service_account = "tf-sandbox@moz-fx-sandbox-terraform-admin.iam.gserviceaccount.com" + region = "us-west1" +} \ No newline at end of file diff --git a/google_permissions/examples/entitlement_basic/terraform.tf b/google_permissions/examples/entitlement_basic/terraform.tf new file mode 100644 index 00000000..792037d2 --- /dev/null +++ b/google_permissions/examples/entitlement_basic/terraform.tf @@ -0,0 +1,7 @@ +terraform { + backend "gcs" { + bucket = "moz-fx-sandbox-terraform-state-global" + prefix = "projects/testapp5/global" + impersonate_service_account = "tf-sandbox@moz-fx-sandbox-terraform-admin.iam.gserviceaccount.com" + } +} diff --git a/google_permissions/examples/entitlement_basic/versions.tf b/google_permissions/examples/entitlement_basic/versions.tf new file mode 100644 index 00000000..4c249ab1 --- /dev/null +++ b/google_permissions/examples/entitlement_basic/versions.tf @@ -0,0 +1,11 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + + google = { + source = "hashicorp/google" + version = "~> 6.12" + } + } +} diff --git a/google_permissions/main.tf b/google_permissions/main.tf index 89e8a0d5..966fdd74 100644 --- a/google_permissions/main.tf +++ b/google_permissions/main.tf @@ -1,18 +1,12 @@ /** * # Google Permissions - * + * * This module provides an interface to adding permissions to your google projects and folders. - * + * * For information on how to add new roles to the modules, please see [this document](./ADDING_NEW_ROLE.md) */ -// if admin_only is true, we don't create these permissions at all -resource "google_folder_iam_binding" "owner" { - count = var.admin_only ? 0 : 1 - folder = var.google_folder_id - role = "roles/owner" - members = module.admins_workgroup.members -} +// ROLES resource "google_folder_iam_binding" "viewer" { count = var.admin_only ? 0 : 1 @@ -68,3 +62,13 @@ resource "google_project_iam_member" "developers_secretmanager_secretVersionAdde role = "roles/secretmanager.secretVersionAdder" member = each.value } + +// legacy code + +// if admin_only is true OR var.use_entitlements is true, we don't create these permissions at all +resource "google_folder_iam_binding" "owner" { + count = var.admin_only || var.entitlement_enabled == true ? 0 : 1 + folder = var.google_folder_id + role = "roles/owner" + members = module.admins_workgroup.members +} diff --git a/google_permissions/other_roles.tf b/google_permissions/other_roles.tf index e55b9bef..c02a1cdf 100644 --- a/google_permissions/other_roles.tf +++ b/google_permissions/other_roles.tf @@ -10,7 +10,7 @@ resource "google_folder_iam_binding" "bq_job_user" { // // NOTE: this uses bq_data_viewer as well as the next resource block so that those we grant data viewer // also have to execute jobs so paired with .dataViewer - count = contains(var.folder_roles, "roles/bigquery.jobUser") && !var.admin_only ? 1 : 0 + count = contains(var.folder_roles, "roles/bigquery.jobUser") && !var.admin_only && var.entitlement_enabled ? 1 : 0 folder = var.google_folder_id role = "roles/bigquery.jobUser" members = setunion( @@ -20,7 +20,7 @@ resource "google_folder_iam_binding" "bq_job_user" { } resource "google_folder_iam_binding" "bq_data_viewer" { - count = contains(var.folder_roles, "roles/bigquery.jobUser") && !var.admin_only ? 1 : 0 + count = contains(var.folder_roles, "roles/bigquery.dataViewer") && !var.admin_only && var.entitlement_enabled ? 1 : 0 folder = var.google_folder_id role = "roles/bigquery.dataViewer" members = setunion( @@ -32,7 +32,7 @@ resource "google_folder_iam_binding" "bq_data_viewer" { # roles/redis.admin as folder_role resource "google_folder_iam_binding" "developers_redis_admin" { - count = contains(var.folder_roles, "roles/redis.admin") && !var.admin_only ? 1 : 0 + count = contains(var.folder_roles, "roles/redis.admin") && !var.admin_only && var.entitlement_enabled ? 1 : 0 folder = var.google_folder_id role = "roles/redis.admin" members = module.developers_workgroup.members @@ -42,7 +42,7 @@ resource "google_folder_iam_binding" "developers_redis_admin" { # roles/logging.admin as folder_role resource "google_folder_iam_binding" "developers_logging_admin" { - count = contains(var.folder_roles, "roles/logging.admin") && !var.admin_only ? 1 : 0 + count = contains(var.folder_roles, "roles/logging.admin") && !var.admin_only && var.entitlement_enabled ? 1 : 0 folder = var.google_folder_id role = "roles/logging.admin" members = module.developers_workgroup.members @@ -52,7 +52,7 @@ resource "google_folder_iam_binding" "developers_logging_admin" { # roles/monitoring.alertPolicyEditor as folder_role resource "google_folder_iam_binding" "developers_monitoring_alertPolicyEditor" { - count = contains(var.folder_roles, "roles/monitoring.alertPolicyEditor") && !var.admin_only ? 1 : 0 + count = contains(var.folder_roles, "roles/monitoring.alertPolicyEditor") && !var.admin_only && var.entitlement_enabled ? 1 : 0 folder = var.google_folder_id role = "roles/monitoring.alertPolicyEditor" members = module.developers_workgroup.members @@ -62,7 +62,7 @@ resource "google_folder_iam_binding" "developers_monitoring_alertPolicyEditor" { # roles/monitoring.notificationChannelEditor in as folder_role resource "google_folder_iam_binding" "developers_monitoring_notificationChannelEditor" { - count = contains(var.folder_roles, "roles/monitoring.notificationChannelEditor") && !var.admin_only ? 1 : 0 + count = contains(var.folder_roles, "roles/monitoring.notificationChannelEditor") && !var.admin_only && var.entitlement_enabled ? 1 : 0 folder = var.google_folder_id role = "roles/monitoring.notificationChannelEditor" members = module.developers_workgroup.members @@ -70,162 +70,162 @@ resource "google_folder_iam_binding" "developers_monitoring_notificationChannelE } resource "google_project_iam_binding" "editor_nonprod" { - count = contains(var.nonprod_roles, "roles/editor") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0 + count = contains(var.nonprod_roles, "roles/editor") && !var.admin_only && var.entitlement_enabled && var.google_nonprod_project_id != "" ? 1 : 0 project = var.google_nonprod_project_id role = "roles/editor" members = module.developers_workgroup.members } resource "google_project_iam_binding" "automl_editor_prod" { - count = contains(var.prod_roles, "roles/automl.editor") && !var.admin_only && var.google_prod_project_id != "" ? 1 : 0 + count = contains(var.prod_roles, "roles/automl.editor") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? 1 : 0 project = var.google_prod_project_id role = "roles/automl.editor" members = module.developers_workgroup.members } resource "google_project_iam_member" "cloudtranslate_editor_prod" { - for_each = contains(var.prod_roles, "roles/cloudtranslate.editor") && !var.admin_only && var.google_prod_project_id != "" ? toset(module.developers_workgroup.members) : toset([]) + for_each = contains(var.prod_roles, "roles/cloudtranslate.editor") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? toset(module.developers_workgroup.members) : toset([]) project = var.google_prod_project_id role = "roles/cloudtranslate.editor" member = each.key } resource "google_project_iam_binding" "storage_objectadmin_prod" { - count = contains(var.prod_roles, "roles/storage.objectAdmin") && !var.admin_only && var.google_prod_project_id != "" ? 1 : 0 + count = contains(var.prod_roles, "roles/storage.objectAdmin") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? 1 : 0 project = var.google_prod_project_id role = "roles/storage.objectAdmin" members = module.developers_workgroup.members } resource "google_project_iam_binding" "translationhub_admin_prod" { - count = contains(var.prod_roles, "roles/translationhub.admin") && !var.admin_only && var.google_prod_project_id != "" ? 1 : 0 + count = contains(var.prod_roles, "roles/translationhub.admin") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? 1 : 0 project = var.google_prod_project_id role = "roles/translationhub.admin" members = module.developers_workgroup.members } resource "google_project_iam_binding" "bucket_admin" { - count = contains(var.nonprod_roles, "roles/storage.admin") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0 + count = contains(var.nonprod_roles, "roles/storage.admin") && !var.admin_only && var.entitlement_enabled && var.google_nonprod_project_id != "" ? 1 : 0 project = var.google_nonprod_project_id role = "roles/storage.admin" members = module.developers_workgroup.members } resource "google_project_iam_binding" "prod_bucket_admin" { - count = contains(var.prod_roles, "roles/storage.admin") && !var.admin_only && var.google_prod_project_id != "" ? 1 : 0 + count = contains(var.prod_roles, "roles/storage.admin") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? 1 : 0 project = var.google_prod_project_id role = "roles/storage.admin" members = module.developers_workgroup.members } resource "google_project_iam_binding" "prod_developer_db_admin" { - count = contains(var.prod_roles, "roles/cloudsql.admin") && !var.admin_only && var.google_prod_project_id != "" ? 1 : 0 + count = contains(var.prod_roles, "roles/cloudsql.admin") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? 1 : 0 project = var.google_prod_project_id role = "roles/cloudsql.admin" members = module.developers_workgroup.members } resource "google_project_iam_binding" "nonprod_developer_db_admin" { - count = contains(var.nonprod_roles, "roles/cloudsql.admin") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0 + count = contains(var.nonprod_roles, "roles/cloudsql.admin") && !var.admin_only && var.entitlement_enabled && var.google_nonprod_project_id != "" ? 1 : 0 project = var.google_nonprod_project_id role = "roles/cloudsql.admin" members = module.developers_workgroup.members } resource "google_project_iam_binding" "prod_developer_monitoring_uptimecheckconfigeditor" { - count = contains(var.prod_roles, "roles/monitoring.uptimeCheckConfigEditor") && !var.admin_only && var.google_prod_project_id != "" ? 1 : 0 + count = contains(var.prod_roles, "roles/monitoring.uptimeCheckConfigEditor") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? 1 : 0 project = var.google_prod_project_id role = "roles/monitoring.uptimeCheckConfigEditor" members = module.developers_workgroup.members } resource "google_project_iam_binding" "nonprod_developer_monitoring_uptimecheckconfigeditor" { - count = contains(var.nonprod_roles, "roles/monitoring.uptimeCheckConfigEditor") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0 + count = contains(var.nonprod_roles, "roles/monitoring.uptimeCheckConfigEditor") && !var.admin_only && var.entitlement_enabled && var.google_nonprod_project_id != "" ? 1 : 0 project = var.google_nonprod_project_id role = "roles/monitoring.uptimeCheckConfigEditor" members = module.developers_workgroup.members } resource "google_project_iam_binding" "prod_developer_pubsub_editor" { - count = contains(var.prod_roles, "roles/pubsub.editor") && !var.admin_only && var.google_prod_project_id != "" ? 1 : 0 + count = contains(var.prod_roles, "roles/pubsub.editor") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? 1 : 0 project = var.google_prod_project_id role = "roles/pubsub.editor" members = module.developers_workgroup.members } resource "google_project_iam_binding" "nonprod_developer_pubsub_editor" { - count = contains(var.nonprod_roles, "roles/pubsub.editor") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0 + count = contains(var.nonprod_roles, "roles/pubsub.editor") && !var.admin_only && var.entitlement_enabled && var.google_nonprod_project_id != "" ? 1 : 0 project = var.google_nonprod_project_id role = "roles/pubsub.editor" members = module.developers_workgroup.members } resource "google_project_iam_binding" "prod_developer_colabEnterpriseUser" { - count = contains(var.prod_roles, "roles/aiplatform.colabEnterpriseUser") && !var.admin_only && var.google_prod_project_id != "" ? 1 : 0 + count = contains(var.prod_roles, "roles/aiplatform.colabEnterpriseUser") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? 1 : 0 project = var.google_prod_project_id role = "roles/aiplatform.colabEnterpriseUser" members = module.developers_workgroup.members } resource "google_project_iam_binding" "nonprod_developer_colabEnterpriseUser" { - count = contains(var.nonprod_roles, "roles/aiplatform.colabEnterpriseUser") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0 + count = contains(var.nonprod_roles, "roles/aiplatform.colabEnterpriseUser") && !var.admin_only && var.entitlement_enabled && var.google_nonprod_project_id != "" ? 1 : 0 project = var.google_nonprod_project_id role = "roles/aiplatform.colabEnterpriseUser" members = module.developers_workgroup.members } resource "google_project_iam_binding" "prod_developer_cloudsql_viewer" { - count = contains(var.prod_roles, "roles/cloudsql.viewer") && !var.admin_only && var.google_prod_project_id != "" ? 1 : 0 + count = contains(var.prod_roles, "roles/cloudsql.viewer") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? 1 : 0 project = var.google_prod_project_id role = "roles/cloudsql.viewer" members = module.developers_workgroup.members } resource "google_project_iam_binding" "nonprod_developer_cloudsql_viewer" { - count = contains(var.nonprod_roles, "roles/cloudsql.viewer") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0 + count = contains(var.nonprod_roles, "roles/cloudsql.viewer") && !var.admin_only && var.entitlement_enabled && var.google_nonprod_project_id != "" ? 1 : 0 project = var.google_nonprod_project_id role = "roles/cloudsql.viewer" members = module.developers_workgroup.members } resource "google_project_iam_binding" "prod_developer_objectUser" { - count = contains(var.prod_roles, "roles/storage.objectUser") && !var.admin_only && var.google_prod_project_id != "" ? 1 : 0 + count = contains(var.prod_roles, "roles/storage.objectUser") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? 1 : 0 project = var.google_prod_project_id role = "roles/storage.objectUser" members = module.developers_workgroup.members } resource "google_project_iam_binding" "nonprod_developer_objectUser" { - count = contains(var.nonprod_roles, "roles/storage.objectUser") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0 + count = contains(var.nonprod_roles, "roles/storage.objectUser") && !var.admin_only && var.entitlement_enabled && var.google_nonprod_project_id != "" ? 1 : 0 project = var.google_nonprod_project_id role = "roles/storage.objectUser" members = module.developers_workgroup.members } resource "google_project_iam_binding" "nonprod_developer_secretmanager_admin" { - count = contains(var.nonprod_roles, "roles/secretmanager.admin") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0 + count = contains(var.nonprod_roles, "roles/secretmanager.admin") && !var.admin_only && var.entitlement_enabled && var.google_nonprod_project_id != "" ? 1 : 0 project = var.google_nonprod_project_id role = "roles/secretmanager.admin" members = module.developers_workgroup.members } resource "google_project_iam_member" "prod_developer_secretmanager_secretAccessor" { - for_each = contains(var.prod_roles, "roles/secretmanager.secretAccessor") && !var.admin_only && var.google_prod_project_id != "" ? toset(module.developers_workgroup.members) : toset([]) + for_each = contains(var.prod_roles, "roles/secretmanager.secretAccessor") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? toset(module.developers_workgroup.members) : toset([]) project = var.google_prod_project_id role = "roles/secretmanager.secretAccessor" member = each.value } resource "google_project_iam_member" "prod_developer_secretmanager_secretVersionAdder" { - for_each = contains(var.prod_roles, "roles/secretmanager.secretVersionAdder") && !var.admin_only && var.google_prod_project_id != "" ? toset(module.developers_workgroup.members) : toset([]) + for_each = contains(var.prod_roles, "roles/secretmanager.secretVersionAdder") && !var.admin_only && var.entitlement_enabled && var.google_prod_project_id != "" ? toset(module.developers_workgroup.members) : toset([]) project = var.google_prod_project_id role = "roles/secretmanager.secretVersionAdder" member = each.value } resource "google_project_iam_binding" "nonprod_developer_oath_config_editor" { - count = contains(var.nonprod_roles, "roles/oauthconfig.editor") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0 + count = contains(var.nonprod_roles, "roles/oauthconfig.editor") && !var.admin_only && var.entitlement_enabled && var.google_nonprod_project_id != "" ? 1 : 0 project = var.google_nonprod_project_id role = "roles/oauthconfig.editor" members = module.developers_workgroup.members -} \ No newline at end of file +} diff --git a/google_permissions/pam_entitlement.tf b/google_permissions/pam_entitlement.tf new file mode 100644 index 00000000..651677eb --- /dev/null +++ b/google_permissions/pam_entitlement.tf @@ -0,0 +1,247 @@ + +locals { + // this is the list of roles that we want all of the entitlements to have by default + default_admin_role_list = [ + "roles/compute.admin", + "roles/dns.admin", + "roles/storage.admin", + "roles/spanner.admin", + "roles/cloudsql.admin", + ] + + // Populate the environments list dynamically + environments = [ + for environment in ["nonprod", "prod"] : environment + if(environment == "nonprod" && var.google_nonprod_project_id != "") || (environment == "prod" && var.google_prod_project_id != "") + ] + + additional_entitlements = flatten([ + for environment in local.environments : [ + for entitlement in try(var.entitlement_data.additional_entitlements, []) : { + key = "${var.app_code}/${environment}/${entitlement.name}" + tenant = var.app_code + project_id = environment == "nonprod" ? var.google_nonprod_project_id : var.google_prod_project_id + entitlement = entitlement + } + ] + ]) + + // The maximum allowed request duration is 4 hours no matter what the user specifies + // GCP allows this to be up to 12 hours, but we're going to limit it to 4 hours. + max_allowed_request_duration = 14400 + + // these are the perms REQUIRED for a user to be able to approve the entitlement + approvers_group_permissions = ["roles/privilegedaccessmanager.viewer"] + + default_admin_entitlement_name = "admin-entitlement-01" + + # Create the map with the hard-coded value and append the distinct principals + entitlement_wg_map = var.app_code != "" ? merge( + { + "default" : ["workgroup:${var.app_code}/developers"] # this the default value for the default system entitlement + }, + { + for name, add_entitlement in try(local.additional_entitlements, []) : add_entitlement.key => add_entitlement.entitlement.principals + } + ) : {} + + # prep the module workgroup lookup list for the approval workflow + # approvals on default currently NYI + approver_wg_map = { + for e in try(local.additional_entitlements, []) : + e.key => e.entitlement.approval_workflow.principals + if can(e.entitlement.approval_workflow) && e.entitlement.approval_workflow != null + } +} + +module "workgroup" { + source = "../mozilla_workgroup" + for_each = local.entitlement_wg_map + ids = each.value +} + +locals { + module_outputs = { + for key, mod in module.workgroup : key => mod.members + } +} + +module "approvals_workgroup" { + source = "../mozilla_workgroup" + for_each = local.approver_wg_map + ids = each.value +} + +locals { + approvals_module_outputs = { + for key, mod in module.approvals_workgroup : key => mod.members + } +} + + + +# now we handle the additional entitlements - these need to be created for BOTH environments +resource "google_privileged_access_manager_entitlement" "default_prod_entitlement" { + count = var.entitlement_enabled && (var.google_prod_project_id != "") ? 1 : 0 + entitlement_id = local.default_admin_entitlement_name + location = "global" + max_request_duration = "${local.max_allowed_request_duration}s" + parent = "projects/${var.google_prod_project_id}" + + requester_justification_config { + unstructured {} + } + + eligible_users { + principals = local.module_outputs["default"].members + } + privileged_access { + gcp_iam_access { + dynamic "role_bindings" { + for_each = setunion(try(var.entitlement_data.additional_roles, []), local.default_admin_role_list) + content { + role = role_bindings.value + } + } + resource = "//cloudresourcemanager.googleapis.com/projects/${var.google_prod_project_id}" + resource_type = "cloudresourcemanager.googleapis.com/Project" + } + } + + # NYI for defaults - need to add to the schema.json file + # + # additional_notification_targets { # leave this empty for now + # admin_email_recipients = [] + # requester_email_recipients = [] + # } + # + # dynamic "approval_workflow" { //optional block + # for_each = var.number_of_approvals > 0 ? [1] : [] + # content { + # manual_approvals { + # require_approver_justification = each.value.prod.entitlement. # leave this false for now + # steps { + # approvals_needed = 1 # this is all that's supported by google ATM + # approver_email_recipients = [] + # approvers { + # principals = + # } + # } + # } + # } + # } +} + +# now we handle the additional entitlements - these need to be created for BOTH environments +resource "google_privileged_access_manager_entitlement" "default_nonprod_entitlement" { + count = var.entitlement_enabled && (var.google_nonprod_project_id != "") ? 1 : 0 + entitlement_id = local.default_admin_entitlement_name + location = "global" + max_request_duration = "${local.max_allowed_request_duration}s" + parent = "projects/${var.google_nonprod_project_id}" + + requester_justification_config { + unstructured {} + } + + eligible_users { + principals = local.module_outputs["default"] + } + privileged_access { + gcp_iam_access { + dynamic "role_bindings" { + for_each = setunion(try(var.entitlement_data.additional_roles, []), local.default_admin_role_list) + content { + role = role_bindings.value + } + } + resource = "//cloudresourcemanager.googleapis.com/projects/${var.google_nonprod_project_id}" + resource_type = "cloudresourcemanager.googleapis.com/Project" + } + } + # NYI for defaults - need to add to the schema.json file + # + # additional_notification_targets { # leave this empty for now + # admin_email_recipients = [] + # requester_email_recipients = [] + # } + # + # dynamic "approval_workflow" { //optional block + # for_each = var.number_of_approvals > 0 ? [1] : [] + # content { + # manual_approvals { + # require_approver_justification = each.value.nonprod.entitlement. # leave this false for now + # steps { + # approvals_needed = 1 # this is all that's supported by google ATM + # approver_email_recipients = [] + # approvers { + # principals = + # } + # } + # } + # } + # } +} + +resource "google_privileged_access_manager_entitlement" "additional_entitlements" { + for_each = var.entitlement_enabled ? { + for entitlement in local.additional_entitlements : entitlement.key => entitlement + } : {} + entitlement_id = each.value.entitlement.name + location = "global" + max_request_duration = "${local.max_allowed_request_duration}s" + parent = "projects/${each.value.project_id}" + + requester_justification_config { + unstructured {} + } + + eligible_users { + principals = local.module_outputs[each.key] + } + privileged_access { + gcp_iam_access { + dynamic "role_bindings" { + for_each = each.value.entitlement.roles + content { + role = role_bindings.value + } + } + resource = "//cloudresourcemanager.googleapis.com/projects/${each.value.project_id}" + resource_type = "cloudresourcemanager.googleapis.com/Project" + } + } + dynamic "approval_workflow" { //optional block + for_each = try(length(each.value.entitlement.approval_workflow.principals), 0) > 0 ? [1] : [] + content { + manual_approvals { + require_approver_justification = try(each.value.entitlement.approval_workflow.require_approver_justification, false) # leave this false for now + steps { + approvals_needed = 1 # this is all that's supported by google ATM + approver_email_recipients = [] + approvers { + principals = local.approvals_module_outputs[each.key] + } + } + } + } + } +} + +# Create a feed that sends notifications about network resource updates. +resource "google_cloud_asset_project_feed" "project_feed" { + for_each = var.entitlement_enabled && var.entitlement_slack_topic != "" ? toset(local.environments) : [] + project = local.environments[each.key] + feed_id = var.feed_id + content_type = "RESOURCE" + + asset_types = [ + "privilegedaccessmanager.googleapis.com/Grant", + ] + + feed_output_config { + pubsub_destination { + topic = var.entitlement_slack_topic + } + } +} diff --git a/google_permissions/variables.tf b/google_permissions/variables.tf index 486df6dd..27cb0c54 100644 --- a/google_permissions/variables.tf +++ b/google_permissions/variables.tf @@ -1,3 +1,48 @@ +variable "app_code" { + description = "The application code for the permissions. See https://github.com/mozilla-services/inventory/blob/master/application_component_registry.csv." + type = string + default = "" +} + +variable "entitlement_enabled" { + description = "Whether or not to enable entitlements." + type = bool + default = false +} + +variable "entitlement_data" { + description = "The entitlement data for the project." + type = object({ + enabled = bool + additional_roles = list(string) + additional_entitlements = list(object({ + name = string + roles = list(string) + principals = list(string) + approval_workflow = optional(object({ + principals = list(string) + })) + })) + }) + default = { + enabled = false + additional_roles = [] + additional_entitlements = [] + } +} + +variable "entitlement_slack_topic" { + description = "The name of the pubsub topic to use for slack notifications." + type = string + default = "" +} + +variable "feed_id" { + description = "The ID of the feed to be created" + type = string + default = "grant_feed" +} + variable "google_folder_id" { description = "The ID of the folder to create the project in." type = string @@ -8,6 +53,8 @@ variable "google_folder_id" { * code later checks for this. */ + +// default for both of these is to pull these values from the remote state variable "google_prod_project_id" { description = "The ID of the prod project." type = string @@ -28,7 +75,7 @@ variable "google_nonprod_project_id" { // roles that are folder-only in scope are in this list variable "folder_roles" { - description = "List of roles to apply at the folder level." + description = "List of roles to apply at the folder level. Also used as the roles in the entitlement." type = list(string) default = [] } @@ -49,7 +96,7 @@ variable "nonprod_roles" { } /* -// Optional - this sets a special flag that sets the role on a project as admin only. It is mutually +// Optional - this sets a special flag that sets the role on a project as admin only. It is mutually // exclusive with the other roles variables and with the core set of roles. */ variable "admin_only" { diff --git a/google_permissions/versions.tf b/google_permissions/versions.tf index 2c9fa212..e4f30875 100644 --- a/google_permissions/versions.tf +++ b/google_permissions/versions.tf @@ -2,7 +2,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 3.0" + version = ">=6.7.0" + } + google-beta = { + source = "hashicorp/google-beta" + version = ">=6.7.0" } } required_version = "~> 1.2" From e8beb0cfe38e9c83b49af8c2f66f75490cad7c75 Mon Sep 17 00:00:00 2001 From: amitchell-moz Date: Tue, 17 Dec 2024 14:40:54 -0800 Subject: [PATCH 11/14] feat!: implement monorepo versioning proposal (#237) * implement monorepo versioning proposal * BREAKING CHANGE!: empty commit to make semantic PR happy --- .github/workflows/actions/action.yml | 147 +++++++++++++ .github/workflows/monorepo-release.yml | 33 +++ .github/workflows/monorepo.yml | 275 +++++++++++++++++++++++++ .github/workflows/release.yaml | 23 --- README.md | 60 +----- 5 files changed, 461 insertions(+), 77 deletions(-) create mode 100644 .github/workflows/actions/action.yml create mode 100644 .github/workflows/monorepo-release.yml create mode 100644 .github/workflows/monorepo.yml delete mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/actions/action.yml b/.github/workflows/actions/action.yml new file mode 100644 index 00000000..16ae2f08 --- /dev/null +++ b/.github/workflows/actions/action.yml @@ -0,0 +1,147 @@ +# GHA does not support dynamic outputs for matrix jobs https://github.com/orgs/community/discussions/26639 +# This composite action is a workaround to allow modules to be separately versioned w/o a bunch of hacks +# Whenever https://github.com/actions/runner/pull/2477#issuecomment-2445640849 lands, this can be folded back in +name: 'version-and-doc' +description: 'handle version bumps & doc generation for TF modules monorepo' +inputs: + package-name: + description: 'Package to version bump & doc' + required: true + changelog-entry: + description: 'Changelog contents from PR body' + required: true + release-type: + description: 'Semver release type' + required: true +outputs: + new-version: + description: "Version after bumping" + value: ${{ steps.new-version.outputs.result }} +runs: + using: "composite" + steps: + - name: Render terraform docs inside the README.md and push changes back to PR branch + uses: terraform-docs/gh-actions@v1.3.0 + with: + working-dir: ${{ inputs.package-name }} + output-file: README.md + git-push: "true" + config-file: .terraform-docs.yml + - name: Checkout all tags + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Detect previous version number + id: prev-version + env: + PACKAGE_NAME: ${{ inputs.package-name }} + shell: bash + run: | + git fetch --tags + TAG=$(git for-each-ref --sort=-creatordate --count 1 --format="%(refname:short)" "refs/tags/$PACKAGE_NAME-[0-9].[0-9].[0-9]") + + if [ -z "$TAG" ] ; then + echo "No git tag found for $PACKAGE_NAME, using 0.0.0 as previous version" + echo "result=0.0.0" >> "$GITHUB_OUTPUT" + exit 0 + fi + + TAG_VERSION="${TAG#*-}" + echo "TAG_VERSION = $TAG_VERSION" + SEMVER_REGEX="^[0-9].[0-9].[0-9]$" + if [[ $TAG_VERSION =~ $SEMVER_REGEX ]] ; then + echo "$TAG is valid semver, using it" + echo "result=${TAG_VERSION}" >> "$GITHUB_OUTPUT" + exit 0 + else + echo "Error: $TAG does not end in a valid semver" + exit 1 + fi + - name: Determine new version number + uses: actions/github-script@v7 + id: new-version + env: + PREV_VERSION: ${{ steps.prev-version.outputs.result }} + RELEASE_TYPE: ${{ inputs.release-type }} + with: + script: | + const { PREV_VERSION, RELEASE_TYPE } = process.env; + console.log('Previous version was', PREV_VERSION); + console.log('Release type is', RELEASE_TYPE); + + const numbers = PREV_VERSION.split('.'); + const numberIdx = ['major', 'minor', 'patch'].indexOf(RELEASE_TYPE); + numbers[numberIdx] = parseInt(numbers[numberIdx]) + 1; + for (let i = numberIdx + 1; i < numbers.length; i++) { + numbers[i] = 0; + } + return numbers.join('.'); + result-encoding: string + - name: Store version numbers + shell: bash + run: | + mkdir output + echo '${{ steps.prev-version.outputs.result }}' > output/previous-version.txt + echo '${{ steps.new-version.outputs.result }}' > output/new-version.txt + - name: Upload version artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.package-name }} + path: output + retention-days: 5 + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + - name: Sparse checkout unmodified changelogs from main + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.ref }} + repository: ${{ github.event.pull_request.base.repo.full_name }} + sparse-checkout: '${{ inputs.package-name }}/' + path: 'old' + - name: Write changelogs(s) + env: + CHANGELOG_ENTRY: ${{ inputs.changelog-entry }} + PACKAGE_NAME: ${{ inputs.package-name }} + NEW_VERSION: ${{ steps.new-version.outputs.result }} + shell: bash + run: | + ORIGINAL_CHANGELOG_PATH="old/$PACKAGE_NAME/CHANGELOG.md" + NEW_CHANGELOG_PATH="$PACKAGE_NAME/CHANGELOG.md" + # Trim off "s that sneak in when passing multiline GHA outputs + CHANGELOG_ENTRY=`sed -e 's/^"//' -e 's/"$//' <<<"$CHANGELOG_ENTRY"` + + if [ -f "$ORIGINAL_CHANGELOG_PATH" ] ; then + echo "Changelog already exists for $PACKAGE_NAME, prepending to it" + # Newline literal to pass to sed since it doesn't like /n + nl=$'\n' + sed -i "1i ${CHANGELOG_ENTRY} ${nl}" "$ORIGINAL_CHANGELOG_PATH" + sed -i "1i ## ${NEW_VERSION} ${nl}" "$ORIGINAL_CHANGELOG_PATH" + mv "$ORIGINAL_CHANGELOG_PATH" "$NEW_CHANGELOG_PATH" + else + echo "No existing changelog found for $PACKAGE_NAME, creating one" + echo -e "## $NEW_VERSION" > "$NEW_CHANGELOG_PATH" + echo -e "$CHANGELOG_ENTRY" >> "$NEW_CHANGELOG_PATH" + fi + echo "New changelog contents:" + cat "$NEW_CHANGELOG_PATH" + - name: Commit changelogs(s) + env: + PACKAGE_NAME: ${{ inputs.package-name }} + shell: bash + run: | + rm '.git/COMMIT_EDITMSG' || true # Prevent fatal: could not open '.git/COMMIT_EDITMSG' + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git pull # In case we have multiple changelog updates + git add "$PACKAGE_NAME/CHANGELOG.md" + if ! git diff --quiet --exit-code --cached ; then + echo "Committing changes to CHANGELOG.md" + git commit -m "Automated changelog for $PACKAGE_NAME" + git push + else + echo "No changes to CHANGELOG.md" + exit 0 + fi diff --git a/.github/workflows/monorepo-release.yml b/.github/workflows/monorepo-release.yml new file mode 100644 index 00000000..52d474a7 --- /dev/null +++ b/.github/workflows/monorepo-release.yml @@ -0,0 +1,33 @@ +name: 'Monorepo: Release' +on: + repository_dispatch: + types: [monorepo_release] + +jobs: + + tag: + runs-on: ubuntu-latest + name: 'Tag Releases' + permissions: + contents: write + steps: + - uses: actions/github-script@v7 + env: + CLIENT_PAYLOAD: ${{ toJSON(github.event.client_payload) }} + with: + script: | + const { sha, releases } = JSON.parse(process.env.CLIENT_PAYLOAD); + + for (const release of releases) { + const tagName = `${release.module}-${release.newVersion}`; + + const ref = `refs/tags/${tagName}`; + console.log('Tagging', tagName, 'as', sha); + console.log('Owner', context.repo.owner, 'repo', context.repo.repo); + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: ref, + sha: sha + }); + } diff --git a/.github/workflows/monorepo.yml b/.github/workflows/monorepo.yml new file mode 100644 index 00000000..333ec311 --- /dev/null +++ b/.github/workflows/monorepo.yml @@ -0,0 +1,275 @@ +name: Monorepo CI +concurrency: #avoid concurrent runs on label events, might cause issues on super fast commits ¯\_(ツ)_/¯ + group: ${{ github.head_ref }} + cancel-in-progress: true + +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + pull_request_target: + types: [closed] + +permissions: + pull-requests: read + +jobs: + detect: + runs-on: ubuntu-latest + name: 'Detect pull request context' + permissions: + pull-requests: write + contents: read + repository-projects: read + outputs: + directories: ${{ steps.condense.outputs.result }} + release-type: ${{ steps.check_pr_label.outputs.release-type}} + is-merge-event: >- + ${{ github.event_name == 'pull_request_target' + && github.event.action == 'closed' + && github.event.pull_request.merged == true }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + # I'm getting the labels from the API and not the context("contains(github.event.pull_request.labels.*.name, 'Env Promote')") as the labels + # are added in 2nd API call so they aren't included in the PR context + - name: Check PR labels for semver + id: check_pr_label + env: + PR_URL: ${{github.event.pull_request.html_url}} + PR_ID: ${{ github.event.pull_request.number }} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + run: | + LABELS=$(gh pr view $PR_URL --json labels --jq '.labels[] | select((.name=="minor") or (.name=="major") or (.name=="patch") or (.name=="no-release")) |.name') + NUMBER_OF_LABELS=$(echo "$LABELS" | wc -w) + if [ "$NUMBER_OF_LABELS" -eq "1" ] ; then + echo "Found: $LABELS" + echo "release-type=$LABELS" >> "$GITHUB_OUTPUT" + elif [ "$NUMBER_OF_LABELS" -gt "1" ] ; then + echo "::error ::Too many release type labels: $( echo $LABELS | tr '\n' ' ' )" + exit 1 + else + echo "::warn ::No release type labels found(patch/minor/major/no-release)" + echo "Checking commit messages for semantic labels" + + # fetch all commits + PR_COMMITS=`gh pr view $PR_ID --json commits | jq '.commits | length'` + git fetch --no-tags --prune --progress --no-recurse-submodules --deepen=$PR_COMMITS + + # retrive the commit messages of the PR + COMMIT_MESSAGES="$(gh pr view $prId --json commits --jq '.[].[].messageHeadline')" + + # Check all commits in PR for conventional commit messages - add appropriate label if found + # Follows https://www.conventionalcommits.org/en/v1.0.0/ + + # If there's conflicting messages take the largest semver change + if echo "$COMMIT_MESSAGES" | grep --quiet --ignore-case 'breaking change:'; then + LABEL="major" + elif echo "$COMMIT_MESSAGES" | grep --quiet --ignore-case 'feat:' ; then + LABEL="minor" + elif echo "$COMMIT_MESSAGES" | grep --quiet --ignore-case 'fix:' ; then + LABEL="patch" + else + echo "::error No release label found, and no conventional commit messages found. Label your PR with major/minor/patch" + exit 2 + fi + echo "release-type=$LABEL" >> "$GITHUB_OUTPUT" + gh pr edit $PR_ID --add-label "$LABEL" + fi + - name: Get changed files + uses: tj-actions/changed-files@v45 + id: raw-files + with: + json: "true" + escape_json: "false" + - name: Condense to directory list + uses: actions/github-script@v7 + id: condense + env: + RAW_FILES: ${{ steps.raw-files.outputs.all_changed_files }} + with: + script: | + const raw = JSON.parse(process.env.RAW_FILES); + const directories = Array.from(new Set(raw + .filter(x => !x.startsWith('.')) + .filter(x => x.includes('/')) + .map(x => x.split('/')[0]) + )); + if (directories.length < 1) return {}; + return { + include: directories.map(directory => ({ directory })), + }; + + plan: + needs: detect + name: 'Release plan & docs for module: ${{ matrix.directory }}' + if: needs.detect.outputs.directories != '{}' && needs.detect.outputs.release-type != 'no-release' + runs-on: ubuntu-latest + outputs: + new-version: ${{ steps.new-version.outputs.result }} + permissions: + contents: write + pull-requests: read + strategy: + matrix: "${{ fromJson(needs.detect.outputs.directories) }}" + fail-fast: false + # Do serially so git operations don't collide + max-parallel: 1 + steps: + - name: Extract changelog entry + uses: actions/github-script@v7 + id: changelog + with: + script: | + const { owner, repo } = context.repo; + const { data: prInfo } = await github.rest.pulls.get({ + owner, repo, + pull_number: context.issue.number, + }); + console.log('Found PR body:|'); + console.log(prInfo.body); + + const changelogEntry = ((prInfo.body + .split(/^#+ ?/m) + .find(x => x.startsWith('Changelog')) + || '').split(/^```/m)[1] || '').trim(); + if (!changelogEntry) + throw `'Changelog' section not found in PR body! Please add it back.`; + if (changelogEntry.match(/^TODO:/m)) + throw `'Changelog' section needs proper text, instead of 'TODO'`; + + const { writeFile } = require('fs').promises; + const entry=`* PR [#${ prInfo.number }](${ prInfo.html_url }) - ${ prInfo.title } + \`\`\` + ${changelogEntry} + \`\`\` + ` + return entry + - name: Checkout repository to use composite action + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + # Do the per-module steps in a composite action because matrixes can't handle dynamic outputs + - name: Generate docs and version bump + uses: ./.github/workflows/actions/ + with: + package-name: ${{ matrix.directory }} + changelog-entry: ${{ steps.changelog.outputs.result }} + release-type: ${{ needs.detect.outputs.release-type }} + + comment: + needs: [detect, plan] + if: needs.detect.outputs.is-merge-event == 'false' && needs.detect.outputs.release-type != 'no-release' + runs-on: ubuntu-latest + name: 'Comment on PR with release plan' + permissions: + issues: write + pull-requests: write + contents: write + steps: + - uses: actions/download-artifact@v4.1.8 + with: + path: outputs + - name: Display structure of downloaded files + run: ls -R + working-directory: outputs + + - uses: actions/github-script@v7 + id: comment + with: + script: | + const { owner, repo } = context.repo; + const { number: issue_number } = context.issue; + const { readdir, readFile } = require('fs').promises; + const utf8 = { encoding: 'utf-8' }; + + const lines = [ + '# Release plan', '', + '| Directory | Previous version | New version |', + '|--|--|--|', + ]; + const sections = []; + + for (const folder of await readdir('outputs', { withFileTypes: true })) { + if (!folder.isDirectory()) continue; + const readText = (name) => readFile(name, utf8).then(x => x.trim()); + + lines.push('| '+[ + `\`${folder.name}\``, + `${await readText(`outputs/${folder.name}/previous-version.txt`)}`, + `**${await readText(`outputs/${folder.name}/new-version.txt`)}**`, + ].join(' | ')+' |'); + + } + + const finalBody = [lines.join('\n'), ...sections].join('\n\n'); + + const {data: allComments} = await github.rest.issues.listComments({ issue_number, owner, repo }); + const ourComments = allComments + .filter(comment => comment.user.login === 'github-actions[bot]') + .filter(comment => comment.body.startsWith(lines[0]+'\n')); + + const latestComment = ourComments.slice(-1)[0]; + if (latestComment && latestComment.body === finalBody) { + console.log('Existing comment is already up to date.'); + return; + } + + const {data: newComment} = await github.rest.issues.createComment({ issue_number, owner, repo, body: finalBody }); + console.log('Posted comment', newComment.id, '@', newComment.html_url); + // Delete all our previous comments + for (const comment of ourComments) { + if (comment.id === newComment.id) continue; + console.log('Deleting previous PR comment from', comment.created_at); + await github.rest.issues.deleteComment({ comment_id: comment.id, owner, repo }); + } + + trigger-release: + needs: [plan] + if: needs.detect.outputs.is-merge-event == 'true' && needs.detect.outputs.release-type != 'no-release' + runs-on: ubuntu-latest + name: 'Dispatch release event' + permissions: + actions: write + contents: write + steps: + - uses: actions/download-artifact@v4.1.8 + with: + path: outputs + - name: Combine version information + id: extract-releases + uses: actions/github-script@v7 + with: + script: | + const { readdir, readFile } = require('fs').promises; + const utf8 = { encoding: 'utf-8' }; + const readText = (name) => readFile(name, utf8).then(x => x.trim()); + + const directories = await readdir('outputs', { withFileTypes: true }); + return await Promise.all(directories + .filter(x => x.isDirectory()) + .map(async folder => ({ + module: folder.name, + prevVersion: await readText(`outputs/${folder.name}/previous-version.txt`), + newVersion: await readText(`outputs/${folder.name}/new-version.txt`), + }))); + - name: Dispatch monorepo_release event + uses: actions/github-script@v7 + env: + RELEASE_LIST: ${{ steps.extract-releases.outputs.result }} + with: + script: | + const payload = { + run_id: "${{ github.run_id }}", + sha: context.sha, + releases: JSON.parse(process.env.RELEASE_LIST), + }; + console.log('Event payload:', JSON.stringify(payload, null, 2)); + + const { owner, repo } = context.repo; + await github.rest.repos.createDispatchEvent({ + owner, repo, + event_type: 'monorepo_release', + client_payload: payload, + }); diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index c63ede1b..00000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: Release -on: - push: - branches: - - main - -jobs: - release: - name: Release - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: "lts/*" - - name: Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npx semantic-release@20 diff --git a/README.md b/README.md index c4dfae0f..ef6c08a4 100644 --- a/README.md +++ b/README.md @@ -20,49 +20,11 @@ This repository also uses [GitHub Actions](.github/workflows/ci.yaml) to run sta ### Versioning and Releases -Versioning is automated based on [Semantic Versioning](https://semver.org/) using [`semantic-release`](https://github.com/semantic-release/semantic-release). -Release changelogs are automated by enforcing [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) -as a PR check using [`semantic-pull-request`](https://github.com/marketplace/actions/semantic-pull-request). - -Conventional commit convention will be checked on: -* commit message for **PRs with a single commit** -* PR title for **PRs with multiple commits** - -> #### 💡 Tip -> -> Push an empty commit to force `Semantic PR` check on the PR title instead of the commit message if `Semantic PR` -> GitHub Action prevents merging because a commit message does not respect the Conventional Commits specification. -> ```shell -> git commit --allow-empty -m "Semantic PR check" -> ``` - - -Additionally, commit squashing is required before merging for PRs with multiple commits. - -#### Release rules matching -From [`semantic-release/commit-analyzer`](https://github.com/semantic-release/commit-analyzer): - -- Commits with a breaking change will be associated with a `major` release. -- Commits with `type` 'feat' will be associated with a `minor` release. -- Commits with `type` 'fix' will be associated with a `patch` release. -- Commits with `type` 'perf' will be associated with a `patch` release. -- Commits with scope `no-release` will not be associated with a release type even if they have a breaking change or the `type` 'feat', 'fix' or 'perf'. -- Commits with `type` 'style' will not be associated with a release type. -- Commits with `type` 'test' will not be associated with a release type. -- Commits with `type` 'chore' will not be associated with a release type. - - -#### Valid commit messages and PR titles : -The tables below shows which commit message or PR title gets you which release type when `semantic-release` runs (using the default configuration): - -| PR title / commit message | Release type | -|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| -| `fix: GKE bastion host default notes.` | ~~Patch~~ Fix Release | -| `feat: Copy google-cdn-external from cloudops-infra.` | ~~Minor~~ Feature Release | -| `feat(google_cloudsql_mysql): Add query insights settings.` | ~~Minor~~ Feature Release | -| `refactor!: Drop support for Terraform 0.12.` | ~~Major~~ Breaking Release
(Note that since PR titles only have a single line, you have to use the `!` syntax for breaking changes.) | -| `perf(pencil): remove graphiteWidth option`

`BREAKING CHANGE: The graphiteWidth option has been removed.`
`The default graphite width of 10mm is always used for performance reasons.` | ~~Major~~ Breaking Release
(Note that the `BREAKING CHANGE: ` token must be in the footer of the commit message) | +Versioning is automated based on [Semantic Versioning](https://semver.org/), and PR labels. The `patch` `minor` and `major` labels will trigger the respective version bump. `no-release` will not generate a new tag and will skip most CI steps - **Use no-release at your own risk**. +If your PR contains [Conventional commits](https://www.conventionalcommits.org/en/v1.0.0/), it will label itself appropriately. +Release changelogs are generated based off of the contents of your PR description. +Readmes will be automatically generated, using `.terraform-docs.yml` from the module directory (if it exists). ## Creating modules @@ -89,18 +51,8 @@ If you want to ensure specific people or teams have to review changes to certain ### Documentation -A `README.md` is required for each module, however its contents are not currently validated. It is strongly recommended that you at least start your `README.md` using [terraform-docs](https://github.com/terraform-docs/terraform-docs). - -If you need to generate documentation run: - -``` -for directory in */ -do - terraform-docs --sort-by required markdown "$directory" > "${directory}README.md" -done -``` - -Alternatively, `pre-commit install` on this repository to automatically format the docs on commit. +A `README.md` is required for each module, and CI will automatically regenerate it using the [tf-docs github action](https://github.com/terraform-docs/gh-actions). +To include non-generated content in your README, place it outside the `` block. ## Using these modules From 562fe316cb2ced1ec3c56e47e42323f8d43d83ac Mon Sep 17 00:00:00 2001 From: amitchell-moz Date: Tue, 17 Dec 2024 15:00:59 -0800 Subject: [PATCH 12/14] fix: add missing pr template (#238) * fix: add missing pr template, remove releaserc now that semantic pr is gone --- .github/pull_request_template.md | 7 +++++++ .releaserc.yaml | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 .github/pull_request_template.md delete mode 100644 .releaserc.yaml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..87143580 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,7 @@ + + +## Changelog entry +``` +TODO: Replace this inner text with a useful message +for users of the affected modules! +``` diff --git a/.releaserc.yaml b/.releaserc.yaml deleted file mode 100644 index 0c812baa..00000000 --- a/.releaserc.yaml +++ /dev/null @@ -1,7 +0,0 @@ -branches: - - main - -plugins: - - "@semantic-release/commit-analyzer" - - "@semantic-release/release-notes-generator" - - "@semantic-release/github" From ad03a49be38df98c540cf3577f04d1a1293a4cb2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 18 Dec 2024 20:58:16 +0000 Subject: [PATCH 13/14] terraform-docs: automated action --- mozilla_workgroup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mozilla_workgroup/README.md b/mozilla_workgroup/README.md index bda9cc1c..e690c91f 100644 --- a/mozilla_workgroup/README.md +++ b/mozilla_workgroup/README.md @@ -90,7 +90,7 @@ module "workgroup" { | [roles](#input\_roles) | List of roles to generate bigquery acls for | `map(string)` | `{}` | no | | [terraform\_remote\_state\_bucket](#input\_terraform\_remote\_state\_bucket) | The GCS bucket used for terraform state that contains the expected workgroups output | `string` | `"moz-fx-platform-mgmt-global-tf"` | no | | [terraform\_remote\_state\_prefix](#input\_terraform\_remote\_state\_prefix) | The path prefix where the terraform state file is located | `string` | `"projects/google-workspace-management"` | no | -| [workgroup\_outputs](#input\_workgroup\_outputs) | Expected outputs from workgroup output definition | `list(any)` |
[
"members",
"google_groups"
]
| no | +| [workgroup\_outputs](#input\_workgroup\_outputs) | Expected outputs from workgroup output definition | `list(any)` |
[
"members",
"google_groups"
]
| no | ## Outputs | Name | Description | From 4d1ff105ed090a06a9198df327c9ed5be55f6169 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 18 Dec 2024 20:58:21 +0000 Subject: [PATCH 14/14] Automated changelog for mozilla_workgroup --- mozilla_workgroup/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 mozilla_workgroup/CHANGELOG.md diff --git a/mozilla_workgroup/CHANGELOG.md b/mozilla_workgroup/CHANGELOG.md new file mode 100644 index 00000000..cccd56a6 --- /dev/null +++ b/mozilla_workgroup/CHANGELOG.md @@ -0,0 +1,6 @@ +## 0.1.0 +* PR [#236](https://github.com/mozilla/terraform-modules/pull/236) - feat:add aws_gke_oidc_config and aws_gke_oidc_role modules +``` +Adds aws_gke_oidc_config and aws_gke_oidc_role modules +``` +