Skip to content

Commit 5bb42c6

Browse files
authored
feat: add tags, iam_role_arns_map, and variable validation (Stage 1 prep for v2) (#11)
1 parent 367b57b commit 5bb42c6

7 files changed

Lines changed: 77 additions & 38 deletions

File tree

README.md

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ module "aws_oidc_github" {
3535
}
3636
3737
output "role_arn" {
38-
value = module.aws_oidc_github.iam_role_arns["deploy-main"]
38+
value = module.aws_oidc_github.iam_role_arns_map["deploy-main"]
3939
}
4040
```
4141

@@ -103,7 +103,7 @@ jobs:
103103
- run: aws sts get-caller-identity
104104
```
105105
106-
The `role-to-assume` value is the full ARN of one of the roles this module created — `module.aws_oidc_github.iam_role_arns[<key>]`.
106+
The `role-to-assume` value is the full ARN of one of the roles this module created — `module.aws_oidc_github.iam_role_arns_map[<role-name>]` (the map key matches the entry name you used in `role_subject-repos_policies`). The older `iam_role_arns` flat-list output is deprecated and will be removed in v2.
107107

108108
## Debugging with `assume_role_names`
109109

@@ -142,45 +142,44 @@ The [`wrappers/`](./wrappers) directory contains thin wrapper modules pre-config
142142
## Requirements
143143

144144
| Name | Version |
145-
|------|---------|
145+
| ---- | ------- |
146146
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5.7 |
147147
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 4.0 |
148-
| <a name="requirement_tls"></a> [tls](#requirement\_tls) | >= 4.0.3 |
149148

150149
## Providers
151150

152151
| Name | Version |
153-
|------|---------|
152+
| ---- | ------- |
154153
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 4.0 |
155-
| <a name="provider_tls"></a> [tls](#provider\_tls) | >= 4.0.3 |
156154

157155
## Modules
158156

159157
| Name | Source | Version |
160-
|------|--------|---------|
158+
| ---- | ------ | ------- |
161159
| <a name="module_aws_oidc_github"></a> [aws\_oidc\_github](#module\_aws\_oidc\_github) | ./modules/aws-roles-oidc-github | n/a |
162160

163161
## Resources
164162

165163
| Name | Type |
166-
|------|------|
164+
| ---- | ---- |
167165
| [aws_iam_openid_connect_provider.github](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource |
168-
| [tls_certificate.github](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/certificate) | data source |
169166

170167
## Inputs
171168

172169
| Name | Description | Type | Default | Required |
173-
|------|-------------|------|---------|:--------:|
170+
| ---- | ----------- | ---- | ------- | :------: |
174171
| <a name="input_aud_value"></a> [aud\_value](#input\_aud\_value) | Audience claim required in the OIDC token. Defaults to the value the official aws-actions/configure-aws-credentials action sends. | `string` | `"sts.amazonaws.com"` | no |
175172
| <a name="input_github_tls_url"></a> [github\_tls\_url](#input\_github\_tls\_url) | GitHub OIDC issuer URL. Override only for GitHub Enterprise Server. | `string` | `"https://token.actions.githubusercontent.com"` | no |
176173
| <a name="input_max_session_duration"></a> [max\_session\_duration](#input\_max\_session\_duration) | Maximum session duration in seconds for every role created. Defaults to 1 hour. Increase up to 43200 (12h) if your workflows need longer sessions. | `number` | `3600` | no |
177-
| <a name="input_role_subject-repos_policies"></a> [role\_subject-repos\_policies](#input\_role\_subject-repos\_policies) | Map of IAM roles to create. The map key is the role name. Each value defines:<br/> - `subject_repos` : OIDC subject claims allowed to assume this role (e.g. "repo:my-org/my-repo:ref:refs/heads/main").<br/> - `policy_arns` : IAM policy ARNs to attach to the role.<br/> - `role_path` : (optional) IAM path for the role. Defaults to "/".<br/> - `assume_role_names` : (optional) IAM role names in the same account that may also assume this role (useful for local debugging). | <pre>map(object({<br/> role_path = optional(string)<br/> subject_repos = list(string)<br/> policy_arns = list(string)<br/> assume_role_names = optional(list(string))<br/> }))</pre> | n/a | yes |
174+
| <a name="input_role_subject-repos_policies"></a> [role\_subject-repos\_policies](#input\_role\_subject-repos\_policies) | Map of IAM roles to create. The map key is the role name. Each value defines:<br/> - `subject_repos` : OIDC subject claims allowed to assume this role (e.g. "repo:my-org/my-repo:ref:refs/heads/main").<br/> - `policy_arns` : IAM policy ARNs to attach to the role.<br/> - `role_path` : (optional) IAM path for the role. Defaults to "/".<br/> - `assume_role_names` : (optional) IAM role names in the same account that may also assume this role (useful for local debugging). | <pre>map(object({<br/> role_path = optional(string, "/")<br/> subject_repos = list(string)<br/> policy_arns = list(string)<br/> assume_role_names = optional(list(string))<br/> }))</pre> | n/a | yes |
175+
| <a name="input_tags"></a> [tags](#input\_tags) | Tags applied to the OIDC provider and every IAM role created by this module. | `map(string)` | `{}` | no |
178176

179177
## Outputs
180178

181179
| Name | Description |
182-
|------|-------------|
180+
| ---- | ----------- |
183181
| <a name="output_github_oidc_provider_arn"></a> [github\_oidc\_provider\_arn](#output\_github\_oidc\_provider\_arn) | oidc provider arn to use for roles/policies |
184182
| <a name="output_github_oidc_provider_url"></a> [github\_oidc\_provider\_url](#output\_github\_oidc\_provider\_url) | oidc provider url to use for roles/policies |
185-
| <a name="output_iam_role_arns"></a> [iam\_role\_arns](#output\_iam\_role\_arns) | Roles that will be assumed by GitHub Action |
183+
| <a name="output_iam_role_arns"></a> [iam\_role\_arns](#output\_iam\_role\_arns) | Roles that will be assumed by GitHub Action. (Deprecated: use `iam_role_arns_map` instead; this output will be removed in v2.) |
184+
| <a name="output_iam_role_arns_map"></a> [iam\_role\_arns\_map](#output\_iam\_role\_arns\_map) | Map of role name to role ARN. Prefer this output; the flat `iam_role_arns` list will be removed in v2. |
186185
<!-- END_TF_DOCS -->

main.tf

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,30 @@ terraform {
55
source = "hashicorp/aws"
66
version = ">= 4.0"
77
}
8-
tls = {
9-
source = "hashicorp/tls"
10-
version = ">= 4.0.3"
11-
}
128
}
139
}
1410

15-
16-
data "tls_certificate" "github" {
17-
url = var.github_tls_url
18-
}
19-
2011
resource "aws_iam_openid_connect_provider" "github" {
21-
url = var.github_tls_url
22-
client_id_list = [var.aud_value]
23-
thumbprint_list = [data.tls_certificate.github.certificates[0].sha1_fingerprint]
12+
url = var.github_tls_url
13+
client_id_list = [var.aud_value]
14+
# AWS stopped enforcing thumbprint verification for the GitHub OIDC issuer in
15+
# mid-2023, but the AWS provider still requires a 40-char hex value. Pass the
16+
# historical GitHub thumbprint as a placeholder; the actual trust is enforced
17+
# by IAM via the provider URL, not this value.
18+
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
19+
tags = var.tags
2420
}
2521

2622
module "aws_oidc_github" {
2723
for_each = var.role_subject-repos_policies
2824
source = "./modules/aws-roles-oidc-github"
2925
role_name = each.key
30-
role_path = each.value.role_path != null ? each.value.role_path : "/"
26+
role_path = each.value.role_path
3127
github_repos = each.value.subject_repos
3228
policy_arns = each.value.policy_arns
3329
assume_role_names = each.value.assume_role_names
3430
github_oidc_provider_arn = aws_iam_openid_connect_provider.github.arn
3531
github_oidc_provider_url = aws_iam_openid_connect_provider.github.url
3632
max_session_duration = var.max_session_duration
33+
tags = var.tags
3734
}

modules/aws-roles-oidc-github/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@ Use this submodule directly **only** if you already manage your `aws_iam_openid_
88
## Requirements
99

1010
| Name | Version |
11-
|------|---------|
11+
| ---- | ------- |
1212
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5.7 |
1313
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 4.0 |
14-
| <a name="requirement_tls"></a> [tls](#requirement\_tls) | >= 4.0.3 |
1514

1615
## Providers
1716

1817
| Name | Version |
19-
|------|---------|
18+
| ---- | ------- |
2019
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 4.0 |
2120

2221
## Modules
@@ -26,7 +25,7 @@ No modules.
2625
## Resources
2726

2827
| Name | Type |
29-
|------|------|
28+
| ---- | ---- |
3029
| [aws_iam_role.github_ci](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
3130
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
3231
| [aws_iam_policy_document.assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
@@ -35,7 +34,7 @@ No modules.
3534
## Inputs
3635

3736
| Name | Description | Type | Default | Required |
38-
|------|-------------|------|---------|:--------:|
37+
| ---- | ----------- | ---- | ------- | :------: |
3938
| <a name="input_assume_role_names"></a> [assume\_role\_names](#input\_assume\_role\_names) | IAM role names in the same account that may assume this role via sts:AssumeRole. Useful for local debugging; remove before production. | `list(string)` | `[]` | no |
4039
| <a name="input_github_oidc_provider_arn"></a> [github\_oidc\_provider\_arn](#input\_github\_oidc\_provider\_arn) | ARN of the GitHub OIDC provider that the IAM role will trust. | `string` | n/a | yes |
4140
| <a name="input_github_oidc_provider_url"></a> [github\_oidc\_provider\_url](#input\_github\_oidc\_provider\_url) | URL of the GitHub OIDC provider (e.g. https://token.actions.githubusercontent.com). | `string` | n/a | yes |
@@ -44,10 +43,11 @@ No modules.
4443
| <a name="input_policy_arns"></a> [policy\_arns](#input\_policy\_arns) | IAM policy ARNs to attach to the role (managed or customer-managed policies). | `list(string)` | n/a | yes |
4544
| <a name="input_role_name"></a> [role\_name](#input\_role\_name) | Name of the IAM role to create. Used verbatim — no prefixing or sanitization is applied. | `string` | n/a | yes |
4645
| <a name="input_role_path"></a> [role\_path](#input\_role\_path) | IAM path to create the role under. Must start and end with '/' (e.g. '/' or '/github/'). | `string` | n/a | yes |
46+
| <a name="input_tags"></a> [tags](#input\_tags) | Tags applied to the IAM role created by this submodule. | `map(string)` | `{}` | no |
4747

4848
## Outputs
4949

5050
| Name | Description |
51-
|------|-------------|
51+
| ---- | ----------- |
5252
| <a name="output_iam_role_arn"></a> [iam\_role\_arn](#output\_iam\_role\_arn) | Role that will be assumed by GitHub Action |
5353
<!-- END_TF_DOCS -->

modules/aws-roles-oidc-github/main.tf

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ terraform {
55
source = "hashicorp/aws"
66
version = ">= 4.0"
77
}
8-
tls = {
9-
source = "hashicorp/tls"
10-
version = ">= 4.0.3"
11-
}
128
}
139
}
1410

@@ -56,4 +52,5 @@ resource "aws_iam_role" "github_ci" {
5652
max_session_duration = var.max_session_duration
5753
assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
5854
managed_policy_arns = var.policy_arns
55+
tags = var.tags
5956
}

modules/aws-roles-oidc-github/variables.tf

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ variable "role_name" {
2121
variable "role_path" {
2222
type = string
2323
description = "IAM path to create the role under. Must start and end with '/' (e.g. '/' or '/github/')."
24+
25+
validation {
26+
condition = can(regex("^/([a-zA-Z0-9._+-]+/)*$", var.role_path))
27+
error_message = "role_path must start and end with '/' and only contain [a-zA-Z0-9._+-] segments (e.g. \"/\", \"/github/\", \"/teams/platform/\")."
28+
}
2429
}
2530

2631
variable "policy_arns" {
@@ -38,4 +43,15 @@ variable "max_session_duration" {
3843
type = number
3944
default = 3600
4045
description = "Maximum session duration in seconds for the role. Defaults to 1 hour. Increase up to 43200 (12h) if your workflows need longer sessions."
46+
47+
validation {
48+
condition = var.max_session_duration >= 3600 && var.max_session_duration <= 43200
49+
error_message = "max_session_duration must be between 3600 (1h) and 43200 (12h) — AWS-enforced bounds."
50+
}
51+
}
52+
53+
variable "tags" {
54+
type = map(string)
55+
default = {}
56+
description = "Tags applied to the IAM role created by this submodule."
4157
}

outputs.tf

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
output "iam_role_arns" {
2-
description = "Roles that will be assumed by GitHub Action"
2+
description = "Roles that will be assumed by GitHub Action. (Deprecated: use `iam_role_arns_map` instead; this output will be removed in v2.)"
33
value = values(module.aws_oidc_github)[*].iam_role_arn
44
}
55

6+
output "iam_role_arns_map" {
7+
description = "Map of role name to role ARN. Prefer this output; the flat `iam_role_arns` list will be removed in v2."
8+
value = { for k, m in module.aws_oidc_github : k => m.iam_role_arn }
9+
}
10+
611
output "github_oidc_provider_arn" {
712
description = "oidc provider arn to use for roles/policies"
813
value = aws_iam_openid_connect_provider.github.arn

variables.tf

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
variable "role_subject-repos_policies" {
22
type = map(object({
3-
role_path = optional(string)
3+
role_path = optional(string, "/")
44
subject_repos = list(string)
55
policy_arns = list(string)
66
assume_role_names = optional(list(string))
@@ -12,6 +12,26 @@ variable "role_subject-repos_policies" {
1212
- `role_path` : (optional) IAM path for the role. Defaults to "/".
1313
- `assume_role_names` : (optional) IAM role names in the same account that may also assume this role (useful for local debugging).
1414
EOT
15+
16+
validation {
17+
condition = alltrue([
18+
for v in var.role_subject-repos_policies : alltrue([for r in v.subject_repos : startswith(r, "repo:")])
19+
])
20+
error_message = "Every subject_repos entry must start with \"repo:\" (e.g. \"repo:my-org/my-repo:ref:refs/heads/main\")."
21+
}
22+
23+
validation {
24+
condition = alltrue([
25+
for v in var.role_subject-repos_policies : can(regex("^/([a-zA-Z0-9._+-]+/)*$", v.role_path))
26+
])
27+
error_message = "Each role_path must start and end with '/' and only contain [a-zA-Z0-9._+-] segments (e.g. \"/\", \"/github/\", \"/teams/platform/\")."
28+
}
29+
}
30+
31+
variable "tags" {
32+
type = map(string)
33+
default = {}
34+
description = "Tags applied to the OIDC provider and every IAM role created by this module."
1535
}
1636

1737
variable "github_tls_url" {
@@ -30,4 +50,9 @@ variable "max_session_duration" {
3050
type = number
3151
default = 3600
3252
description = "Maximum session duration in seconds for every role created. Defaults to 1 hour. Increase up to 43200 (12h) if your workflows need longer sessions."
53+
54+
validation {
55+
condition = var.max_session_duration >= 3600 && var.max_session_duration <= 43200
56+
error_message = "max_session_duration must be between 3600 (1h) and 43200 (12h) — AWS-enforced bounds."
57+
}
3358
}

0 commit comments

Comments
 (0)