Skip to content
This repository was archived by the owner on Mar 9, 2026. It is now read-only.

fix: resolve all 6 tfsec findings, fix CI/CD pipeline, clean up repo #32

fix: resolve all 6 tfsec findings, fix CI/CD pipeline, clean up repo

fix: resolve all 6 tfsec findings, fix CI/CD pipeline, clean up repo #32

Workflow file for this run

name: Build, Scan, and Push to ECR
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
AWS_REGION: eu-west-2
ECR_REPOSITORY: cyber-airways-app
jobs:
build-scan-and-push:
name: Build, Scan, and Push Docker Image
runs-on: ubuntu-latest
# Explicit permissions following principle of least privilege.
# security-events: write — required to upload SARIF to GitHub Security tab.
# actions: read / contents: read — required by the CodeQL upload action.
permissions:
contents: read
actions: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v4
# -------------------------------------------------------------------------
# STAGE 1 — Secret Detection (Shift-Left)
# -------------------------------------------------------------------------
# Gitleaks scans every commit in the push for hardcoded secrets, API keys,
# and credentials before any other step runs. Fail-fast: no point building
# an image that already contains a committed secret.
- name: Run Gitleaks secret scan
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# -------------------------------------------------------------------------
# STAGE 2 — Infrastructure-as-Code Security Scan (tfsec)
# -------------------------------------------------------------------------
# Scans Terraform files against CIS benchmarks and AWS best practices.
# Runs with soft_fail so findings are reported but don't block the build
# (IaC issues are fixed iteratively, not treated as release blockers).
- name: Run tfsec IaC scan
uses: aquasecurity/tfsec-action@v1.0.0
with:
working_directory: terraform
soft_fail: true
# -------------------------------------------------------------------------
# STAGE 3 — Build the Docker image
# -------------------------------------------------------------------------
# Only runs when AWS secrets are configured. On forks or personal repos
# without secrets, stages 1-2 still execute (secret scan + IaC scan).
- name: Configure AWS credentials
if: env.AWS_ACCESS_KEY_ID != ''
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
- name: Login to Amazon ECR
if: env.AWS_ACCESS_KEY_ID != ''
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
- name: Build Docker image
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
if [ -n "$ECR_REGISTRY" ]; then
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
echo "image_name=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
else
echo "⚠️ No ECR registry — building local image for scan only"
docker build -t $ECR_REPOSITORY:$IMAGE_TAG .
echo "image_name=$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
fi
# -------------------------------------------------------------------------
# STAGE 4 — Enterprise Container Vulnerability Scanning (Defence-in-Depth)
# -------------------------------------------------------------------------
# Anchore Grype: enterprise-grade open-source vulnerability scanner used
# widely in regulated industries. Scans against NVD, GitHub Security
# Advisories, and vendor-specific feeds.
- name: Run Anchore Grype vulnerability scanner
id: grype-scan
uses: anchore/scan-action@v6
# continue-on-error: Grype exits non-zero when it finds HIGH/CRITICAL CVEs,
# which would stop the step before it finishes writing grype-results.sarif.
# We capture the outcome here and enforce the gate AFTER the SARIF is uploaded.
continue-on-error: true
with:
image: ${{ steps.build-image.outputs.image_name }}
fail-build: false
severity-cutoff: high
output-format: sarif
output-file: grype-results.sarif
# Upload SARIF results to GitHub Security tab for centralised visibility.
# Only runs if the SARIF file was actually created (i.e., Grype ran).
- name: Upload Grype SARIF report to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always() && hashFiles('grype-results.sarif') != ''
with:
sarif_file: grype-results.sarif
# Build gate — fail the job now if Grype found any HIGH or CRITICAL CVEs.
# Separating the gate from the scan means SARIF results are always uploaded
# and visible in the Security tab, whether the build passes or fails.
- name: Enforce Grype vulnerability gate
if: steps.grype-scan.outcome == 'failure'
run: |
echo "❌ Grype found HIGH or CRITICAL vulnerabilities. Build blocked."
echo " Review findings in the GitHub Security tab → Code scanning alerts."
exit 1
# Trivy as a complementary scanner for defence-in-depth.
# Two scanners catch more CVEs than one — different scanners use
# different vulnerability databases and detection heuristics.
# Pinned to a specific release tag (not @master) to prevent supply-chain risk.
- name: Run Trivy vulnerability scanner (defence-in-depth)
uses: aquasecurity/trivy-action@0.30.0
with:
image-ref: ${{ steps.build-image.outputs.image_name }}
format: 'table'
exit-code: '1'
ignore-unfixed: true
severity: 'CRITICAL,HIGH'
# -------------------------------------------------------------------------
# STAGE 5 — Push to ECR (only runs when AWS credentials are configured)
# -------------------------------------------------------------------------
- name: Push image to Amazon ECR
if: env.AWS_ACCESS_KEY_ID != ''
env:
IMAGE_NAME: ${{ steps.build-image.outputs.image_name }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
run: docker push $IMAGE_NAME