From 4350c5d25a5c5bdb4fa09e346e63cc4cf8e9f48f Mon Sep 17 00:00:00 2001 From: Sebastian neb Date: Wed, 6 Jan 2021 22:06:05 +0100 Subject: [PATCH] fix: Make project service account creation optional (#528) --- .gitignore | 1 + CONTRIBUTING.md | 35 ++++++++++++++++------- README.md | 1 + main.tf | 1 + modules/core_project_factory/main.tf | 19 ++++++------ modules/core_project_factory/outputs.tf | 10 +++---- modules/core_project_factory/variables.tf | 6 ++++ modules/gsuite_enabled/README.md | 1 + modules/gsuite_enabled/main.tf | 1 + modules/gsuite_enabled/variables.tf | 6 ++++ modules/svpc_service_project/README.md | 1 + modules/svpc_service_project/main.tf | 1 + modules/svpc_service_project/variables.tf | 6 ++++ variables.tf | 6 ++++ 14 files changed, 71 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index ac888a4a..3d7c3ffd 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ credentials.json .idea env/ test/fixtures/shared/terraform.tfvars +.envrc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8790588a..1b5fd04c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,16 +52,30 @@ The general strategy for these tests is to verify the behaviour of the submodules, and example modules are all functionally correct. ### Test Environment -The easiest way to test the module is in an isolated test project. The setup for such a project is defined in [test/setup](./test/setup/) directory. -To use this setup, you need a service account with Project Creator access on a folder. Export the Service Account credentials to your environment like so: +The easiest way to test the module is in an isolated test project and folder. +The setup for such a project and folder is defined in [test/setup](./test/setup/) directory. +This setup will create a dedicated folder, a project within the folder to hold a service +account that will be used to run the integration tests. It will assign all needed roles +to the service account and will also create a access context manager policy needed for test execution. -``` +To use and execute this setup, you need a service account with the following roles: + +- Project Creator access on the folder (if you want to delete the setup later ProjectDeleter is also needed). +- Folder Admin on the folder. +- Access Context Manager Editor or Admin on the organization. +- Billing Account Administrator on the billing account or on the organization. +- Organization Administrator on the organization in order to grant the created service account permissions on organization level. + +Export the Service Account credentials to your environment like so: + +```bash export SERVICE_ACCOUNT_JSON=$(< credentials.json) ``` You will also need to set a few environment variables: -``` + +```bash export TF_VAR_org_id="your_org_id" export TF_VAR_folder_id="your_folder_id" export TF_VAR_billing_account="your_billing_account_id" @@ -69,9 +83,9 @@ export TF_VAR_gsuite_admin_email="your_gsuite_admin_email" export TF_VAR_gsuite_domain="your_gsuite_domain" ``` -With these settings in place, you can prepare a test project using Docker: +With these settings in place, you can prepare the test setup using Docker: -``` +```bash make docker_test_prepare ``` @@ -109,7 +123,7 @@ Run `make docker_test_lint`. New versions can be released by pushing tags to this repository's origin on GitHub. There is a Make target to facilitate the process: -``` +```bash make release-new-version ``` @@ -140,14 +154,14 @@ Two test-kitchen instances are defined: - `full-local` - Test coverage for all project-factory features. - `full-minimal` - Test coverage for a minimal set of project-factory features. -#### Setup +### Setup 1. Configure the [test fixtures](#test-configuration). 2. Download a Service Account key with the necessary [permissions](#permissions) and put it in the module's root directory with the name `credentials.json`. 3. Add appropriate variables to your environment - ``` + ```bash export BILLING_ACCOUNT_ID="YOUR_BILLUNG_ACCOUNT" export DOMAIN="YOUR_DOMAIN" export FOLDER_ID="YOUR_FOLDER_ID" @@ -160,7 +174,8 @@ Two test-kitchen instances are defined: ``` 4. Run the testing container in interactive mode. - ``` + + ```bash make docker_run ``` diff --git a/README.md b/README.md index 5f3825cd..2cc29f68 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ determining that location is as follows: | budget\_alert\_spent\_percents | A list of percentages of the budget to alert on when threshold is exceeded | `list(number)` |
[
0.5,
0.7,
1
]
| no | | budget\_amount | The amount to use for a budget alert | `number` | `null` | no | | budget\_monitoring\_notification\_channels | A list of monitoring notification channels in the form `[projects/{project_id}/notificationChannels/{channel_id}]`. A maximum of 5 channels are allowed. | `list(string)` | `[]` | no | +| create\_project\_sa | Whether the default service account for the project shall be created | `bool` | `true` | no | | credentials\_path | Path to a service account credentials file with rights to run the Project Factory. If this file is absent Terraform will fall back to Application Default Credentials. | `string` | `""` | no | | default\_service\_account | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | `string` | `"disable"` | no | | disable\_dependent\_services | Whether services that are enabled and which depend on this service should also be disabled when this service is destroyed. | `bool` | `true` | no | diff --git a/main.tf b/main.tf index 16e41145..42a9d4c9 100644 --- a/main.tf +++ b/main.tf @@ -41,6 +41,7 @@ module "project-factory" { enable_shared_vpc_host_project = var.enable_shared_vpc_host_project billing_account = var.billing_account folder_id = var.folder_id + create_project_sa = var.create_project_sa sa_role = var.sa_role activate_apis = var.activate_apis activate_api_identities = var.activate_api_identities diff --git a/modules/core_project_factory/main.tf b/modules/core_project_factory/main.tf index d9053492..7514874c 100644 --- a/modules/core_project_factory/main.tf +++ b/modules/core_project_factory/main.tf @@ -34,10 +34,10 @@ locals { local.base_project_id, random_id.random_project_id_suffix.hex, ) : local.base_project_id - s_account_fmt = format( + s_account_fmt = var.create_project_sa ? format( "serviceAccount:%s", - google_service_account.default_service_account.email, - ) + google_service_account.default_service_account[0].email, + ) : "" api_s_account = format( "%s@cloudservices.gserviceaccount.com", google_project.main.number, @@ -56,7 +56,7 @@ locals { ) # Workaround for https://github.com/hashicorp/terraform/issues/10857 - shared_vpc_users_length = 3 + shared_vpc_users_length = var.create_project_sa ? 3 : 2 } /******************************************* @@ -129,6 +129,7 @@ resource "google_project_default_service_accounts" "default_service_accounts" { Default Service Account configuration *****************************************/ resource "google_service_account" "default_service_account" { + count = var.create_project_sa ? 1 : 0 account_id = "project-service-account" display_name = "${var.name} Project Service Account" project = google_project.main.project_id @@ -138,7 +139,7 @@ resource "google_service_account" "default_service_account" { Policy to operate instances in shared subnetwork *************************************************/ resource "google_project_iam_member" "default_service_account_membership" { - count = var.sa_role != "" ? 1 : 0 + count = var.sa_role != "" && var.create_project_sa ? 1 : 0 project = google_project.main.project_id role = var.sa_role @@ -160,12 +161,12 @@ resource "google_project_iam_member" "gsuite_group_role" { Granting serviceAccountUser to group *****************************************/ resource "google_service_account_iam_member" "service_account_grant_to_group" { - count = var.manage_group ? 1 : 0 + count = var.manage_group && var.create_project_sa ? 1 : 0 member = local.group_id role = "roles/iam.serviceAccountUser" - service_account_id = "projects/${google_project.main.project_id}/serviceAccounts/${google_service_account.default_service_account.email}" + service_account_id = "projects/${google_project.main.project_id}/serviceAccounts/${google_service_account.default_service_account[0].email}" } /****************************************************************************************************************** @@ -188,7 +189,7 @@ resource "google_project_iam_member" "controlling_group_vpc_membership" { *************************************************************************************/ resource "google_compute_subnetwork_iam_member" "service_account_role_to_vpc_subnets" { provider = google-beta - count = var.enable_shared_vpc_service_project && length(var.shared_vpc_subnets) > 0 ? length(var.shared_vpc_subnets) : 0 + count = var.enable_shared_vpc_service_project && length(var.shared_vpc_subnets) > 0 && var.create_project_sa ? length(var.shared_vpc_subnets) : 0 subnetwork = element( split("/", var.shared_vpc_subnets[count.index]), @@ -301,7 +302,7 @@ resource "google_storage_bucket_iam_member" "group_storage_admin_on_project_buck Project's bucket storage.admin granting to default compute service account ***********************************************/ resource "google_storage_bucket_iam_member" "s_account_storage_admin_on_project_bucket" { - count = local.create_bucket ? 1 : 0 + count = local.create_bucket && var.create_project_sa ? 1 : 0 bucket = google_storage_bucket.project_bucket[0].name role = "roles/storage.admin" diff --git a/modules/core_project_factory/outputs.tf b/modules/core_project_factory/outputs.tf index aa4dc653..6f88f55a 100644 --- a/modules/core_project_factory/outputs.tf +++ b/modules/core_project_factory/outputs.tf @@ -37,27 +37,27 @@ output "project_number" { } output "service_account_id" { - value = google_service_account.default_service_account.account_id + value = var.create_project_sa ? google_service_account.default_service_account[0].account_id : "" description = "The id of the default service account" } output "service_account_display_name" { - value = google_service_account.default_service_account.display_name + value = var.create_project_sa ? google_service_account.default_service_account[0].display_name : "" description = "The display name of the default service account" } output "service_account_email" { - value = google_service_account.default_service_account.email + value = var.create_project_sa ? google_service_account.default_service_account[0].email : "" description = "The email of the default service account" } output "service_account_name" { - value = google_service_account.default_service_account.name + value = var.create_project_sa ? google_service_account.default_service_account[0].name : "" description = "The fully-qualified name of the default service account" } output "service_account_unique_id" { - value = google_service_account.default_service_account.unique_id + value = var.create_project_sa ? google_service_account.default_service_account[0].unique_id : "" description = "The unique id of the default service account" } diff --git a/modules/core_project_factory/variables.tf b/modules/core_project_factory/variables.tf index 9744a695..aadd0b91 100644 --- a/modules/core_project_factory/variables.tf +++ b/modules/core_project_factory/variables.tf @@ -77,6 +77,12 @@ variable "folder_id" { default = "" } +variable "create_project_sa" { + description = "Whether the default service account for the project shall be created" + type = bool + default = true +} + variable "sa_role" { description = "A role to give the default Service Account for the project (defaults to none)" type = string diff --git a/modules/gsuite_enabled/README.md b/modules/gsuite_enabled/README.md index 16a99cec..37d5aa4c 100644 --- a/modules/gsuite_enabled/README.md +++ b/modules/gsuite_enabled/README.md @@ -71,6 +71,7 @@ The roles granted are specifically: | budget\_amount | The amount to use for a budget alert | `number` | `null` | no | | budget\_monitoring\_notification\_channels | A list of monitoring notification channels in the form `[projects/{project_id}/notificationChannels/{channel_id}]`. A maximum of 5 channels are allowed. | `list(string)` | `[]` | no | | create\_group | Whether to create the group or not | `bool` | `false` | no | +| create\_project\_sa | Whether the default service account for the project shall be created | `bool` | `true` | no | | credentials\_path | Path to a service account credentials file with rights to run the Project Factory. If this file is absent Terraform will fall back to Application Default Credentials. | `string` | `""` | no | | default\_service\_account | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | `string` | `"disable"` | no | | disable\_dependent\_services | Whether services that are enabled and which depend on this service should also be disabled when this service is destroyed. | `string` | `"true"` | no | diff --git a/modules/gsuite_enabled/main.tf b/modules/gsuite_enabled/main.tf index c745cb7e..624d7205 100644 --- a/modules/gsuite_enabled/main.tf +++ b/modules/gsuite_enabled/main.tf @@ -83,6 +83,7 @@ module "project-factory" { enable_shared_vpc_host_project = var.enable_shared_vpc_host_project billing_account = var.billing_account folder_id = var.folder_id + create_project_sa = var.create_project_sa sa_role = var.sa_role activate_apis = var.activate_apis usage_bucket_name = var.usage_bucket_name diff --git a/modules/gsuite_enabled/variables.tf b/modules/gsuite_enabled/variables.tf index 149065b1..d1722616 100644 --- a/modules/gsuite_enabled/variables.tf +++ b/modules/gsuite_enabled/variables.tf @@ -78,6 +78,12 @@ variable "sa_group" { default = "" } +variable "create_project_sa" { + description = "Whether the default service account for the project shall be created" + type = bool + default = true +} + variable "sa_role" { description = "A role to give the default Service Account for the project (defaults to none)" default = "" diff --git a/modules/svpc_service_project/README.md b/modules/svpc_service_project/README.md index 3446ea51..675212c2 100644 --- a/modules/svpc_service_project/README.md +++ b/modules/svpc_service_project/README.md @@ -43,6 +43,7 @@ module "service-project" { | budget\_alert\_spent\_percents | A list of percentages of the budget to alert on when threshold is exceeded | `list(number)` |
[
0.5,
0.7,
1
]
| no | | budget\_amount | The amount to use for a budget alert | `number` | `null` | no | | budget\_monitoring\_notification\_channels | A list of monitoring notification channels in the form `[projects/{project_id}/notificationChannels/{channel_id}]`. A maximum of 5 channels are allowed. | `list(string)` | `[]` | no | +| create\_project\_sa | Whether the default service account for the project shall be created | `bool` | `true` | no | | credentials\_path | Path to a service account credentials file with rights to run the Project Factory. If this file is absent Terraform will fall back to Application Default Credentials. | `string` | `""` | no | | default\_service\_account | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | `string` | `"disable"` | no | | disable\_dependent\_services | Whether services that are enabled and which depend on this service should also be disabled when this service is destroyed. | `bool` | `true` | no | diff --git a/modules/svpc_service_project/main.tf b/modules/svpc_service_project/main.tf index 5c35c07c..50f6793d 100755 --- a/modules/svpc_service_project/main.tf +++ b/modules/svpc_service_project/main.tf @@ -40,6 +40,7 @@ module "project-factory" { enable_shared_vpc_service_project = true billing_account = var.billing_account folder_id = var.folder_id + create_project_sa = var.create_project_sa sa_role = var.sa_role activate_apis = var.activate_apis activate_api_identities = var.activate_api_identities diff --git a/modules/svpc_service_project/variables.tf b/modules/svpc_service_project/variables.tf index 168bb18d..142fc6d4 100755 --- a/modules/svpc_service_project/variables.tf +++ b/modules/svpc_service_project/variables.tf @@ -71,6 +71,12 @@ variable "group_role" { default = "roles/editor" } +variable "create_project_sa" { + description = "Whether the default service account for the project shall be created" + type = bool + default = true +} + variable "sa_role" { description = "A role to give the default Service Account for the project (defaults to none)" default = "" diff --git a/variables.tf b/variables.tf index 22ee075d..5f23bd95 100644 --- a/variables.tf +++ b/variables.tf @@ -77,6 +77,12 @@ variable "group_role" { default = "roles/editor" } +variable "create_project_sa" { + description = "Whether the default service account for the project shall be created" + type = bool + default = true +} + variable "sa_role" { description = "A role to give the default Service Account for the project (defaults to none)" type = string