diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..987835b --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,81 @@ +name: CI +# CI Wakdo - Forgejo Actions (runner stark-wakdo, label `docker`). +# Strategie solo dev : PR obligatoire + auto-merge sur CI verte (voir SECURITY.md). +# +# Etat des jobs selon la phase projet : +# - secret-scan : fonctionnel des maintenant (gitleaks scanne tout le depot) +# - php-lint : fonctionnel sur les fichiers PHP presents (stubs P1, code P2+) +# - static-tests: PHPStan + PHPUnit GARDES - s'activent quand P2 ajoute +# composer.json / phpstan.neon / tests + phpunit.xml + +on: + pull_request: + branches: [dev, main] + push: + branches: [dev, main] + +jobs: + secret-scan: + runs-on: docker + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install tools + run: | + apt-get update -qq + apt-get install -y -qq curl ca-certificates tar >/dev/null + - name: Install gitleaks + run: | + VER=8.21.2 + curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${VER}/gitleaks_${VER}_linux_x64.tar.gz" -o /tmp/gl.tgz + tar -xzf /tmp/gl.tgz -C /usr/local/bin gitleaks + gitleaks version + - name: Scan for secrets + run: gitleaks detect --config .gitleaks.toml --redact --no-banner --verbose + + php-lint: + runs-on: docker + steps: + - uses: actions/checkout@v4 + - name: Install PHP CLI + run: | + apt-get update -qq + apt-get install -y -qq php-cli >/dev/null + php --version + - name: Lint all PHP files + run: | + set -eu + files=$(find . -path ./node_modules -prune -o -name '*.php' -print) + if [ -z "$files" ]; then echo "No PHP files yet - skip"; exit 0; fi + echo "$files" | while IFS= read -r f; do + [ -z "$f" ] && continue + php -l "$f" + done + + static-tests: + runs-on: docker + steps: + - uses: actions/checkout@v4 + - name: PHPStan (guarded) + run: | + if [ -f composer.json ] && [ -f phpstan.neon ]; then + echo "phpstan config detected - running" + apt-get update -qq && apt-get install -y -qq php-cli unzip git >/dev/null + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + composer install --no-interaction --no-progress + vendor/bin/phpstan analyse --no-progress + else + echo "PHPStan skipped: no composer.json/phpstan.neon yet (activates in P2)" + fi + - name: PHPUnit (guarded) + run: | + if [ -d tests ] && [ -f phpunit.xml ]; then + echo "phpunit config detected - running" + apt-get update -qq && apt-get install -y -qq php-cli >/dev/null + if [ -f vendor/bin/phpunit ]; then vendor/bin/phpunit; \ + elif [ -f phpunit.phar ]; then php phpunit.phar; \ + else echo "phpunit binary missing despite config" && exit 1; fi + else + echo "PHPUnit skipped: no tests/ + phpunit.xml yet (activates in P2)" + fi diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..5c6aa3f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,55 @@ +# Politique de securite - Wakdo + +Wakdo est un projet de fin de formation (RNCP 37805) construit en +**security-by-design** : la menace est modelisee avant le code. Ce document +resume la posture, le signalement de vulnerabilites et les garde-fous CI. + +## Modele de menace + +Le modele STRIDE complet, le registre des risques et la classification des +donnees (4 niveaux) vivent dans `docs/PROJECT_CONTEXT.md` section 19, et le flux +d'authentification durci dans `docs/uml/security-sequence.md`. + +## Mesures en place (resume) + +| Domaine | Mesure | +|---|---| +| Mots de passe | `password_hash` argon2id (cout configurable, defauts OWASP) | +| Actions sensibles | PIN equipier hashe argon2id (`pin_hash`) | +| Brute-force | double throttle : compteur par compte (`user`) + par IP (`login_throttle`), backoff degressif | +| Sessions | cookies `HttpOnly` + `Secure` + `SameSite=Strict`, regeneration d'ID a la connexion (anti-fixation), idle 4h / absolu 10h | +| Injection | PDO prepared statements exclusivement | +| Upload | validation MIME + taille, stockage hors webroot | +| En-tetes / PHP | `expose_php=Off`, `allow_url_fopen/include=Off`, `cgi.fix_pathinfo=0`, fonctions d'execution systeme desactivees | +| RGPD | retention limitee (audit ~12 mois, throttle 24h, commandes ~3 ans), droit consultation/modif/suppression | +| Secrets | `.env` gitignore, tenu hors de `.git/config` (credential helper lisant `.env`), secret-scan gitleaks en CI | + +Les seuils operationnels (couts argon2, lockout, throttle, retention) sont +documentes dans `.env.example`. + +## Garde-fous CI (Forgejo Actions) + +Chaque PR vers `dev` ou `main` declenche `.forgejo/workflows/ci.yml` : + +- **secret-scan** (gitleaks) : empeche un secret d'entrer dans l'historique +- **php-lint** : `php -l` sur tous les fichiers PHP +- **static-tests** : PHPStan + PHPUnit (s'activent quand le code PHP arrive en P2) + +La strategie de merge est **PR + auto-merge sur CI verte** (travail solo) : la +PR est obligatoire (trace de gouvernance), le merge se declenche automatiquement +une fois les checks au vert. Voir `scripts/forgejo-pr-automerge.sh` et +`scripts/forgejo-branch-protection.sh`. + +## Signaler une vulnerabilite + +Projet pedagogique non destine a la production publique. Pour signaler un +probleme de securite : ouvrir une issue sur le depot Forgejo +(`https://git.acadenice.com/AcadeNice/corentin_wakdo`) ou contacter l'auteur. +Merci de ne pas divulguer publiquement un detail exploitable avant correction. + +## Perimetre + +Couvert : authentification, autorisation (RBAC), gestion de session, validation +d'entree, integrite des donnees de commande, hygiene des secrets. +Hors perimetre : paiement reel (remplace par numero de commande), durcissement +OS de l'hote, securite physique de la borne. diff --git a/scripts/forgejo-pr-automerge.sh b/scripts/forgejo-pr-automerge.sh new file mode 100755 index 0000000..46f5952 --- /dev/null +++ b/scripts/forgejo-pr-automerge.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# +# Wakdo - ouvre une PR Forgejo et planifie son auto-merge quand la CI passe. +# +# Strategie solo dev : la PR reste obligatoire (trace de gouvernance, Cr 4.f) +# mais le merge se declenche tout seul des que les checks requis sont verts. +# Prerequis : status checks requis sur la branche de base +# (voir scripts/forgejo-branch-protection.sh avec REQUIRE_CI=1). +# +# Usage : +# scripts/forgejo-pr-automerge.sh [HEAD] [BASE] ["Titre"] +# Defauts : HEAD = branche courante, BASE = dev, titre = dernier sujet de commit. +# +set -euo pipefail + +REPO_API="https://git.acadenice.com/api/v1/repos/AcadeNice/corentin_wakdo" +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +ENV_FILE="$ROOT/.env" + +TOKEN="$(grep -E '^FORGEJO_TOKEN=' "$ENV_FILE" | cut -d= -f2-)" +[ -n "${TOKEN:-}" ] || { echo "ERREUR : FORGEJO_TOKEN absent de $ENV_FILE" >&2; exit 1; } + +HEAD="${1:-$(git -C "$ROOT" rev-parse --abbrev-ref HEAD)}" +BASE="${2:-dev}" +TITLE="${3:-$(git -C "$ROOT" log -1 --pretty=%s "$HEAD")}" + +if [ "$BASE" = "main" ] && [ "$HEAD" != "dev" ]; then + echo "Garde-fou : seules les PR depuis 'dev' visent 'main'. Abandon." >&2 + exit 1 +fi + +echo "PR : $HEAD -> $BASE" +echo "Titre : $TITLE" + +# 1. Creer la PR (ou recuperer l'index si elle existe deja). +create_resp=$(curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \ + -d "$(printf '{"head":"%s","base":"%s","title":"%s"}' "$HEAD" "$BASE" "$TITLE")" \ + "$REPO_API/pulls") +index=$(printf '%s' "$create_resp" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('number',''))" 2>/dev/null || true) + +if [ -z "$index" ]; then + # PR deja existante : la retrouver par branche head. + index=$(curl -s -H "Authorization: token $TOKEN" "$REPO_API/pulls?state=open&limit=50" \ + | python3 -c "import sys,json;hs='$HEAD';d=json.load(sys.stdin);print(next((p['number'] for p in d if p['head']['ref']==hs),''))" 2>/dev/null || true) +fi +[ -n "$index" ] || { echo "Impossible de creer/trouver la PR. Reponse : $create_resp" >&2; exit 1; } +echo "PR #$index" + +# 2. Planifier l'auto-merge (squash) quand les checks requis sont verts. +merge_resp=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \ + -d '{"Do":"squash","merge_when_checks_succeed":true,"delete_branch_after_merge":false}' \ + "$REPO_API/pulls/$index/merge") + +case "$merge_resp" in + 200|201|202) echo "Auto-merge planifie sur PR #$index (squash a la CI verte)." ;; + 405) echo "PR #$index : merge differe - checks pas encore verts, auto-merge en attente." ;; + *) echo "Reponse merge HTTP $merge_resp sur PR #$index (verifier l'etat des checks / protections)." ;; +esac