Skip to content
Merged
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
101 changes: 101 additions & 0 deletions infrastructure/aws/iam/ci-build-workflow-user/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Module: build-user

## Description

Provisions the shared CI/CD build workflow IAM identity (user, access key, and group) used to publish application assets to any asset repository (ECR, S3, etc.)

## Architecture

The module creates a single `aws_iam_user` with an `aws_iam_access_key` for CI/CD build workflows, plus an `aws_iam_group` named `asset-publishers` and the `aws_iam_user_group_membership` that adds the user to it. The group is the attachment point for per-destination permission modules: `infrastructure/aws/iam/ecr` attaches its ECR policy to this group, and `infrastructure/aws/iam/s3-assets` attaches its S3 policy to the same group. The build user therefore accumulates the permissions of every enabled destination through a single group, which matches how the platform CLI publishes assets (one credential set used for all asset types).

## Features

- Creates a single namespaced `aws_iam_user` and `aws_iam_access_key` for CI/CD build workflow authentication
- Creates a destination-agnostic `aws_iam_group` (`asset-publishers`) that permission modules attach their policies to
- Adds the build user to the group via `aws_iam_user_group_membership`
- Exposes `group_name` so asset-repository modules (`ecr`, `s3-assets`) can grant permissions without recreating the identity

## Basic Usage

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

cluster_name = "your-cluster-name"
}
```

The `group_name` output is consumed by the asset-repository permission modules:

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

cluster_name = "your-cluster-name"
build_workflow_group_name = module.build_user.group_name
}

module "s3_assets" {
source = "git::https://github.com/nullplatform/tofu-modules.git//infrastructure/aws/iam/s3-assets?ref=v5.0.0"

cluster_name = "your-cluster-name"
build_workflow_group_name = module.build_user.group_name
assets_bucket = "your-assets-bucket"
}
```

## Migration from < v5.0.0 (build user previously created by `iam/ecr`)

Before v5.0.0 the build workflow user, its access key and the group lived inside the `iam/ecr`
module. This module extracts them. To migrate **without rotating the access keys** (which would
break CI), move the user and its access key in state — the group is renamed
(`ecr-managers` → `asset-publishers`) and is recreated, which does not affect the user's credentials:

```bash
tofu state mv 'module.ecr.aws_iam_user.nullplatform_build_workflow_user' \
'module.build_user.aws_iam_user.nullplatform_build_workflow_user'

tofu state mv 'module.ecr.aws_iam_access_key.nullplatform_build_workflow_user_key' \
'module.build_user.aws_iam_access_key.nullplatform_build_workflow_user_key'
```

After the moves, a `tofu plan` should show **no changes** to the user and access key (only their
state address moved), the group + membership recreated as `asset-publishers`, and the ECR policy
re-attached to the new group.

> **Security note:** the build credentials are read by the platform on each CI run (they are not
> stored as per-repository secrets), so rotating them periodically is a good practice and this
> module makes it easy — regenerate the access key and let the platform re-read the new value.

<!-- BEGIN_TF_DOCS -->


## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | n/a |

## Resources

