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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |