Skip to content

Commit 68b6a15

Browse files
dudymascloudpossebot
authored andcommitted
chore(dynamic-roles): updates for remaining changes
1 parent e70473b commit 68b6a15

File tree

16 files changed

+242
-288
lines changed

16 files changed

+242
-288
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## Breaking changes
2+
3+
### overridable_team_permission_sets_enabled deprecated, default changed
4+
5+
In `modules/roles-to-principals`, the input `overridable_team_permission_sets_enabled`
6+
has been deprecated and the default value has been changed to `false`. This will
7+
cause changes in the Terraform plan, but it is likely that they will be
8+
inconsequential, because this feature never worked with Dynamic Terraform Roles,
9+
even though it was introduced in the same PR.
10+
11+
To enable the intended behavior, a new feature has been added to `aws-team-roles`
12+
and `modules/iam-roles`: `trusted_identity_permission_sets`. This feature
13+
allows you to explicitly configure permission sets in the `identity` account to
14+
be allowed to assume roles in other accounts, just as you do with `trusted_teams`.
15+
This has the added advantage of being able to configure non-team permissions
16+
sets to be trusted.

src/README.md

Lines changed: 3 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/dynamic-roles--stacks.tf

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Part of the Terraform Dynamic Roles implementation,
2+
# this files reads in all the `aws-team` and `aws-team-roles` stacks
3+
# and extracts the relevant information.
4+
5+
# The `utils_describe_stacks` data resources use the Cloud Posse Utils provider to describe Atmos stacks, and then
6+
# we merge the results into `local.all_team_vars`. This is the same as running the following locally:
7+
# ```
8+
# atmos describe stacks --components=aws-teams,aws-team-roles --component-types=terraform --sections=vars
9+
# ```
10+
# The result of these stack descriptions includes all metadata for the given components. For example, we now
11+
# can filter the result to find all stacks where either `aws-teams` or `aws-team-roles` are deployed.
12+
# Note that unlike in earlier implementations, we now expect `aws-team-roles` to be deployed in all accounts,
13+
# including the `identity` account.
14+
#
15+
# In particular, we can use this data to find the name of the account via `null-label` (defined by
16+
# `null-label.descriptor_formats.account_name`, typically `<tenant>-<stage>`) where team roles are deployed.
17+
# We then determine which roles are provisioned and which teams can access any given role in any particular account.
18+
#
19+
# `descriptor_formats.account_name` is typically defined in `stacks/orgs/NAMESPACE/_defaults.yaml`, and if not
20+
# defined, the stack name will default to `stage`.`
21+
#
22+
# If `namespace` is included in `descriptor_formats.account_name`, then we additionally filter to only stacks with
23+
# the same `namespace` as `module.this.namespace`. See `local.stack_namespace_index` and `local.stack_namespace_index`
24+
#
25+
# https://atmos.tools/cli/commands/describe/stacks/
26+
# https://registry.terraform.io/providers/cloudposse/utils/latest/docs/data-sources/describe_stacks
27+
28+
locals {
29+
# We would like to use code like this:
30+
# teams_stacks = local.dynamic_role_enabled ? { for k, v ... } : {}
31+
# but that generates an error: "Inconsistent conditional result types"
32+
# See https://github.com/hashicorp/terraform/issues/33303
33+
# To work around this, we have "empty" values that depend on the condition.
34+
empty_map = {
35+
true = null
36+
false = {}
37+
}
38+
empty = local.empty_map[local.dynamic_role_enabled]
39+
40+
# If a namespace is included with the stack name, only loop through stacks in the same namespace
41+
# zero-based index showing position of the namespace in the stack name
42+
stack_namespace_index = try(index(module.this.normalized_context.descriptor_formats.stack.labels, "namespace"), -1)
43+
stack_has_namespace = local.stack_namespace_index >= 0
44+
# stack_account_map maps stack name to account short name
45+
stack_account_map = { for k, v in module.atmos : k => lookup(v.descriptors, "account_name", v.stage) }
46+
47+
# ASSUMPTIONS: The stack pattern is the same for all accounts and uses the same delimiter as null-label
48+
49+
# Part 1, get all the aws-teams information:
50+
# Get all the aws-teams stacks, optionally filtered by namespace, so that we only work in the current namespace.
51+
teams_stacks = local.dynamic_role_enabled ? {
52+
for k, v in yamldecode(data.utils_describe_stacks.teams[0].output) : k => v if !local.stack_has_namespace || try(split(module.this.delimiter, k)[local.stack_namespace_index] == module.this.namespace, false)
53+
} : local.empty
54+
55+
# Extract components.terraform.aws-teams.vars. Key is the stack name, value is the vars.
56+
teams_vars = { for k, v in local.teams_stacks : k => v.components.terraform.aws-teams.vars if try(v.components.terraform.aws-teams.vars, null) != null }
57+
# Extract components.terraform.aws-teams.vars.teams_config, drop the stack name.
58+
teams_config = local.dynamic_role_enabled ? values(local.teams_vars)[0].teams_config : local.empty
59+
# Extract enabled team names from components.terraform.aws-teams.vars.teams_config
60+
team_names = [for k, v in local.teams_config : k if try(v.enabled, true)]
61+
# Convert team names to IAM role ARNs
62+
team_arns = { for team_name in local.team_names : team_name => format(local.iam_role_arn_templates[local.account_role_map.identity], team_name) }
63+
# Now we have local.team_arns which is a list of IAM role ARNs for each team.
64+
# This covers all the roles we have explicitly configured as teams.
65+
66+
# Part 2, get all the aws-team-roles information:
67+
# Get all the aws-team-roles stacks, optionally filtered by namespace, so that we only work in the current namespace.
68+
team_roles_stacks = local.dynamic_role_enabled ? {
69+
for k, v in yamldecode(data.utils_describe_stacks.team_roles[0].output) : k => v if !local.stack_has_namespace || try(split(module.this.delimiter, k)[local.stack_namespace_index] == module.this.namespace, false)
70+
} : local.empty
71+
72+
# Extract components.terraform.aws-team-roles.vars
73+
team_roles_vars = { for k, v in local.team_roles_stacks : k => v.components.terraform.aws-team-roles.vars if try(v.components.terraform.aws-team-roles.vars, null) != null }
74+
75+
# Merge all the vars together so that for each stack name (the keys in the maps),
76+
# we can map it to the appropriate account short name, which we will use as keys in the final map.
77+
all_team_vars = merge(local.teams_vars, local.team_roles_vars)
78+
79+
}
80+
81+
data "utils_describe_stacks" "teams" {
82+
count = local.dynamic_role_enabled ? 1 : 0
83+
84+
components = ["aws-teams"]
85+
component_types = ["terraform"]
86+
sections = ["vars"]
87+
}
88+
89+
data "utils_describe_stacks" "team_roles" {
90+
count = local.dynamic_role_enabled ? 1 : 0
91+
92+
components = ["aws-team-roles"]
93+
component_types = ["terraform"]
94+
sections = ["vars"]
95+
}
96+
97+
98+
99+
module "atmos" {
100+
# local.all_team_vars is empty map when dynamic_role_enabled is false
101+
for_each = local.all_team_vars
102+
103+
source = "cloudposse/label/null"
104+
version = "0.25.0"
105+
106+
enabled = true
107+
namespace = lookup(each.value, "namespace", null)
108+
tenant = lookup(each.value, "tenant", null)
109+
environment = lookup(each.value, "environment", null)
110+
stage = lookup(each.value, "stage", null)
111+
name = lookup(each.value, "name", null)
112+
delimiter = lookup(each.value, "delimiter", null)
113+
attributes = lookup(each.value, "attributes", [])
114+
tags = lookup(each.value, "tags", {})
115+
additional_tag_map = lookup(each.value, "additional_tag_map", {})
116+
label_order = lookup(each.value, "label_order", [])
117+
regex_replace_chars = lookup(each.value, "regex_replace_chars", null)
118+
id_length_limit = lookup(each.value, "id_length_limit", null)
119+
label_key_case = lookup(each.value, "label_key_case", null)
120+
label_value_case = lookup(each.value, "label_value_case", null)
121+
descriptor_formats = lookup(each.value, "descriptor_formats", {})
122+
labels_as_tags = lookup(each.value, "labels_as_tags", [])
123+
}

0 commit comments

Comments
 (0)