Skip to content

Commit

Permalink
Merge pull request #18 from mineiros-io/waxb/add-computed
Browse files Browse the repository at this point in the history
feat: add support for `computed_members`
  • Loading branch information
waxb authored Nov 28, 2022
2 parents fcf3db7 + e81e9e9 commit 84ab5b3
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 67 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## Added

- Add support for `computed_members`

## Removed

- BREAKING CHANGE: remove support for google provider version `3.x`
Expand Down
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,27 @@ See [variables.tf] and [examples/] for details and use-cases.

- [**`members`**](#var-members): *(Optional `set(string)`)*<a name="var-members"></a>

A set of identities that will be granted the privilege in role. Each entry can have one of the following values:

Identities that will be granted the privilege in role. Each entry can have one of the following values:
- `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account.
- `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account.
- `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@gmail.com` or `joe@example.com`.
- `serviceAccount:{emailid}`: An email address that represents a service account. For example, `my-other-app@appspot.gserviceaccount.com`.
- `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`.
- `domain:{domain}`: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, `google.com` or `example.com`.
- `user:{emailid}`: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com.
- `serviceAccount:{emailid}`: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com.
- `group:{emailid}`: An email address that represents a Google group. For example, admins@example.com.
- `domain:{domain}`: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com.
- `projectOwner:projectid`: Owners of the given project. For example, `projectOwner:my-example-project`
- `projectEditor:projectid`: Editors of the given project. For example, `projectEditor:my-example-project`
- `projectViewer:projectid`: Viewers of the given project. For example, `projectViewer:my-example-project`
- `computed:{identifier}`: An existing key from var.computed_members_map.

Default is `[]`.

- [**`computed_members_map`**](#var-computed_members_map): *(Optional `map(string)`)*<a name="var-computed_members_map"></a>

A map of identifiers to identities to be replaced in 'var.members' or in members of `policy_bindings` to handle terraform computed values.
The format of each value must satisfy the format as described in `var.members`.

Default is `{}`.

- [**`role`**](#var-role): *(Optional `string`)*<a name="var-role"></a>

The role that should be applied.
Expand Down
38 changes: 30 additions & 8 deletions README.tfdoc.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,39 @@ section {

variable "members" {
type = set(string)
default = []
description = <<-END
A set of identities that will be granted the privilege in role. Each entry can have one of the following values:
Identities that will be granted the privilege in role. Each entry can have one of the following values:
- `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account.
- `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account.
- `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@gmail.com` or `joe@example.com`.
- `serviceAccount:{emailid}`: An email address that represents a service account. For example, `my-other-app@appspot.gserviceaccount.com`.
- `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`.
- `domain:{domain}`: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, `google.com` or `example.com`.
END
default = []
- `user:{emailid}`: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com.
- `serviceAccount:{emailid}`: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com.
- `group:{emailid}`: An email address that represents a Google group. For example, admins@example.com.
- `domain:{domain}`: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com.
- `projectOwner:projectid`: Owners of the given project. For example, `projectOwner:my-example-project`
- `projectEditor:projectid`: Editors of the given project. For example, `projectEditor:my-example-project`
- `projectViewer:projectid`: Viewers of the given project. For example, `projectViewer:my-example-project`
- `computed:{identifier}`: An existing key from var.computed_members_map.
END
}

variable "computed_members_map" {
type = map(string)
default = {}
description = <<-END
A map of identifiers to identities to be replaced in 'var.members' or in members of `policy_bindings` to handle terraform computed values.
The format of each value must satisfy the format as described in `var.members`.
END
# TODO: terradoc does not allow use of variables in examples
# readme_example = <<-END
# members = [
# "user:member@example.com",
# "computed:myserviceaccount",
# ]
# computed_members_map = {
# myserviceaccount = "serviceAccount:${google_service_account.service_account.id}"
# }
# END
}

variable "role" {
Expand Down
22 changes: 14 additions & 8 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
locals {
create_binding = var.module_enabled && var.policy_bindings == null && var.authoritative
create_member = var.module_enabled && var.policy_bindings == null && var.authoritative == false
create_policy = var.module_enabled && var.policy_bindings != null
}

resource "google_secret_manager_secret_iam_binding" "binding" {
count = var.module_enabled && var.policy_bindings == null && var.authoritative ? 1 : 0
count = local.create_binding ? 1 : 0

project = var.project
secret_id = var.secret_id
role = var.role
members = var.members
members = [for m in var.members : try(var.computed_members_map[regex("^computed:(.*)", m)[0]], m)]

depends_on = [var.module_depends_on]
}

resource "google_secret_manager_secret_iam_member" "member" {
for_each = var.module_enabled && var.policy_bindings == null && var.authoritative == false ? var.members : []
for_each = local.create_member ? var.members : []

project = var.project
secret_id = var.secret_id
role = var.role
member = each.value
member = try(var.computed_members_map[regex("^computed:(.*)", each.value)[0]], each.value)

depends_on = [var.module_depends_on]
}

resource "google_secret_manager_secret_iam_policy" "policy" {
count = var.module_enabled && var.policy_bindings != null ? 1 : 0
count = local.create_policy ? 1 : 0

project = var.project
secret_id = var.secret_id
Expand All @@ -31,14 +37,14 @@ resource "google_secret_manager_secret_iam_policy" "policy" {
}

data "google_iam_policy" "policy" {
count = var.module_enabled && var.policy_bindings != null ? 1 : 0
count = local.create_policy ? 1 : 0

dynamic "binding" {
for_each = [for pb in var.policy_bindings : pb if length(tolist(pb.members)) > 0]
for_each = var.policy_bindings

content {
role = binding.value.role
members = binding.value.members
members = [for m in binding.value.members : try(var.computed_members_map[regex("^computed:(.*)", m)[0]], m)]

dynamic "condition" {
for_each = try([binding.value.condition], [])
Expand Down
6 changes: 3 additions & 3 deletions outputs.tf
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

locals {
binding = try(google_secret_manager_secret_iam_binding.binding[0], null)
member = try(google_secret_manager_secret_iam_member.member, null)
policy = try(google_secret_manager_secret_iam_policy.policy[0], null)
binding = one(google_secret_manager_secret_iam_binding.binding)
member = google_secret_manager_secret_iam_member.member
policy = one(google_secret_manager_secret_iam_policy.policy)

iam_output = [local.binding, local.member, local.policy]

Expand Down
41 changes: 0 additions & 41 deletions test/terramate_aws.tm.hcl

This file was deleted.

99 changes: 98 additions & 1 deletion test/unit-complete/main.tf
Original file line number Diff line number Diff line change
@@ -1,12 +1,109 @@
module "test-sa" {
source = "github.com/mineiros-io/terraform-google-service-account?ref=v0.1.0"

account_id = "service-account-id-${local.random_suffix}"
}

module "test" {
source = "../.."

# add all required arguments

secret_id = "unit-complete"
project = local.project_id

role = "roles/viewer"

# add all optional arguments that create additional/extended resources

members = [
"user:member@example.com",
"computed:myserviceaccount",
]
computed_members_map = {
myserviceaccount = "serviceAccount:${module.test-sa.service_account.email}"
}
# add most/all other optional arguments
}

module "test2" {
source = "../.."

# add all required arguments

secret_id = "unit-complete"

role = "roles/viewer"

# add all optional arguments that create additional/extended resources

authoritative = false
members = [
"user:member@example.com",
"computed:myserviceaccount",
]
computed_members_map = {
myserviceaccount = "serviceAccount:${module.test-sa.service_account.email}"
}

# add most/all other optional arguments
}

module "test3" {
source = "../.."

# add all required arguments

secret_id = "unit-complete"

policy_bindings = [
{
role = "roles/viewer"
members = [
"user:member@example.com",
"computed:myserviceaccount",
]
},
{
role = "roles/browser"
members = [
"user:member@example.com",
]
}
]

computed_members_map = {
myserviceaccount = "serviceAccount:${module.test-sa.service_account.email}"
}
# add all optional arguments that create additional/extended resources

# add most/all other optional arguments
}

module "test4" {
source = "../.."

# add all required arguments

secret_id = "unit-complete"

role = "roles/editor"
policy_bindings = [
{
role = "roles/viewer"
members = [
"user:member@example.com",
"computed:myserviceaccount",
]
condition = {
expression = "request.time < timestamp(\"2022-01-01T00:00:00Z\")"
title = "expires_after_2021_12_31"
}
},
]

computed_members_map = {
myserviceaccount = "serviceAccount:${module.test-sa.service_account.email}"
}
# add all optional arguments that create additional/extended resources

# add most/all other optional arguments
Expand Down
16 changes: 16 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ variable "members" {
type = set(string)
description = "(Optional) Identities that will be granted the privilege in role. Each entry can have one of the following values: 'allUsers', 'allAuthenticatedUsers', 'user:{emailid}', 'serviceAccount:{emailid}', 'group:{emailid}', 'domain:{domain}', 'projectOwner:projectid', 'projectEditor:projectid', 'projectViewer:projectid'."
default = []

validation {
condition = alltrue([for m in var.members : can(regex("^(allUsers|allAuthenticatedUsers|(user|serviceAccount|group|domain|projectOwner|projectEditor|projectViewer|computed):)", m))])
error_message = "The value must be a non-empty list of strings where each entry is a valid principal type identified with `user:`, `serviceAccount:`, `group:`, `domain:`, `projectOwner:`, `projectEditor:`, `projectViewer:` or `computed`."
}
}

variable "computed_members_map" {
type = map(string)
description = "(Optional) A map of members to replace in 'var.members' or in members of 'policy_bindings' to handle terraform computed values."
default = {}

validation {
condition = alltrue([for k, v in var.computed_members_map : can(regex("^(allUsers|allAuthenticatedUsers|(user|serviceAccount|group|domain|projectOwner|projectEditor|projectViewer):)", v))])
error_message = "The value must be a non-empty list of strings where each entry is a valid principal type identified with `user:`, `serviceAccount:`, `group:`, `domain:`, `projectOwner:`, `projectEditor:` or `projectViewer:`."
}
}

variable "role" {
Expand Down

0 comments on commit 84ab5b3

Please sign in to comment.