Skip to content

Commit a7791d7

Browse files
committed
publish to ECR
1 parent e914bea commit a7791d7

File tree

3 files changed

+312
-6
lines changed

3 files changed

+312
-6
lines changed

.github/SETUP.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# GitHub Actions Environment Variables Setup
2+
3+
This is a quick reference guide. For complete setup instructions, see [`OPS.md`](../OPS.md#github-actions-aws-oidc-setup).
4+
5+
## Required Secret
6+
7+
The workflow requires one GitHub repository secret:
8+
9+
- **`AWS_ROLE_ARN`**: The ARN of the IAM role that GitHub Actions will assume to push Docker images to ECR
10+
11+
## Setup Steps
12+
13+
### 1. Create AWS IAM Role
14+
15+
**Follow the complete instructions in [`OPS.md`](../OPS.md#github-actions-aws-oidc-setup)** which includes:
16+
- Creating the OIDC identity provider
17+
- Creating the IAM role with trust policy
18+
- Attaching ECR permissions policy
19+
20+
After completing the AWS setup, you'll have an IAM role ARN (typically: `arn:aws:iam::633607774026:role/GitHubActions-ECR-Push`)
21+
22+
### 2. Add GitHub Secret
23+
24+
Add the IAM role ARN as a GitHub repository secret using the GitHub CLI:
25+
26+
```bash
27+
# Ensure you're authenticated with GitHub CLI
28+
# If not already authenticated, run: gh auth login
29+
30+
# Set the secret (replace with your actual role ARN if different)
31+
gh secret set AWS_ROLE_ARN --body "arn:aws:iam::633607774026:role/GitHubActions-ECR-Push"
32+
```
33+
34+
**Note**: Make sure you're in the repository directory or specify the repo with `--repo operationcode/back-end`.
35+
36+
### 3. Verify Setup
37+
38+
After adding the secret, the workflow will automatically:
39+
- Authenticate to AWS using OIDC (no credentials stored)
40+
- Build Docker images for ARM64 platform
41+
- Push to ECR with appropriate tags:
42+
- `:staging` for non-master branches
43+
- `:prod` for master branch (after CI passes)
44+
45+
## Testing
46+
47+
To test the setup:
48+
49+
1. **Test staging build**: Push to any branch except `master`
50+
- Should trigger Docker build and push to `:staging` tag
51+
- Check ECR repository to verify image was pushed
52+
53+
2. **Test production build**: Merge to `master` branch
54+
- Should run lint, test, security checks first
55+
- If all pass, should build and push to `:prod` tag
56+
- Check ECR repository to verify image was pushed
57+
58+
## Troubleshooting
59+
60+
### Build fails with "Error assuming role"
61+
- Verify the `AWS_ROLE_ARN` secret is set correctly
62+
- Check that the IAM role exists and has the correct trust policy
63+
- Ensure the OIDC identity provider is configured
64+
65+
### Build fails with "AccessDenied" to ECR
66+
- Verify the IAM role has the ECR push policy attached
67+
- Check that the policy allows access to the correct ECR repository
68+
- Ensure the repository path matches: `633607774026.dkr.ecr.us-east-2.amazonaws.com/back-end`
69+
70+
### Production build runs even when tests fail
71+
- This shouldn't happen - production builds depend on `ci-success` job
72+
- Check that `ci-success` job is properly failing when tests fail
73+
- Verify branch protection rules if using them
74+
75+
## Additional Resources
76+
77+
- Full AWS OIDC setup: See [`OPS.md`](../OPS.md#github-actions-aws-oidc-setup)
78+
- GitHub Actions secrets: https://docs.github.com/en/actions/security-guides/encrypted-secrets
79+
- AWS OIDC with GitHub: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services

.github/workflows/ci.yml

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: CI
22

33
on:
44
push:
5-
branches: [master]
5+
branches: ['**'] # Trigger on push to any branch for Docker builds
66
pull_request:
77
branches: [master]
88

@@ -14,6 +14,8 @@ jobs:
1414
lint:
1515
name: Lint
1616
runs-on: ubuntu-latest
17+
# Only run on master branch pushes and PRs to master
18+
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master'
1719
steps:
1820
- name: Checkout code
1921
uses: actions/checkout@v4
@@ -60,6 +62,8 @@ jobs:
6062
test:
6163
name: Test
6264
runs-on: ubuntu-latest
65+
# Only run on master branch pushes and PRs to master
66+
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master'
6367
steps:
6468
- name: Checkout code
6569
uses: actions/checkout@v4
@@ -121,6 +125,8 @@ jobs:
121125
security:
122126
name: Security Scan
123127
runs-on: ubuntu-latest
128+
# Only run on master branch pushes and PRs to master
129+
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master'
124130
steps:
125131
- name: Checkout code
126132
uses: actions/checkout@v4
@@ -164,22 +170,97 @@ jobs:
164170
- name: Display Bandit results
165171
run: poetry run bandit -r src --skip B101 --severity-level high -f txt || true
166172

173+
docker-build-push:
174+
name: Build and Push Docker Image
175+
runs-on: ubuntu-latest
176+
# Run on push events only (not pull_request)
177+
if: github.event_name == 'push'
178+
# For master branch, wait for CI checks to pass; for other branches, ci-success will pass immediately
179+
needs: [ci-success]
180+
permissions:
181+
id-token: write # Required for OIDC authentication
182+
contents: read # Required to checkout code
183+
steps:
184+
- name: Checkout code
185+
uses: actions/checkout@v4
186+
187+
- name: Determine Docker tag
188+
id: docker-tag
189+
run: |
190+
if [ "${{ github.ref }}" == "refs/heads/master" ]; then
191+
echo "tag=prod" >> $GITHUB_OUTPUT
192+
echo "environment=Production" >> $GITHUB_OUTPUT
193+
else
194+
echo "tag=staging" >> $GITHUB_OUTPUT
195+
echo "environment=Staging" >> $GITHUB_OUTPUT
196+
fi
197+
echo "Building for ${{ steps.docker-tag.outputs.environment }} with tag: ${{ steps.docker-tag.outputs.tag }}"
198+
199+
- name: Set up Docker Buildx
200+
uses: docker/setup-buildx-action@v3
201+
with:
202+
platforms: linux/arm64
203+
204+
- name: Configure AWS credentials
205+
uses: aws-actions/configure-aws-credentials@v4
206+
with:
207+
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
208+
role-session-name: GitHubActions-DockerBuild-${{ steps.docker-tag.outputs.environment }}
209+
aws-region: us-east-2
210+
211+
- name: Login to Amazon ECR
212+
id: login-ecr
213+
uses: aws-actions/amazon-ecr-login@v2
214+
215+
- name: Build and push Docker image
216+
uses: docker/build-push-action@v5
217+
with:
218+
context: .
219+
target: runtime
220+
platforms: linux/arm64
221+
push: true
222+
tags: |
223+
633607774026.dkr.ecr.us-east-2.amazonaws.com/back-end:${{ steps.docker-tag.outputs.tag }}
224+
provenance: false
225+
cache-from: type=gha
226+
cache-to: type=gha,mode=max
227+
228+
- name: Output image URI
229+
run: |
230+
echo "Successfully pushed ${{ steps.docker-tag.outputs.environment }} image:"
231+
echo "633607774026.dkr.ecr.us-east-2.amazonaws.com/back-end:${{ steps.docker-tag.outputs.tag }}"
232+
167233
# Final status check for branch protection
168234
ci-success:
169235
name: CI Success
170236
needs: [lint, test, security]
171237
runs-on: ubuntu-latest
238+
# Always run to satisfy docker-build-push dependency
172239
if: always()
173240
steps:
174-
- name: Check all jobs passed
241+
- name: Check all jobs passed (master/PR only)
242+
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master'
175243
run: |
244+
# Check if jobs were skipped (non-master) or failed
245+
if [[ "${{ needs.lint.result }}" == "skipped" ]]; then
246+
echo "Lint job was skipped - this should not happen on master/PR"
247+
exit 1
248+
fi
176249
if [[ "${{ needs.lint.result }}" != "success" ]]; then
177250
echo "Lint job failed"
178251
exit 1
179252
fi
253+
if [[ "${{ needs.test.result }}" == "skipped" ]]; then
254+
echo "Test job was skipped - this should not happen on master/PR"
255+
exit 1
256+
fi
180257
if [[ "${{ needs.test.result }}" != "success" ]]; then
181258
echo "Test job failed"
182259
exit 1
183260
fi
184261
# Security is informational, doesn't fail CI
185262
echo "All required jobs passed!"
263+
- name: Pass through for non-master branches
264+
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/master'
265+
run: |
266+
echo "Skipping CI checks for non-master branch (staging build will proceed)"

OPS.md

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@ The backend is deployed to AWS ECS (Elastic Container Service) with separate sta
44

55
## Building and Pushing Docker Images
66

7-
Use the `docker-build.sh` script to build multi-architecture images and push to AWS ECR:
7+
### Automated Builds (Recommended)
8+
9+
Docker images are automatically built and pushed to AWS ECR via GitHub Actions:
10+
11+
- **PR branches** (any branch except `master`): Automatically builds and pushes to `:staging` tag
12+
- **Master branch**: Automatically builds and pushes to `:prod` tag after CI checks pass
13+
14+
The automated builds use AWS OIDC for secure authentication (no long-lived credentials).
15+
16+
### Manual Builds (Legacy)
17+
18+
For manual builds, use the `docker-build.sh` script:
819

920
```bash
1021
# Build and push staging images
@@ -14,9 +25,7 @@ Use the `docker-build.sh` script to build multi-architecture images and push to
1425
./docker-build.sh prod
1526
```
1627

17-
This creates:
18-
- `back-end:staging-amd64` and `back-end:staging-arm64` images
19-
- A multi-arch manifest at `back-end:staging`
28+
This creates ARM64 images tagged as `back-end:staging` or `back-end:prod`.
2029

2130
## Deploying to ECS
2231

@@ -74,3 +83,140 @@ aws logs tail /ecs/back-end-staging --follow
7483
aws logs tail /ecs/back-end-production --follow
7584
```
7685

86+
# GitHub Actions AWS OIDC Setup
87+
88+
The CI/CD pipeline uses AWS OIDC (OpenID Connect) for secure authentication to AWS without storing long-lived credentials. This follows AWS security best practices.
89+
90+
## Prerequisites
91+
92+
- AWS account with ECR repository: `633607774026.dkr.ecr.us-east-2.amazonaws.com/back-end`
93+
- GitHub repository: `operationcode/back-end`
94+
- AWS IAM permissions to create IAM roles and policies
95+
96+
## Setup Instructions
97+
98+
### 1. Create IAM OIDC Identity Provider
99+
100+
If not already configured, create an OIDC identity provider for GitHub:
101+
102+
```bash
103+
aws iam create-open-id-connect-provider \
104+
--url https://token.actions.githubusercontent.com \
105+
--client-id-list sts.amazonaws.com \
106+
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
107+
```
108+
109+
### 2. Create IAM Role for GitHub Actions
110+
111+
Create an IAM role that GitHub Actions can assume:
112+
113+
```bash
114+
# Create trust policy file
115+
cat > github-actions-trust-policy.json <<EOF
116+
{
117+
"Version": "2012-10-17",
118+
"Statement": [
119+
{
120+
"Effect": "Allow",
121+
"Principal": {
122+
"Federated": "arn:aws:iam::633607774026:oidc-provider/token.actions.githubusercontent.com"
123+
},
124+
"Action": "sts:AssumeRoleWithWebIdentity",
125+
"Condition": {
126+
"StringEquals": {
127+
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
128+
},
129+
"StringLike": {
130+
"token.actions.githubusercontent.com:sub": "repo:operationcode/back-end:*"
131+
}
132+
}
133+
}
134+
]
135+
}
136+
EOF
137+
138+
# Create the role
139+
aws iam create-role \
140+
--role-name GitHubActions-ECR-Push \
141+
--assume-role-policy-document file://github-actions-trust-policy.json \
142+
--description "Allows GitHub Actions to push Docker images to ECR"
143+
```
144+
145+
### 3. Attach ECR Permissions Policy
146+
147+
Create and attach a policy that allows pushing to ECR:
148+
149+
```bash
150+
# Create policy file
151+
cat > ecr-push-policy.json <<EOF
152+
{
153+
"Version": "2012-10-17",
154+
"Statement": [
155+
{
156+
"Effect": "Allow",
157+
"Action": [
158+
"ecr:GetAuthorizationToken",
159+
"ecr:BatchCheckLayerAvailability",
160+
"ecr:GetDownloadUrlForLayer",
161+
"ecr:BatchGetImage",
162+
"ecr:PutImage",
163+
"ecr:InitiateLayerUpload",
164+
"ecr:UploadLayerPart",
165+
"ecr:CompleteLayerUpload"
166+
],
167+
"Resource": "arn:aws:ecr:us-east-2:633607774026:repository/back-end"
168+
},
169+
{
170+
"Effect": "Allow",
171+
"Action": "ecr:GetAuthorizationToken",
172+
"Resource": "*"
173+
}
174+
]
175+
}
176+
EOF
177+
178+
# Create the policy
179+
aws iam create-policy \
180+
--policy-name GitHubActions-ECR-Push-Policy \
181+
--policy-document file://ecr-push-policy.json
182+
183+
# Attach policy to role
184+
aws iam attach-role-policy \
185+
--role-name GitHubActions-ECR-Push \
186+
--policy-arn arn:aws:iam::633607774026:policy/GitHubActions-ECR-Push-Policy
187+
```
188+
189+
### 4. Configure GitHub Secret
190+
191+
Add the IAM role ARN as a GitHub repository secret using the GitHub CLI:
192+
193+
```bash
194+
# Ensure you're authenticated with GitHub CLI
195+
# If not already authenticated, run: gh auth login
196+
197+
# Set the secret (replace with your actual role ARN if different)
198+
gh secret set AWS_ROLE_ARN --body "arn:aws:iam::633607774026:role/GitHubActions-ECR-Push"
199+
```
200+
201+
**Note**: Make sure you're in the repository directory or specify the repo with `--repo operationcode/back-end`.
202+
203+
### 5. Verify Setup
204+
205+
After setup, the GitHub Actions workflow will automatically:
206+
- Authenticate to AWS using OIDC
207+
- Build Docker images for ARM64 platform
208+
- Push images to ECR with appropriate tags (`:staging` or `:prod`)
209+
210+
You can verify by:
211+
1. Pushing a commit to a non-master branch (should push `:staging`)
212+
2. Merging to master (should push `:prod` after tests pass)
213+
3. Checking ECR repository for new images
214+
215+
## Security Best Practices
216+
217+
**OIDC Authentication**: No long-lived AWS credentials stored in GitHub
218+
**Least Privilege**: IAM role only has permissions needed for ECR push operations
219+
**Repository Scoping**: Trust policy restricts access to `operationcode/back-end` repository
220+
**Conditional Access**: Production builds only run after CI checks pass
221+
**Build Caching**: Uses GitHub Actions cache to speed up builds
222+

0 commit comments

Comments
 (0)