Skip to content

fix(gateways): persist health-check failure reason #10934

fix(gateways): persist health-check failure reason

fix(gateways): persist health-check failure reason #10934

Workflow file for this run

# ===============================================================
# 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