Makefile und scripts/security/stamp.py aus cj/security-baseline, leere docs/RELEASE-AUSNAHMEN.md, AGENTS.md-Abschnitt zu Stempeln und Release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
206 lines
11 KiB
Makefile
206 lines
11 KiB
Makefile
# Test- & Security-Baseline
|
|
# Diese Datei unverändert aus cj/security-baseline übernehmen. App-Spezifisches gehört in
|
|
# baseline.mk (wird hier eingelesen), damit Updates der Baseline einfach drüberkopiert werden können.
|
|
#
|
|
# Werkzeuge: Python-Tools im venv der App, Scanner (gitleaks, Semgrep, Trivy, hadolint, ZAP,
|
|
# schemathesis) als Docker-Images, npm lokal oder im node-Container.
|
|
#
|
|
# Wann was läuft:
|
|
# beim Coden make quick (Lint + Tests; TESTS=... nur betroffene + Sicherheitstests)
|
|
# vor dem Push make prepush (nur was der Diff braucht, siehe scripts/security/changed_scope.py)
|
|
# vor Deploy make release-checks (check + mutation [+ container], nur ohne gültigen Stempel;
|
|
# über Skill pre-release-gate)
|
|
# nach Deploy make zap / make fuzz (gegen die eigene Live-Instanz, blockiert das Go nicht)
|
|
#
|
|
# Stempel: check, prepush, mutation, container, zap und fuzz merken sich in reports/stamps/, für
|
|
# welchen Commit sie grün waren (scripts/security/stamp.py). `make stamp` zeigt den Stand.
|
|
# wöchentlich make weekly (check + Trivy, zeitabhängige Funde wie neue CVEs)
|
|
|
|
-include baseline.mk
|
|
|
|
PY_DIR ?= backend
|
|
PY_SRC ?= app
|
|
NODE_DIR ?= frontend
|
|
SEMGREP_RULES ?= .semgrep
|
|
SCRIPTS ?= scripts/security
|
|
MUTATIONS ?= security-mutations.toml
|
|
STAMP ?= python3 $(SCRIPTS)/stamp.py
|
|
NODE_IMAGE ?= node:24
|
|
OPENAPI_PATH ?= /openapi.json
|
|
FUZZ_CHECKS ?= not_a_server_error
|
|
ZAP_ARGS ?= -I
|
|
# prepush vergleicht gegen BASE (leer = Upstream des Branches, sonst origin/HEAD bzw. main).
|
|
BASE ?=
|
|
# make quick TESTS="tests/test_x.py": nur diese Python-Tests (relativ zu PY_DIR) plus Sicherheitstests.
|
|
TESTS ?=
|
|
PY_SECURITY_TESTS ?= $(patsubst $(PY_DIR)/%,%,$(wildcard $(PY_DIR)/tests/test_security_baseline.py))
|
|
TRIVY_CACHE ?= $(HOME)/.cache/trivy
|
|
# Standardquelle mirror.gcr.io lieferte am 16.10.2026 nur 404, ghcr.io funktioniert.
|
|
TRIVY_DB ?= ghcr.io/aquasecurity/trivy-db:2
|
|
# Einteilung des Diffs für prepush (reguläre Ausdrücke, leer = Standard aus changed_scope.py).
|
|
SECURITY_PATHS ?=
|
|
DOCS_PATHS ?=
|
|
DEPS_PATHS ?=
|
|
TOOLING_PATHS ?=
|
|
export SECURITY_PATHS DOCS_PATHS DEPS_PATHS TOOLING_PATHS MUTATIONS
|
|
|
|
GITLEAKS_IMAGE ?= ghcr.io/gitleaks/gitleaks:v8.30.1
|
|
SEMGREP_IMAGE ?= semgrep/semgrep:1.179.0
|
|
# Scanner mit Tag UND Digest pinnen: ein umgehängter Tag (Trivy-Kompromittierung 03/2026,
|
|
# GHSA-69fq-xp46-6x23) kommt so nicht still herein. Wichtig vor allem für Trivy, das in
|
|
# `make container` den Docker-Socket bekommt (= root auf dem Host). Aktualisieren:
|
|
# docker buildx imagetools inspect <image>:<neue-version> --format '{{.Manifest.Digest}}'
|
|
# und hier wie in ci/security.yml eintragen; neue Versionen erst nach ~2 Wochen übernehmen.
|
|
TRIVY_IMAGE ?= aquasec/trivy:0.75.0@sha256:af6acf9a6b85dfe389a1941505c0ce9efef52a4719635e1a962f022a3d855daa
|
|
HADOLINT_IMAGE ?= hadolint/hadolint:v2.15.1@sha256:32dac94127fd60b7b7e3fbfc65e1383b9b5e25c9bfd7b8536de7a539fe68a12d
|
|
ZAP_IMAGE ?= ghcr.io/zaproxy/zaproxy:2.17.0@sha256:7aaa659b0d43078febd82e29bad112285c370727e86ab8340444220e17d9f0d2
|
|
SCHEMATHESIS_IMAGE ?= schemathesis/schemathesis:v4.31.0@sha256:58a4ffa68cd690a04eb70fd2d1c790acd54cd73986c777ec2556c901b6b707e4
|
|
|
|
HAS_PY := $(wildcard $(PY_DIR)/requirements.txt $(PY_DIR)/pyproject.toml)
|
|
HAS_NODE := $(wildcard $(NODE_DIR)/package.json)
|
|
VENV := $(PY_DIR)/.venv
|
|
TOOLS := .venv-tools
|
|
UID_GID := $(shell id -u):$(shell id -g)
|
|
# In einem git-Worktree ist .git nur ein Verweis auf das Haupt-Repo. Ohne dieses im Container
|
|
# findet gitleaks keine Commits und meldet trotzdem "no leaks found" (grün ohne Prüfung), und
|
|
# Semgrep kennt die .gitignore nicht und scannt Build-Ausgaben wie .next/.
|
|
GIT_COMMON := $(shell git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
|
|
GIT_MOUNT := $(if $(filter-out $(CURDIR)/.git,$(GIT_COMMON)),-v "$(GIT_COMMON)":"$(GIT_COMMON)":ro)
|
|
DOCKER := docker run --rm -u $(UID_GID) -e HOME=/tmp -v "$(CURDIR)":/src $(GIT_MOUNT) -w /src
|
|
GITLEAKS :=docker run --rm -u $(UID_GID) -v "$(CURDIR)":/repo $(GIT_MOUNT) $(GITLEAKS_IMAGE) git /repo --redact --no-banner $(if $(wildcard .gitleaks.toml),-c /repo/.gitleaks.toml)
|
|
NPM ?= $(if $(shell command -v npm 2>/dev/null),npm --prefix $(NODE_DIR),docker run --rm -u $(UID_GID) -e HOME=/tmp -v "$(CURDIR)":/w -w /w/$(NODE_DIR) $(NODE_IMAGE) npm)
|
|
|
|
.DEFAULT_GOAL := help
|
|
.PHONY: help setup quick prepush check release-checks release-report stamp weekly lint test secrets secrets-diff sast sast-diff audit trivy-fs container zap fuzz mutation mutmut vet-deps
|
|
|
|
help: ## Diese Übersicht
|
|
@grep -hE '^[a-z-]+:.*## ' $(MAKEFILE_LIST) | awk -F':.*## ' '{printf " make %-12s %s\n", $$1, $$2}'
|
|
|
|
setup: ## venvs, npm-Pakete, pre-commit-Hook einrichten
|
|
python3 -m venv $(TOOLS)
|
|
$(TOOLS)/bin/pip install -q pre-commit==4.7.0
|
|
ifneq ($(HAS_PY),)
|
|
python3 -m venv $(VENV)
|
|
$(VENV)/bin/pip install -q -r $(PY_DIR)/requirements-dev.txt
|
|
endif
|
|
ifneq ($(HAS_NODE),)
|
|
$(NPM) ci --no-audit --no-fund
|
|
endif
|
|
$(TOOLS)/bin/pre-commit install
|
|
@echo "Setup fertig. Weiter mit: make quick"
|
|
|
|
quick: lint test ## Lint + Tests beim Coden; nur betroffene: make quick TESTS="tests/test_x.py"
|
|
|
|
prepush: ## Vor dem Push: nur die Prüfungen, die der Diff braucht (BASE=ref setzt die Basis)
|
|
@$(STAMP) begin prepush
|
|
@python3 $(SCRIPTS)/changed_scope.py explain $(BASE)
|
|
@t=$$(python3 $(SCRIPTS)/changed_scope.py targets $(BASE)) && t=$$($(STAMP) pending $$t) && if [ -z "$$t" ]; then echo "make prepush: für diesen Commit schon alles grün (make stamp)"; else $(MAKE) --no-print-directory $$t && $(STAMP) record prepush $$t && echo "make prepush: grün ($$t)"; fi
|
|
|
|
check: ## Alles: Secrets, Lint, Tests, SAST, Audit (vor Deploy, bei Werkzeug-Änderung)
|
|
@$(STAMP) begin check
|
|
@$(MAKE) --no-print-directory secrets lint test sast audit
|
|
@$(STAMP) record check
|
|
@echo "make check: alles grün"
|
|
|
|
RELEASE_CHECKS = check $(if $(wildcard $(MUTATIONS)),mutation) $(if $(IMAGE),container=$(IMAGE))
|
|
release-checks: ## Vor Deploy: check, mutation, container (IMAGE=) nur, wenn für den Commit kein gültiger Stempel da ist
|
|
@$(STAMP) clean || (echo "release-checks: Arbeitsbaum nicht sauber, erst committen" && exit 1)
|
|
@$(STAMP) status check >/dev/null || $(MAKE) --no-print-directory check
|
|
@if [ -f "$(MUTATIONS)" ]; then $(STAMP) status mutation >/dev/null || $(MAKE) --no-print-directory mutation; fi
|
|
@if [ -n "$(IMAGE)" ]; then $(STAMP) status "container=$(IMAGE)" >/dev/null || $(MAKE) --no-print-directory container IMAGE="$(IMAGE)"; fi
|
|
@$(STAMP) status $(RELEASE_CHECKS)
|
|
|
|
release-report: ## Markdown-Tabelle der Prüfungen fürs Release-Protokoll (docs/releases/)
|
|
@$(STAMP) report $(patsubst container=%,container,$(RELEASE_CHECKS)) zap fuzz
|
|
|
|
stamp: ## Stempel für den aktuellen Commit anzeigen
|
|
@$(STAMP) status
|
|
|
|
weekly: ## Wochenlauf: check + Trivy, läuft nach Fehlern weiter (neue CVEs kommen ohne Code-Änderung)
|
|
$(MAKE) -k --no-print-directory secrets lint test sast audit trivy-fs
|
|
@echo "make weekly: alles grün"
|
|
|
|
lint: ## Ruff (Python), ESLint + Typecheck (Node, falls als npm-Skript vorhanden)
|
|
ifneq ($(HAS_PY),)
|
|
cd $(PY_DIR) && .venv/bin/ruff check . && .venv/bin/ruff format --check .
|
|
endif
|
|
ifneq ($(HAS_NODE),)
|
|
$(NPM) run lint --if-present
|
|
$(NPM) run typecheck --if-present
|
|
endif
|
|
|
|
test: ## Unit-, Integrations- und Sicherheitstests
|
|
ifneq ($(HAS_PY),)
|
|
cd $(PY_DIR) && .venv/bin/python -m pytest -q $(if $(TESTS),$(TESTS) $(PY_SECURITY_TESTS))
|
|
endif
|
|
ifneq ($(HAS_NODE),)
|
|
$(NPM) test
|
|
endif
|
|
|
|
secrets: ## gitleaks über die ganze Git-Historie
|
|
$(GITLEAKS)
|
|
|
|
secrets-diff: ## gitleaks nur über die Commits seit BASE (offene Änderungen fängt der pre-commit-Hook)
|
|
@range=$$(python3 $(SCRIPTS)/changed_scope.py range $(BASE)); \
|
|
if [ -z "$$range" ]; then echo "secrets-diff: keine Basis gefunden, prüfe die ganze Historie"; $(GITLEAKS); \
|
|
else $(GITLEAKS) --log-opts="$$range"; fi
|
|
|
|
sast: ## Semgrep (eigene Regeln) + Bandit
|
|
$(DOCKER) $(SEMGREP_IMAGE) semgrep scan --config $(SEMGREP_RULES) --error --metrics=off --quiet
|
|
ifneq ($(HAS_PY),)
|
|
$(VENV)/bin/bandit -q -r $(PY_DIR)/$(PY_SRC)
|
|
endif
|
|
|
|
PY_PREFIX := $(if $(filter .,$(PY_DIR)),./,./$(PY_DIR)/)
|
|
sast-diff: ## Semgrep + Bandit nur über die geänderten Dateien (vor dem Push)
|
|
@files=$$(python3 $(SCRIPTS)/changed_scope.py files $(BASE) | sed 's|^|./|'); \
|
|
if [ -z "$$files" ]; then echo "sast-diff: keine geänderten Dateien"; exit 0; fi; \
|
|
printf '%s\n' "$$files" | xargs -d '\n' $(DOCKER) $(SEMGREP_IMAGE) semgrep scan --config $(SEMGREP_RULES) --error --metrics=off --quiet || exit 1; \
|
|
[ -n "$(HAS_PY)" ] || exit 0; \
|
|
py=$$(printf '%s\n' "$$files" | awk -v p='$(PY_PREFIX)' 'index($$0, p) == 1 && /\.py$$/ && !/\/(tests?|\.venv[^/]*|node_modules)\//'); \
|
|
if [ -n "$$py" ]; then printf '%s\n' "$$py" | xargs -d '\n' $(VENV)/bin/bandit -q; fi
|
|
|
|
audit: ## Bekannte Lücken in Abhängigkeiten (pip-audit, npm audit)
|
|
ifneq ($(HAS_PY),)
|
|
$(VENV)/bin/pip-audit -r $(PY_DIR)/requirements.txt
|
|
endif
|
|
ifneq ($(HAS_NODE),)
|
|
$(NPM) audit --audit-level=high
|
|
endif
|
|
|
|
trivy-fs: ## Trivy übers Repo: Abhängigkeiten, Secrets, Fehlkonfiguration (HIGH/CRITICAL mit Fix)
|
|
@mkdir -p "$(TRIVY_CACHE)"
|
|
$(DOCKER) -v "$(TRIVY_CACHE)":/cache $(TRIVY_IMAGE) fs --cache-dir /cache --db-repository $(TRIVY_DB) --quiet --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --skip-dirs '**/node_modules' --skip-dirs '**/.venv' --skip-dirs '**/.venv-tools' .
|
|
|
|
container: ## Image + Dockerfile/Compose scannen: make container IMAGE=name:tag
|
|
@test -n "$(IMAGE)" || (echo "IMAGE=name:tag angeben" && exit 2)
|
|
@$(STAMP) begin container
|
|
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock $(TRIVY_IMAGE) image --severity HIGH,CRITICAL --exit-code 1 --ignore-unfixed $(IMAGE)
|
|
$(DOCKER) $(TRIVY_IMAGE) config --severity HIGH,CRITICAL --exit-code 1 /src
|
|
for f in $$(ls Dockerfile* 2>/dev/null); do docker run --rm -i $(HADOLINT_IMAGE) < $$f || exit 1; done
|
|
@$(STAMP) record container $(IMAGE)
|
|
|
|
zap: ## ZAP-Baseline (bricht nur bei FAIL ab, ZAP_ARGS= für strikt): make zap TARGET=https://...
|
|
@test -n "$(TARGET)" || (echo "TARGET=url angeben" && exit 2)
|
|
mkdir -p reports
|
|
@$(STAMP) begin zap
|
|
docker run --rm --network host -u $(UID_GID) -v "$(CURDIR)/reports":/zap/wrk $(ZAP_IMAGE) zap-baseline.py -t $(TARGET) -r zap-baseline.html $(ZAP_ARGS)
|
|
@$(STAMP) record zap $(TARGET)
|
|
|
|
fuzz: ## API-Fuzzing (5xx-Suche; FUZZ_CHECKS=all für volle Spec-Prüfung): make fuzz TARGET=http://...
|
|
@test -n "$(TARGET)" || (echo "TARGET=url angeben" && exit 2)
|
|
@$(STAMP) begin fuzz
|
|
docker run --rm --network host $(SCHEMATHESIS_IMAGE) run $(TARGET)$(OPENAPI_PATH) --checks $(FUZZ_CHECKS) $(FUZZ_ARGS)
|
|
@$(STAMP) record fuzz $(TARGET)
|
|
|
|
mutation: ## Sicherheits-Mutationen: jede muss einen Test rot machen
|
|
@$(STAMP) begin mutation
|
|
python3 $(SCRIPTS)/mutation_check.py $(MUTATIONS)
|
|
@$(STAMP) record mutation
|
|
|
|
mutmut: ## Mutation-Testing des ganzen Python-Codes (langsam)
|
|
cd $(PY_DIR) && .venv/bin/mutmut run && .venv/bin/mutmut results
|
|
|
|
vet-deps: ## Neue Pakete prüfen: make vet-deps PKGS="pypi:name npm:name"
|
|
@test -n "$(PKGS)" || (echo 'PKGS="pypi:name npm:name" angeben' && exit 2)
|
|
python3 $(SCRIPTS)/vet_deps.py $(PKGS)
|