|
| 1 | +# aws-ecr-pull-through |
| 2 | + |
| 3 | +Terraform module that configures an **AWS ECR pull-through cache** for `cgr.dev`. The pull token is automatically rotated by a scheduled Lambda function. |
| 4 | + |
| 5 | +## How it works |
| 6 | + |
| 7 | +```mermaid |
| 8 | +flowchart TD |
| 9 | + Schedule([first run / on schedule]) --> Lambda |
| 10 | + Lambda[Lambda<br/>rotator] -->|mints| Identity[Chainguard identity<br/>static, RSA-keyed] |
| 11 | + Lambda -->|writes| Secret[Secrets Manager<br/>ecr-pullthroughcache/...] |
| 12 | + Rule[ECR pull-through<br/>cache rule] -->|service-linked role reads| Secret |
| 13 | + Rule -->|on cache miss, pulls from cgr.dev| CGR[(cgr.dev)] |
| 14 | + Consumer([AWS IAM role / user]) -->|docker pull| Rule |
| 15 | +``` |
| 16 | + |
| 17 | +1. The Lambda authenticates to the Chainguard API using an [assumable identity](https://edu.chainguard.dev/chainguard/administration/assumable-ids/identity-examples/aws-identity-oidc/). |
| 18 | +2. It creates a pull token. |
| 19 | +3. It updates the Secrets Manager secret used by the pull-through cache rule. |
| 20 | +4. On each rotation, the Lambda deletes expired tokens. |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## Prerequisites |
| 25 | + |
| 26 | +### Tools |
| 27 | + |
| 28 | +- [Terraform](https://developer.hashicorp.com/terraform/install) >= 1.5 |
| 29 | +- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) — authenticated (`aws configure` or `AWS_PROFILE`) |
| 30 | +- [Chainguard CLI (`chainctl`)](https://edu.chainguard.dev/chainguard/chainguard-enforce/how-to-install-chainctl/) — authenticated with `chainctl auth login` |
| 31 | +- [`ko`](https://ko.build/) credential helper available so Terraform can push the Lambda image to ECR |
| 32 | + |
| 33 | +### AWS account configuration |
| 34 | + |
| 35 | +[Outbound identity federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_outbound_identity_federation.html) must be enabled on the AWS account. This is the mechanism the Lambda uses to authenticate to Chainguard. |
| 36 | + |
| 37 | +### AWS permissions |
| 38 | + |
| 39 | +The identity running Terraform needs to create: |
| 40 | + |
| 41 | +- ECR repositories and pull-through cache rules |
| 42 | +- Secrets Manager secrets |
| 43 | +- IAM roles + inline policies |
| 44 | +- Lambda functions (and invoke them once for bootstrap) |
| 45 | +- EventBridge schedules |
| 46 | + |
| 47 | +### Chainguard permissions |
| 48 | + |
| 49 | +The identity running Terraform needs **Owner** or **Editor** on the target Chainguard group to create the Lambda's identity, the custom cleaner role, and the role bindings. |
| 50 | + |
| 51 | +--- |
| 52 | + |
| 53 | +## Deployment |
| 54 | + |
| 55 | +### 1. Authenticate |
| 56 | + |
| 57 | +```sh |
| 58 | +aws configure # or set AWS_PROFILE |
| 59 | +chainctl auth login |
| 60 | +``` |
| 61 | + |
| 62 | +### 2. Configure |
| 63 | + |
| 64 | +Set your tfvars (at minimum, `org_name`): |
| 65 | + |
| 66 | +```hcl |
| 67 | +# iac/terraform.tfvars |
| 68 | +org_name = "your.org" |
| 69 | +``` |
| 70 | + |
| 71 | +### 3. Apply |
| 72 | + |
| 73 | +```sh |
| 74 | +cd iac |
| 75 | +tf init |
| 76 | +tf apply |
| 77 | +``` |
| 78 | + |
| 79 | +### 4. Use the cache |
| 80 | + |
| 81 | +```sh |
| 82 | +PREFIX=$(tf output -raw pull_through_cache_uri) |
| 83 | +docker pull ${PREFIX}/<image>:<tag> |
| 84 | +# e.g. for cgr.dev/your.org/python:latest |
| 85 | +docker pull ${PREFIX}/python:latest |
| 86 | +``` |
| 87 | + |
| 88 | +The first pull of any tag is slow (ECR fetches it from `cgr.dev` synchronously and creates the cached repo). Subsequent pulls are cached. |
| 89 | + |
| 90 | +--- |
| 91 | + |
| 92 | +## Operations |
| 93 | + |
| 94 | +### Force a rotation |
| 95 | + |
| 96 | +```sh |
| 97 | +aws lambda invoke --function-name $(tf output -raw rotator_function_name) /dev/null |
| 98 | +``` |
| 99 | + |
| 100 | +### Inspect minted pull tokens |
| 101 | + |
| 102 | +```sh |
| 103 | +chainctl iam identity list --parent $(tf output -raw chainguard_identity_id | cut -d/ -f1) --type pull_token |
| 104 | +``` |
| 105 | + |
| 106 | +### Teardown |
| 107 | + |
| 108 | +```sh |
| 109 | +cd iac |
| 110 | +tf destroy |
| 111 | +``` |
| 112 | + |
| 113 | +**Note**: pull-token identities the Lambda created in Chainguard are not deleted by `tf destroy` — they'll expire on their own (within `token_ttl`). To remove them immediately, list them with `chainctl iam identity list` and delete them manually. |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +## Resources created |
| 118 | + |
| 119 | +### AWS |
| 120 | + |
| 121 | +| Resource | Notes | |
| 122 | +|---|---| |
| 123 | +| `aws_ecr_pull_through_cache_rule` | Maps `cgr.dev/<group>` to the local `<ecr_repository_prefix>` namespace | |
| 124 | +| `aws_secretsmanager_secret` | Named `ecr-pullthroughcache/<name>`; holds the rotated pull token | |
| 125 | +| `aws_secretsmanager_secret_version` | Initial placeholder; `secret_string` is owned by the Lambda thereafter (`ignore_changes`) | |
| 126 | +| `aws_ecr_repository` | Dedicated repo for the rotator Lambda's container image | |
| 127 | +| `aws_iam_role` (lambda) | Lambda execution role with `secretsmanager:PutSecretValue` on the secret | |
| 128 | +| `aws_iam_role` (scheduler) | EventBridge Scheduler role that invokes the Lambda | |
| 129 | +| `aws_lambda_function` | The rotator, packaged as an OCI image built with `ko` | |
| 130 | +| `aws_lambda_invocation` | One-shot bootstrap so the secret is populated immediately on apply | |
| 131 | +| `aws_scheduler_schedule` | Recurring rotation (default `rate(1 day)`) | |
| 132 | + |
| 133 | +### Chainguard |
| 134 | + |
| 135 | +| Resource | Description | |
| 136 | +|---|---| |
| 137 | +| `chainguard_identity` | Workload identity for the Lambda, bound to the AWS role | |
| 138 | +| `chainguard_rolebinding` (creator) | Grants the built-in `registry.pull_token_creator` role on the group | |
| 139 | +| `chainguard_role` (cleaner) | Custom role with `iam.identities.delete` only | |
| 140 | +| `chainguard_rolebinding` (cleaner) | Grants the custom cleaner role to the Lambda's identity | |
| 141 | + |
| 142 | +The Lambda also creates one `chainguard_identity` (of type static, with an attached `chainguard_rolebinding`) per rotation. These are the actual pull tokens — outside Terraform's management — and the Lambda is responsible for deleting them once they expire. |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +## Variables |
| 147 | + |
| 148 | +| Variable | Required | Default | Description | |
| 149 | +|---|---|---|---| |
| 150 | +| `org_name` | yes | — | Chainguard organization name (e.g. `your.org`) | |
| 151 | +| `suffix` | no | (random) | Suffix appended to every resource name; auto-generated if empty | |
| 152 | +| `ecr_repository_prefix` | no | `chainguard-<suffix>` | Local ECR namespace for cached images | |
| 153 | +| `upstream_repository_prefix` | no | `var.org_name` | Upstream prefix on `cgr.dev` | |
| 154 | +| `token_ttl` | no | `336h` | Pull-token lifetime as a Go duration string (e.g. `336h` = 14 days) | |
| 155 | +| `rotation_schedule` | no | `rate(1 day)` | EventBridge schedule expression for rotation | |
| 156 | + |
| 157 | +--- |
| 158 | + |
| 159 | +## Outputs |
| 160 | + |
| 161 | +| Output | Description | |
| 162 | +|---|---| |
| 163 | +| `suffix` | Suffix used across all resource names in this deployment | |
| 164 | +| `ecr_repository_prefix` | ECR namespace for cached images | |
| 165 | +| `pull_through_cache_uri` | Full base URI to pull through (e.g. `123456789.dkr.ecr.us-east-1.amazonaws.com/cgr`) | |
| 166 | +| `secret_arn` | ARN of the Secrets Manager secret backing the cache rule | |
| 167 | +| `rotator_function_name` | Lambda function name for the rotator | |
| 168 | +| `chainguard_identity_id` | Chainguard identity ID for the rotator Lambda | |
| 169 | + |
0 commit comments