Skip to content

feat(security): apply IP whitelisting to all endpoints including heal… #64

feat(security): apply IP whitelisting to all endpoints including heal…

feat(security): apply IP whitelisting to all endpoints including heal… #64

Workflow file for this run

name: Production Docker Build
on:
push:
branches:
- main
paths:
- 'docker/**'
- '.github/workflows/prod.yml'
workflow_dispatch:
permissions:
id-token: write # Required for OIDC authentication to AWS
contents: read
jobs:
build-and-push:
name: Build and Push Production Docker Image
runs-on: ubuntu-latest
outputs:
image_uri_sha: ${{ steps.image.outputs.IMAGE_URI_SHA }}
image_uri_latest: ${{ steps.image.outputs.IMAGE_URI_LATEST }}
git_sha: ${{ steps.git.outputs.GIT_SHA }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Extract git SHA
id: git
run: |
GIT_SHA=$(git rev-parse HEAD)
echo "GIT_SHA=$GIT_SHA" >> $GITHUB_OUTPUT
echo "Git SHA: $GIT_SHA"
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.14'
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install Python dependencies
working-directory: docker
run: uv sync --all-extras
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::730278974607:role/github/GitHub-benchling-webhook
aws-region: us-east-1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64
- name: Build and push Docker image
working-directory: docker
run: make push-ci VERSION=${{ steps.git.outputs.GIT_SHA }}
env:
DOCKER_DEFAULT_PLATFORM: linux/amd64
AWS_REGION: us-east-1
- name: Get Docker image URIs
id: image
run: |
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
GIT_SHA="${{ steps.git.outputs.GIT_SHA }}"
IMAGE_URI_SHA="${AWS_ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/quiltdata/benchling:${GIT_SHA}"
IMAGE_URI_LATEST="${AWS_ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/quiltdata/benchling:latest"
echo "IMAGE_URI_SHA=$IMAGE_URI_SHA" >> $GITHUB_OUTPUT
echo "IMAGE_URI_LATEST=$IMAGE_URI_LATEST" >> $GITHUB_OUTPUT
echo "Docker Images:"
echo " SHA: $IMAGE_URI_SHA"
echo " Latest: $IMAGE_URI_LATEST"
validate:
name: Validate Production Image
runs-on: ubuntu-latest
needs: build-and-push
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.14'
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install Python dependencies
working-directory: docker
run: uv sync --all-extras
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::730278974607:role/github/GitHub-benchling-webhook
aws-region: us-east-1
- name: Validate image architecture
working-directory: docker
run: |
echo "Validating production image architecture..."
DOCKER_IMAGE_NAME=quiltdata/benchling \
uv run python scripts/docker.py validate \
--version ${{ needs.build-and-push.outputs.git_sha }} \
--region us-east-1
env:
AWS_REGION: us-east-1
- name: Validate application startup
working-directory: docker
run: |
echo "Validating application starts successfully in degraded mode..."
echo "(This mirrors 'npm run test:minimal' for local testing)"
echo ""
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
IMAGE_URI="${AWS_ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/quiltdata/benchling:${{ needs.build-and-push.outputs.git_sha }}"
# Pull the image
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com
docker pull ${IMAGE_URI}
# Run container with minimal config to test startup (no valid secrets)
# The application should start in degraded mode with health checks passing
CONTAINER_ID=$(docker run -d \
-p 8080:8080 \
-e PORT=8080 \
-e AWS_REGION=us-east-1 \
-e PACKAGER_SQS_URL=https://sqs.us-east-1.amazonaws.com/000000000000/test-queue \
-e QUILT_WEB_HOST=https://example.quiltdata.com \
-e ATHENA_USER_DATABASE=test_db \
-e BenchlingSecret=test-secret \
${IMAGE_URI})
# Wait up to 30 seconds for container to start
echo "Waiting for Gunicorn to start..."
for i in $(seq 1 30); do
sleep 1
if docker logs ${CONTAINER_ID} 2>&1 | grep -q "Booting worker with pid"; then
echo "✅ Gunicorn started successfully"
echo ""
echo "Container logs:"
docker logs ${CONTAINER_ID}
echo ""
# Verify health endpoints work in degraded mode
echo "Testing health endpoints..."
if curl -f -s http://localhost:8080/health >/dev/null; then
echo "✅ Health endpoint responds"
HEALTH_STATUS=$(curl -s http://localhost:8080/health | jq -r '.status')
if [ "$HEALTH_STATUS" = "healthy" ]; then
echo "✅ Health check passed (degraded mode expected)"
else
echo "⚠️ Unexpected health status: $HEALTH_STATUS"
fi
else
echo "❌ Health endpoint failed"
docker stop ${CONTAINER_ID} >/dev/null 2>&1 || true
docker rm ${CONTAINER_ID} >/dev/null 2>&1 || true
exit 1
fi
docker stop ${CONTAINER_ID} >/dev/null 2>&1 || true
docker rm ${CONTAINER_ID} >/dev/null 2>&1 || true
exit 0
fi
done
echo "❌ Gunicorn did not start within 30 seconds"
echo ""
echo "Container logs:"
docker logs ${CONTAINER_ID}
docker stop ${CONTAINER_ID} >/dev/null 2>&1 || true
docker rm ${CONTAINER_ID} >/dev/null 2>&1 || true
exit 1
env:
AWS_REGION: us-east-1
- name: Validation summary
run: |
echo "## Validation Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Image architecture validated: linux/amd64" >> $GITHUB_STEP_SUMMARY
echo "✅ Application starts successfully" >> $GITHUB_STEP_SUMMARY
echo "✅ Latest tag verified" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**SHA Image:**" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "${{ needs.build-and-push.outputs.image_uri_sha }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Latest Image:**" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "${{ needs.build-and-push.outputs.image_uri_latest }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
summary:
name: Deployment Summary
runs-on: ubuntu-latest
needs: [build-and-push, validate]
if: always()
steps:
- name: Build and deployment summary
run: |
echo "## Production Docker Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Git SHA:** \`${{ needs.build-and-push.outputs.git_sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Build Status:** ${{ needs.build-and-push.result }}" >> $GITHUB_STEP_SUMMARY
echo "**Validation Status:** ${{ needs.validate.result }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.build-and-push.result }}" == "success" ]; then
echo "### Docker Images" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**SHA-tagged image:**" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "${{ needs.build-and-push.outputs.image_uri_sha }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Latest-tagged image:**" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "${{ needs.build-and-push.outputs.image_uri_latest }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Usage" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Reference in deployments:" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`yaml" >> $GITHUB_STEP_SUMMARY
echo "image: ${{ needs.build-and-push.outputs.image_uri_sha }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Build failed. Check job logs for details." >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ needs.validate.result }}" != "success" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "⚠️ Validation failed. Image may not meet production requirements." >> $GITHUB_STEP_SUMMARY
fi