Skip to content

Commit

Permalink
feat: OPST-1096 - terraform module to manage tenant roles #146 (#146)
Browse files Browse the repository at this point in the history
Provide a consistent way to manage GCP permissions for tenant GCP projects.

Today it’s a lot of copy/pasta when generating new projects, but when permissions are added/changed, it requires a lot of replacements.
  • Loading branch information
tcotav authored Dec 13, 2023
1 parent cb578d0 commit e472de5
Show file tree
Hide file tree
Showing 17 changed files with 541 additions and 0 deletions.
1 change: 1 addition & 0 deletions google_permissions/.terraform-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.6.5
72 changes: 72 additions & 0 deletions google_permissions/ADDING_NEW_ROLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
## Adding New GCP Role to the Module

The other resources grouping of resources are those that fall outside the core resource collection (aka the defaults) and the admin-only resource collection (which is a special case).

For most things, it is assumed that you'll start with the [core resource set](./main.tf) and then add resources as necessary for your target environment.

### TL;DR

- update local list matching your role `folder_additional_roles` or `project_additional_roles` in [variables.tf](./variables.tf)
- add appropriate resource block to [other_roles.tf](./other_roles.tf)

### Adding New Role

You will work with two files in adding a resource: `variables.tf` and `other_roles.tf`.

In [variables.tf](./variables.tf), you want to add your new role to one of the locals->*_additional_roles variable depending on the role type itself (folder or project):

```hcl
locals {
// This is a list of all the roles that we support in this module
// IN ADDITION to the roles added via the core rules in main.tf
// and that already have have existing supporting resource definitions.
folder_additional_roles = [
"roles/bigquery.jobUser",
]
project_additional_roles = [
"roles/automl.editor",
"roles/cloudtranslate.editor",
"roles/storage.objectAdmin",
"roles/translationhub.admin",
"roles/storage.admin",
"roles/cloudsql.admin"
]
}
```

That's it for that file. We use this list to validate user input.

The other thing to notice here is that three types of input lists are declared - folder, prod, and non_prod. This will allow the module user to set which role but also at which level of permissions. Why?

This gets a little convoluted as they represent two different things -- folder level roles and project level roles (of two types: prod and non-prod -- or at least those are the two arbitrary types that we use.). However, the roles provided by Google Cloud fall into those same two distinctions: hierarchy where folders contain projects and roles are available at folder or project level. So, in setting them in our environments, we have to understand which are folder level and which are project level. Fortunately, as we define or update our other roles available in [the file](./other_roles.tf), we'll see this explicitly called out.

We see the resource declared here as type `google_folder_iam_binding`, and we create this role by checking if it was in the input list `var.folder_roles`. We also have to use the appropriate folder_id.

```hcl
resource "google_folder_iam_binding" "bq_job_user" {
count = contains(var.folder_roles, "roles/bigquery.jobUser") && !var.admin_only ? 1 : 0
folder = var.google_folder_id
role = "roles/bigquery.jobUser"
members = setunion(
module.viewers_workgroup.members,
module.developers_workgroup.members
)
}
```

Similarly, a project role is of type `google_project_iam_binding`. Additionally, this requires another piece of information - which environment. This then corresponds to which variable list to check it against. In the case below, we see we check the role name against the `var.nonprod_roles` list. Also, we apply it to the variable input named `var.google_nonprod_project_id`

```hcl
resource "google_project_iam_binding" "editor_nonprod" {
count = contains(var.nonprod_roles, "roles/editor") && !var.admin_only && var.google_nonprod_project_id != "" ? 1 : 0
project = var.google_nonprod_project_id
role = "roles/editor"
members = module.developers_workgroup.members
}
```






26 changes: 26 additions & 0 deletions google_permissions/Design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## Design

### Three scenarios

The first two are default settings and the third is the flexible way of adding additional permissions IN ADDITION to the core dev permissions:

- admin_only - one of the use cases found in looking at the existing permissions. admin_only, a single permission setting
- core dev permissions - this is the default and includes settings for folder, project, and then secretmanager access and adder
- adhoc adding of other permissions IN ADDITION to the standard


### Why do the adhoc that way?

We want:

