Skip to content

Commit 93a6ffd

Browse files
committed
Support multiple roles per repo
This module currently offers the input variable `var.aws_iam_role_names` that allows a custom IAM role name to be specified for each repo, and the input variable `var.github_custom_claim` that allows a custom OIDC claim to be specified for each repo. There are some notable limitations to the current implementations of `var.aws_iam_role_names` and `var.github_custom_claim`: - Only one role name can be specified per repo - Only one custom OIDC claim can be specified per repo. This commit will update `var.aws_iam_role_names` to address these limitations. Instead of only accepting a single role name per repo, the variable will now also support a map for each repo. Each key of the map should be the custom role name, and each value should be a list of custom claims to add to the role trust policy. ```hcl aws_iam_role_names = { "really-long-github-org-name/really-long-github-repo-name" = { "read-only-role" = ["*"], "write-role" = ["ref:refs/heads/main", "ref_type:tag"], "custom-role" = ["*"], } } ``` For backwards compatibility, `var.aws_iam_role_names` will continue to support the old format: ```hcl aws_iam_role_names = { "really-long-github-org-name/really-long-github-repo-name" = "custom-role-name" } ```
1 parent bb0d0ba commit 93a6ffd

File tree

3 files changed

+56
-19
lines changed

3 files changed

+56
-19
lines changed

main.tf

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,22 @@ locals {
1717
]
1818
)
1919
}
20+
aws_iam_roles = merge([
21+
for key, value in local.github_repos : can(tomap(try(var.aws_iam_role_names[value], ""))) ? {
22+
for role_name, claims in var.aws_iam_role_names[value] :
23+
"${key}-${substr(role_name, 0, 64)}" => {
24+
claims = claims
25+
repo = value
26+
role_name = substr(role_name, 0, 64)
27+
}
28+
} : {
29+
"${key}" = {
30+
claims = [var.github_custom_claim]
31+
repo = value
32+
role_name = substr(try(var.aws_iam_role_names[value], local.aws_iam_role_name_defaults[key]), 0, 64)
33+
}
34+
}
35+
]...)
2036
github_repos = { for repo in var.github_repos : lower(replace(repo, "/", "-")) => repo }
2137
oidc_client_ids = ["sts.amazonaws.com"]
2238
oidc_issuer_domain = "token.actions.githubusercontent.com"
@@ -40,7 +56,7 @@ resource "aws_iam_openid_connect_provider" "github" {
4056
# https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_identity-vs-resource.html
4157

4258
data "aws_iam_policy_document" "role_trust_policy" {
43-
for_each = local.github_repos
59+
for_each = local.aws_iam_roles
4460
statement {
4561
actions = ["sts:AssumeRoleWithWebIdentity", "sts:TagSession"]
4662
principals {
@@ -55,7 +71,7 @@ data "aws_iam_policy_document" "role_trust_policy" {
5571
condition {
5672
test = "StringLike"
5773
variable = "${local.oidc_issuer_domain}:sub"
58-
values = ["repo:${each.value}:${var.github_custom_claim}"]
74+
values = [for claim in each.value.claims : "repo:${each.value.repo}:${claim}"]
5975
}
6076
}
6177
}
@@ -64,12 +80,8 @@ data "aws_iam_policy_document" "role_trust_policy" {
6480
# https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp.html
6581

6682
resource "aws_iam_role" "github_actions_oidc" {
67-
for_each = local.github_repos
83+
for_each = local.aws_iam_roles
6884
assume_role_policy = data.aws_iam_policy_document.role_trust_policy[each.key].json
69-
description = "IAM assumed role for GitHub Actions in the ${each.value} repo"
70-
name = (
71-
length(lookup(var.aws_iam_role_names, each.value, "")) != 0
72-
? substr(var.aws_iam_role_names[each.value], 0, 64)
73-
: substr(local.aws_iam_role_name_defaults[each.key], 0, 64)
74-
)
85+
description = "IAM assumed role for GitHub Actions in the ${each.value.repo} repo"
86+
name = each.value.role_name
7587
}

outputs.tf

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
output "aws_iam_roles" {
2-
depends_on = [aws_iam_role.github_actions_oidc]
32
description = "ARNs and names of AWS IAM roles that have been provisioned"
43
value = {
5-
for key, value in local.github_repos :
4+
for key, value in aws_iam_role.github_actions_oidc :
65
key => {
7-
arn = aws_iam_role.github_actions_oidc[key].arn
8-
name = aws_iam_role.github_actions_oidc[key].name
6+
arn = value.arn
7+
name = value.name
8+
repo = local.aws_iam_roles[key].repo
99
}
1010
}
1111
}

variables.tf

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,45 @@
11
variable "aws_iam_role_names" {
22
description = <<-DESCRIPTION
33
Optional mapping of GitHub repos to names of IAM roles that will be assumed by GitHub for OIDC.
4-
If set, the IAM role name for the repo will be exactly the variable value (64 characters max).
54
6-
Example: `{ "really-long-github-org-name/really-long-github-repo-name" = "custom-role-name" }`
7-
8-
If unset, the default IAM role name for each repo will be
5+
If unset, a single IAM role will be provisioned for each repo with the name in the format
96
`<aws_iam_role_prefix><aws_iam_role_separator><repo_owner><aws_iam_role_separator><repo_name>`,
107
truncated to 64 characters.
8+
9+
If set, IAM role names will be as provided, truncated to 64 characters (the max length enforced by IAM).
10+
Optionally, a list of custom claims can be provided for each role.
11+
For more details on what can be specified in these claims, see the
12+
[OIDC reference docs](https://docs.github.com/en/actions/reference/security/oidc) and
13+
[OIDC how-to for AWS](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws).
14+
15+
Examples:
16+
17+
```hcl
18+
aws_iam_role_names = {
19+
"really-long-github-org-name/really-long-github-repo-name" = "custom-role-name"
20+
}
21+
```
22+
23+
```hcl
24+
aws_iam_role_names = {
25+
"really-long-github-org-name/really-long-github-repo-name" = {
26+
"read-only-role" = ["*"],
27+
"write-role" = ["ref:refs/heads/main", "ref_type:tag"],
28+
"custom-role" = ["*"],
29+
}
30+
}
31+
```
1132
DESCRIPTION
12-
type = map(string)
13-
default = {}
33+
type = map(any)
34+
default = null
1435
validation {
1536
condition = alltrue([for key, value in var.aws_iam_role_names : length(value) <= 64])
1637
error_message = "The IAM role name must be less than or equal to 64 characters."
1738
}
39+
validation {
40+
condition = alltrue([for key, value in var.aws_iam_role_names : can(tostring(value)) || can(tomap(value))])
41+
error_message = "The IAM role names should be supplied as either a single string or a map."
42+
}
1843
}
1944

2045
variable "aws_iam_role_prefix" {

0 commit comments

Comments
 (0)