@@ -113,11 +113,13 @@ gp3, 50–200 GiB autoscaling, 7-day backups, deletion protection),
1131133 ECR repositories (coder, base-fips, desktop-fips — all
114114KMS-encrypted with scan-on-push), KMS CMK (shared key for RDS/EBS/
115115ECR/Secrets Manager), Secrets Manager secrets (RDS password
116- auto-generated, Coder license placeholder).
116+ auto-generated, Coder license placeholder), GitHub Actions OIDC
117+ provider + IAM role for CI/CD (scoped to this repo, ECR push only).
117118
118119** Why:** Coder needs a PostgreSQL database. ECR stores the FIPS
119120container images. Secrets Manager holds credentials that
120- ExternalSecrets syncs into Kubernetes.
121+ ExternalSecrets syncs into Kubernetes. The OIDC + IAM role lets
122+ GitHub Actions push images to ECR without static access keys.
121123
122124``` bash
123125cd infra/terraform/2-data
@@ -289,141 +291,37 @@ this depending on your setup):
289291
290292## Phase D — Set Up CI/CD (GitHub Actions → ECR)
291293
292- ECR repos now exist (created by layer 2). This phase wires GitHub
293- Actions to push FIPS images into them.
294+ The OIDC provider, IAM role, and ECR permissions were all created
295+ by Terraform layer 2 (` infra/terraform/2-data/ci.tf ` ). No manual
296+ AWS CLI commands needed.
294297
295- ### D1. Get your AWS account ID
298+ ### D1. Get the CI values from Terraform outputs
296299
297300``` bash
298- aws sts get-caller-identity --query Account --output text
299- # e.g. 123456789012
300- ```
301-
302- ### D2. Create the OIDC identity provider
303-
304- ** What this does:** Tells AWS to trust GitHub Actions as an identity
305- provider. When a GitHub Actions workflow runs, GitHub issues a JWT.
306- This OIDC provider lets AWS validate that JWT so the workflow can
307- assume an IAM role — no static access keys needed.
308-
309- ** One-time setup. Skip if you already have this from another repo.**
310-
311- Check first:
312- ``` bash
313- aws iam list-open-id-connect-providers \
314- | grep token.actions.githubusercontent.com
315- # If it prints something, skip to D3
316- ```
317-
318- If nothing returned:
319- ``` bash
320- aws iam create-open-id-connect-provider \
321- --url https://token.actions.githubusercontent.com \
322- --client-id-list sts.amazonaws.com \
323- --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
324- ```
325-
326- ### D3. Create the IAM role for CI
327-
328- ** What this does:** Creates an IAM role that only GitHub Actions
329- workflows running in the ` coder/usgov-deploy-aws ` repo can assume.
330- The trust policy uses the OIDC provider from D2 and restricts
331- access to this specific repo. The permissions allow pushing and
332- pulling container images to/from ECR.
333-
334- Save this as ` /tmp/trust-policy.json ` (replace ` ACCOUNT_ID ` ):
335-
336- ``` json
337- {
338- "Version" : " 2012-10-17" ,
339- "Statement" : [
340- {
341- "Effect" : " Allow" ,
342- "Principal" : {
343- "Federated" : " arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
344- },
345- "Action" : " sts:AssumeRoleWithWebIdentity" ,
346- "Condition" : {
347- "StringEquals" : {
348- "token.actions.githubusercontent.com:aud" : " sts.amazonaws.com"
349- },
350- "StringLike" : {
351- "token.actions.githubusercontent.com:sub" : " repo:coder/usgov-deploy-aws:*"
352- }
353- }
354- }
355- ]
356- }
357- ```
358-
359- ``` bash
360- # Replace ACCOUNT_ID in the file first, then:
361- aws iam create-role \
362- --role-name usgov-deploy-aws-ci \
363- --assume-role-policy-document file:///tmp/trust-policy.json
364- ```
365-
366- ### D4. Attach ECR permissions to the role
367-
368- ** What this does:** Grants the CI role permission to authenticate
369- with ECR (GetAuthorizationToken works globally) and push/pull
370- images to the three repos created by Terraform layer 2. Scoped
371- to ` coder4gov/* ` repos only — can't touch anything else in ECR.
372-
373- Save as ` /tmp/ecr-policy.json ` (replace ` ACCOUNT_ID ` ):
374-
375- ``` json
376- {
377- "Version" : " 2012-10-17" ,
378- "Statement" : [
379- {
380- "Sid" : " ECRAuth" ,
381- "Effect" : " Allow" ,
382- "Action" : " ecr:GetAuthorizationToken" ,
383- "Resource" : " *"
384- },
385- {
386- "Sid" : " ECRPush" ,
387- "Effect" : " Allow" ,
388- "Action" : [
389- " ecr:BatchCheckLayerAvailability" ,
390- " ecr:GetDownloadUrlForLayer" ,
391- " ecr:BatchGetImage" ,
392- " ecr:PutImage" ,
393- " ecr:InitiateLayerUpload" ,
394- " ecr:UploadLayerPart" ,
395- " ecr:CompleteLayerUpload" ,
396- " ecr:CreateRepository" ,
397- " ecr:DescribeRepositories"
398- ],
399- "Resource" : " arn:aws:ecr:us-west-2:ACCOUNT_ID:repository/coder4gov/*"
400- }
401- ]
402- }
403- ```
301+ cd infra/terraform/2-data
302+ terraform output github_actions_role_arn
303+ # e.g. arn:aws:iam::123456789012:role/coder4gov-github-actions-ci
404304
405- ``` bash
406- aws iam put-role-policy \
407- --role-name usgov-deploy-aws-ci \
408- --policy-name ecr-push \
409- --policy-document file:///tmp/ecr-policy.json
305+ terraform output ecr_registry
306+ # e.g. 123456789012.dkr.ecr.us-west-2.amazonaws.com
307+ cd ../../..
410308```
411309
412- ### D5 . Add GitHub repo secrets
310+ ### D2 . Add GitHub repo secrets
413311
414312** Where:** https://github.com/coder/usgov-deploy-aws/settings/secrets/actions
415313
416314** Why:** The GitHub Actions workflows reference these secrets to
417315authenticate with AWS and know which ECR registry to push to. No
418316AWS access keys are stored — the workflow uses OIDC federation to
419- get short-lived credentials via the role from D3 .
317+ get short-lived credentials via the IAM role Terraform created .
420318
421- | Secret name | Value | Example |
319+ | Secret name | Value | Source |
422320| ---| ---| ---|
423- | ` ECR_REGISTRY ` | ` <ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com ` | ` 123456789012.dkr.ecr.us-west-2.amazonaws.com ` |
424- | ` AWS_ROLE_ARN ` | ` arn:aws:iam::<ACCOUNT_ID>:role/usgov-deploy-aws-ci ` | ` arn:aws:iam::123456789012:role/usgov-deploy-aws-ci ` |
321+ | ` AWS_ROLE_ARN ` | The role ARN from D1 | ` terraform output github_actions_role_arn ` |
322+ | ` ECR_REGISTRY ` | The registry URL from D1 | ` terraform output ecr_registry ` |
425323
426- ### D6 . Test the CI pipeline
324+ ### D3 . Test the CI pipeline
427325
428326** What this does:** Manually triggers the Coder FIPS build workflow.
429327It clones the Coder source, compiles a FIPS-enabled binary with
@@ -445,14 +343,24 @@ Successfully pushed <REGISTRY>/coder4gov/coder:latest-fips
445343Then test ** Workspace FIPS Images** the same way. This builds the
446344RHEL 9 base-fips and desktop-fips images.
447345
448- ### D7 . Verify images in ECR
346+ ### D4 . Verify images in ECR
449347
450348``` bash
451349aws ecr list-images --repository-name coder4gov/coder
452350aws ecr list-images --repository-name coder4gov/base-fips
453351aws ecr list-images --repository-name coder4gov/desktop-fips
454352```
455353
354+ > ** Note:** If the OIDC provider already exists in your account from
355+ > another repo, Terraform will fail on ` aws_iam_openid_connect_provider.github ` .
356+ > Import it instead:
357+ > ``` bash
358+ > cd infra/terraform/2-data
359+ > terraform import aws_iam_openid_connect_provider.github \
360+ > arn:aws:iam::< ACCOUNT_ID> :oidc-provider/token.actions.githubusercontent.com
361+ > ` ` `
362+
363+
456364---
457365
458366# # Phase E — Seed API Keys (for usgov-env-demo only)
@@ -478,7 +386,7 @@ aws secretsmanager create-secret \
478386A1–A2 Clone + configure
479387A3 Layer 0: S3 state bucket (~1 min)
480388A4 Layer 1: VPC, DNS, ACM (~3 min) → update NS records
481- A5 Layer 2: RDS, ECR, KMS, Secrets (~10 min)
389+ A5 Layer 2: RDS, ECR, KMS, Secrets, CI (~10 min)
482390A6 Layer 3: EKS cluster (~15 min)
483391A7 Layer 4: Karpenter, ALB, ESO (~5 min)
484392B1–B2 Seed Coder license
0 commit comments