Skip to content

Commit

Permalink
fix: Make project service account creation optional (#528)
Browse files Browse the repository at this point in the history
  • Loading branch information
schostin authored Jan 6, 2021
1 parent e3111d2 commit 4350c5d
Show file tree
Hide file tree
Showing 14 changed files with 71 additions and 24 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ credentials.json
.idea
env/
test/fixtures/shared/terraform.tfvars
.envrc
35 changes: 25 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,26 +52,40 @@ 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"
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
```

Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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"
Expand All @@ -160,7 +174,8 @@ Two test-kitchen instances are defined:
```

4. Run the testing container in interactive mode.
```

```bash
make docker_run
```

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)` | <pre>[<br> 0.5,<br> 0.7,<br> 1<br>]</pre> | 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 |
Expand Down
1 change: 1 addition & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 10 additions & 9 deletions modules/core_project_factory/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
}

/*******************************************
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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}"
}

/******************************************************************************************************************
Expand All @@ -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]),
Expand Down Expand Up @@ -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"
Expand Down
10 changes: 5 additions & 5 deletions modules/core_project_factory/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Expand Down
6 changes: 6 additions & 0 deletions modules/core_project_factory/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions modules/gsuite_enabled/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
1 change: 1 addition & 0 deletions modules/gsuite_enabled/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions modules/gsuite_enabled/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand Down
1 change: 1 addition & 0 deletions modules/svpc_service_project/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)` | <pre>[<br> 0.5,<br> 0.7,<br> 1<br>]</pre> | 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 |
Expand Down
1 change: 1 addition & 0 deletions modules/svpc_service_project/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions modules/svpc_service_project/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand Down
6 changes: 6 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 4350c5d

Please sign in to comment.