Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## [6.0.0](https://github.com/nullplatform/tofu-modules/compare/v5.0.0...v6.0.0) (2026-06-25)


### ⚠ BREAKING CHANGES

* **iam/agent:** the IRSA token no longer has Route53/EKS/ELB/AVP permissions directly. The agent must assume the permissions role (exposed via the nullplatform_agent_permissions_role_arn output) to use them.

### Features

* **iam/agent:** split agent role into agent + permissions roles ([#397](https://github.com/nullplatform/tofu-modules/issues/397)) ([9df28f5](https://github.com/nullplatform/tofu-modules/commit/9df28f53ae726a251eb7ed937b88dd1057452672))

## [5.0.0](https://github.com/nullplatform/tofu-modules/compare/v4.6.0...v5.0.0) (2026-06-25)


Expand Down
2 changes: 1 addition & 1 deletion infrastructure/aws/acm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The module creates an aws_acm_certificate resource with DNS validation, which is

```hcl
module "acm" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/acm?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/acm?ref=v6.0.0"

domain_name = "your-domain-name"
zone_id = "your-zone-id"
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/aws/aws_load_balancer_controller/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This module creates a helm_release resource to deploy the AWS Load Balancer Cont

```hcl
module "aws_load_balancer_controller" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/aws_load_balancer_controller?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/aws_load_balancer_controller?ref=v6.0.0"

cluster_name = "your-cluster-name"
vpc_id = "your-vpc-id"
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/aws/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ This module creates an S3 bucket with versioning and server-side encryption enab

```hcl
module "backend" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/backend?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/backend?ref=v6.0.0"
}
```

Expand Down
2 changes: 1 addition & 1 deletion infrastructure/aws/dns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The module conditionally creates an aws_route53_zone resource for a public hoste

```hcl
module "dns" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/dns?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/dns?ref=v6.0.0"

domain_name = "your-domain-name"
vpc_id = "your-vpc-id"
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/aws/eks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The module wraps terraform-aws-modules/eks to create the EKS cluster (aws_eks_cl

```hcl
module "eks" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/eks?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/eks?ref=v6.0.0"

aws_subnets_private_ids = "your-aws-subnets-private-ids"
aws_vpc_vpc_id = "your-aws-vpc-vpc-id"
Expand Down
96 changes: 30 additions & 66 deletions infrastructure/aws/iam/agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,34 @@

## Description

Creates an IRSA-enabled IAM agent role for the nullplatform Kubernetes service account on EKS, using privilege separation: the agent role only carries an sts:AssumeRole policy and assumes a separate permissions role (provisioned outside this module) that holds the scoped workload policies
Creates an IRSA (IAM Roles for Service Accounts) IAM role for the nullplatform agent Kubernetes service account, with an assume-role policy allowing it to assume a conventionally-named permissions role and any additional roles

## Architecture

The module uses the terraform-aws-modules/iam//modules/iam-role-for-service-accounts submodule to create an aws_iam_role (the agent role) with an OIDC trust policy bound to a specific Kubernetes namespace and service account. The agent role only carries an sts:AssumeRole policy that allows it to assume a permissions role (and any additional assume_role_arns).

The default permissions role and its workload policies (Route53, ELB, EKS, AVP) are **no longer created by this module**: they are provisioned per-cluster by the k8s scope's OpenTofu module (`k8s/scope/tofu/iam/modules` in the scopes repo). This module still authorizes assuming that role by its conventional ARN (`nullplatform-{cluster_name}-agent-permissions-role`), derived from the role name and the caller account id, and exposes that ARN as an output. The scope module must create the permissions role with that same conventional name so the wiring matches.
The module uses the terraform-aws-modules/iam//modules/iam-role-for-service-accounts submodule to create an aws_iam_role (agent role) trusted by a specific OIDC provider and Kubernetes service account namespace pair. An aws_iam_policy (nullplatform_assume_role_policy) is created to allow sts:AssumeRole on the externally-managed permissions role ARN, any extra permissions role ARNs, and any caller-supplied assume_role_arns, then attached to the agent role. Optionally, aws_iam_role resources are created for each entry in var.permissions_roles with the agent role as their trusted principal, and aws_iam_role_policy_attachment resources bind the specified policy ARNs to each of those roles. The module outputs the agent role ARN, the conventional permissions role ARN (constructed deterministically from account ID and cluster name), and a map of extra permissions role ARNs.

## Features

- Creates an IRSA IAM agent role scoped to a specific Kubernetes namespace and service account via OIDC provider trust
- Keeps the agent role minimal: it only carries an sts:AssumeRole policy targeting the (externally-created) permissions role and any additional assume_role_arns
- Authorizes assuming the conventional permissions role ARN even though the role itself is created elsewhere (k8s scope tofu module)
- Supports attaching additional custom IAM policies to the agent role via the additional_policies map
- Supports creating additional permissions roles via the permissions_roles map, each trusting the agent role and assumable by it
- Creates an IRSA-enabled aws_iam_role scoped to a specific Kubernetes namespace and service account via OIDC provider trust
- Creates an aws_iam_policy granting sts:AssumeRole on a conventionally-named permissions role, extra permissions roles, and caller-supplied role ARNs
- Creates optional extra aws_iam_role resources per var.permissions_roles entry, each trusting only the agent role
- Attaches caller-specified policy ARNs to each extra permissions role via aws_iam_role_policy_attachment
- Supports attaching additional arbitrary IAM policies directly to the agent role via var.additional_policies
- Derives all role and policy names from cluster_name with optional overrides to avoid naming conflicts across clusters
- Validates all IAM role and policy ARN inputs with regex to enforce correct ARN format before applying

## Basic Usage

```hcl
module "agent" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/agent?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/agent?ref=v6.0.0"

agent_namespace = "your-agent-namespace"
aws_iam_openid_connect_provider_arn = "your-aws-iam-openid-connect-provider-arn"
cluster_name = "your-cluster-name"
}
```

## Multiple permissions roles

The agent is always allowed to assume the default permissions role by its
conventional ARN (`nullplatform-{cluster_name}-agent-permissions-role`), which is
created externally by the k8s scope tofu module. To have the agent assume
additional, module-created roles with their own policies, use the
`permissions_roles` map. Each entry creates a role that trusts the agent role and
gets the given policy ARNs attached; the agent's assume policy is extended with
all of them.

```hcl
module "agent" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/agent?ref=v4.5.0"

agent_namespace = "your-agent-namespace"
aws_iam_openid_connect_provider_arn = "your-aws-iam-openid-connect-provider-arn"
cluster_name = "your-cluster-name"

permissions_roles = {
data = {
policy_arns = ["arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"]
}
ops = {
name = "custom-ops-role"
policy_arns = ["arn:aws:iam::123456789012:policy/ops-policy"]
}
}
}
```

For roles that already exist elsewhere (not created by this module), use
`assume_role_arns` instead — the agent will be allowed to assume them directly.

## Using Outputs

```hcl
Expand Down Expand Up @@ -94,7 +61,6 @@ resource "example_resource" "this" {
| [aws_iam_policy.nullplatform_assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.extra_permissions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.extra_permissions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |

## Inputs

Expand All @@ -106,7 +72,7 @@ resource "example_resource" "this" {
| <a name="input_aws_iam_openid_connect_provider_arn"></a> [aws\_iam\_openid\_connect\_provider\_arn](#input\_aws\_iam\_openid\_connect\_provider\_arn) | ARN of the AWS IAM OIDC provider for EKS service account authentication | `string` | n/a | yes |
| <a name="input_cluster_name"></a> [cluster\_name](#input\_cluster\_name) | Name of the cluster where the policy runs | `string` | n/a | yes |
| <a name="input_permissions_role_name"></a> [permissions\_role\_name](#input\_permissions\_role\_name) | Override for the permissions IAM role name. Defaults to nullplatform-{cluster\_name}-agent-permissions-role | `string` | `""` | no |
| <a name="input_permissions_roles"></a> [permissions\_roles](#input\_permissions\_roles) | Additional permissions roles created by this module and assumable by the agent role. Map key is a logical name; name overrides the role name (defaults to nullplatform-{cluster\_name}-{key}); policy\_arns are the policy ARNs attached to the role. | <pre>map(object({<br> name = optional(string)<br> policy_arns = optional(list(string), [])<br> }))</pre> | `{}` | no |
| <a name="input_permissions_roles"></a> [permissions\_roles](#input\_permissions\_roles) | Additional permissions roles created by this module and assumable by the agent role. Map key is a logical name; name overrides the role name (defaults to nullplatform-{cluster\_name}-{key}); policy\_arns are the policy ARNs attached to the role. | <pre>map(object({<br/> name = optional(string)<br/> policy_arns = optional(list(string), [])<br/> }))</pre> | `{}` | no |
| <a name="input_policies_name_prefix"></a> [policies\_name\_prefix](#input\_policies\_name\_prefix) | Override for IAM policy name prefix. Defaults to nullplatform\_{cluster\_name} | `string` | `""` | no |
| <a name="input_role_name"></a> [role\_name](#input\_role\_name) | Override for the IAM role name. Defaults to nullplatform-{cluster\_name}-agent-role | `string` | `""` | no |
| <a name="input_service_account_name"></a> [service\_account\_name](#input\_service\_account\_name) | Kubernetes service account name trusted by the IRSA role | `string` | `"nullplatform-agent"` | no |
Expand All @@ -123,18 +89,16 @@ resource "example_resource" "this" {
<!-- BEGIN_AI_METADATA
{
"name": "agent",
"description": "Creates an IRSA-enabled IAM agent role for the nullplatform Kubernetes service account on EKS, using privilege separation: the agent role only carries an sts:AssumeRole policy and assumes a separate permissions role that holds the scoped workload policies",
"architecture": "The module uses the terraform-aws-modules/iam//modules/iam-role-for-service-accounts submodule to create an aws_iam_role (the agent role) with an OIDC trust policy bound to a specific Kubernetes namespace and service account. The agent role only carries an sts:AssumeRole policy that allows it to assume a separate permissions role (and any additional assume_role_arns). The permissions role is a standalone aws_iam_role whose trust policy allows only the agent role to assume it, and the four aws_iam_policy resources (Route53, ELB, EKS, and Amazon Verified Permissions) are attached to it. ARNs are derived from role names and the caller account id to avoid a circular dependency between the two roles. Both role ARNs are exposed as outputs.",
"description": "Creates an IRSA (IAM Roles for Service Accounts) IAM role for the nullplatform agent Kubernetes service account, with an assume-role policy allowing it to assume a conventionally-named permissions role and any additional roles",
"architecture": "The module uses the terraform-aws-modules/iam//modules/iam-role-for-service-accounts submodule to create an aws_iam_role (agent role) trusted by a specific OIDC provider and Kubernetes service account namespace pair. An aws_iam_policy (nullplatform_assume_role_policy) is created to allow sts:AssumeRole on the externally-managed permissions role ARN, any extra permissions role ARNs, and any caller-supplied assume_role_arns, then attached to the agent role. Optionally, aws_iam_role resources are created for each entry in var.permissions_roles with the agent role as their trusted principal, and aws_iam_role_policy_attachment resources bind the specified policy ARNs to each of those roles. The module outputs the agent role ARN, the conventional permissions role ARN (constructed deterministically from account ID and cluster name), and a map of extra permissions role ARNs.",
"features": [
"Creates an IRSA IAM agent role scoped to a specific Kubernetes namespace and service account via OIDC provider trust",
"Keeps the agent role minimal: it only carries an sts:AssumeRole policy targeting the permissions role and any additional assume_role_arns",
"Creates a separate permissions role that trusts only the agent role and holds the workload policies",
"Attaches a Route53 policy granting DNS record management permissions for hosted zones to the permissions role",
"Attaches an ELB policy granting describe permissions for load balancers and target groups to the permissions role",
"Attaches an EKS policy granting read access to clusters, node groups, and addons to the permissions role",
"Attaches an Amazon Verified Permissions (AVP) policy granting full verifiedpermissions access to the permissions role",
"Supports attaching additional custom IAM policies to the agent role via the additional_policies map",
"Supports creating additional permissions roles via the permissions_roles map, each trusting the agent role and assumable by it"
"Creates an IRSA-enabled aws_iam_role scoped to a specific Kubernetes namespace and service account via OIDC provider trust",
"Creates an aws_iam_policy granting sts:AssumeRole on a conventionally-named permissions role, extra permissions roles, and caller-supplied role ARNs",
"Creates optional extra aws_iam_role resources per var.permissions_roles entry, each trusting only the agent role",
"Attaches caller-specified policy ARNs to each extra permissions role via aws_iam_role_policy_attachment",
"Supports attaching additional arbitrary IAM policies directly to the agent role via var.additional_policies",
"Derives all role and policy names from cluster_name with optional overrides to avoid naming conflicts across clusters",
"Validates all IAM role and policy ARN inputs with regex to enforce correct ARN format before applying"
],
"inputs": [
{
Expand All @@ -158,18 +122,13 @@ resource "example_resource" "this" {
"required": false
},
{
"name": "additional_policies",
"description": "Additional policy ARNs to attach to the agent role",
"required": false
},
{
"name": "permissions_role_name",
"description": "Override for the permissions IAM role name. Defaults to nullplatform-{cluster_name}-agent-permissions-role",
"name": "permissions_roles",
"description": "Additional permissions roles created by this module and assumable by the agent role. Map key is a logical name; name overrides the role name (defaults to nullplatform-{cluster_name}-{key}); policy_arns are the policy ARNs attached to the role.",
"required": false
},
{
"name": "permissions_roles",
"description": "Additional permissions roles created by this module and assumable by the agent role. Map key is a logical name; name overrides the role name (defaults to nullplatform-{cluster_name}-{key}); policy_arns are the policy ARNs attached to the role.",
"name": "additional_policies",
"description": "Additional policy ARNs to attach to the agent role",
"required": false
},
{
Expand All @@ -182,6 +141,11 @@ resource "example_resource" "this" {
"description": "Override for the IAM role name. Defaults to nullplatform-{cluster_name}-agent-role",
"required": false
},
{
"name": "permissions_role_name",
"description": "Override for the permissions IAM role name. Defaults to nullplatform-{cluster_name}-agent-permissions-role",
"required": false
},
{
"name": "policies_name_prefix",
"description": "Override for IAM policy name prefix. Defaults to nullplatform_{cluster_name}",
Expand All @@ -193,6 +157,6 @@ resource "example_resource" "this" {
"nullplatform_agent_permissions_role_arn",
"nullplatform_agent_extra_permissions_role_arns"
],
"hash": "5142461751e55436dbc95fa82a376955"
"hash": "080cc2f1402698f5884c98e39f0ef01a"
}
END_AI_METADATA -->
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This module creates an IAM role for the AWS Load Balancer Controller using the t

```hcl
module "aws_load_balancer_controller_iam" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/aws_load_balancer_controller_iam?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/aws_load_balancer_controller_iam?ref=v6.0.0"

aws_iam_openid_connect_provider_arn = "your-aws-iam-openid-connect-provider-arn"
cluster_name = "your-cluster-name"
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/aws/iam/cert_manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ An aws_iam_policy is created granting Route53 permissions (GetChange, ChangeReso

```hcl
module "cert_manager" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/cert_manager?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/cert_manager?ref=v6.0.0"

aws_iam_openid_connect_provider_arn = "your-aws-iam-openid-connect-provider-arn"
cluster_name = "your-cluster-name"
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/aws/iam/ci-build-workflow-user/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The module creates an aws_iam_user named with the cluster_name prefix and genera

```hcl
module "ci-build-workflow-user" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/ci-build-workflow-user?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/ci-build-workflow-user?ref=v6.0.0"

cluster_name = "your-cluster-name"
}
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/aws/iam/ecr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The module creates an aws_iam_role named nullplatform-{cluster_name}-application

```hcl
module "ecr" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/ecr?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/ecr?ref=v6.0.0"

build_workflow_group_name = "your-build-workflow-group-name"
cluster_name = "your-cluster-name"
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/aws/iam/external_dns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The module creates an aws_iam_policy granting Route53 permissions scoped to the

```hcl
module "external_dns" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/external_dns?ref=v5.0.0"
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/external_dns?ref=v6.0.0"

aws_iam_openid_connect_provider_arn = "your-aws-iam-openid-connect-provider-arn"
cluster_name = "your-cluster-name"
Expand Down
Loading