Skip to content

diga: consolidate compliance matrix to single canonical artefact #19

diga: consolidate compliance matrix to single canonical artefact

diga: consolidate compliance matrix to single canonical artefact #19

Workflow file for this run

name: Security
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Wöchentlicher Lauf, Montag 06:00 UTC — fängt zwischen-Release-Drift in
# Geheimnissen, TLS-Posture und HTTP-Headern auf.
- cron: "0 6 * * 1"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
security-events: write # für SARIF-Uploads (gitleaks)
pull-requests: read
jobs:
# ──────────────────────────────────────────────────────────────────────
# 1. Geheimnis-Scan — Quellcode + gesamte Git-History
# Schließt: TR-03161 O.Cryp_1, O.Source_8
# ──────────────────────────────────────────────────────────────────────
secrets:
name: Secret Scanning (gitleaks)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # vollständige History scannen
- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ──────────────────────────────────────────────────────────────────────
# 2. Markdown-Lint — Konsistenz der diga/-Dokumente
# Verhindert die Lint-Warnungen, die der IDE-Linter heute gemeldet hat.
# ──────────────────────────────────────────────────────────────────────
markdown-lint:
name: Markdown Lint (diga/)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run markdownlint-cli2
uses: DavidAnson/markdownlint-cli2-action@v18
with:
globs: |
diga/**/*.md
!diga/regulations/markdown/**
config: '.markdownlint.json'
# ──────────────────────────────────────────────────────────────────────
# 3. Link-Check über alle Markdown-Dokumente in diga/
# Stellt sicher, dass Quellenverzeichnisse (BSI/BfArM/Gesetze)
# erreichbar bleiben.
# ──────────────────────────────────────────────────────────────────────
link-check:
name: Link Check (lychee)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run lychee
uses: lycheeverse/lychee-action@v2
with:
args: >-
--verbose
--no-progress
--max-concurrency 4
--timeout 30
--retry-wait-time 5
--max-retries 2
--exclude-path diga/regulations/markdown
--exclude '^https?://(www\.)?linkedin\.com'
--exclude '^https?://localhost'
--exclude '^https?://twobreath\.com'
'diga/**/*.md'
'README.md'
fail: false # Linkfehler blockieren keinen Build, sind nur Warnungen
token: ${{ secrets.GITHUB_TOKEN }}
# ──────────────────────────────────────────────────────────────────────
# 4. Dependency Review — sobald in einem PR ein Lock-File geändert wird
# Hinweis: dieser Job läuft NUR auf pull_request-Events. Auf push und
# workflow_dispatch wird er korrekt als "skipped" angezeigt — die
# Action benötigt eine Base-/Head-Diff aus dem PR-Kontext und kann
# außerhalb dessen nicht funktionieren. Für Push/Cron-Scans siehe
# den `osv-scan` Job darunter.
# ──────────────────────────────────────────────────────────────────────
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Dependency review
uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
comment-summary-in-pr: on-failure
# ──────────────────────────────────────────────────────────────────────
# 4b. OSV-Scanner — Manifest-basierter CVE-Scan auf push + PR + cron
# Schließt: TR-03161 O.TrdP_3 (regelmäßiger Schwachstellen-Check
# auf eingesetzte Drittanbieter-Software).
# Aktuell: dieses Repo enthält noch kein package.json / Lockfile;
# der Scan produziert eine leere Baseline-SARIF. Sobald Manifeste
# einziehen (Build-Toolchain, MCP-Server, etc.), schlägt der Scan
# ohne weiteren Konfigurationsaufwand an.
# ──────────────────────────────────────────────────────────────────────
osv-scan:
name: OSV Scanner (manifest-CVE)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- name: Install osv-scanner from upstream GitHub release
env:
OSV_VERSION: "2.3.6" # pin; bump in PR after upstream release
run: |
set -euo pipefail
url="https://github.com/google/osv-scanner/releases/download/v${OSV_VERSION}/osv-scanner_linux_amd64"
echo "Downloading osv-scanner v${OSV_VERSION}…"
curl -sSLf -o osv-scanner "$url"
chmod +x osv-scanner
sudo mv osv-scanner /usr/local/bin/
osv-scanner --version
- name: Run osv-scanner (recursive, manifest-based)
run: |
set -uo pipefail
# exit-codes: 0 = no vulnerable, 1 = vulnerable found, 127 = misuse,
# 128 = generic error. Bei leerer Manifest-Lage exit 128 ohne Treffer —
# wir wollen den Job nicht hart auf 0 zwingen, sondern nur dann scharf
# stellen, wenn echte Vulns gefunden werden. Heute (mit nur einem
# devDependency) liefert der Scan eine reale, saubere SARIF.
# v2-CLI: explizit `scan source` als Subkommando.
# `--skip-git` aus v1 entfällt — v2 scannt Manifeste per default,
# nicht den Git-Index.
osv-scanner scan source \
--recursive \
--format=sarif \
--output=osv.sarif \
./ || rc=$?
rc=${rc:-0}
echo "osv-scanner exit code: $rc"
if [ -f osv.sarif ]; then
ls -la osv.sarif
# Anzahl gefundener Treffer in den Job-Log
jq -r '.runs[]?.results | length' osv.sarif | head -3 | xargs -I{} echo "SARIF results count: {}"
else
echo "::warning::osv-scanner produced no SARIF (likely no manifests detected — that's OK for a first run)"
fi
# Job nur dann hart fail, wenn echte Vulns gefunden (exit 1)
if [ "$rc" = "1" ]; then exit 1; fi
- name: Upload SARIF to GitHub code scanning
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: osv.sarif
category: osv-scanner
continue-on-error: true # falls keine SARIF erzeugt — soll nicht hart failen
- name: Upload SARIF as artefact
if: always()
uses: actions/upload-artifact@v4
with:
name: osv-scanner-report
path: osv.sarif
retention-days: 90
if-no-files-found: ignore
# ──────────────────────────────────────────────────────────────────────
# 4c. Cross-Repo OSV-Scanner — privates ma3u/TwoBreath-app
# Voraussetzung: Repository-Secret `APP_REPO_TOKEN` mit fine-grained
# PAT, Read-Berechtigung auf `ma3u/TwoBreath-app/Contents`. Ohne
# Secret skippt der Job ohne Fehler (Soft-Skip via Step-Output).
#
# Privacy-Hinweis: SARIF-Upload landet im PUBLIC Code-Scanning-Tab
# dieses Repos — Vulnerability-Findings über die App werden damit
# öffentlich sichtbar. Das ist im Rahmen des öffentlichen DiGA-
# Audit-Trail-Charakters dieses Repos akzeptiert.
# ──────────────────────────────────────────────────────────────────────
osv-scan-app:
name: OSV Scanner (private app repo)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
if: github.event_name != 'pull_request' # cross-repo nur auf push/cron/dispatch
steps:
- name: Check token availability
id: have-token
run: |
if [ -n "${{ secrets.APP_REPO_TOKEN }}" ]; then
echo "ok=true" >> "$GITHUB_OUTPUT"
echo "Token present — proceeding."
else
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "::warning::APP_REPO_TOKEN secret not set — cross-repo scan skipped. Add a fine-grained PAT with Contents:Read on ma3u/TwoBreath-app to enable."
fi
- name: Checkout private TwoBreath-app
if: steps.have-token.outputs.ok == 'true'
uses: actions/checkout@v4
with:
repository: ma3u/TwoBreath-app
token: ${{ secrets.APP_REPO_TOKEN }}
path: app-src
fetch-depth: 1
- name: Install osv-scanner
if: steps.have-token.outputs.ok == 'true'
env:
OSV_VERSION: "2.3.6"
run: |
set -euo pipefail
curl -sSLf -o osv-scanner \
"https://github.com/google/osv-scanner/releases/download/v${OSV_VERSION}/osv-scanner_linux_amd64"
chmod +x osv-scanner
sudo mv osv-scanner /usr/local/bin/
osv-scanner --version
- name: Run osv-scanner against private app
if: steps.have-token.outputs.ok == 'true'
run: |
set -uo pipefail
osv-scanner scan source \
--recursive \
--format=sarif \
--output=osv-app.sarif \
./app-src || rc=$?
rc=${rc:-0}
echo "osv-scanner exit code: $rc"
if [ -f osv-app.sarif ]; then
jq -r '.runs[]?.results | length' osv-app.sarif | head -3 | xargs -I{} echo "App SARIF results count: {}"
fi
# Hart fail nur bei echten Vulns (exit 1)
if [ "$rc" = "1" ]; then exit 1; fi
- name: Upload App SARIF to GitHub code scanning
if: always() && steps.have-token.outputs.ok == 'true'
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: osv-app.sarif
category: osv-scanner-app
continue-on-error: true
- name: Upload App SARIF as artefact
if: always() && steps.have-token.outputs.ok == 'true'
uses: actions/upload-artifact@v4
with:
name: osv-scanner-app-report
path: osv-app.sarif
retention-days: 90
if-no-files-found: ignore
# ──────────────────────────────────────────────────────────────────────
# 5. TLS-Posture-Check — twobreath.com (testssl.sh)
# Schließt: TR-03161 O.Ntwk_2, O.Ntwk_7 (Marketing-Site)
# Nur scheduled / manuell — vermeidet jeden-Push-Last gegen den Server.
# ──────────────────────────────────────────────────────────────────────
tls-posture:
name: TLS Posture (testssl.sh)
runs-on: ubuntu-latest
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
steps:
- name: Run testssl.sh against www.twobreath.com
run: |
set -eu
mkdir -p reports
chmod 777 reports
# `--user` bindet die Container-UID an den Runner-User, damit
# die JSON-Ausgabe in das gemountete Volume geschrieben werden
# kann. Ohne diesen Schritt läuft der Scan, aber die Datei
# wird nicht persistiert (Permission denied).
docker run --rm \
--user "$(id -u):$(id -g)" \
-v "$PWD/reports:/data" \
drwetter/testssl.sh:latest \
--jsonfile-pretty /data/testssl.json \
--severity LOW \
--quiet --color 0 \
https://www.twobreath.com || true
if [ -f reports/testssl.json ]; then
echo "=== testssl.sh summary ==="
jq '.scanResult[0] | {target, ip, service, severity_summary: (
[.protocols[]?, .vulnerabilities[]?, .cipherTests[]?]
| group_by(.severity) | map({(.[0].severity): length}) | add
)}' reports/testssl.json 2>/dev/null || cat reports/testssl.json
else
echo "::warning::testssl.json was not produced — see step log for raw output."
fi
- uses: actions/upload-artifact@v4
if: always()
with:
name: testssl-report
path: reports/testssl.json
if-no-files-found: warn
retention-days: 90
# ──────────────────────────────────────────────────────────────────────
# 6. HTTP-Security-Header — twobreath.com (Mozilla HTTP Observatory)
# Schließt: TR-03161 O.Ntwk_2 (Marketing-Site)
# ──────────────────────────────────────────────────────────────────────
http-headers:
name: HTTP Security Headers (Observatory)
runs-on: ubuntu-latest
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
steps:
- name: Trigger Observatory scan
run: |
set -eu
mkdir -p reports
curl -sS -X POST \
"https://observatory-api.mdn.mozilla.net/api/v2/scan?host=www.twobreath.com" \
-H "Content-Type: application/json" \
-d '{}' \
| tee reports/observatory.json
echo
echo "=== Observatory grade ==="
jq -r '.grade // "n/a"' reports/observatory.json || true
- uses: actions/upload-artifact@v4
if: always()
with:
name: observatory-report
path: reports/observatory.json
if-no-files-found: warn
retention-days: 90