diff --git a/.env.example b/.env.example index 32e902b..1375dd3 100644 --- a/.env.example +++ b/.env.example @@ -3,17 +3,13 @@ # # Usage : # cp .env.example .env -# Editer .env (gitignore) avec les valeurs reelles. +# docker compose up -d # -# Audience : -# Destine a l'auteur, au jury et aux contributeurs futurs. -# -# Modele de deploiement : -# Ce projet tourne sur serveur derriere un reverse proxy Traefik. Il n'y a -# pas de binding de ports hote : l'acces se fait uniquement via les FQDN -# configures ci-dessous et routes par Traefik (reseau admin_proxy). -# Les distinctions dev / staging / prod se font par FQDN distincts -# (ex : .dev.acadenice.fr vs .acadenice.fr) et par .env dedie. +# Ce template fonctionne EN LOCAL tel quel (valeurs dev) : la stack est joignable +# sur http://kiosk.localhost:8080 (borne) et http://admin.localhost:8080 (admin). +# Le deploiement derriere un reverse proxy (Traefik) se fait via l'overlay +# docker-compose.prod.yml + les variables du bloc "Deploiement prod" en fin de +# fichier. En prod : changer les mots de passe, APP_DEBUG=false, vrais FQDN. # # =================================================================== @@ -24,27 +20,30 @@ APP_ENV=dev # dev | staging | prod APP_DEBUG=true # true en dev, false en prod APP_TIMEZONE=Europe/Paris -# URL publique de la borne (Bloc 1), doit pointer vers le FQDN Traefik. -# Placeholder example.com (RFC 2606) - a remplacer par le FQDN reel. -APP_URL_KIOSK=https://kiosk.example.com +# Port hote publie par wakdo-web (acces local). Change si 8080 est pris. +HTTP_PORT=8080 -# URL publique du back-office + API (Bloc 2). -# Placeholder example.com (RFC 2606) - a remplacer par le FQDN reel. -APP_URL_ADMIN=https://admin.example.com +# Hostnames des deux vhosts Apache (ServerName). En local : *.localhost resout +# vers 127.0.0.1 nativement. En prod : les vrais FQDN (voir bloc prod en bas). +APP_HOST_KIOSK=kiosk.localhost +APP_HOST_ADMIN=admin.localhost + +# URLs publiques (consommees par l'app). En local = les hostnames sur HTTP_PORT. +APP_URL_KIOSK=http://kiosk.localhost:8080 +APP_URL_ADMIN=http://admin.localhost:8080 # =================================================================== # Base de donnees (MariaDB) # =================================================================== -# Valeurs ci-dessous = PLACEHOLDERS. Remplacer par des mots de passe forts. -# Pas accessible depuis l'exterieur : le service wakdo-db est sur le reseau -# interne uniquement, aucun port exposé a l'hote. +# Valeurs dev ci-dessous : OK en local. EN PROD : mots de passe forts. +# wakdo-db est sur le reseau interne, aucun port expose a l'hote. DB_HOST=wakdo-db # nom du service docker-compose DB_PORT=3306 DB_NAME=wakdo DB_USER=wakdo -DB_PASSWORD=change_me_strong_password -DB_ROOT_PASSWORD=change_me_root_password +DB_PASSWORD=wakdo_dev_password +DB_ROOT_PASSWORD=wakdo_dev_root_password # =================================================================== # Sessions @@ -58,18 +57,15 @@ SESSION_NAME=WAKDO_SID # nom du cookie (evite PHPSESSID) # Securite # =================================================================== -# Origine autorisee pour les requetes CORS de l'API. -# Doit correspondre exactement a APP_URL_KIOSK (pas de wildcard). -CORS_ALLOWED_ORIGIN=https://kiosk.example.com +# Origine autorisee pour les requetes CORS de l'API. Doit correspondre +# exactement a APP_URL_KIOSK (pas de wildcard). +CORS_ALLOWED_ORIGIN=http://kiosk.localhost:8080 # Algorithme de hashage : argon2id, FIXE dans le code (App\Auth\PasswordHasher), -# choix security-by-design non configurable (pas de bascule runtime vers un algo -# plus faible). Seuls les COUTS ci-dessous sont reglables. +# choix security-by-design non configurable. Seuls les COUTS ci-dessous sont reglables. -# Parametres de cout argon2id (password_hash options). -# Defauts alignes sur les recommandations OWASP Password Storage Cheat Sheet -# (memoire >= 19 MiB, >= 2 iterations). 65536 KiB = 64 MiB, marge confortable. -# Ces valeurs servent aussi au hash du PIN equipier (pin_hash, actions sensibles). +# Parametres de cout argon2id (password_hash options). Defauts alignes OWASP +# (memoire >= 19 MiB, >= 2 iterations). Servent aussi au hash du PIN equipier. ARGON2_MEMORY_COST=65536 # KiB (64 MiB) ARGON2_TIME_COST=4 # nombre d'iterations ARGON2_THREADS=1 # parallelisme (1 = portable, deterministe) @@ -77,49 +73,38 @@ ARGON2_THREADS=1 # parallelisme (1 = portable, determini # =================================================================== # Anti brute-force - throttling de connexion (security-by-design) # =================================================================== -# Deux gardes complementaires (cf. docs/merise/mld.md 4.21 + PROJECT_CONTEXT 19) : -# 1. par compte : colonnes user.failed_login_attempts / user.lockout_until -# 2. par IP source : table login_throttle (entite 21) -# Backoff degressif (pas de lock definitif) : evite le DoS sur compte legitime. +# Deux gardes : par compte (user.failed_login_attempts / lockout_until) et par IP +# (table login_throttle). Backoff degressif, pas de lock definitif. -# Compteur par compte : nombre d'echecs avant declenchement du backoff. ACCOUNT_LOCKOUT_THRESHOLD=5 -# Duree de base du backoff (secondes). Croit de facon degressive a chaque -# palier d'echecs supplementaires (ex : 60 -> 300 -> 900). ACCOUNT_LOCKOUT_BASE_SECONDS=60 ACCOUNT_LOCKOUT_MAX_SECONDS=900 # plafond du backoff (15 min) -# Gate par IP : fenetre glissante et plafond de tentatives sur cette fenetre. IP_THROTTLE_WINDOW_SECONDS=900 # 15 min IP_THROTTLE_MAX_ATTEMPTS=20 # par IP sur la fenetre -# PIN equipier pour actions sensibles (annulation, override). Chiffres uniquement, -# bornes min ET max (RG-T18 : validation serveur + longueur bornee). +# PIN equipier pour actions sensibles. Chiffres, bornes min ET max (RG-T18). STAFF_PIN_MIN_LENGTH=4 STAFF_PIN_MAX_LENGTH=12 -# Throttle du PIN d'action sensible (RG-T22) - compteurs SEPARES du login : la -# dimension est l'utilisateur AGISSANT (session), pas l'email cible ni l'IP. Bornes -# volontairement plus permissives que le login (controle de dissuasion) : ne pas -# bloquer un manager en plein rush sur quelques fautes de frappe. -PIN_THROTTLE_THRESHOLD=5 # echecs avant le backoff (par acteur) -PIN_THROTTLE_BASE_SECONDS=30 # 1er palier (vs 60 au login) -PIN_THROTTLE_MAX_SECONDS=300 # plafond du backoff (5 min, vs 900 au login) -PIN_THROTTLE_WINDOW_SECONDS=900 # fenetre glissante (15 min) +# Throttle du PIN d'action sensible (RG-T22) - compteurs SEPARES du login, +# dimension = utilisateur agissant. Bornes plus permissives que le login. +PIN_THROTTLE_THRESHOLD=5 +PIN_THROTTLE_BASE_SECONDS=30 +PIN_THROTTLE_MAX_SECONDS=300 +PIN_THROTTLE_WINDOW_SECONDS=900 # Expiration du token de reinitialisation de mot de passe (secondes). PASSWORD_RESET_TTL=3600 # 1h # =================================================================== -# Retention des donnees (RGPD - minimisation et limitation de conservation) +# Retention des donnees (RGPD) # =================================================================== # Purges executees par le service cron (docker/cron/crontab). -# Justification documentee : interet legitime / obligations probatoires. AUDIT_LOG_RETENTION_DAYS=365 # journal d'audit ~12 mois THROTTLE_PURGE_AFTER_HOURS=24 # login_throttle : lignes sans lockout actif > 24h ORDER_RETENTION_DAYS=1095 # commandes (historique/stats) ~3 ans -# Purge des sessions expirees : deja geree par le cron */15 (voir crontab). # =================================================================== # Upload images produits @@ -131,34 +116,18 @@ UPLOAD_ALLOWED_MIME=image/jpeg,image/png,image/webp # =================================================================== # Cron (fenetre de maintenance 01h30 - 09h30) # =================================================================== -# Les jobs sont definis dans docker/cron/crontab. Ici uniquement le TZ. CRON_TIMEZONE=Europe/Paris # =================================================================== -# Exposition via Traefik +# Deploiement prod (overlay docker-compose.prod.yml) - OPTIONNEL # =================================================================== -# FQDN consommes par les labels docker-compose.yml pour generer les routes -# Traefik et les certificats TLS (Traefik gere le resolver par defaut). -# Le Traefik de l'hote prend en charge Let's Encrypt automatiquement. - -TRAEFIK_DOMAIN_KIOSK=kiosk.example.com -TRAEFIK_DOMAIN_ADMIN=admin.example.com - -# =================================================================== -# Reseau Docker externe du reverse proxy -# =================================================================== -# Nom du reseau Docker externe auquel le service web doit se connecter -# pour etre expose par le reverse proxy de l'hote. +# A ignorer pour un usage local. Necessaire UNIQUEMENT derriere un reverse proxy +# Traefik, avec : docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d # -# Adapter selon votre infrastructure. Valeurs courantes : -# traefik_proxy - convention neutre (placeholder) -# traefik_public - convention doc Traefik -# traefik - setups simples -# proxy - autre convention frequente +# En prod, surcharger aussi : APP_ENV=prod, APP_DEBUG=false, mots de passe forts, +# et APP_HOST_*/APP_URL_*/CORS_ALLOWED_ORIGIN avec les vrais FQDN HTTPS. # -# Le reseau doit exister AVANT 'docker compose up' (cree par votre stack de -# reverse proxy, ou manuellement : docker network create ). Sinon -# 'docker compose up' echoue avec une erreur sur le reseau externe introuvable. - -REVERSE_PROXY_NETWORK=traefik_proxy +# Nom du reseau Docker externe partage avec le Traefik de l'hote (doit exister +# AVANT le up : cree par la stack Traefik, ou `docker network create `). +REVERSE_PROXY_NETWORK=admin_proxy diff --git a/.gitignore b/.gitignore index b876df3..d7099b1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,11 @@ *.pem *.key +# Compose de production (propre a chaque hote : Traefik/reverse proxy, FQDN). +# Le repo ne ship que docker-compose.yml (standalone, local). Chaque hote derriere +# un proxy maintient son propre docker-compose.prod.yml (hors versionnement). +docker-compose.prod.yml + # === BYAN — plateforme (moteur), masquee === # Le code moteur des agents n'est pas part du rendu RNCP. # La methodologie appliquee (CLAUDE.md + rules + hooks) reste dans .claude/ diff --git a/README.md b/README.md index da14429..a2a8a7d 100644 --- a/README.md +++ b/README.md @@ -89,80 +89,41 @@ Reseaux, volumes, services et decoupage reseau interne / reseau proxy : voir `do --- -## Quickstart - -Ce projet tourne **sur serveur derriere un reverse proxy Traefik** : pas de binding de ports hote, pas d'acces `localhost`. L'acces public se fait par FQDN HTTPS (TLS gere automatiquement par Traefik). Les environnements `dev`, `staging` et `prod` se distinguent par des FQDN et des fichiers `.env` separes. - -### Prerequis sur l'hote - -1. Docker Engine + docker compose v2 (voir ci-dessous) -2. Un reverse proxy Traefik deja en place, avec un reseau Docker externe dedie. Le **nom du reseau** est configurable via la variable `REVERSE_PROXY_NETWORK` du `.env` (defaut : `admin_proxy` — convention de l'auteur). A adapter a votre infrastructure. -3. Les FQDN cibles pointent en DNS vers l'hote - -### Sur un hote deja equipe (Docker + Traefik) +## Quickstart (local) ```bash git clone git@github.com:AcadeNice/wakdo_corentin.git cd wakdo_corentin cp .env.example .env -# Editer .env : DB_PASSWORD, DB_ROOT_PASSWORD, APP_URL_*, TRAEFIK_DOMAIN_* docker compose up -d ``` -> **Attention au `.env` pre-existant.** Si un fichier `.env` existe deja a la racine (tooling externe, autre plateforme installee dans le meme repertoire), **ne pas faire** `cp .env.example .env` — cela ecraserait les variables existantes. Faire un **merge manuel** a la place : ajouter les variables manquantes du template dans le `.env` actuel. Les prefixes de variables de ce projet (`APP_`, `DB_`, `SESSION_`, `CORS_`, `UPLOAD_`, `CRON_`, `TRAEFIK_`, `REVERSE_PROXY_`) sont disjoints de ceux utilises par des outils tiers courants, donc la cohabitation est safe. +Une seule commande lance la stack complete (Cr 7.c.4) : le service one-shot +`wakdo-migrate` applique les migrations puis le seed (idempotents, tables de suivi +`schema_migrations` / `seeds_applied`) avant que l'app ne serve. Ensuite : -Critere RNCP Cr 7.c.4 couvert : une seule commande (`docker compose up -d`) lance la -stack complete. Le service one-shot `wakdo-migrate` applique le schema (migrations) -puis les donnees de reference (seed) avant que l'app ne serve ; `wakdo-app`/`wakdo-web` -attendent sa completion (`depends_on: service_completed_successfully`). Migrations et -seed sont idempotents (tables de suivi `schema_migrations` / `seeds_applied`). -Detail : `docs/journal/2026-06-17--makefile-to-compose-migrate.md`. +- Borne : http://kiosk.localhost:8080 +- Admin + API : http://admin.localhost:8080 -Services accessibles apres `docker compose up -d` : -- Borne : la valeur de `TRAEFIK_DOMAIN_KIOSK` dans `.env` -- Admin + API : la valeur de `TRAEFIK_DOMAIN_ADMIN` dans `.env` +`*.localhost` resout vers `127.0.0.1` nativement ; changer le port via `HTTP_PORT` +dans `.env`. Le `.env.example` fonctionne tel quel en local (valeurs dev). -### Installation Docker sur un hote neuf (Debian / Ubuntu) +Docker non installe ? Voir https://docs.docker.com/engine/install/ -Procedure officielle detaillee : `https://docs.docker.com/engine/install/` (selectionner la distribution). Resume pour Debian stable : +### Deploiement prod (derriere un reverse proxy Traefik) + +Le repo ne ship que `docker-compose.yml` (standalone). En production derriere un +reverse proxy, chaque hote maintient son **propre `docker-compose.prod.yml`** +(gitignore, hors repo, comme `.env`) : meme stack, mais exposee via Traefik (reseau +externe + labels TLS) au lieu d'un port hote. ```bash -sudo apt update -sudo apt install -y ca-certificates curl -sudo install -m 0755 -d /etc/apt/keyrings -sudo curl -fsSL https://download.docker.com/linux/debian/gpg \ - -o /etc/apt/keyrings/docker.asc -sudo chmod a+r /etc/apt/keyrings/docker.asc -echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \ - https://download.docker.com/linux/debian \ - $(. /etc/os-release && echo $VERSION_CODENAME) stable" \ - | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null -sudo apt update -sudo apt install -y docker-ce docker-ce-cli containerd.io \ - docker-buildx-plugin docker-compose-plugin -sudo usermod -aG docker $USER -# Fermer et rouvrir la session pour activer le groupe docker +docker compose -f docker-compose.prod.yml up -d ``` -### Reseau externe du reverse proxy - -Le `docker-compose.yml` attend un reseau Docker externe deja existant sur l'hote, dont le nom est donne par la variable `REVERSE_PROXY_NETWORK` (defaut : `admin_proxy`). - -Si vous avez deja un Traefik en place, ce reseau a generalement ete cree par son propre stack. Adaptez la variable `REVERSE_PROXY_NETWORK` dans votre `.env` au nom utilise par votre proxy. Sinon, creez-le manuellement : - -```bash -docker network create mon_reseau_proxy -# puis dans .env : -# REVERSE_PROXY_NETWORK=mon_reseau_proxy -``` - -Avant le premier `docker compose up`, s'assurer que le reseau existe. Verification rapide : - -```bash -docker network inspect "$(grep ^REVERSE_PROXY_NETWORK .env | cut -d= -f2)" -``` - -Si la commande retourne une erreur, soit adapter `REVERSE_PROXY_NETWORK` au nom du reseau utilise par votre proxy, soit creer le reseau manuellement (le reseau externe doit exister avant `docker compose up`). +Avec un `.env` adapte : `APP_ENV=prod`, `APP_DEBUG=false`, mots de passe forts, +`APP_HOST_*` / `APP_URL_*` / `CORS_ALLOWED_ORIGIN` en vrais FQDN HTTPS, et +`REVERSE_PROXY_NETWORK` = reseau Docker du Traefik de l'hote (doit exister avant le up). *Section mise a jour au fil de l'implementation (migrations reelles, seed, CI/CD deploiement).* diff --git a/docker-compose.yml b/docker-compose.yml index a9e8ed7..5b8bfef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,87 +1,19 @@ -# -# Wakdo - orchestration des 4 services de la stack (Bloc 5 DevOps, Cr 7.c.3 / 7.c.4). -# -# Services : -# wakdo-web : Apache httpd, reverse proxy FastCGI -> wakdo-app, expose 80 via Traefik -# wakdo-app : PHP-FPM 8.3, execute le code PHP back-office + API -# wakdo-db : MariaDB 11.4, persistance des donnees metier -# wakdo-cron : Alpine + dcron, backups nocturnes et taches planifiees -# -# Reseaux : -# default (nom = wakdo_internal) : bridge interne pour l'inter-service, -# non expose a l'hote. app/db/cron ne sont PAS joignables publiquement. -# : reseau externe (ex. traefik_proxy) partage -# avec le Traefik de l'hote. Seul wakdo-web y est attache. -# -# Volumes : -# wakdo_db_data : named volume pour /var/lib/mysql (persistance BDD) -# wakdo_uploads : named volume pour les uploads produits back-office -# ./var/backups : bind-mount lecture-ecriture pour les dumps SQL -# -# Variables d'env consommees depuis .env : docker compose lit automatiquement le -# fichier .env a la racine et fait l'expansion des ${...} ci-dessous. -# -# Persistance : `docker compose down` preserve les named volumes ; seul -# `docker compose down -v` supprime les donnees. -# - name: wakdo networks: - # Reseau interne : isolement des services applicatifs. - # wakdo-app, wakdo-db et wakdo-cron n'y sont accessibles que via les autres - # conteneurs de la stack. Aucun port hote expose. wakdo_internal: driver: bridge - internal: false - # internal: false (par defaut) car wakdo-app et wakdo-cron ont besoin - # de sortir sur internet (pour telecharger des packages au build et pour - # de potentiels appels API externes futurs). L'isolation vient du fait - # qu'aucun port hote n'est binde ici. - # - # Subnet explicite (RFC 1918) : l'auto-allocateur Docker du daemon hote - # est sature (15 /16 + 15 /20 deja alloues par d'autres stacks), il ne - # peut plus creer de reseau bridge sans subnet explicite. 192.168.148.0/24 - # est dans le gap libre 192.168.144-159 (256 IP, largement suffisant pour - # 4 services), aucune collision avec les /24 acquagest voisins (150/154/ - # 155/157). Choix defendable : right-sizing + isolation des fluctuations - # d'allocation auto sur cet hote mutualise. - ipam: - driver: default - config: - - subnet: 192.168.148.0/24 - - # Reseau du reverse proxy (Traefik) pre-existant sur l'hote. - # Son nom est configurable via REVERSE_PROXY_NETWORK dans .env pour - # supporter differents setups (traefik_proxy, traefik_public, proxy, ...). - reverse_proxy: - name: ${REVERSE_PROXY_NETWORK} - external: true volumes: wakdo_db_data: - # Named volume MariaDB. Permissions gerees par Docker (UID mysql=999 - # dans le conteneur), zero souci cote hote. Survit a `docker compose down`. - # Pour remise a zero : `docker compose down -v` (destructif : supprime les volumes). - wakdo_uploads: - # Images produits uploadees par les equipiers depuis le back-office. - # Named volume pour les memes raisons que wakdo_db_data : permissions - # propres (www-data UID 82 en Alpine) et pas de pollution du repo git - # par des binaires. services: - # ======================================================================= - # wakdo-db : MariaDB 11.4 LTS - # ======================================================================= wakdo-db: image: mariadb:11.4 container_name: wakdo-db restart: unless-stopped - - # Variables d'env d'initialisation (ne s'appliquent qu'au premier demarrage - # sur volume vide - voir docs/notes/docker-volumes-vs-bind-mounts.md). environment: MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MARIADB_DATABASE: ${DB_NAME} @@ -89,22 +21,11 @@ services: MARIADB_PASSWORD: ${DB_PASSWORD} MARIADB_AUTO_UPGRADE: "1" TZ: ${APP_TIMEZONE:-Europe/Paris} - volumes: - wakdo_db_data:/var/lib/mysql - # Scripts d'init MariaDB, executes UNIQUEMENT au premier demarrage sur - # volume vierge : durcit le privilege du user applicatif (moindre - # privilege, db/init/10-scope-app-user.sh). Lecture seule. - ./db/init:/docker-entrypoint-initdb.d:ro - networks: - wakdo_internal - - # Pas de ports exposes a l'hote : seuls wakdo-app et wakdo-cron peuvent - # joindre wakdo-db:3306 via le reseau interne. - - # Healthcheck officiel fourni par l'image mariadb : le script bundled - # healthcheck.sh teste la connexion et l'init innodb. healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s @@ -112,48 +33,30 @@ services: retries: 6 start_period: 30s - # ======================================================================= - # wakdo-migrate : service ONE-SHOT (migrations + seed idempotents) - # ======================================================================= - # Tourne une fois apres que wakdo-db soit healthy, applique le schema puis les - # donnees de reference (idempotent via schema_migrations / seeds_applied), et - # sort. app/web attendent sa COMPLETION : `docker compose up` rend ainsi la stack - # complete + utilisable en UNE commande, sans dependance a l'hote (Cr 7.c.4). wakdo-migrate: image: mariadb:11.4 container_name: wakdo-migrate restart: "no" - environment: DB_HOST: ${DB_HOST} DB_PORT: ${DB_PORT} DB_NAME: ${DB_NAME} - # Root : migrations = DDL, seeds = INSERT de reference. DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} - volumes: - # migrations + seeds + le runner, en lecture seule. - ./db:/db:ro - networks: - wakdo_internal - depends_on: wakdo-db: condition: service_healthy - entrypoint: ["bash", "/db/migrate-container.sh"] - # ======================================================================= - # wakdo-app : PHP-FPM 8.3 (execute le code back-office + API) - # ======================================================================= wakdo-app: build: context: ./docker/php-fpm dockerfile: Dockerfile container_name: wakdo-app restart: unless-stopped - environment: APP_ENV: ${APP_ENV} APP_DEBUG: ${APP_DEBUG} @@ -169,80 +72,50 @@ services: SESSION_LIFETIME_ABSOLUTE: ${SESSION_LIFETIME_ABSOLUTE} SESSION_NAME: ${SESSION_NAME} CORS_ALLOWED_ORIGIN: ${CORS_ALLOWED_ORIGIN} - # Cout argon2id (password_hash) : aligne sur .env.example / OWASP. Sert au - # hash du mot de passe ET du PIN equipier (actions sensibles, P3). ARGON2_MEMORY_COST: ${ARGON2_MEMORY_COST} ARGON2_TIME_COST: ${ARGON2_TIME_COST} ARGON2_THREADS: ${ARGON2_THREADS} - # Anti brute-force : backoff degressif par compte (user.lockout_until) et - # par IP source (table login_throttle). Voir mlt.md 12.1 RG-8/RG-9. ACCOUNT_LOCKOUT_THRESHOLD: ${ACCOUNT_LOCKOUT_THRESHOLD} ACCOUNT_LOCKOUT_BASE_SECONDS: ${ACCOUNT_LOCKOUT_BASE_SECONDS} ACCOUNT_LOCKOUT_MAX_SECONDS: ${ACCOUNT_LOCKOUT_MAX_SECONDS} IP_THROTTLE_WINDOW_SECONDS: ${IP_THROTTLE_WINDOW_SECONDS} IP_THROTTLE_MAX_ATTEMPTS: ${IP_THROTTLE_MAX_ATTEMPTS} - # Bornes du PIN equipier (actions sensibles, P3) : longueur min ET max. STAFF_PIN_MIN_LENGTH: ${STAFF_PIN_MIN_LENGTH} STAFF_PIN_MAX_LENGTH: ${STAFF_PIN_MAX_LENGTH} - # Throttle du PIN d'action sensible (RG-T22) : compteurs SEPARES du login. PIN_THROTTLE_THRESHOLD: ${PIN_THROTTLE_THRESHOLD} PIN_THROTTLE_BASE_SECONDS: ${PIN_THROTTLE_BASE_SECONDS} PIN_THROTTLE_MAX_SECONDS: ${PIN_THROTTLE_MAX_SECONDS} PIN_THROTTLE_WINDOW_SECONDS: ${PIN_THROTTLE_WINDOW_SECONDS} - # Expiration du token de reinitialisation de mot de passe (mlt.md 12.3). PASSWORD_RESET_TTL: ${PASSWORD_RESET_TTL} UPLOAD_MAX_SIZE_MB: ${UPLOAD_MAX_SIZE_MB} UPLOAD_ALLOWED_MIME: ${UPLOAD_ALLOWED_MIME} - volumes: - # Bind-mount du code source pour le hot-reload en dev. - # En prod, cette ligne est remplacee par un COPY dans l'image - # via docker-compose.prod.yml (override a venir en P7). - ./src:/var/www/html - - # Named volume pour les uploads, plus specifique que le bind-mount - # parent : les uploads ne vont pas dans ./src, ils restent dans le - # volume Docker et survivent aux `docker compose down`. - wakdo_uploads:/var/www/html/public/uploads - networks: - wakdo_internal - depends_on: - # Le schema + les donnees de reference sont en place AVANT que l'app serve. wakdo-migrate: condition: service_completed_successfully wakdo-db: condition: service_healthy - # Healthcheck defini dans le Dockerfile (php -r exit 0). - - # ======================================================================= - # wakdo-web : Apache httpd (reverse FastCGI vers wakdo-app, expose via Traefik) - # ======================================================================= wakdo-web: build: context: ./docker/apache dockerfile: Dockerfile container_name: wakdo-web restart: unless-stopped - environment: - # Noms de domaine injectes dans les vhosts (ServerName ${TRAEFIK_DOMAIN_*}). - TRAEFIK_DOMAIN_KIOSK: ${TRAEFIK_DOMAIN_KIOSK} - TRAEFIK_DOMAIN_ADMIN: ${TRAEFIK_DOMAIN_ADMIN} - + APP_HOST_KIOSK: ${APP_HOST_KIOSK} + APP_HOST_ADMIN: ${APP_HOST_ADMIN} + ports: + - "${HTTP_PORT:-8080}:80" volumes: - # Meme bind-mount que wakdo-app : Apache sert les fichiers statiques - # (HTML, CSS, JS, images) depuis ce dossier. PHP ne passe pas par - # ce chemin ici, il passe par le proxy FastCGI vers wakdo-app. - ./src:/var/www/html - wakdo_uploads:/var/www/html/public/uploads - networks: - wakdo_internal - - reverse_proxy - depends_on: wakdo-migrate: condition: service_completed_successfully @@ -251,79 +124,26 @@ services: wakdo-db: condition: service_healthy - # === Labels Traefik : deux routers (kiosk + admin) sur le meme conteneur === - # Le Traefik de l'hote decouvre ces labels automatiquement (provider docker). - # On ne configure PAS le certresolver ici : le Traefik hote le gere via - # sa propre config (acme.json, resolver par defaut). - labels: - - "traefik.enable=true" - - "traefik.docker.network=${REVERSE_PROXY_NETWORK}" - - # --- Router kiosk (borne client) --- - - "traefik.http.routers.wakdo-kiosk.rule=Host(`${TRAEFIK_DOMAIN_KIOSK}`)" - - "traefik.http.routers.wakdo-kiosk.entrypoints=websecure" - - "traefik.http.routers.wakdo-kiosk.tls=true" - - "traefik.http.routers.wakdo-kiosk.tls.certresolver=letsencrypt" - - "traefik.http.routers.wakdo-kiosk.service=wakdo-kiosk-svc" - - "traefik.http.services.wakdo-kiosk-svc.loadbalancer.server.port=80" - - # --- Router admin (back-office + API) --- - - "traefik.http.routers.wakdo-admin.rule=Host(`${TRAEFIK_DOMAIN_ADMIN}`)" - - "traefik.http.routers.wakdo-admin.entrypoints=websecure" - - "traefik.http.routers.wakdo-admin.tls=true" - - "traefik.http.routers.wakdo-admin.tls.certresolver=letsencrypt" - - "traefik.http.routers.wakdo-admin.service=wakdo-admin-svc" - - "traefik.http.services.wakdo-admin-svc.loadbalancer.server.port=80" - - # --- Middleware : redirection HTTP -> HTTPS --- - # Applique aux 2 hosts via un router "catch-all" sur entrypoints=web. - - "traefik.http.routers.wakdo-kiosk-http.rule=Host(`${TRAEFIK_DOMAIN_KIOSK}`)" - - "traefik.http.routers.wakdo-kiosk-http.entrypoints=web" - - "traefik.http.routers.wakdo-kiosk-http.middlewares=wakdo-to-https" - - "traefik.http.routers.wakdo-admin-http.rule=Host(`${TRAEFIK_DOMAIN_ADMIN}`)" - - "traefik.http.routers.wakdo-admin-http.entrypoints=web" - - "traefik.http.routers.wakdo-admin-http.middlewares=wakdo-to-https" - - "traefik.http.middlewares.wakdo-to-https.redirectscheme.scheme=https" - - "traefik.http.middlewares.wakdo-to-https.redirectscheme.permanent=true" - - # ======================================================================= - # wakdo-cron : taches planifiees (backup, purge, agregations) - # ======================================================================= wakdo-cron: build: context: ./docker/cron dockerfile: Dockerfile container_name: wakdo-cron restart: unless-stopped - # init: true -> Docker injecte tini comme PID 1. dcron exige un init - # parent pour pouvoir setpgid() sur ses jobs (sinon "Operation not - # permitted" en boucle car un PID 1 sans init ne peut pas changer les - # groupes de processus). Cf. busybox-utils issue tracker. init: true - environment: - # Credentials BDD pour mysqldump et les purges. Le user applicatif est en - # moindre privilege (DML + SELECT/SHOW VIEW/TRIGGER/LOCK TABLES, jamais le - # root password ; cf. db/init/10-scope-app-user.sh). DB_HOST: ${DB_HOST} DB_PORT: ${DB_PORT} DB_NAME: ${DB_NAME} DB_USER: ${DB_USER} DB_PASSWORD: ${DB_PASSWORD} - # Retention des donnees (mlt.md 13.4/13.5). Defaut applique par les scripts - # ET ici, pour rester coherent si la var manque du .env. AUDIT_LOG_RETENTION_DAYS: ${AUDIT_LOG_RETENTION_DAYS:-365} THROTTLE_PURGE_AFTER_HOURS: ${THROTTLE_PURGE_AFTER_HOURS:-24} TZ: ${CRON_TIMEZONE:-Europe/Paris} - volumes: - # Bind-mount vers l'hote pour les dumps : inspectables par ls, scp-able - # hors docker. Le dossier ./var/backups est gitignore. - ./var/backups:/backups - networks: - wakdo_internal - depends_on: wakdo-db: condition: service_healthy diff --git a/docker/apache/vhost.conf b/docker/apache/vhost.conf index d0411f2..141483e 100644 --- a/docker/apache/vhost.conf +++ b/docker/apache/vhost.conf @@ -2,8 +2,8 @@ # Wakdo - vhosts applicatifs # # Un seul conteneur Apache derriere Traefik sert **les deux** FQDN : -# - TRAEFIK_DOMAIN_KIOSK -> /var/www/html/public/borne (borne client, Bloc 1) -# - TRAEFIK_DOMAIN_ADMIN -> /var/www/html/public/admin (back-office + API, Bloc 2) +# - APP_HOST_KIOSK -> /var/www/html/public/borne (borne client, Bloc 1) +# - APP_HOST_ADMIN -> /var/www/html/public/admin (back-office + API, Bloc 2) # # Comme Traefik termine TLS en amont et communique en HTTP clair avec Apache # sur le reseau docker, les vhosts ecoutent sur :80 et font confiance aux @@ -39,8 +39,8 @@ # === Borne client (Bloc 1 - front vanilla HTML/CSS/JS) === - # Hostname injecte par la var d'env TRAEFIK_DOMAIN_KIOSK au runtime. - ServerName ${TRAEFIK_DOMAIN_KIOSK} + # Hostname injecte par la var d'env APP_HOST_KIOSK au runtime. + ServerName ${APP_HOST_KIOSK} DocumentRoot "/var/www/html/public/borne" @@ -89,7 +89,7 @@ # === Back-office + API REST (Bloc 2 - PHP from scratch + MVC) === - ServerName ${TRAEFIK_DOMAIN_ADMIN} + ServerName ${APP_HOST_ADMIN} DocumentRoot "/var/www/html/public/admin" @@ -128,7 +128,7 @@ # CORS : l'API admin sous /api/* doit accepter les requetes venant - # de la borne kiosk (TRAEFIK_DOMAIN_KIOSK). Wildcard interdit. + # de la borne kiosk (APP_HOST_KIOSK). Wildcard interdit. # La vraie valeur vient de CORS_ALLOWED_ORIGIN dans .env, lue cote PHP. # Ici on pose juste les headers de prealable OPTIONS.