99 lines
4.2 KiB
YAML
99 lines
4.2 KiB
YAML
name: CASAN Supply Chain and Provenance
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
verify-scan-attest:
|
|
runs-on: ci-runner
|
|
timeout-minutes: 60
|
|
env:
|
|
CASAN_CI_RUN_FRONTEND: "1"
|
|
CASAN_CI_RUN_BACKEND: "1"
|
|
CASAN_CI_RUN_INFRA_LAB: "0"
|
|
CASAN_CI_RUN_CONTROL_PANEL: "1"
|
|
CASAN_REQUIRE_LIVE_SCANNERS: "1"
|
|
CASAN_TOOLCHAIN_TRUSTED_DIRS: /usr/bin:/bin:/usr/local/bin
|
|
steps:
|
|
- name: Checkout full signed history
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Verify trusted toolchain
|
|
run: |
|
|
set -euo pipefail
|
|
bash packages/casan-harness/scripts/bash/toolchain-verify.sh npm osv-scanner trivy docker openssl python3 git
|
|
|
|
- name: Enforce signed commits on main
|
|
if: github.ref == 'refs/heads/main'
|
|
run: |
|
|
set -euo pipefail
|
|
RANGE="${{ github.event.before }}..${{ github.sha }}"
|
|
if [ "${{ github.event.before }}" = "0000000000000000000000000000000000000000" ]; then RANGE="${{ github.sha }}"; fi
|
|
bash packages/casan-harness/scripts/bash/verify-signed-commits.sh "$RANGE"
|
|
|
|
- name: Install locked dependencies
|
|
run: npm ci
|
|
|
|
- name: Run governed test gate
|
|
run: bash packages/casan-harness/scripts/bash/ci-harness-gate.sh
|
|
|
|
- name: Build immutable candidate images
|
|
run: |
|
|
set -euo pipefail
|
|
docker build --pull -t casan-control-panel-api:${GITHUB_SHA} -f Dockerfile.control-panel-api .
|
|
docker build --pull -t casan-control-panel-ui:${GITHUB_SHA} -f Dockerfile.control-panel-ui .
|
|
|
|
- name: Live CVE, secret and misconfiguration scan
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist/security
|
|
bash packages/casan-harness/scripts/bash/live-supply-chain-scan.sh dist/security \
|
|
casan-control-panel-api:${GITHUB_SHA} casan-control-panel-ui:${GITHUB_SHA}
|
|
|
|
- name: Push scanned images by immutable digest (main only)
|
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
CASAN_REGISTRY: ${{ vars.CASAN_REGISTRY }}
|
|
run: |
|
|
set -euo pipefail
|
|
[ -n "${GITEA_TOKEN:-}" ] || { echo 'missing GITEA_TOKEN'; exit 1; }
|
|
REGISTRY="${CASAN_REGISTRY:-${GITHUB_SERVER_URL#https://}}"
|
|
API_IMAGE="$REGISTRY/${GITHUB_REPOSITORY_OWNER}/casan-control-panel-api:${GITHUB_SHA}"
|
|
UI_IMAGE="$REGISTRY/${GITHUB_REPOSITORY_OWNER}/casan-control-panel-ui:${GITHUB_SHA}"
|
|
printf '%s' "$GITEA_TOKEN" | docker login "$REGISTRY" -u "$GITHUB_ACTOR" --password-stdin
|
|
docker tag casan-control-panel-api:${GITHUB_SHA} "$API_IMAGE"
|
|
docker tag casan-control-panel-ui:${GITHUB_SHA} "$UI_IMAGE"
|
|
docker push "$API_IMAGE"; docker push "$UI_IMAGE"
|
|
docker image inspect --format '{{index .RepoDigests 0}}' "$API_IMAGE" > dist/security/api-image.digest
|
|
docker image inspect --format '{{index .RepoDigests 0}}' "$UI_IMAGE" > dist/security/ui-image.digest
|
|
|
|
- name: Sign SLSA provenance (main only)
|
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
env:
|
|
CASAN_ATTESTATION_PRIVATE_KEY: ${{ secrets.CASAN_ATTESTATION_PRIVATE_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
[ -n "${CASAN_ATTESTATION_PRIVATE_KEY:-}" ] || { echo 'missing CASAN_ATTESTATION_PRIVATE_KEY'; exit 1; }
|
|
key="$(mktemp)"; trap 'rm -f "$key"' EXIT
|
|
printf '%s\n' "$CASAN_ATTESTATION_PRIVATE_KEY" > "$key"; chmod 600 "$key"
|
|
mapfile -t images < <(cat dist/security/api-image.digest dist/security/ui-image.digest)
|
|
bash packages/casan-harness/scripts/bash/release-provenance.sh create \
|
|
dist/security/provenance.intoto.json "$key" dist/security/npm.cyclonedx.json \
|
|
docs/output/casan/evidence/harness-test-report.md "${images[@]}"
|
|
openssl pkey -in "$key" -pubout -out dist/security/provenance-public.pem
|
|
|
|
- name: Upload security evidence and provenance
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: casan-security-${{ github.sha }}
|
|
path: dist/security/
|
|
retention-days: 90
|