Add support for IAM additive to folder module (#580)
This commit is contained in:
parent
a40493a433
commit
ecadebe90b
|
@ -256,7 +256,7 @@ module "folder" {
|
||||||
| name | description | resources |
|
| name | description | resources |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| [firewall-policies.tf](./firewall-policies.tf) | None | <code>google_compute_firewall_policy</code> · <code>google_compute_firewall_policy_association</code> · <code>google_compute_firewall_policy_rule</code> |
|
| [firewall-policies.tf](./firewall-policies.tf) | None | <code>google_compute_firewall_policy</code> · <code>google_compute_firewall_policy_association</code> · <code>google_compute_firewall_policy_rule</code> |
|
||||||
| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | <code>google_folder_iam_binding</code> |
|
| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | <code>google_folder_iam_binding</code> · <code>google_folder_iam_member</code> |
|
||||||
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | <code>google_bigquery_dataset_iam_member</code> · <code>google_logging_folder_exclusion</code> · <code>google_logging_folder_sink</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
|
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | <code>google_bigquery_dataset_iam_member</code> · <code>google_logging_folder_exclusion</code> · <code>google_logging_folder_sink</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
|
||||||
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_essential_contacts_contact</code> · <code>google_folder</code> |
|
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_essential_contacts_contact</code> · <code>google_folder</code> |
|
||||||
| [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | <code>google_folder_organization_policy</code> |
|
| [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | <code>google_folder_organization_policy</code> |
|
||||||
|
@ -276,14 +276,16 @@ module "folder" {
|
||||||
| [folder_create](variables.tf#L58) | Create folder. When set to false, uses id to reference an existing folder. | <code>bool</code> | | <code>true</code> |
|
| [folder_create](variables.tf#L58) | Create folder. When set to false, uses id to reference an existing folder. | <code>bool</code> | | <code>true</code> |
|
||||||
| [group_iam](variables.tf#L64) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map(list(string))</code> | | <code>{}</code> |
|
| [group_iam](variables.tf#L64) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||||
| [iam](variables.tf#L71) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
| [iam](variables.tf#L71) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||||
| [id](variables.tf#L78) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> |
|
| [iam_additive](variables.tf#L78) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||||
| [logging_exclusions](variables.tf#L84) | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
| [iam_additive_members](variables.tf#L85) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||||
| [logging_sinks](variables.tf#L91) | Logging sinks to create for this folder. | <code title="map(object({ destination = string type = string filter = string include_children = bool exclusions = map(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
| [id](variables.tf#L92) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> |
|
||||||
| [name](variables.tf#L112) | Folder name. | <code>string</code> | | <code>null</code> |
|
| [logging_exclusions](variables.tf#L98) | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
||||||
| [parent](variables.tf#L118) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
|
| [logging_sinks](variables.tf#L105) | Logging sinks to create for this folder. | <code title="map(object({ destination = string type = string filter = string include_children = bool exclusions = map(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||||
| [policy_boolean](variables.tf#L128) | Map of boolean org policies and enforcement value, set value to null for policy restore. | <code>map(bool)</code> | | <code>{}</code> |
|
| [name](variables.tf#L126) | Folder name. | <code>string</code> | | <code>null</code> |
|
||||||
| [policy_list](variables.tf#L135) | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | <code title="map(object({ inherit_from_parent = bool suggested_value = string status = bool values = list(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
| [parent](variables.tf#L132) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
|
||||||
| [tag_bindings](variables.tf#L147) | Tag bindings for this folder, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
| [policy_boolean](variables.tf#L142) | Map of boolean org policies and enforcement value, set value to null for policy restore. | <code>map(bool)</code> | | <code>{}</code> |
|
||||||
|
| [policy_list](variables.tf#L149) | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | <code title="map(object({ inherit_from_parent = bool suggested_value = string status = bool values = list(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||||
|
| [tag_bindings](variables.tf#L161) | Tag bindings for this folder, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
|
|
|
@ -17,19 +17,33 @@
|
||||||
# tfdoc:file:description IAM bindings, roles and audit logging resources.
|
# tfdoc:file:description IAM bindings, roles and audit logging resources.
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
group_iam_roles = distinct(flatten(values(var.group_iam)))
|
_group_iam_roles = distinct(flatten(values(var.group_iam)))
|
||||||
group_iam = {
|
_group_iam = {
|
||||||
for r in local.group_iam_roles : r => [
|
for r in local._group_iam_roles : r => [
|
||||||
for k, v in var.group_iam : "group:${k}" if try(index(v, r), null) != null
|
for k, v in var.group_iam : "group:${k}" if try(index(v, r), null) != null
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
_iam_additive_pairs = flatten([
|
||||||
|
for role, members in var.iam_additive : [
|
||||||
|
for member in members : { role = role, member = member }
|
||||||
|
]
|
||||||
|
])
|
||||||
|
_iam_additive_member_pairs = flatten([
|
||||||
|
for member, roles in var.iam_additive_members : [
|
||||||
|
for role in roles : { role = role, member = member }
|
||||||
|
]
|
||||||
|
])
|
||||||
iam = {
|
iam = {
|
||||||
for role in distinct(concat(keys(var.iam), keys(local.group_iam))) :
|
for role in distinct(concat(keys(var.iam), keys(local._group_iam))) :
|
||||||
role => concat(
|
role => concat(
|
||||||
try(var.iam[role], []),
|
try(var.iam[role], []),
|
||||||
try(local.group_iam[role], [])
|
try(local._group_iam[role], [])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
iam_additive = {
|
||||||
|
for pair in concat(local._iam_additive_pairs, local._iam_additive_member_pairs) :
|
||||||
|
"${pair.role}-${pair.member}" => pair
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_folder_iam_binding" "authoritative" {
|
resource "google_folder_iam_binding" "authoritative" {
|
||||||
|
@ -38,3 +52,14 @@ resource "google_folder_iam_binding" "authoritative" {
|
||||||
role = each.key
|
role = each.key
|
||||||
members = each.value
|
members = each.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "google_folder_iam_member" "additive" {
|
||||||
|
for_each = (
|
||||||
|
length(var.iam_additive) + length(var.iam_additive_members) > 0
|
||||||
|
? local.iam_additive
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
folder = local.folder.name
|
||||||
|
role = each.value.role
|
||||||
|
member = each.value.member
|
||||||
|
}
|
||||||
|
|
|
@ -75,6 +75,20 @@ variable "iam" {
|
||||||
nullable = false
|
nullable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "iam_additive" {
|
||||||
|
description = "Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format."
|
||||||
|
type = map(list(string))
|
||||||
|
default = {}
|
||||||
|
nullable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "iam_additive_members" {
|
||||||
|
description = "IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values."
|
||||||
|
type = map(list(string))
|
||||||
|
default = {}
|
||||||
|
nullable = false
|
||||||
|
}
|
||||||
|
|
||||||
variable "id" {
|
variable "id" {
|
||||||
description = "Folder ID in case you use folder_create=false."
|
description = "Folder ID in case you use folder_create=false."
|
||||||
type = string
|
type = string
|
||||||
|
|
|
@ -18,7 +18,10 @@ module "test" {
|
||||||
source = "../../../../modules/folder"
|
source = "../../../../modules/folder"
|
||||||
parent = "organizations/12345678"
|
parent = "organizations/12345678"
|
||||||
name = "folder-a"
|
name = "folder-a"
|
||||||
|
group_iam = var.group_iam
|
||||||
iam = var.iam
|
iam = var.iam
|
||||||
|
iam_additive = var.iam_additive
|
||||||
|
iam_additive_members = var.iam_additive_members
|
||||||
policy_boolean = var.policy_boolean
|
policy_boolean = var.policy_boolean
|
||||||
policy_list = var.policy_list
|
policy_list = var.policy_list
|
||||||
firewall_policies = var.firewall_policies
|
firewall_policies = var.firewall_policies
|
||||||
|
|
|
@ -14,59 +14,52 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
variable "group_iam" {
|
||||||
|
type = any
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
variable "iam" {
|
variable "iam" {
|
||||||
type = map(list(string))
|
type = any
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "iam_additive" {
|
||||||
|
type = any
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "iam_additive_members" {
|
||||||
|
type = any
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "policy_boolean" {
|
variable "policy_boolean" {
|
||||||
type = map(bool)
|
type = any
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "policy_list" {
|
variable "policy_list" {
|
||||||
type = map(object({
|
type = any
|
||||||
inherit_from_parent = bool
|
|
||||||
suggested_value = string
|
|
||||||
status = bool
|
|
||||||
values = list(string)
|
|
||||||
}))
|
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "firewall_policies" {
|
variable "firewall_policies" {
|
||||||
type = map(map(object({
|
type = any
|
||||||
description = string
|
|
||||||
direction = string
|
|
||||||
action = string
|
|
||||||
priority = number
|
|
||||||
ranges = list(string)
|
|
||||||
ports = map(list(string))
|
|
||||||
target_service_accounts = list(string)
|
|
||||||
target_resources = list(string)
|
|
||||||
logging = bool
|
|
||||||
})))
|
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "firewall_policy_association" {
|
variable "firewall_policy_association" {
|
||||||
type = map(string)
|
type = any
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "logging_sinks" {
|
variable "logging_sinks" {
|
||||||
type = map(object({
|
type = any
|
||||||
destination = string
|
|
||||||
type = string
|
|
||||||
filter = string
|
|
||||||
iam = bool
|
|
||||||
include_children = bool
|
|
||||||
exclusions = map(string)
|
|
||||||
}))
|
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "logging_exclusions" {
|
variable "logging_exclusions" {
|
||||||
type = map(string)
|
type = any
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
def test_folder(plan_runner):
|
def test_folder(plan_runner):
|
||||||
"Test folder resources."
|
"Test folder resources."
|
||||||
_, resources = plan_runner()
|
_, resources = plan_runner()
|
||||||
|
@ -23,26 +24,49 @@ def test_folder(plan_runner):
|
||||||
|
|
||||||
|
|
||||||
def test_iam(plan_runner):
|
def test_iam(plan_runner):
|
||||||
"Test folder resources with iam roles and members."
|
"Test IAM."
|
||||||
iam = '{"roles/owner" = ["user:a@b.com"] }'
|
group_iam = (
|
||||||
_, resources = plan_runner(iam=iam)
|
'{'
|
||||||
assert len(resources) == 2
|
'"owners@example.org" = ["roles/owner", "roles/resourcemanager.folderAdmin"],'
|
||||||
|
'"viewers@example.org" = ["roles/viewer"]'
|
||||||
|
'}')
|
||||||
def test_iam_multiple_members(plan_runner):
|
iam = ('{'
|
||||||
"Test folder resources with multiple iam members."
|
'"roles/owner" = ["user:one@example.org", "user:two@example.org"],'
|
||||||
iam = '{"roles/owner" = ["user:a@b.com", "user:c@d.com"] }'
|
'"roles/browser" = ["domain:example.org"]'
|
||||||
_, resources = plan_runner(iam=iam)
|
'}')
|
||||||
assert len(resources) == 2
|
_, resources = plan_runner(group_iam=group_iam, iam=iam)
|
||||||
|
roles = sorted([(r['values']['role'], sorted(r['values']['members']))
|
||||||
|
for r in resources
|
||||||
|
if r['type'] == 'google_folder_iam_binding'])
|
||||||
|
assert roles == [
|
||||||
|
('roles/browser', ['domain:example.org']),
|
||||||
|
('roles/owner', [
|
||||||
|
'group:owners@example.org', 'user:one@example.org',
|
||||||
|
'user:two@example.org'
|
||||||
|
]),
|
||||||
|
('roles/resourcemanager.folderAdmin', ['group:owners@example.org']),
|
||||||
|
('roles/viewer', ['group:viewers@example.org']),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_iam_multiple_roles(plan_runner):
|
def test_iam_multiple_roles(plan_runner):
|
||||||
"Test folder resources with multiple iam roles."
|
"Test folder resources with multiple iam roles."
|
||||||
iam = (
|
iam = ('{ '
|
||||||
'{ '
|
|
||||||
'"roles/owner" = ["user:a@b.com"], '
|
'"roles/owner" = ["user:a@b.com"], '
|
||||||
'"roles/viewer" = ["user:c@d.com"] '
|
'"roles/viewer" = ["user:c@d.com"] '
|
||||||
'} '
|
'} ')
|
||||||
)
|
|
||||||
_, resources = plan_runner(iam=iam)
|
_, resources = plan_runner(iam=iam)
|
||||||
assert len(resources) == 3
|
assert len(resources) == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_iam_additive_members(plan_runner):
|
||||||
|
"Test IAM additive members."
|
||||||
|
iam = ('{"user:one@example.org" = ["roles/owner"],'
|
||||||
|
'"user:two@example.org" = ["roles/owner", "roles/editor"]}')
|
||||||
|
_, resources = plan_runner(iam_additive_members=iam)
|
||||||
|
roles = set((r['values']['role'], r['values']['member'])
|
||||||
|
for r in resources
|
||||||
|
if r['type'] == 'google_folder_iam_member')
|
||||||
|
assert roles == set([('roles/owner', 'user:one@example.org'),
|
||||||
|
('roles/owner', 'user:two@example.org'),
|
||||||
|
('roles/editor', 'user:two@example.org')])
|
||||||
|
|
Loading…
Reference in New Issue