{{ user.name }}
{# Bon - échappement explicite #}{{ user.description|e }}
{# Mauvais - risque XSS #}{{ user.html|raw }}
{# Bon - sanitisation HTML #}{{ user.html|sanitize_html }}
``` ### Protection contre l'injection SQL Utilisez toujours Doctrine avec des requêtes préparées : ```php // src/Repository/UserRepository.php namespace App\Repository; use App\Entity\User; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; class UserRepository extends ServiceEntityRepository { // Bon - Requête préparée avec paramètres public function findByEmail(string $email): ?User { return $this->createQueryBuilder('u') ->where('u.email = :email') ->setParameter('email', $email) ->getQuery() ->getOneOrNullResult(); } // Mauvais - Concaténation directe (NE JAMAIS FAIRE) // public function dangerousFind(string $email): ?User // { // return $this->createQueryBuilder('u') // ->where('u.email = "' . $email . '"') // DANGER! // ->getQuery() // ->getOneOrNullResult(); // } } ``` ## 4. Authentification et autorisation renforcées ### Configuration du Security Bundle ```yaml # config/packages/security.yaml security: password_hashers: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: algorithm: auto cost: 15 # Augmenter le coût pour plus de sécurité providers: app_user_provider: entity: class: App\Entity\User property: email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true provider: app_user_provider custom_authenticator: App\Security\LoginFormAuthenticator logout: path: app_logout invalidate_session: true clear_site_data: - cache - cookies - storage remember_me: secret: '%kernel.secret%' lifetime: 604800 path: / always_remember_me: false secure: true # Uniquement en HTTPS httponly: true # Protection XSS samesite: lax # Protection CSRF # Limitation des tentatives de connexion login_throttling: max_attempts: 5 interval: '15 minutes' access_control: - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/api, roles: ROLE_USER } role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] ``` ### Implémenter l'authentification à deux facteurs ```bash composer require scheb/2fa-bundle ``` ```php // src/Entity/User.php use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration; use Scheb\TwoFactorBundle\Model\Totp\TotpConfigurationInterface; use Scheb\TwoFactorBundle\Model\Totp\TwoFactorInterface; class User implements TwoFactorInterface { private ?string $totpSecret = null; public function isTotpAuthenticationEnabled(): bool { return null !== $this->totpSecret; } public function getTotpAuthenticationUsername(): string { return $this->email; } public function getTotpAuthenticationConfiguration(): ?TotpConfigurationInterface { return new TotpConfiguration($this->totpSecret, TotpConfiguration::ALGORITHM_SHA1, 30, 6); } public function setTotpSecret(?string $totpSecret): self { $this->totpSecret = $totpSecret; return $this; } } ``` ## 5. Sécurisation du réseau Docker ### Configuration réseau isolée ```yaml # docker-compose.yml version: '3.8' services: nginx: image: nginx:alpine ports: - "80:80" - "443:443" networks: - frontend depends_on: - app app: build: context: . dockerfile: Dockerfile networks: - frontend - backend depends_on: - db - redis db: image: mysql:8.0 networks: - backend environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password secrets: - db_root_password volumes: - db_data:/var/lib/mysql redis: image: redis:alpine networks: - backend command: redis-server --requirepass ${REDIS_PASSWORD} networks: frontend: driver: bridge backend: driver: bridge internal: true # Pas d'accès externe volumes: db_data: secrets: db_root_password: external: true ``` ## 6. Logging et monitoring de sécurité ### Configuration Monolog pour la sécurité ```yaml # config/packages/prod/monolog.yaml monolog: handlers: security: type: stream path: "%kernel.logs_dir%/security.log" level: info channels: ["security"] authentication_failures: type: stream path: "%kernel.logs_dir%/auth_failures.log" level: warning channels: ["security"] ``` ### Créer un listener pour les événements de sécurité ```php // src/EventListener/SecurityEventListener.php namespace App\EventListener; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\Security\Http\Event\LoginFailureEvent; use Symfony\Component\Security\Http\Event\LoginSuccessEvent; #[AsEventListener(event: LoginSuccessEvent::class)] #[AsEventListener(event: LoginFailureEvent::class)] class SecurityEventListener { public function __construct(private LoggerInterface $securityLogger) { } public function onLoginSuccess(LoginSuccessEvent $event): void { $user = $event->getUser(); $request = $event->getRequest(); $this->securityLogger->info('Successful login', [ 'user' => $user->getUserIdentifier(), 'ip' => $request->getClientIp(), 'user_agent' => $request->headers->get('User-Agent'), ]); } public function onLoginFailure(LoginFailureEvent $event): void { $request = $event->getRequest(); $exception = $event->getException(); $this->securityLogger->warning('Failed login attempt', [ 'username' => $request->request->get('_username'), 'ip' => $request->getClientIp(), 'reason' => $exception->getMessage(), 'user_agent' => $request->headers->get('User-Agent'), ]); } } ``` ## 7. Pipeline CI/CD sécurisé ### Configuration GitLab CI complète ```yaml # .gitlab-ci.yml stages: - build - security - test - deploy variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "/certs" build: stage: build image: docker:latest services: - docker:dind script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - docker push $CI_REGISTRY_IMAGE:latest security:container-scan: stage: security image: aquasec/trivy:latest script: - trivy image --severity HIGH,CRITICAL --exit-code 1 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA allow_failure: false security:dependency-scan: stage: security image: composer:latest script: - composer install --no-scripts - composer audit allow_failure: false security:sast: stage: security image: php:8.2-cli script: - curl -L https://github.com/phpstan/phpstan/releases/latest/download/phpstan.phar -o phpstan.phar - php phpstan.phar analyse src --level=8 allow_failure: false test: stage: test image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA script: - php bin/phpunit coverage: '/^\s*Lines:\s*(\d+\.\d+%)/' deploy:production: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - mkdir -p ~/.ssh - chmod 700 ~/.ssh script: - ssh -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_HOST "docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" - ssh -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_HOST "docker-compose up -d" only: - main when: manual ``` ## 8. Checklist de sécurité finale ### Avant chaque déploiement - [ ] Toutes les dépendances sont à jour (`composer audit`) - [ ] Les images Docker sont scannées (Trivy) - [ ] APP_ENV=prod et APP_DEBUG=0 - [ ] Tous les secrets sont externalisés - [ ] HTTPS activé avec certificat valide - [ ] En-têtes de sécurité configurés - [ ] Rate limiting activé - [ ] Logs de sécurité configurés - [ ] Backups automatiques en place - [ ] WAF (Web Application Firewall) configuré si possible ### Configuration de sauvegarde ```bash #!/bin/bash # backup.sh DATE=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="/backups" # Backup de la base de données docker exec mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD} \ --all-databases --single-transaction --quick --lock-tables=false \ > "${BACKUP_DIR}/db_backup_${DATE}.sql" # Chiffrement du backup gpg --encrypt --recipient admin@example.com "${BACKUP_DIR}/db_backup_${DATE}.sql" # Suppression du fichier non chiffré rm "${BACKUP_DIR}/db_backup_${DATE}.sql" # Rotation des backups (garder 30 jours) find ${BACKUP_DIR} -name "db_backup_*.sql.gpg" -mtime +30 -delete # Upload vers stockage distant (S3, etc.) aws s3 cp "${BACKUP_DIR}/db_backup_${DATE}.sql.gpg" \ s3://my-secure-backups/mysql/ --sse AES256 ``` ## Conclusion La sécurisation d'une application Symfony avec Docker est un processus continu qui nécessite une attention constante. En suivant ces bonnes pratiques, vous établissez une base solide pour protéger votre application contre les menaces courantes. ### Points clés à retenir : 1. **Conteneurs sécurisés** : Utilisez des images minimales, scannez les vulnérabilités et n'exécutez jamais en root 2. **Secrets externalisés** : Jamais de credentials dans le code ou les images 3. **Défense en profondeur** : Multipliez les couches de sécurité (réseau, application, données) 4. **Monitoring actif** : Loggez les événements de sécurité et mettez en place des alertes 5. **CI/CD sécurisé** : Intégrez les tests de sécurité dans votre pipeline La sécurité n'est pas une destination mais un voyage. Restez informé des dernières vulnérabilités, mettez régulièrement à jour vos dépendances et effectuez des audits de sécurité périodiques. ### Ressources supplémentaires : - [Symfony Security Documentation](https://symfony.com/doc/current/security.html) - [OWASP Top 10](https://owasp.org/www-project-top-ten/) - [Docker Security Best Practices](https://docs.docker.com/engine/security/) - [CIS Docker Benchmark](https://www.cisecurity.org/benchmark/docker) N'hésitez pas à partager vos propres pratiques de sécurité dans les commentaires !