Skip to content

Commit

Permalink
feat: add condition block to all iam_member type resources (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
bludot authored Jun 7, 2022
1 parent 7819f28 commit a918d45
Show file tree
Hide file tree
Showing 27 changed files with 1,454 additions and 45 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ on:
env:
tf_version: "latest"
tf_working_dir: "."
TF_VAR_google_credentials: ${{ secrets.GOOGLE_CREDENTIALS_READ_ALL_PROJECTS }}
TF_VAR_google_project: ${{ secrets.TERRATEST_GOOGLE_PROJECT }}
TF_VAR_google_credentials: ${{ secrets.TERRATEST_GOOGLE_CREDENTIALS }}
jobs:
terraform:
name: "terraform"
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "examples/modules/terraform-gcp-gcs"]
path = examples/modules/terraform-gcp-gcs
url = https://github.com/honestbank/terraform-gcp-gcs
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Account. Inputs and outputs below are for the wrapper - for the actual module to

| Name | Version |
|------|---------|
| <a name="provider_random"></a> [random](#provider\_random) | 3.1.1 |
| <a name="provider_random"></a> [random](#provider\_random) | 3.2.0 |

## Modules

Expand Down
21 changes: 21 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Contributing to this repo

## Testing this repo
In order to test this repo, use Terratest github secrets to be able to run the tests.
* secrets.TERRATEST_GOOGLE_PROJECT
* secrets.TERRATEST_GOOGLE_CREDENTIALS
* secrets.TERRATEST_GCP_SA_EMAIL

These will ensure the right projects are used with the right credentials.
Additionally for this repo you must enable permissions if they are not already enabled:
* enable permission `iam.serviceAccounts.create`
* enable permission `resourcemanager.folders.getIamPolicy`


## A note on service account conditions
Google does not allow you to create conditions on primitive roles. These roles are:
* `roles/viewer`
* `roles/admin`
* `roles/owner`

Instead, roles should be scoped down such as `roles/storage.objectViewer` or custom roles. see [here](https://cloud.google.com/storage/docs/access-control/iam-permissions)
26 changes: 26 additions & 0 deletions examples/google_service_account_cross_project_test/bucket.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
locals {
bucket_name = var.run_id
}

module "test_bucket" {
source = "../modules/terraform-gcp-gcs/modules/gcp_gcs_bucket"

location = "${var.google_region}2"
name = local.bucket_name

force_destroy = true
}

resource "google_storage_bucket_object" "readable_file" {
bucket = module.test_bucket.name
name = "readable.txt"
content = "hello world"
content_type = "text/plain; charset=utf-8"
}

resource "google_storage_bucket_object" "unreadable_file" {
bucket = module.test_bucket.name
name = "unreadable.txt"
content = "goodbye world"
content_type = "text/plain; charset=utf-8"
}
28 changes: 28 additions & 0 deletions examples/google_service_account_cross_project_test/inputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
variable "google_credentials" {
type = string
description = "GCP Service Account JSON keyfile contents."
}

variable "google_project" {
type = string
description = "The GCP project to use when initializing the google Terraform provider."
}

variable "google_region" {
type = string
description = "The GCP region to use when initializing the google Terraform provider."
}
variable "run_id" {
type = string
description = "The unique ID of the run."
}

variable "cross_project_iam_role_memberships" {
default = {}
description = "A map of GCP project IDs and an associated list of IAM roles to add a membership to."
type = map(list(string))
validation {
condition = length(var.cross_project_iam_role_memberships) < 2
error_message = "To maintain a cleaner security model, only one project is currently supported for cross-project role memberships."
}
}
3 changes: 3 additions & 0 deletions examples/google_service_account_cross_project_test/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "bucket_name" {
value = module.test_bucket.name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
provider "google" {
region = var.google_region
project = var.google_project
credentials = var.google_credentials
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module "bucket_service_account" {
source = "../../modules/google_service_account"

account_id = "terratest-${var.run_id}"
display_name = "terratest-${var.run_id}"
description = "An instance of the google_service_account Terraform component module."

cross_project_iam_role_memberships = var.cross_project_iam_role_memberships

iam_role_membership_type = "CROSS_PROJECT"

cross_project_conditions = [
{
title = "User can read readable file"
description = "User can read readable file"
expression = <<EOF
resource.service == 'storage.googleapis.com' &&
resource.name == 'projects/_/buckets/${local.bucket_name}/readable.txt'
EOF
}
]

project = var.google_project
}
26 changes: 26 additions & 0 deletions examples/google_service_account_test_with_condition/bucket.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
locals {
bucket_name = var.run_id
}

module "test_bucket" {
source = "../modules/terraform-gcp-gcs/modules/gcp_gcs_bucket"

location = "${var.google_region}2"
name = local.bucket_name

force_destroy = true
}

resource "google_storage_bucket_object" "readable_file" {
bucket = module.test_bucket.name
name = "readable.txt"
content = "hello world"
content_type = "text/plain; charset=utf-8"
}

resource "google_storage_bucket_object" "unreadable_file" {
bucket = module.test_bucket.name
name = "unreadable.txt"
content = "goodbye world"
content_type = "text/plain; charset=utf-8"
}
18 changes: 18 additions & 0 deletions examples/google_service_account_test_with_condition/inputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
variable "google_credentials" {
type = string
description = "GCP Service Account JSON keyfile contents."
}

variable "google_project" {
type = string
description = "The GCP project to use when initializing the google Terraform provider."
}

variable "google_region" {
type = string
description = "The GCP region to use when initializing the google Terraform provider."
}
variable "run_id" {
type = string
description = "The unique ID of the run."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "bucket_name" {
value = module.test_bucket.name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
provider "google" {
region = var.google_region
project = var.google_project
credentials = var.google_credentials
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module "bucket_service_account" {
source = "../../modules/google_service_account"

account_id = "terratest-${var.run_id}"
display_name = "terratest-${var.run_id}"
description = "An instance of the google_service_account Terraform component module."

in_project_roles = ["roles/viewer"]
iam_role_membership_type = "IN_PROJECT"

in_project_conditions = [
{
title = "User can read readable file"
description = "User can read readable file"
expression = <<EOF
resource.service == 'storage.googleapis.com' &&
resource.name == 'projects/_/buckets/${local.bucket_name}/readable.txt'
EOF
}
]

project = var.google_project
}
1 change: 1 addition & 0 deletions examples/modules/terraform-gcp-gcs
Submodule terraform-gcp-gcs added at 7fd4ce
5 changes: 4 additions & 1 deletion modules/google_service_account/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

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

## Modules

Expand All @@ -32,11 +32,14 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_account_id"></a> [account\_id](#input\_account\_id) | The account id that is used to generate the service account email address and a stable unique id. It is unique within a project, must be 6-30 characters long, and match the regular expression [a-z]([-a-z0-9]*[a-z0-9]) to comply with RFC1035. Changing this forces a new service account to be created. | `string` | n/a | yes |
| <a name="input_cross_project_conditions"></a> [cross\_project\_conditions](#input\_cross\_project\_conditions) | A list of conditions to be applied to in project service account.<br> Example:<pre>module "google_service_account_instance" {<br> source = "./modules/google_service_account"<br><br> account_id = "terraform-id"<br> display_name = "google_service_account_instance"<br> description = "An instance of the google_service_account Terraform component module."<br><br> cross_project_iam_role_memberships = ["somemembership"]<br><br> cross_project_conditions = [{<br> title = "User is in the same organization as the Terraform project"<br> description = "The user is in the same organization as the Terraform project."<br> expression = "request.resource.labels.organization_id == project.project_id"<br> }]<br><br> key_aliases = ["primary", "secondary", "another_key"]<br> project = var.service_account_host_project<br> }</pre> | <pre>list(object({<br> title = string,<br> description = string,<br> expression = string,<br> }))</pre> | `[]` | no |
| <a name="input_cross_project_iam_role_memberships"></a> [cross\_project\_iam\_role\_memberships](#input\_cross\_project\_iam\_role\_memberships) | A map of GCP project IDs and an associated list of IAM roles to add a membership to. | `map(list(string))` | `{}` | no |
| <a name="input_description"></a> [description](#input\_description) | A text description of the service account. Must be less than or equal to 256 UTF-8 bytes. | `string` | n/a | yes |
| <a name="input_display_name"></a> [display\_name](#input\_display\_name) | The display name for the service account. Can be updated without creating a new resource. | `string` | n/a | yes |
| <a name="input_folder_conditions"></a> [folder\_conditions](#input\_folder\_conditions) | A list of conditions to be applied to in project service account.<br> Example:<pre>module "google_service_account_instance" {<br> source = "./modules/google_service_account"<br><br> account_id = "terraform-id"<br> display_name = "google_service_account_instance"<br> description = "An instance of the google_service_account Terraform component module."<br><br> folder_iam_role_memberships = ["folder_memberships"]<br><br> folder_conditions = [{<br> title = "User is in the same organization as the Terraform project"<br> description = "The user is in the same organization as the Terraform project."<br> expression = "request.resource.labels.organization_id == project.project_id"<br> }]<br><br> key_aliases = ["primary", "secondary", "another_key"]<br> project = var.service_account_host_project<br> }</pre> | <pre>list(object({<br> title = string,<br> description = string,<br> expression = string,<br> }))</pre> | `[]` | no |
| <a name="input_folder_iam_role_memberships"></a> [folder\_iam\_role\_memberships](#input\_folder\_iam\_role\_memberships) | A map of GCP folder IDs and an associated list of IAM roles to add a membership to. | `map(list(string))` | `{}` | no |
| <a name="input_iam_role_membership_type"></a> [iam\_role\_membership\_type](#input\_iam\_role\_membership\_type) | One of [CROSS\_PROJECT, FOLDER, IN\_PROJECT]. | `string` | `"IN_PROJECT"` | no |
| <a name="input_in_project_conditions"></a> [in\_project\_conditions](#input\_in\_project\_conditions) | A list of conditions to be applied to in project service account.<br> Example:<pre>module "google_service_account_instance" {<br> source = "./modules/google_service_account"<br><br> account_id = "terraform-id"<br> display_name = "google_service_account_instance"<br> description = "An instance of the google_service_account Terraform component module."<br><br> in_project_roles = ["roles/viewer"]<br><br> in_project_conditions = [{<br> title = "User is in the same organization as the Terraform project"<br> description = "The user is in the same organization as the Terraform project."<br> expression = "request.resource.labels.organization_id == project.project_id"<br> }]<br><br> key_aliases = ["primary", "secondary", "another_key"]<br> project = var.service_account_host_project<br> }</pre> | <pre>list(object({<br> title = string,<br> description = string,<br> expression = string,<br> }))</pre> | `[]` | no |
| <a name="input_in_project_roles"></a> [in\_project\_roles](#input\_in\_project\_roles) | Roles to assign service account within its own project. | `list(string)` | `[]` | no |
| <a name="input_key_aliases"></a> [key\_aliases](#input\_key\_aliases) | A JSON key will be created and output for each entry in this list. | `list(string)` | `[]` | no |
| <a name="input_project"></a> [project](#input\_project) | The ID of the project that the service account will be created in. Defaults to the provider project configuration. | `string` | n/a | yes |
Expand Down
33 changes: 33 additions & 0 deletions modules/google_service_account/inputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,36 @@ variable "project" {
description = "The ID of the project that the service account will be created in. Defaults to the provider project configuration."
type = string
}

variable "conditions" {
type = list(object({
title = string,
description = string,
expression = string,
}))
default = []
description = <<DESC
A list of conditions to be applied to in project service account.
Example:
```
module "google_service_account_instance" {
source = "./modules/google_service_account"
account_id = "terraform-id"
display_name = "google_service_account_instance"
description = "An instance of the google_service_account Terraform component module."
in_project_roles = ["roles/viewer"]
conditions = [{
title = "User is in the same organization as the Terraform project"
description = "The user is in the same organization as the Terraform project."
expression = "request.resource.labels.organization_id == project.project_id"
}]
key_aliases = ["primary", "secondary", "another_key"]
project = var.service_account_host_project
}
```
DESC
}
26 changes: 26 additions & 0 deletions modules/google_service_account/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ resource "google_project_iam_member" "in_project_roles" {
project = var.project
role = each.value
member = "serviceAccount:${google_service_account.service_account.email}"
dynamic "condition" {
for_each = var.conditions
content {
expression = condition.value.expression
description = condition.value.description
title = condition.value.title
}
}
}

resource "google_project_iam_member" "cross_project_iam_role_memberships" {
Expand All @@ -48,6 +56,15 @@ resource "google_project_iam_member" "cross_project_iam_role_memberships" {
member = "serviceAccount:${google_service_account.service_account.email}"
project = local.cross_project_iam_role_membership_project_id
role = each.value

dynamic "condition" {
for_each = var.conditions
content {
expression = condition.value.expression
description = condition.value.description
title = condition.value.title
}
}
}

resource "google_folder_iam_member" "folder_iam_role_memberships" {
Expand All @@ -56,4 +73,13 @@ resource "google_folder_iam_member" "folder_iam_role_memberships" {
folder = local.folder_iam_role_membership_folder_id
member = "serviceAccount:${google_service_account.service_account.email}"
role = each.value

dynamic "condition" {
for_each = var.conditions
content {
expression = condition.value.expression
description = condition.value.description
title = condition.value.title
}
}
}
3 changes: 1 addition & 2 deletions terraform-gcp-iam-wrapper.auto.tfvars
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
google_project = "test-terraform-project-01"
google_region = "asia-southeast2"
service_account_host_project = "test-terraform-project-01"
service_account_host_project = "compute-df9f" # This is a project in our GCP to run terratest in.
Loading

0 comments on commit a918d45

Please sign in to comment.