fix(gateways): persist health-check failure reason #10934
Workflow file for this run
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
| # =============================================================== | |
| # Docker Security Scan Workflow | |
| # =============================================================== | |
| # | |
| # This workflow runs Docker-specific security checks: | |
| # 1. Hadolint — lint shipped runtime Dockerfiles (Containerfile, | |
| # nginx proxy) against best-practice rules | |
| # 2. Container smoke — build auxiliary images to verify they compile cleanly | |
| # 3. FedRAMP compliance — build Containerfile with FIPS enabled, run FedRAMP | |
| # compliance validation, generate SBOM (anchore/sbom-action), | |
| # scan for CVEs ≥ HIGH (Grype, report-only), upload SBOM | |
| # 4. Security Scan — build Containerfile (lite), generate SBOM, scan for | |
| # CVEs ≥ HIGH (Grype, blocking), upload SBOM | |
| # | |
| # Trigger model: | |
| # - pull_request : Hadolint only (fast, deterministic feedback on PRs) | |
| # - merge_group : full gate — Hadolint + Container smoke + FedRAMP compliance | |
| # + Security Scan (this is the build/CVE gate that protects main), | |
| # with SARIF output uploaded to code scanning | |
| # - push (main) : FedRAMP compliance + Security Scan re-run so SARIF results | |
| # land on the stable main ref for code scanning | |
| # - workflow_dispatch : run everything on demand | |
| # | |
| # =============================================================== | |
| name: Docker Security Scan | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, ready_for_review] | |
| branches: ["main"] | |
| paths: | |
| - 'Containerfile' | |
| - 'infra/nginx/**' | |
| - 'mcpgateway/**' | |
| - 'plugins/**' | |
| - 'pyproject.toml' | |
| - '.github/workflows/docker-scan.yml' | |
| merge_group: | |
| branches: ["main"] | |
| push: | |
| branches: ["main"] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| IMAGE_NAME: mcp-context-forge-scan | |
| jobs: | |
| # --------------------------------------------------------------- | |
| # Lint all Dockerfiles before any build | |
| # --------------------------------------------------------------- | |
| hadolint: | |
| if: github.event_name != 'pull_request' || !github.event.pull_request.draft | |
| name: Lint Dockerfiles (Hadolint) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| persist-credentials: false | |
| - name: Lint Dockerfiles (Hadolint) | |
| # Run Hadolint via its container image instead of the GitHub Action, | |
| # because hadolint/hadolint-action is blocked by the org Actions | |
| # allowlist (only IBM/GitHub/verified/allowlisted actions may run). | |
| # The image picks up the repo's .hadolint.yaml from the mounted workdir. | |
| # Warnings/info are printed but only errors fail the build | |
| # (--failure-threshold error); tighten to "warning" once the | |
| # existing Dockerfile warnings are cleaned up. | |
| run: | | |
| set -uo pipefail | |
| files=( | |
| Containerfile | |
| infra/nginx/Dockerfile | |
| ) | |
| status=0 | |
| for f in "${files[@]}"; do | |
| echo "::group::hadolint ${f}" | |
| if ! docker run --rm -i -v "${PWD}/.hadolint.yaml:/.hadolint.yaml:ro" \ | |
| ghcr.io/hadolint/hadolint:v2.12.0 \ | |
| hadolint --config /.hadolint.yaml --failure-threshold error - < "${f}"; then | |
| echo "::error file=${f}::Hadolint found error-level issues in ${f}" | |
| status=1 | |
| fi | |
| echo "::endgroup::" | |
| done | |
| exit "${status}" | |
| # --------------------------------------------------------------- | |
| # FedRAMP compliance validation | |
| # --------------------------------------------------------------- | |
| fedramp-compliance: | |
| needs: [hadolint] | |
| if: github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' || github.event_name == 'push' | |
| name: FedRAMP Compliance | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| # security-events: write is required by github/codeql-action/upload-sarif; | |
| # without it the upload 403s and fails the job (and the merge queue). | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Build image locally (FIPS enabled) | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: . | |
| file: Containerfile | |
| platforms: linux/amd64 | |
| push: false | |
| load: true | |
| tags: ${{ env.IMAGE_NAME }}:fedramp-validate | |
| # WHEELS_REF pinned to UBI_MINIMAL: installs from PyPI rather than the | |
| # hermetic wheel closure (which carries cp312 wheels matching the default | |
| # PYTHON_VERSION=3.12 in infra/wheels/Containerfile). | |
| build-args: | | |
| ENABLE_FIPS=true | |
| PYTHON_VERSION=3.12 | |
| UBI_BASE=registry.access.redhat.com/ubi9/ubi:latest | |
| NODEJS_IMAGE=registry.access.redhat.com/ubi9/nodejs-20:latest | |
| UBI_MINIMAL=registry.access.redhat.com/ubi9/ubi-minimal@sha256:6ea809bdd8164f8b2b607e38f01b14c374a15bdfbc74fcd0155161512dd4e00e | |
| WHEELS_REF=registry.access.redhat.com/ubi9/ubi-minimal@sha256:6ea809bdd8164f8b2b607e38f01b14c374a15bdfbc74fcd0155161512dd4e00e | |
| - name: FedRAMP post-build compliance validation | |
| run: | | |
| docker run --rm \ | |
| --user root \ | |
| --entrypoint /bin/bash \ | |
| ${{ env.IMAGE_NAME }}:fedramp-validate \ | |
| -c "$(cat scripts/fedramp-validate.sh)" | |
| - name: Generate SBOM | |
| uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # 0.24.0 | |
| with: | |
| image: ${{ env.IMAGE_NAME }}:fedramp-validate | |
| output-file: sbom-fedramp.spdx.json | |
| # sbom-action uploads its own artifact by default; disable that so the | |
| # explicit Upload SBOM step below is the single upload (with retention). | |
| upload-artifact: false | |
| - name: Scan SBOM for vulnerabilities (Grype) | |
| id: scan-fedramp | |
| uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # 7.4.0 | |
| with: | |
| sbom: sbom-fedramp.spdx.json | |
| only-fixed: true | |
| # Report-only: this image is built from full ubi9/ubi (not ubi-minimal), | |
| # and .grype.yaml's ignore list was calibrated for the lite image's base, | |
| # so a blocking gate here would be non-deterministic against floating tags. | |
| # SARIF is still uploaded; flip to true once ignores match this base. | |
| fail-build: false | |
| severity-cutoff: high | |
| cache-db: true | |
| output-format: ${{ (github.event_name == 'merge_group' || github.event_name == 'push') && 'sarif' || 'table' }} | |
| - name: Display SARIF file (merge_group/push only) | |
| if: always() && (github.event_name == 'merge_group' || github.event_name == 'push') && steps.scan-fedramp.outputs.sarif != '' | |
| run: | | |
| echo "::group::SARIF file contents (FedRAMP)" | |
| cat "${{ steps.scan-fedramp.outputs.sarif }}" | |
| echo "::endgroup::" | |
| - name: Upload SARIF to CodeQL | |
| if: always() && (github.event_name == 'merge_group' || github.event_name == 'push') && steps.scan-fedramp.outputs.sarif != '' | |
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 | |
| with: | |
| sarif_file: "${{ steps.scan-fedramp.outputs.sarif }}" | |
| category: fedramp-fips | |
| - name: Upload SBOM | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: sbom-fedramp | |
| path: sbom-fedramp.spdx.json | |
| retention-days: 30 | |
| # --------------------------------------------------------------- | |
| # Build image and generate SBOM | |
| # --------------------------------------------------------------- | |
| scan: | |
| needs: [hadolint] | |
| if: github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' || github.event_name == 'push' | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Build image locally | |
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| with: | |
| context: . | |
| file: Containerfile | |
| platforms: linux/amd64 | |
| push: false | |
| load: true | |
| tags: ${{ env.IMAGE_NAME }}:scan | |
| cache-from: type=gha,scope=containerfile-lite-amd64 | |
| cache-to: type=gha,mode=max,scope=containerfile-lite-amd64 | |
| - name: Generate SBOM | |
| uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # 0.24.0 | |
| with: | |
| image: ${{ env.IMAGE_NAME }}:scan | |
| output-file: sbom.spdx.json | |
| upload-artifact: false | |
| - name: Scan SBOM for vulnerabilities (Grype) | |
| uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0 | |
| id: scan | |
| with: | |
| sbom: sbom.spdx.json | |
| only-fixed: true | |
| fail-build: true | |
| severity-cutoff: high | |
| cache-db: true | |
| output-format: ${{ (github.event_name == 'merge_group' || github.event_name == 'push') && 'sarif' || 'table' }} | |
| - name: Display SARIF file (merge_group/push only) | |
| if: always() && (github.event_name == 'merge_group' || github.event_name == 'push') && steps.scan.outputs.sarif != '' | |
| run: | | |
| echo "::group::SARIF file contents" | |
| cat "${{ steps.scan.outputs.sarif }}" | |
| echo "::endgroup::" | |
| - name: Upload SARIF to CodeQL | |
| if: always() && (github.event_name == 'merge_group' || github.event_name == 'push') && steps.scan.outputs.sarif != '' | |
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 | |
| with: | |
| sarif_file: "${{ steps.scan.outputs.sarif }}" | |
| category: standard | |
| - name: Upload SBOM | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: sbom | |
| path: sbom.spdx.json | |
| retention-days: 30 | |
| # --------------------------------------------------------------- | |
| # Aggregator gate — single required status check. | |
| # | |
| # Reports the combined result of all docker-scan jobs so branch | |
| # protection only needs to require "Docker Scan Complete". | |
| # - pull_request : hadolint runs; fedramp-compliance/scan are skipped | |
| # (skipped counts as OK) → green if hadolint passes | |
| # - merge_group/dispatch : the full gate runs → green only if every job passes | |
| # - push (main) : fedramp-compliance/scan re-run | |
| # Complemented by docker-scan-required.yml, which reports the same | |
| # check on PRs that touch no docker-scan paths. | |
| # --------------------------------------------------------------- | |
| docker-scan-complete: | |
| name: Docker Scan Complete | |
| needs: [hadolint, fedramp-compliance, scan] | |
| runs-on: ubuntu-slim | |
| if: always() | |
| steps: | |
| - name: Check all docker-scan gates passed | |
| run: | | |
| results=( | |
| "${{ needs.hadolint.result }}" | |
| "${{ needs.fedramp-compliance.result }}" | |
| "${{ needs.scan.result }}" | |
| ) | |
| failed=0 | |
| for r in "${results[@]}"; do | |
| if [[ "$r" != "success" && "$r" != "skipped" ]]; then | |
| echo "❌ A docker-scan job failed or was cancelled (result: $r)" | |
| failed=1 | |
| fi | |
| done | |
| if [[ "$failed" == "0" ]]; then | |
| echo "✅ All docker-scan gates passed" | |
| else | |
| exit 1 | |
| fi |