| Name | Type |
|------|------|
| [aws_iam_access_key.nullplatform_build_workflow_user_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
| [aws_iam_group.asset_publishers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group) | resource |
| [aws_iam_user.nullplatform_build_workflow_user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
| [aws_iam_user_group_membership.asset_publishers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_group_membership) | resource |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_cluster_name"></a> [cluster\_name](#input\_cluster\_name) | Name of the cluster, used to namespace IAM resource names | `string` | n/a | yes |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_build_workflow_access_key_id"></a> [build\_workflow\_access\_key\_id](#output\_build\_workflow\_access\_key\_id) | Access key ID for the CI/CD build workflow IAM user |
| <a name="output_build_workflow_access_key_secret"></a> [build\_workflow\_access\_key\_secret](#output\_build\_workflow\_access\_key\_secret) | Secret access key for the CI/CD build workflow IAM user |
| <a name="output_group_name"></a> [group\_name](#output\_group\_name) | Name of the IAM group that asset-repository permission modules (ecr, s3-assets) attach their policies to. The build workflow user is a member of this group. |
<!-- END_TF_DOCS -->
16 changes: 16 additions & 0 deletions infrastructure/aws/iam/ci-build-workflow-user/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
resource "aws_iam_user" "nullplatform_build_workflow_user" {
name = "nullplatform-${var.cluster_name}-build-workflow-user"
}

resource "aws_iam_access_key" "nullplatform_build_workflow_user_key" {
user = aws_iam_user.nullplatform_build_workflow_user.name
}

resource "aws_iam_group" "asset_publishers" {
Comment thread
sebastiancorrea81 marked this conversation as resolved.
name = "nullplatform-${var.cluster_name}-asset-publishers"
}

Check warning

Code scanning / Trivy

IAM groups should have MFA enforcement activated. Medium

Artifact: infrastructure/aws/iam/ci-build-workflow-user/main.tf
Type: terraform
Vulnerability AWS-0123
Severity: MEDIUM
Message: Multi-Factor authentication is not enforced for group
Link: AWS-0123
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment on lines +9 to +11

resource "aws_iam_user_group_membership" "asset_publishers" {
user = aws_iam_user.nullplatform_build_workflow_user.name
groups = [aws_iam_group.asset_publishers.name]
}
15 changes: 15 additions & 0 deletions infrastructure/aws/iam/ci-build-workflow-user/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
output "build_workflow_access_key_id" {
description = "Access key ID for the CI/CD build workflow IAM user"
value = aws_iam_access_key.nullplatform_build_workflow_user_key.id
}

output "build_workflow_access_key_secret" {
description = "Secret access key for the CI/CD build workflow IAM user"
value = aws_iam_access_key.nullplatform_build_workflow_user_key.secret
sensitive = true
}

output "group_name" {
description = "Name of the IAM group that asset-repository permission modules (ecr, s3-assets) attach their policies to. The build workflow user is a member of this group."
value = aws_iam_group.asset_publishers.name
}
4 changes: 4 additions & 0 deletions infrastructure/aws/iam/ci-build-workflow-user/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
variable "cluster_name" {
description = "Name of the cluster, used to namespace IAM resource names"
type = string
}
46 changes: 26 additions & 20 deletions infrastructure/aws/iam/ecr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,41 @@

## Description

Provisions IAM resources for ECR image management and optional cross-account ECR pull access within a named cluster namespace
Provisions IAM resources for ECR image management and optional cross-account ECR pull access within a named cluster namespace. The build workflow identity (user, access key, group) lives in the `ci-build-workflow-user` module; this module only grants ECR permissions to that group.

## Architecture

The module creates two aws_iam_role resources (an application role with a configurable assume-role principal and an optional cross-account pull role), an aws_iam_policy for ECR management actions, and an aws_iam_user with an aws_iam_access_key for CI/CD build workflows. The ECR manager policy is attached to both the application role via aws_iam_role_policy_attachment and to an aws_iam_group via aws_iam_group_policy_attachment, with the build workflow user added to that group through aws_iam_user_group_membership. When enable_cross_account_pull is true, a separate aws_iam_role and aws_iam_policy scoped to read-only ECR actions are created and linked, with pull_account_ids driving the Principal trust statements.
The module creates an aws_iam_role (application role with a configurable assume-role principal) and an aws_iam_policy for ECR management actions. The ECR manager policy is attached to the application role via aws_iam_role_policy_attachment and to the shared build-workflow group via aws_iam_group_policy_attachment. The group itself is created by the `ci-build-workflow-user` module and passed in through `build_workflow_group_name`. When enable_cross_account_pull is true, a separate aws_iam_role and aws_iam_policy scoped to read-only ECR actions are created and linked, with pull_account_ids driving the Principal trust statements.

## Features

- Creates a namespaced aws_iam_role for application image pulling with a configurable assume-role principal
- Creates an aws_iam_policy granting full ECR repository lifecycle permissions including push, pull, and repository management
- Creates an aws_iam_user and aws_iam_access_key for CI/CD build workflow authentication to ECR
- Creates an aws_iam_group and attaches the ECR manager policy for group-based permission management
- Attaches the ECR manager policy to the shared build-workflow group (created by the ci-build-workflow-user module) for group-based permission management
- Optionally creates a cross-account aws_iam_role and read-only ECR pull policy for external AWS accounts
- Outputs a ready-to-use ECR repository policy JSON for cross-account pull access configuration

## Basic Usage

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

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"
cluster_name = "your-cluster-name"
}

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

cluster_name = "your-cluster-name"
build_workflow_group_name = module.ci_build_workflow_user.group_name
}
```

> **Migration from < v5.0.0:** the build workflow user, access key and group were previously
> created by this module. They now live in `ci-build-workflow-user`. See that module's README for the
> `tofu state mv` steps to migrate without rotating the access keys.

## Using Outputs

```hcl
Expand All @@ -50,21 +60,18 @@ resource "example_resource" "this" {

| Name | Type |
|------|------|
| [aws_iam_access_key.nullplatform_build_workflow_user_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
| [aws_iam_group.nullplatform_ecr_managers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group) | resource |
| [aws_iam_group_policy_attachment.ecr_manager_policy_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_policy_attachment) | resource |
| [aws_iam_policy.nullplatform_ecr_manager_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.nullplatform_application_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.ecr_manager_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_user.nullplatform_build_workflow_user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
| [aws_iam_user_group_membership.build_workflow_ecr_managers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_group_membership) | resource |
| [terraform_data.validations](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_application_manager_assume_role"></a> [application\_manager\_assume\_role](#input\_application\_manager\_assume\_role) | ARN of the IAM role assumed by the application manager | `string` | `"arn:aws:iam::283477532906:role/application_manager"` | no |
| <a name="input_build_workflow_group_name"></a> [build\_workflow\_group\_name](#input\_build\_workflow\_group\_name) | Name of the IAM group (from the ci-build-workflow-user module) to which the ECR manager policy is attached. The build workflow user is a member of this group. | `string` | n/a | yes |
| <a name="input_cluster_name"></a> [cluster\_name](#input\_cluster\_name) | Name of the cluster, used to namespace IAM resource names | `string` | n/a | yes |
| <a name="input_enable_cross_account_pull"></a> [enable\_cross\_account\_pull](#input\_enable\_cross\_account\_pull) | Enable cross-account ECR pull access by creating an IAM role that external accounts can assume | `bool` | `false` | no |
| <a name="input_pull_account_ids"></a> [pull\_account\_ids](#input\_pull\_account\_ids) | AWS account IDs allowed to assume the cross-account ECR pull role. Required when enable\_cross\_account\_pull is true. | `list(string)` | `[]` | no |
Expand All @@ -74,21 +81,18 @@ resource "example_resource" "this" {
| Name | Description |
|------|-------------|
| <a name="output_application_role_arn"></a> [application\_role\_arn](#output\_application\_role\_arn) | ARN of the IAM role used by applications to pull ECR images |
| <a name="output_build_workflow_access_key_id"></a> [build\_workflow\_access\_key\_id](#output\_build\_workflow\_access\_key\_id) | Access key ID for the CI/CD build workflow IAM user |
| <a name="output_build_workflow_access_key_secret"></a> [build\_workflow\_access\_key\_secret](#output\_build\_workflow\_access\_key\_secret) | Secret access key for the CI/CD build workflow IAM user |
| <a name="output_ecr_repository_policy"></a> [ecr\_repository\_policy](#output\_ecr\_repository\_policy) | ECR repository policy JSON granting pull access to the configured cross-account IDs. Empty string when enable\_cross\_account\_pull is false. |
<!-- END_TF_DOCS -->

<!-- BEGIN_AI_METADATA
{
"name": "ecr",
"description": "Provisions IAM resources for ECR image management and optional cross-account ECR pull access within a named cluster namespace",
"architecture": "The module creates two aws_iam_role resources (an application role with a configurable assume-role principal and an optional cross-account pull role), an aws_iam_policy for ECR management actions, and an aws_iam_user with an aws_iam_access_key for CI/CD build workflows. The ECR manager policy is attached to both the application role via aws_iam_role_policy_attachment and to an aws_iam_group via aws_iam_group_policy_attachment, with the build workflow user added to that group through aws_iam_user_group_membership. When enable_cross_account_pull is true, a separate aws_iam_role and aws_iam_policy scoped to read-only ECR actions are created and linked, with pull_account_ids driving the Principal trust statements.",
"description": "Provisions IAM resources for ECR image management and optional cross-account ECR pull access within a named cluster namespace. The build workflow identity (user, access key, group) lives in the ci-build-workflow-user module; this module only grants ECR permissions to that group.",
"architecture": "The module creates an aws_iam_role (application role with a configurable assume-role principal) and an aws_iam_policy for ECR management actions. The ECR manager policy is attached to the application role via aws_iam_role_policy_attachment and to the shared build-workflow group via aws_iam_group_policy_attachment. The group itself is created by the ci-build-workflow-user module and passed in through build_workflow_group_name. When enable_cross_account_pull is true, a separate aws_iam_role and aws_iam_policy scoped to read-only ECR actions are created and linked, with pull_account_ids driving the Principal trust statements.",
"features": [
"Creates a namespaced aws_iam_role for application image pulling with a configurable assume-role principal",
"Creates an aws_iam_policy granting full ECR repository lifecycle permissions including push, pull, and repository management",
"Creates an aws_iam_user and aws_iam_access_key for CI/CD build workflow authentication to ECR",
"Creates an aws_iam_group and attaches the ECR manager policy for group-based permission management",
"Attaches the ECR manager policy to the shared build-workflow group (created by the ci-build-workflow-user module) for group-based permission management",
"Optionally creates a cross-account aws_iam_role and read-only ECR pull policy for external AWS accounts",
"Outputs a ready-to-use ECR repository policy JSON for cross-account pull access configuration"
],
Expand All @@ -98,6 +102,11 @@ resource "example_resource" "this" {
"description": "Name of the cluster, used to namespace IAM resource names",
"required": true
},
{
"name": "build_workflow_group_name",
"description": "Name of the IAM group (from the ci-build-workflow-user module) to which the ECR manager policy is attached. The build workflow user is a member of this group.",
"required": true
},
{
"name": "application_manager_assume_role",
"description": "ARN of the IAM role assumed by the application manager",
Expand All @@ -116,11 +125,8 @@ resource "example_resource" "this" {
],
"outputs": [
"application_role_arn",
"build_workflow_access_key_id",
"build_workflow_access_key_secret",
"cross_account_pull_role_arn",
"ecr_repository_policy"
],
"hash": "3fec1e27ab807f00fd3468e628c7cae3"
"hash": "regenerate-with-terraform-docs"
}
END_AI_METADATA -->
19 changes: 1 addition & 18 deletions infrastructure/aws/iam/ecr/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,13 @@ resource "aws_iam_policy" "nullplatform_ecr_manager_policy" {
})
}

resource "aws_iam_user" "nullplatform_build_workflow_user" {
name = "nullplatform-${var.cluster_name}-build-workflow-user"
}

resource "aws_iam_access_key" "nullplatform_build_workflow_user_key" {
user = aws_iam_user.nullplatform_build_workflow_user.name
}

resource "aws_iam_role_policy_attachment" "ecr_manager_policy" {
role = aws_iam_role.nullplatform_application_role.name
policy_arn = aws_iam_policy.nullplatform_ecr_manager_policy.arn
}

resource "aws_iam_group" "nullplatform_ecr_managers" {
name = "nullplatform-${var.cluster_name}-ecr-managers"
}

resource "aws_iam_group_policy_attachment" "ecr_manager_policy_group" {
group = aws_iam_group.nullplatform_ecr_managers.name
group = var.build_workflow_group_name
policy_arn = aws_iam_policy.nullplatform_ecr_manager_policy.arn
}

resource "aws_iam_user_group_membership" "build_workflow_ecr_managers" {
user = aws_iam_user.nullplatform_build_workflow_user.name
groups = [aws_iam_group.nullplatform_ecr_managers.name]
}

11 changes: 0 additions & 11 deletions infrastructure/aws/iam/ecr/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,6 @@ output "application_role_arn" {
value = aws_iam_role.nullplatform_application_role.arn
}

output "build_workflow_access_key_id" {
description = "Access key ID for the CI/CD build workflow IAM user"
value = aws_iam_access_key.nullplatform_build_workflow_user_key.id
}

output "build_workflow_access_key_secret" {
description = "Secret access key for the CI/CD build workflow IAM user"
value = aws_iam_access_key.nullplatform_build_workflow_user_key.secret
sensitive = true
}

output "ecr_repository_policy" {
description = "ECR repository policy JSON granting pull access to the configured cross-account IDs. Empty string when enable_cross_account_pull is false."
value = var.enable_cross_account_pull ? jsonencode({
Expand Down
5 changes: 5 additions & 0 deletions infrastructure/aws/iam/ecr/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ variable "cluster_name" {
type = string
}

variable "build_workflow_group_name" {
description = "Name of the IAM group (from the ci-build-workflow-user module) to which the ECR manager policy is attached. The build workflow user is a member of this group."
type = string
}

variable "application_manager_assume_role" {
description = "ARN of the IAM role assumed by the application manager"
type = string
Expand Down
Loading