- to control which cloud resources permissions are used by the organization
- a central process to add new cloud resource permissions as necessary
- how with terraform would we keep a developer from getting around this?
- keep it back in the module
- exposing the list of desired permissions as strings
- if that string matches one in our module, then the resource is created (unless admin_only flag is set)


### Why do we want to control which resources are available?

It's less about controlling it and more about gating it. This will ensure that if people need to use a particular type of cloud resource that they take the extra steps of adding it to the shared repository. That includes taking whatever steps are in place for making such a change.
72 changes: 72 additions & 0 deletions google_permissions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# 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)

## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 1.0 |
| <a name="requirement_google"></a> [google](#requirement\_google) | >= 3.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_google"></a> [google](#provider\_google) | 5.7.0 |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_admins_workgroup"></a> [admins\_workgroup](#module\_admins\_workgroup) | ../mozilla_workgroup | n/a |
| <a name="module_developers_workgroup"></a> [developers\_workgroup](#module\_developers\_workgroup) | ../mozilla_workgroup | n/a |
| <a name="module_viewers_workgroup"></a> [viewers\_workgroup](#module\_viewers\_workgroup) | ../mozilla_workgroup | n/a |

## Resources

| Name | Type |
|------|------|
| [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_privateLogViewer](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/folder_iam_binding) | resource |
| [google_folder_iam_binding.developers_techsupport_editor](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/folder_iam_binding) | resource |
| [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_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 |
| [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.prod_bucket_admin](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource |
| [google_project_iam_binding.prod_developer_db_admin](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource |
| [google_project_iam_binding.storage_objectadmin_prod](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource |
| [google_project_iam_binding.translationhub_admin_prod](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_binding) | resource |
| [google_project_iam_member.cloudtranslate_editor_prod](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google_project_iam_member.developers_secretmanager_secretAccessor](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google_project_iam_member.developers_secretmanager_secretVersionAdder](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_admin_ids"></a> [admin\_ids](#input\_admin\_ids) | List of admin IDs to add to the project. | `list(string)` | `[]` | no |
| <a name="input_admin_only"></a> [admin\_only](#input\_admin\_only) | Whether or not to create a project with admin-only role. | `bool` | `false` | no |
| <a name="input_developer_ids"></a> [developer\_ids](#input\_developer\_ids) | List of developer IDs to add to the project. | `list(string)` | `[]` | no |
| <a name="input_folder_roles"></a> [folder\_roles](#input\_folder\_roles) | List of roles to apply at the folder level. | `list(string)` | `[]` | no |
| <a name="input_google_folder_id"></a> [google\_folder\_id](#input\_google\_folder\_id) | The ID of the folder to create the project in. | `string` | n/a | yes |
| <a name="input_google_nonprod_project_id"></a> [google\_nonprod\_project\_id](#input\_google\_nonprod\_project\_id) | The ID of the nonprod project. | `string` | `""` | no |
| <a name="input_google_prod_project_id"></a> [google\_prod\_project\_id](#input\_google\_prod\_project\_id) | The ID of the prod project. | `string` | `""` | no |
| <a name="input_nonprod_roles"></a> [nonprod\_roles](#input\_nonprod\_roles) | List of roles to apply to the nonprod project. | `list(string)` | `[]` | no |
| <a name="input_prod_roles"></a> [prod\_roles](#input\_prod\_roles) | List of roles to apply to the prod project. | `list(string)` | `[]` | no |
| <a name="input_viewer_ids"></a> [viewer\_ids](#input\_viewer\_ids) | List of viewer IDs to add to the project. | `list(string)` | `[]` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_validate_folder_roles"></a> [validate\_folder\_roles](#output\_validate\_folder\_roles) | n/a |
| <a name="output_validate_nonprod_roles"></a> [validate\_nonprod\_roles](#output\_validate\_nonprod\_roles) | n/a |
| <a name="output_validate_prod_roles"></a> [validate\_prod\_roles](#output\_validate\_prod\_roles) | n/a |
7 changes: 7 additions & 0 deletions google_permissions/admin_only.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

resource "google_folder_iam_binding" "folder" {
count = var.admin_only ? 1 : 0
folder = var.google_folder_id
role = "roles/owner"
members = module.admins_workgroup.members
}
9 changes: 9 additions & 0 deletions google_permissions/examples/admin_only/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Admin-only Example

This example shows how you would grant permissions to a tenant project where you *ONLY* want admin access and don't need the granularity of the other options.

The permissions granted can be found [here](../../admin_only.tf).

`admin_only` toggles on the admin-only permission set.

`admin_members` is the admin workgroup already created.
9 changes: 9 additions & 0 deletions google_permissions/examples/admin_only/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module "permissions" {
source = "../../../google_permissions"
// it is assumed that you loaded and have available a local.project
google_folder_id = local.project.folder.id
google_prod_project_id = local.project["prod"].id
google_nonprod_project_id = local.project["nonprod"].id
admin_only = true
admin_ids = ["workgroup:my-project/admins"]
}
5 changes: 5 additions & 0 deletions google_permissions/examples/basic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Basic Example

This example shows how to grant just the core set of permissions as specified [here](../../main.tf).

This is a good way to get started on most projects.
10 changes: 10 additions & 0 deletions google_permissions/examples/basic/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module "permissions" {
source = "github.com/mozilla/terraform-modules//google_permissions?ref=main"
// it is assumed that you loaded and have available a local.project
google_folder_id = local.project.folder.id
google_prod_project_id = local.project["prod"].id
google_nonprod_project_id = local.project["nonprod"].id
admin_ids = ["workgroup:my-project/workgroup_subgroup"]
developer_ids = ["workgroup:my-project/developers"]
viewer_ids = ["workgroup:my-project/viewers"]
}
5 changes: 5 additions & 0 deletions google_permissions/examples/basic_with_addon/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Basic With Additional Role Example

This example shows how to grant the core set of permissions as specified [here](../../main.tf) but then add additional roles to the project.

The current list of available additional roles can be found [here](../../other_roles.tf)
22 changes: 22 additions & 0 deletions google_permissions/examples/basic_with_addon/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module "permissions" {
source = "../../../google_permissions"
// it is assumed that you loaded and have available a local.project
google_folder_id = local.project.folder.id
google_prod_project_id = local.project["prod"].id
google_nonprod_project_id = local.project["nonprod"].id
admin_ids = ["workgroup:my-project/admins"]
developer_ids = ["workgroup:my-project/developers"]
folder_roles = [
"roles/bigquery.jobUser",
]
prod_roles = [
"roles/storage.objectAdmin",
"roles/storage.admin",
"roles/cloudsql.admin"
]
nonprod_roles = [
"roles/editor",
"roles/storage.admin",
"roles/cloudsql.admin"
]
}
59 changes: 59 additions & 0 deletions google_permissions/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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
}

resource "google_folder_iam_binding" "viewer" {
count = var.admin_only ? 0 : 1
folder = var.google_folder_id
role = "roles/viewer"
members = setunion(
module.developers_workgroup.members,
module.viewers_workgroup.members
)
}

//
// additional permissions, folder level
//

// required to grant access to data logs
resource "google_folder_iam_binding" "developers_logging_privateLogViewer" {
count = var.admin_only ? 0 : 1
folder = var.google_folder_id
role = "roles/logging.privateLogViewer"
members = module.developers_workgroup.members
}

//
// Grant the ability to open support tickets to the developers
//
resource "google_folder_iam_binding" "developers_techsupport_editor" {
count = var.admin_only ? 0 : 1
folder = var.google_folder_id
role = "roles/cloudsupport.techSupportEditor"
members = module.developers_workgroup.members
}

//
// additional permissions, project level
//

// Give developers access to r/w secrets in nonprod
resource "google_project_iam_member" "developers_secretmanager_secretAccessor" {
//for_each = module.developers_workgroup.members
for_each = !var.admin_only && var.google_nonprod_project_id != "" ? toset(module.developers_workgroup.members) : toset([])
project = var.google_nonprod_project_id
role = "roles/secretmanager.secretAccessor"
member = each.value
}

resource "google_project_iam_member" "developers_secretmanager_secretVersionAdder" {
for_each = !var.admin_only && var.google_nonprod_project_id != "" ? toset(module.developers_workgroup.members) : toset([])
project = var.google_nonprod_project_id
role = "roles/secretmanager.secretVersionAdder"
member = each.value
}
Loading

0 comments on commit e472de5

Please sign in to comment.