Skip to content

Commit 10db599

Browse files
committed
feat: per-role max_session_duration override and inline policy support
Two additive fields on each var.roles entry: - max_session_duration: per-role override of the module-level duration, enforced to the same 3600-43200 band. Lets a single long-running CI job bump its own session without leaking that duration to every role. - inline_policies: map of policy name to rendered policy document JSON, rendered as aws_iam_role_policy resources. Removes the need to create a standalone aws_iam_policy for grants that will never be reused. Both fields are optional and default to inherit-module-default / empty, so existing configurations pass through unchanged and plans emit zero diff on upgrade. Validation blocks reject out-of-band durations and invalid IAM policy names at plan time. README's "Full example" is extended with a role exercising both fields, plus a key-by-key description of the roles object for quick reference.
1 parent 42ca2b4 commit 10db599

5 files changed

Lines changed: 67 additions & 10 deletions

File tree

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,31 @@ module "aws_oidc_github" {
6565
subject_repos = ["repo:my-org/infrastructure:pull_request"]
6666
policy_arns = ["arn:aws:iam::aws:policy/ReadOnlyAccess"]
6767
}
68+
69+
# Long integration job: bump the session to 4h and add a bespoke inline policy
70+
# for a grant that's too narrow to justify a standalone aws_iam_policy.
71+
"infra-integration-tests" = {
72+
subject_repos = ["repo:my-org/infrastructure:environment:integration"]
73+
policy_arns = ["arn:aws:iam::aws:policy/ReadOnlyAccess"]
74+
max_session_duration = 14400 # 4h, overrides the module-level default
75+
76+
inline_policies = {
77+
"describe-integration-stacks" = data.aws_iam_policy_document.describe_integration_stacks.json
78+
}
79+
}
6880
}
6981
}
7082
```
7183

84+
Each entry in `roles` supports the following keys:
85+
86+
- `subject_repos` (required) — OIDC subject claims allowed to assume the role. See the cheat sheet below.
87+
- `policy_arns` (required, may be empty) — IAM policy ARNs to attach.
88+
- `inline_policies` (optional) — Map of policy name → rendered policy document JSON. Good for one-off grants that don't deserve a standalone `aws_iam_policy`.
89+
- `max_session_duration` (optional) — Per-role override in seconds (3600–43200). Omit to inherit the module-level `var.max_session_duration`.
90+
- `role_path` (optional) — IAM path. Defaults to `/`.
91+
- `assume_role_names` (optional) — IAM role names in the same account that may also `sts:AssumeRole` this role. Development-only escape hatch; see [Security notes](#security-notes).
92+
7293
## Subject string cheat sheet
7394

7495
The `subject_repos` list contains GitHub OIDC `sub` claim patterns. Common shapes:
@@ -177,7 +198,7 @@ If you set `audience:` on `aws-actions/configure-aws-credentials`, set the match
177198
| <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 |
178199
| <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 |
179200
| <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 |
180-
| <a name="input_roles"></a> [roles](#input\_roles) | 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 |
201+
| <a name="input_roles"></a> [roles](#input\_roles) | 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).<br/> - `max_session_duration` : (optional) Per-role override of the module-level max\_session\_duration, in seconds. Must be 3600-43200. Omit to inherit var.max\_session\_duration.<br/> - `inline_policies` : (optional) Map of inline IAM policy name to rendered policy document JSON (typically from data.aws\_iam\_policy\_document.<name>.json). | <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/> max_session_duration = optional(number)<br/> inline_policies = optional(map(string), {})<br/> }))</pre> | n/a | yes |
181202
| <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 |
182203

183204
## Outputs

main.tf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ module "aws_oidc_github" {
2424
assume_role_names = each.value.assume_role_names
2525
github_oidc_provider_arn = aws_iam_openid_connect_provider.github.arn
2626
github_oidc_provider_url = aws_iam_openid_connect_provider.github.url
27-
max_session_duration = var.max_session_duration
27+
max_session_duration = coalesce(each.value.max_session_duration, var.max_session_duration)
28+
inline_policies = each.value.inline_policies
2829
tags = var.tags
2930
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,11 @@ resource "aws_iam_role_policy_attachment" "github_ci" {
5959
role = aws_iam_role.github_ci.name
6060
policy_arn = each.value
6161
}
62+
63+
resource "aws_iam_role_policy" "inline" {
64+
for_each = var.inline_policies
65+
66+
name = each.key
67+
role = aws_iam_role.github_ci.name
68+
policy = each.value
69+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ variable "policy_arns" {
3333
description = "IAM policy ARNs to attach to the role (managed or customer-managed policies)."
3434
}
3535

36+
variable "inline_policies" {
37+
type = map(string)
38+
default = {}
39+
description = "Inline IAM policies to attach to the role. Map of policy name to rendered policy document JSON (typically from data.aws_iam_policy_document.<name>.json)."
40+
}
41+
3642
variable "assume_role_names" {
3743
type = list(string)
3844
default = []

variables.tf

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
variable "roles" {
22
type = map(object({
3-
role_path = optional(string, "/")
4-
subject_repos = list(string)
5-
policy_arns = list(string)
6-
assume_role_names = optional(list(string))
3+
role_path = optional(string, "/")
4+
subject_repos = list(string)
5+
policy_arns = list(string)
6+
assume_role_names = optional(list(string))
7+
max_session_duration = optional(number)
8+
inline_policies = optional(map(string), {})
79
}))
810
description = <<-EOT
911
Map of IAM roles to create. The map key is the role name. Each value defines:
10-
- `subject_repos` : OIDC subject claims allowed to assume this role (e.g. "repo:my-org/my-repo:ref:refs/heads/main").
11-
- `policy_arns` : IAM policy ARNs to attach to the role.
12-
- `role_path` : (optional) IAM path for the role. Defaults to "/".
13-
- `assume_role_names` : (optional) IAM role names in the same account that may also assume this role (useful for local debugging).
12+
- `subject_repos` : OIDC subject claims allowed to assume this role (e.g. "repo:my-org/my-repo:ref:refs/heads/main").
13+
- `policy_arns` : IAM policy ARNs to attach to the role.
14+
- `role_path` : (optional) IAM path for the role. Defaults to "/".
15+
- `assume_role_names` : (optional) IAM role names in the same account that may also assume this role (useful for local debugging).
16+
- `max_session_duration` : (optional) Per-role override of the module-level max_session_duration, in seconds. Must be 3600-43200. Omit to inherit var.max_session_duration.
17+
- `inline_policies` : (optional) Map of inline IAM policy name to rendered policy document JSON (typically from data.aws_iam_policy_document.<name>.json).
1418
EOT
1519

1620
validation {
@@ -42,6 +46,23 @@ variable "roles" {
4246
])
4347
error_message = "Every policy_arns entry must be a valid IAM policy ARN (e.g. \"arn:aws:iam::aws:policy/ReadOnlyAccess\" or \"arn:aws:iam::123456789012:policy/my-policy\")."
4448
}
49+
50+
validation {
51+
condition = alltrue([
52+
for v in var.roles :
53+
try(v.max_session_duration >= 3600 && v.max_session_duration <= 43200, true)
54+
])
55+
error_message = "Per-role max_session_duration must be null (to inherit the module default) or between 3600 (1h) and 43200 (12h) — AWS-enforced bounds."
56+
}
57+
58+
validation {
59+
condition = alltrue([
60+
for v in var.roles : alltrue([
61+
for name, _ in v.inline_policies : can(regex("^[a-zA-Z0-9+=,.@_-]{1,128}$", name))
62+
])
63+
])
64+
error_message = "Each inline_policies map key (the IAM policy name) must be 1–128 characters and match [a-zA-Z0-9+=,.@_-]."
65+
}
4566
}
4667

4768
variable "tags" {

0 commit comments

Comments
 (0)