Aller au contenu principal
Avancé 6 chapitres

🔒 SĂ©curitĂ© & DevSecOps

Formation complÚte DevSecOps : hardening Linux, sécurité conteneurs, gestion des secrets, security in CI/CD, conformité suisse nLPD. Du threat modeling à la production sécurisée.

Progression 6 chapitres

Programme

Introduction — La sĂ©curitĂ© n’est plus optionnelle

En 2026, chaque semaine apporte son lot de breaches, de supply chain attacks, et de ransomwares. Les attaques sont automatisĂ©es, industrialisĂ©es, et ciblent tout le monde — pas juste les grands groupes.

Le DevSecOps, c’est pas un buzzword. C’est un constat pragmatique : la sĂ©curitĂ© doit ĂȘtre intĂ©grĂ©e dans le pipeline, pas ajoutĂ©e Ă  la fin. “Shift left” — dĂ©tecte les problĂšmes le plus tĂŽt possible, quand ils coĂ»tent le moins cher Ă  corriger.

Ce que cette formation couvre :

  • Les fondamentaux de la sĂ©curitĂ© applicative
  • Le hardening Linux pour tes serveurs
  • La sĂ©curitĂ© des conteneurs et de Kubernetes
  • La gestion des secrets en production
  • L’intĂ©gration de la sĂ©curitĂ© dans le CI/CD
  • La conformitĂ© suisse (nLPD) et europĂ©enne (RGPD)

Chaque section vient avec des exemples concrets, des configs que tu peux copier-coller, et des outils que tu peux dĂ©ployer aujourd’hui.


Module 1 — Fondamentaux de la sĂ©curitĂ©

La triade CIA

Non, pas la Central Intelligence Agency. La triade de la sĂ©curitĂ© de l’information :

ConfidentialitĂ© — Seules les personnes autorisĂ©es accĂšdent aux donnĂ©es. Chiffrement, contrĂŽle d’accĂšs, classification des donnĂ©es.

IntĂ©gritĂ© — Les donnĂ©es ne sont pas modifiĂ©es sans autorisation. Signatures, checksums, audit logs.

DisponibilitĂ© — Les systĂšmes sont accessibles quand on en a besoin. Redondance, backups, DRP.

Chaque dĂ©cision de sĂ©curitĂ© doit ĂȘtre Ă©valuĂ©e contre ces trois piliers. Un systĂšme ultra-confidentiel mais jamais disponible est aussi inutile qu’un systĂšme toujours up mais dont les donnĂ©es fuitent.

OWASP Top 10 (2025)

Les 10 risques les plus critiques pour les applications web :

A01 — Broken Access Control Le #1 depuis des annĂ©es. Quand un utilisateur peut faire des choses qu’il ne devrait pas.

# ❌ MAUVAIS — pas de vĂ©rification d'autorisation
@app.route('/api/users/<user_id>/data')
def get_user_data(user_id):
    return db.get_user_data(user_id)

# ✅ BON — vĂ©rification que l'utilisateur accĂšde Ă  SES donnĂ©es
@app.route('/api/users/<user_id>/data')
@login_required
def get_user_data(user_id):
    if current_user.id != user_id and not current_user.is_admin:
        abort(403)
    return db.get_user_data(user_id)

A02 — Cryptographic Failures DonnĂ©es sensibles non chiffrĂ©es, algorithmes obsolĂštes (MD5, SHA1), certificats expirĂ©s.

A03 — Injection SQL injection, NoSQL injection, OS command injection. Toujours valide, toujours dangereux.

# ❌ SQL Injection
query = f"SELECT * FROM users WHERE name = '{user_input}'"

# ✅ RequĂȘte paramĂ©trĂ©e
query = "SELECT * FROM users WHERE name = %s"
cursor.execute(query, (user_input,))

A04 — Insecure Design Le problĂšme est dans l’architecture, pas dans le code. Pas de rate limiting, pas de validation mĂ©tier.

A05 — Security Misconfiguration Configs par dĂ©faut, headers manquants, stack traces exposĂ©es, services inutiles actifs.

A06 — Vulnerable and Outdated Components DĂ©pendances avec des CVE connues. npm audit, trivy, dependabot sont tes amis.

A07 — Identification and Authentication Failures Mots de passe faibles, pas de MFA, session mal gĂ©rĂ©e.

A08 — Software and Data Integrity Failures CI/CD non sĂ©curisĂ©, dĂ©pendances non vĂ©rifiĂ©es, mises Ă  jour non signĂ©es.

A09 — Security Logging and Monitoring Failures Pas de logs, pas d’alertes, pas de dĂ©tection. Tu te fais hacker et tu ne le sais pas.

A10 — Server-Side Request Forgery (SSRF) L’application fait des requĂȘtes vers des URLs contrĂŽlĂ©es par l’attaquant.

Threat Modeling — STRIDE

Avant de sécuriser, il faut comprendre les menaces. STRIDE est un framework simple :

  • Spoofing — Usurpation d’identitĂ©
  • Tampering — Modification non autorisĂ©e
  • Repudiation — Nier une action
  • Information Disclosure — Fuite de donnĂ©es
  • Denial of Service — Rendre indisponible
  • Elevation of Privilege — Obtenir des droits non autorisĂ©s

Pour chaque composant de ton systùme, demande-toi : “Quelles menaces STRIDE s’appliquent ici ?”

graph LR
    Client["Client (Browser)"] --> GW["API GW (Nginx)"]
    GW --> Backend["Backend (Node.js)"]
    Backend --> DB["Database (Postgres)"]
    style Client fill:#1a2332,stroke:#3b82f6,color:#f1f5f9
    style GW fill:#1a2332,stroke:#f59e0b,color:#f1f5f9
    style Backend fill:#1a2332,stroke:#22c55e,color:#f1f5f9
    style DB fill:#1a2332,stroke:#dc2626,color:#f1f5f9

Menaces STRIDE :

  • Spoofing : JWT forgĂ©, session hijacking
  • Tampering : modification des requĂȘtes en transit (→ TLS)
  • Repudiation : pas de logs d’audit (→ audit trail)
  • Info Disclosure : stack traces, headers verbeux (→ config)
  • DoS : pas de rate limiting (→ API GW config)
  • Elevation : IDOR, broken access control (→ authz middleware)

Module 2 — Hardening Linux

SSH — La porte d’entrĂ©e

La config SSH par défaut est beaucoup trop permissive. Voici une config durcie :

# /etc/ssh/sshd_config.d/hardening.conf

# Authentification
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
MaxSessions 3
LoginGraceTime 30

# Protocole
Protocol 2
# Algorithmes forts uniquement
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256

# Réseau
Port 2222                    # Change le port par défaut
AddressFamily inet           # IPv4 uniquement (ou inet6, ou any)
ListenAddress 0.0.0.0
ClientAliveInterval 300
ClientAliveCountMax 2

# Restrictions
AllowUsers deploy admin      # Whitelist des utilisateurs
DenyUsers root
PermitEmptyPasswords no
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no

# Logging
LogLevel VERBOSE
SyslogFacility AUTH

# SFTP (si nécessaire, restreint)
Subsystem sftp internal-sftp
Match Group sftponly
    ChrootDirectory /data/sftp/%u
    ForceCommand internal-sftp
    AllowTcpForwarding no
    X11Forwarding no

Applique les changements :

# Valider la config avant de redémarrer (crucial !)
sudo sshd -t

# Si OK, redémarrer
sudo systemctl restart sshd

# IMPORTANT : garde ta session ouverte et teste dans un autre terminal !
ssh -p 2222 deploy@ton-serveur

Gestion des clés SSH

# Générer une clé Ed25519 (recommandé)
ssh-keygen -t ed25519 -C "deploy@devopslab.ch" -f ~/.ssh/id_ed25519_deploy

# Copier la clé publique
ssh-copy-id -i ~/.ssh/id_ed25519_deploy.pub -p 2222 deploy@ton-serveur

# Config client (~/.ssh/config)
Host prod-server
    HostName 203.0.113.42
    Port 2222
    User deploy
    IdentityFile ~/.ssh/id_ed25519_deploy
    IdentitiesOnly yes
    AddKeysToAgent yes
    ForwardAgent no

Firewall — ufw (simple) et nftables (avancĂ©)

ufw — Pour commencer :

# Reset et config de base
sudo ufw reset
sudo ufw default deny incoming
sudo ufw default allow outgoing

# SSH (port custom)
sudo ufw allow 2222/tcp comment 'SSH'

# HTTP/HTTPS
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

# Kubernetes API (depuis un réseau spécifique)
sudo ufw allow from 10.0.0.0/8 to any port 6443 proto tcp comment 'K8s API'

# Rate limiting sur SSH
sudo ufw limit 2222/tcp comment 'SSH rate limit'

# Activer
sudo ufw enable
sudo ufw status verbose

nftables — Pour les configs avancĂ©es :

#!/usr/sbin/nft -f
# /etc/nftables.conf

flush ruleset

table inet filter {
    # Sets pour le rate limiting
    set ssh_meter {
        type ipv4_addr
        flags dynamic
        timeout 5m
    }

    set blocked {
        type ipv4_addr
        flags timeout
    }

    chain input {
        type filter hook input priority 0; policy drop;

        # Trafic établi/relatif
        ct state established,related accept
        ct state invalid drop

        # Loopback
        iif lo accept

        # ICMP (limité)
        ip protocol icmp limit rate 10/second accept
        ip6 nexthdr icmpv6 limit rate 10/second accept

        # SSH avec rate limiting
        tcp dport 2222 ct state new \
            add @ssh_meter { ip saddr limit rate 3/minute burst 5 packets } accept
        tcp dport 2222 ct state new \
            add @blocked { ip saddr timeout 1h } drop

        # HTTP/HTTPS
        tcp dport { 80, 443 } accept

        # Kubernetes (depuis réseau interne)
        ip saddr 10.0.0.0/8 tcp dport 6443 accept

        # Log les drops
        log prefix "[nftables-drop] " flags all counter drop
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}
# Appliquer
sudo nft -f /etc/nftables.conf

# Vérifier
sudo nft list ruleset

# Persister
sudo systemctl enable nftables

AppArmor — Confinement des applications

# Vérifier le statut
sudo aa-status

# Créer un profil pour une app
sudo aa-genprof /usr/local/bin/myapp

# Exemple de profil AppArmor
# /etc/apparmor.d/usr.local.bin.myapp
#include <tunables/global>

/usr/local/bin/myapp {
  #include <abstractions/base>
  #include <abstractions/nameservice>
  #include <abstractions/openssl>

  # Binaire
  /usr/local/bin/myapp mr,

  # Fichiers de configuration (lecture seule)
  /etc/myapp/ r,
  /etc/myapp/** r,

  # Données (lecture/écriture)
  /var/lib/myapp/ rw,
  /var/lib/myapp/** rw,

  # Logs
  /var/log/myapp/ rw,
  /var/log/myapp/** rw,

  # Réseau
  network inet stream,
  network inet dgram,

  # Deny explicite
  deny /etc/shadow r,
  deny /etc/passwd w,
  deny /home/** rw,
  deny /root/** rw,

  # Pas d'exécution d'autres binaires
  deny /bin/** x,
  deny /usr/bin/** x,
}
# Charger le profil
sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.myapp

# Mettre en mode enforce
sudo aa-enforce /usr/local/bin/myapp

# Tester en mode complain d'abord (log sans bloquer)
sudo aa-complain /usr/local/bin/myapp

Audit systĂšme avec auditd

# Installer
sudo apt install auditd audispd-plugins

# RĂšgles d'audit essentielles
# /etc/audit/rules.d/hardening.rules

# Surveillance des fichiers sensibles
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
-w /etc/ssh/sshd_config -p wa -k sshd

# Surveillance des commandes admin
-a always,exit -F arch=b64 -S execve -F euid=0 -k root_commands
-a always,exit -F arch=b32 -S execve -F euid=0 -k root_commands

# Modifications de fichiers dans /etc
-w /etc/ -p wa -k etc_changes

# Surveillance des montages
-a always,exit -F arch=b64 -S mount -S umount2 -k mounts

# Surveillance de Docker
-w /usr/bin/docker -p x -k docker
-w /var/lib/docker -p wa -k docker
-w /etc/docker -p wa -k docker
-w /usr/lib/systemd/system/docker.service -p wa -k docker

# Immutable (doit ĂȘtre la derniĂšre rĂšgle)
-e 2
# Charger les rĂšgles
sudo augenrules --load

# Chercher dans les logs
sudo ausearch -k sshd --start today
sudo ausearch -k root_commands --start recent

# Rapport
sudo aureport --summary
sudo aureport --auth --summary

Hardening automatisé avec un script

#!/bin/bash
# hardening.sh — Script de hardening de base
set -euo pipefail

echo "=== Hardening Linux Server ==="

# 1. Mises Ă  jour
echo "[1/8] Mises Ă  jour systĂšme..."
apt update && apt upgrade -y
apt install -y unattended-upgrades fail2ban ufw auditd

# 2. Automatic security updates
echo "[2/8] Configuration des mises Ă  jour automatiques..."
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Mail "admin@devopslab.ch";
EOF

# 3. Fail2ban
echo "[3/8] Configuration de Fail2ban..."
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
backend = systemd

[sshd]
enabled = true
port = 2222
filter = sshd
maxretry = 3
bantime = 86400
EOF

systemctl enable fail2ban
systemctl restart fail2ban

# 4. Sysctl hardening
echo "[4/8] Hardening kernel (sysctl)..."
cat > /etc/sysctl.d/99-hardening.conf << 'EOF'
# Désactiver le routage IP
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0

# Protection contre le spoofing
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignorer les ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Ignorer les ICMP broadcasts
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Protection SYN flood
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2

# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0

# Kernel hardening
kernel.randomize_va_space = 2
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.yama.ptrace_scope = 1
fs.suid_dumpable = 0
EOF

sysctl --system

# 5. Permissions des fichiers sensibles
echo "[5/8] Permissions des fichiers..."
chmod 600 /etc/shadow
chmod 600 /etc/gshadow
chmod 644 /etc/passwd
chmod 644 /etc/group
chmod 700 /root
chmod 600 /boot/grub/grub.cfg 2>/dev/null || true

# 6. Désactiver les services inutiles
echo "[6/8] Désactivation des services inutiles..."
for svc in avahi-daemon cups bluetooth; do
    systemctl disable "$svc" 2>/dev/null || true
    systemctl stop "$svc" 2>/dev/null || true
done

# 7. BanniĂšre de connexion
echo "[7/8] BanniÚre de sécurité..."
cat > /etc/issue.net << 'EOF'
*******************************************************************
*  Authorized access only. All activity is monitored and logged.  *
*******************************************************************
EOF

# 8. AIDE (intégrité)
echo "[8/8] Installation de AIDE..."
apt install -y aide
aideinit
cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db

echo "=== Hardening terminé ==="
echo "IMPORTANT : Redémarrer le serveur pour appliquer tous les changements"

Module 3 — SĂ©curitĂ© des conteneurs

Scanner les images avec Trivy

Trivy est l’outil de rĂ©fĂ©rence pour scanner les vulnĂ©rabilitĂ©s dans les images Docker, les fichiers IaC, et les dĂ©pendances.

# Installation
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# Scanner une image
trivy image nginx:latest

# Scanner avec seuil de sévérité
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:latest

# Scanner un Dockerfile / IaC
trivy config .
trivy config --severity HIGH,CRITICAL ./terraform/

# Scanner le filesystem (dépendances)
trivy fs --security-checks vuln,secret,config .

# Format JSON pour intégration CI
trivy image --format json --output trivy-report.json myapp:latest

# Ignorer certaines CVE (fichier .trivyignore)
cat > .trivyignore << 'EOF'
# CVE acceptée car pas exploitable dans notre contexte
CVE-2023-12345
# Pas de fix disponible
CVE-2024-67890 exp:2026-06-01
EOF

IntĂ©gration dans le Dockerfile — build multi-stage sĂ©curisĂ© :

# syntax=docker/dockerfile:1

# ─── Build stage ───
FROM node:20-alpine AS builder

# Utilisateur non-root dĂšs le build
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup

WORKDIR /app

# Copier d'abord les fichiers de dépendances (cache layer)
COPY --chown=appuser:appgroup package*.json ./
RUN npm ci --only=production && npm cache clean --force

COPY --chown=appuser:appgroup . .
RUN npm run build

# ─── Production stage ───
FROM node:20-alpine AS production

# Labels OCI
LABEL org.opencontainers.image.source="https://github.com/company/myapp"
LABEL org.opencontainers.image.description="My App"

# Security updates
RUN apk update && apk upgrade --no-cache && \
    apk add --no-cache dumb-init && \
    rm -rf /var/cache/apk/*

# Utilisateur non-root
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup

WORKDIR /app

# Copier uniquement ce qui est nécessaire
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./

# Filesystem en lecture seule autant que possible
RUN chmod -R 555 /app/dist

# Pas de shell pour réduire la surface d'attaque
# (commentĂ© car ça complique le debug — Ă  activer en prod durcie)
# RUN rm /bin/sh

USER appuser

# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/healthz || exit 1

EXPOSE 3000

# dumb-init pour gérer les signaux correctement
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/main.js"]

Rootless containers

# Docker rootless mode
dockerd-rootless-setuptool.sh install
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

# Podman (rootless par défaut)
podman run --rm -it nginx:latest

# Vérifier qu'un conteneur tourne en non-root
docker inspect --format='{{.Config.User}}' mycontainer
# Doit retourner un user non-vide et != "0"/"root"

Pod Security Standards (Kubernetes)

Kubernetes a trois niveaux de sécurité pour les pods :

# Namespace avec Pod Security Standards
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    # Enforce = bloque les pods non conformes
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    # Warn = avertit mais laisse passer
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: latest
    # Audit = log seulement
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: latest

Un pod conforme au niveau restricted :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-app
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: secure-app
  template:
    metadata:
      labels:
        app: secure-app
    spec:
      # Pas de service account auto-monté
      automountServiceAccountToken: false

      # Security context au niveau du pod
      securityContext:
        runAsNonRoot: true
        runAsUser: 1001
        runAsGroup: 1001
        fsGroup: 1001
        seccompProfile:
          type: RuntimeDefault

      containers:
        - name: app
          image: ghcr.io/company/myapp:v1.2.3@sha256:abc123...
          ports:
            - containerPort: 3000
              protocol: TCP

          # Security context au niveau du conteneur
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
            privileged: false

          # Resources obligatoires
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi

          # Probes
          livenessProbe:
            httpGet:
              path: /healthz
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /readyz
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5

          # Volumes pour les répertoires qui ont besoin d'écriture
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: cache
              mountPath: /app/.cache

      volumes:
        - name: tmp
          emptyDir:
            sizeLimit: 100Mi
        - name: cache
          emptyDir:
            sizeLimit: 200Mi

Falco — DĂ©tection d’intrusion runtime

Falco surveille les appels systÚme et détecte les comportements suspects en temps réel.

# Installation avec Helm
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update

helm install falco falcosecurity/falco \
  --namespace falco \
  --create-namespace \
  --set falcosidekick.enabled=true \
  --set falcosidekick.config.slack.webhookurl="https://hooks.slack.com/services/xxx" \
  --set driver.kind=ebpf

RÚgles Falco personnalisées :

# /etc/falco/rules.d/custom-rules.yaml

# Détecter un shell dans un conteneur
- rule: Shell in container
  desc: A shell was started in a container
  condition: >
    spawned_process and
    container and
    proc.name in (bash, sh, zsh, dash, ash) and
    not container.image.repository in (allowed_shell_images)
  output: >
    Shell started in container
    (user=%user.name container=%container.name
    image=%container.image.repository
    shell=%proc.name parent=%proc.pname
    cmdline=%proc.cmdline)
  priority: WARNING
  tags: [container, shell, mitre_execution]

# Détecter la lecture de fichiers sensibles
- rule: Read sensitive file in container
  desc: Sensitive file read in container
  condition: >
    open_read and
    container and
    fd.name in (/etc/shadow, /etc/passwd, /etc/kubernetes/admin.conf) and
    not proc.name in (sshd, login)
  output: >
    Sensitive file read in container
    (file=%fd.name user=%user.name container=%container.name
    image=%container.image.repository)
  priority: ERROR
  tags: [container, sensitive_files]

# Détecter le minage de crypto
- rule: Crypto mining detected
  desc: Potential cryptocurrency mining activity
  condition: >
    spawned_process and
    container and
    (proc.name in (xmrig, minerd, cpuminer) or
     proc.cmdline contains "stratum+tcp" or
     proc.cmdline contains "stratum+ssl")
  output: >
    Crypto mining detected
    (container=%container.name image=%container.image.repository
    process=%proc.name cmdline=%proc.cmdline)
  priority: CRITICAL
  tags: [container, crypto, mitre_execution]

# Listes utilisées dans les rÚgles
- list: allowed_shell_images
  items: [debug-container, kubectl]

Network Policies — Segmentation rĂ©seau K8s

# Deny all par dĂ©faut (CRUCIAL — Ă  appliquer sur chaque namespace)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

---
# Autoriser uniquement le trafic nécessaire
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-app-traffic
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: myapp
  policyTypes:
    - Ingress
    - Egress
  ingress:
    # Trafic depuis l'ingress controller
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
          podSelector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx
      ports:
        - protocol: TCP
          port: 3000
  egress:
    # DNS
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
    # Base de données
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432
    # Redis
    - to:
        - podSelector:
            matchLabels:
              app: redis
      ports:
        - protocol: TCP
          port: 6379

Module 4 — Gestion des secrets

Le problĂšme

Les secrets (mots de passe, API keys, certificats) sont le talon d’Achille de la plupart des systùmes. En 2026, on trouve encore des secrets dans :

  • Le code source (git history)
  • Les variables d’environnement en clair
  • Les fichiers de config non chiffrĂ©s
  • Les CI/CD sans rotation

RĂšgle d’or : Un secret qui a touchĂ© Git est compromis. Point.

HashiCorp Vault

Vault est le standard pour la gestion centralisée des secrets.

# Installation (dev mode pour apprendre)
vault server -dev -dev-root-token-id="dev-token"

# En prod, config réelle :
# /etc/vault.d/vault.hcl
# /etc/vault.d/vault.hcl
storage "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-1"
}

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_cert_file = "/opt/vault/tls/vault.crt"
  tls_key_file  = "/opt/vault/tls/vault.key"
}

api_addr = "https://vault.devopslab.ch:8200"
cluster_addr = "https://vault.devopslab.ch:8201"

ui = true
disable_mlock = false

telemetry {
  prometheus_retention_time = "30s"
  disable_hostname = true
}

Utilisation de Vault :

# Authentification
export VAULT_ADDR='https://vault.devopslab.ch:8200'
vault login -method=oidc

# Écrire un secret
vault kv put secret/myapp/production \
  db_password="SuperSecretP@ss!" \
  api_key="sk-abc123456" \
  redis_url="redis://:password@redis:6379"

# Lire un secret
vault kv get secret/myapp/production
vault kv get -field=db_password secret/myapp/production

# Secrets dynamiques (base de données)
vault secrets enable database

vault write database/config/postgres \
  plugin_name=postgresql-database-plugin \
  connection_url="postgresql://{{username}}:{{password}}@postgres:5432/mydb" \
  allowed_roles="myapp-role" \
  username="vault_admin" \
  password="vault_admin_pass"

vault write database/roles/myapp-role \
  db_name=postgres \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

# Obtenir des credentials dynamiques (expiration automatique !)
vault read database/creds/myapp-role

Vault dans Kubernetes avec l’Agent Injector :

# Pod avec injection de secrets Vault
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    metadata:
      annotations:
        # Vault Agent Injector annotations
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "myapp"
        vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp/production"
        vault.hashicorp.com/agent-inject-template-config: |
          {{- with secret "secret/data/myapp/production" -}}
          DB_PASSWORD={{ .Data.data.db_password }}
          API_KEY={{ .Data.data.api_key }}
          REDIS_URL={{ .Data.data.redis_url }}
          {{- end }}
    spec:
      serviceAccountName: myapp
      containers:
        - name: myapp
          image: myapp:latest
          command: ["sh", "-c", "source /vault/secrets/config && node dist/main.js"]

Sealed Secrets (Kubernetes)

Pour stocker des secrets chiffrés dans Git :

# Installation du controller
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system

# Installation du CLI
brew install kubeseal

# Créer un secret standard
kubectl create secret generic myapp-secrets \
  --from-literal=db-password='SuperSecret!' \
  --from-literal=api-key='sk-abc123' \
  --dry-run=client -o yaml > secret.yaml

# Le sceller (chiffrer avec la clé publique du cluster)
kubeseal --format yaml < secret.yaml > sealed-secret.yaml

# Le sealed secret peut aller dans Git en toute sécurité
cat sealed-secret.yaml
# sealed-secret.yaml — SAFE TO COMMIT
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: myapp-secrets
  namespace: production
spec:
  encryptedData:
    db-password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...
    api-key: AgBjY9GEk7k3h0sVPeJ5kSQHhZ...
  template:
    metadata:
      name: myapp-secrets
      namespace: production

SOPS — Encryption de fichiers

# Installation
brew install sops

# Avec age (recommandé, plus simple que PGP)
age-keygen -o ~/.sops/key.txt
export SOPS_AGE_KEY_FILE=~/.sops/key.txt

# Créer un fichier .sops.yaml pour la config
cat > .sops.yaml << 'EOF'
creation_rules:
  - path_regex: \.enc\.yaml$
    age: >-
      age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
  - path_regex: environments/production/.*
    age: >-
      age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
EOF

# Chiffrer un fichier
sops --encrypt secrets.yaml > secrets.enc.yaml

# Éditer un fichier chiffrĂ© (dĂ©chiffre temporairement)
sops secrets.enc.yaml

# Déchiffrer
sops --decrypt secrets.enc.yaml

Exemple de fichier SOPS :

# secrets.enc.yaml — le contenu est chiffrĂ©, la structure est visible
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
stringData:
  db-password: ENC[AES256_GCM,data:abc123...,iv:xxx,tag:yyy,type:str]
  api-key: ENC[AES256_GCM,data:def456...,iv:xxx,tag:yyy,type:str]
sops:
  age:
    - recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
      enc: |
        -----BEGIN AGE ENCRYPTED FILE-----
        ...
        -----END AGE ENCRYPTED FILE-----
  lastmodified: "2026-03-20T08:00:00Z"
  version: 3.8.1

External Secrets Operator (ESO)

ESO synchronise les secrets depuis des providers externes (Vault, AWS SM, GCP SM, Azure KV) vers des Kubernetes Secrets.

# Installation
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
  -n external-secrets --create-namespace
# ClusterSecretStore — connexion à Vault
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.devopslab.ch:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "external-secrets"
          serviceAccountRef:
            name: "external-secrets"
            namespace: "external-secrets"

---
# ExternalSecret — synchronise un secret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: myapp-secrets
  namespace: production
spec:
  refreshInterval: 5m
  secretStoreRef:
    kind: ClusterSecretStore
    name: vault-backend
  target:
    name: myapp-secrets
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        DATABASE_URL: "postgresql://app:{{ .db_password }}@postgres:5432/mydb"
        REDIS_URL: "{{ .redis_url }}"
  data:
    - secretKey: db_password
      remoteRef:
        key: secret/data/myapp/production
        property: db_password
    - secretKey: redis_url
      remoteRef:
        key: secret/data/myapp/production
        property: redis_url

Module 5 — SĂ©curitĂ© dans le CI/CD

SAST — Static Application Security Testing

Analyse le code source sans l’exĂ©cuter.

# GitHub Actions — SAST avec Semgrep
sast:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4

    - name: Semgrep SAST
      uses: returntocorp/semgrep-action@v1
      with:
        config: >-
          p/security-audit
          p/secrets
          p/owasp-top-ten
          p/javascript
          p/typescript
          p/python
      env:
        SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}

    - name: CodeQL Analysis
      uses: github/codeql-action/analyze@v3
      with:
        languages: javascript, python

DAST — Dynamic Application Security Testing

Teste l’application en cours d’exĂ©cution.

# DAST avec OWASP ZAP
dast:
  runs-on: ubuntu-latest
  needs: [deploy-staging]
  steps:
    - name: OWASP ZAP Scan
      uses: zaproxy/action-full-scan@v0.10.0
      with:
        target: 'https://staging.monapp.ch'
        rules_file_name: '.zap/rules.tsv'
        allow_issue_writing: false
        artifact_name: zap-report

    - name: Upload ZAP Report
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: zap-report
        path: report_html.html

SCA — Software Composition Analysis

Analyse les dépendances pour les vulnérabilités connues.

# Dependabot config
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
    open-pull-requests-limit: 10
    reviewers:
      - "security-team"
    labels:
      - "dependencies"
      - "security"
    groups:
      production-deps:
        patterns:
          - "*"
        exclude-patterns:
          - "@types/*"
          - "eslint*"
          - "prettier*"

  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Cosign — Signature d’images

# Générer une paire de clés
cosign generate-key-pair

# Signer une image
cosign sign --key cosign.key ghcr.io/company/myapp:v1.2.3

# Vérifier la signature
cosign verify --key cosign.pub ghcr.io/company/myapp:v1.2.3

# Keyless signing (avec Sigstore/Fulcio) — recommandĂ©
cosign sign ghcr.io/company/myapp@sha256:abc123...
cosign verify \
  --certificate-identity=deploy@company.com \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  ghcr.io/company/myapp@sha256:abc123...

Dans le CI :

sign-image:
  runs-on: ubuntu-latest
  needs: [build-docker]
  permissions:
    packages: write
    id-token: write  # Nécessaire pour keyless signing
  steps:
    - name: Install Cosign
      uses: sigstore/cosign-installer@v3

    - name: Login to GHCR
      uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Sign image
      run: |
        cosign sign --yes \
          ghcr.io/${{ github.repository }}@${{ needs.build-docker.outputs.digest }}

OPA / Kyverno — Policy as Code

Kyverno — Plus simple, natif Kubernetes :

# Interdire les images sans tag ou avec 'latest'
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: require-image-tag
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "L'image doit avoir un tag spécifique (pas 'latest')."
        pattern:
          spec:
            containers:
              - image: "*:*"
    - name: validate-image-tag
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Le tag 'latest' est interdit."
        pattern:
          spec:
            containers:
              - image: "!*:latest"

---
# Exiger des resource limits
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-limits
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Les containers doivent avoir des resource limits."
        pattern:
          spec:
            containers:
              - resources:
                  limits:
                    memory: "?*"
                    cpu: "?*"

---
# Exiger des images signées avec Cosign
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  webhookTimeoutSeconds: 30
  rules:
    - name: verify-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "ghcr.io/company/*"
          attestors:
            - entries:
                - keyless:
                    subject: "deploy@company.com"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev

Pipeline sécurisé complet

# .github/workflows/secure-pipeline.yml
name: Secure CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read
  security-events: write
  packages: write
  id-token: write

jobs:
  # ─── Pre-commit checks ───
  secrets-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: TruffleHog scan
        uses: trufflesecurity/trufflehog@main
        with:
          extra_args: --only-verified

  # ─── SAST ───
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Semgrep
        uses: returntocorp/semgrep-action@v1
        with:
          config: p/security-audit p/owasp-top-ten

  # ─── SCA ───
  sca:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm audit --audit-level=high
      - name: Trivy filesystem scan
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          severity: 'HIGH,CRITICAL'
          exit-code: '1'

  # ─── Build & scan image ───
  build:
    needs: [secrets-scan, sast, sca]
    runs-on: ubuntu-latest
    outputs:
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        id: build
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Trivy image scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
          severity: 'HIGH,CRITICAL'
          exit-code: '1'

  # ─── Sign ───
  sign:
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - uses: sigstore/cosign-installer@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - run: |
          cosign sign --yes \
            ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}

  # ─── Deploy ───
  deploy:
    needs: [sign]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment: production
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to K8s
        run: |
          # Le cluster Kyverno vérifie la signature automatiquement
          kubectl set image deployment/app \
            app=ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }} \
            -n production

Module 6 — ConformitĂ© Suisse

nLPD vs RGPD — Les diffĂ©rences qui comptent

La nouvelle Loi sur la Protection des DonnĂ©es (nLPD), en vigueur depuis septembre 2023 en Suisse, s’inspire du RGPD mais a ses spĂ©cificitĂ©s.

Points communs :

  • Consentement Ă©clairĂ© pour le traitement des donnĂ©es
  • Droit d’accĂšs, de rectification, de suppression
  • Notification en cas de violation
  • Privacy by Design et Privacy by Default
  • Registre des activitĂ©s de traitement

Différences clés :

  • Amendes : nLPD = jusqu’à CHF 250’000 (personnes physiques responsables, pas l’entreprise). RGPD = jusqu’à 4% du CA mondial (l’entreprise).
  • DPO : nLPD = pas obligatoire (mais recommandĂ©, appelĂ© “conseiller Ă  la protection des donnĂ©es”). RGPD = obligatoire dans certains cas.
  • Transfert hors CH : nLPD = liste des pays avec protection adĂ©quate publiĂ©e par le Conseil fĂ©dĂ©ral. Similaire au RGPD mais liste propre.
  • AIPD : Analyse d’Impact relative Ă  la Protection des DonnĂ©es — obligatoire si traitement Ă  risque Ă©levĂ© (comme le RGPD).
  • PortĂ©e : nLPD s’applique aux donnĂ©es des personnes physiques uniquement (plus des personnes morales, contrairement Ă  l’ancienne LPD).

Hébergement des données en Suisse

Pour la conformitĂ© nLPD, l’hĂ©bergement en Suisse est souvent prĂ©fĂ©rĂ© :

Providers cloud suisses :

  • Exoscale — BasĂ© Ă  Lausanne, datacenters CH-GVA-2, CH-DK-2
  • Infomaniak — GenĂšve, certifiĂ© ISO 27001
  • Proton — GenĂšve, focus privacy
  • Open Systems — Zurich
  • SWITCH — Pour l’acadĂ©mique et la recherche

Hyperscalers avec régions suisses :

  • Azure Switzerland North (Zurich) et Switzerland West (GenĂšve)
  • Google Cloud Zurich (europe-west6)
  • AWS — Pas de rĂ©gion suisse en 2026 (la plus proche : Frankfurt)
  • Oracle Cloud — Zurich
# Terraform — Forcer la rĂ©gion suisse
# Exoscale
provider "exoscale" {
  key    = var.exoscale_key
  secret = var.exoscale_secret
}

resource "exoscale_compute_instance" "app" {
  zone         = "ch-gva-2"
  name         = "app-server"
  template_id  = data.exoscale_template.ubuntu.id
  type         = "standard.medium"
  disk_size    = 50
  # ...
}

# Azure — RĂ©gion Suisse
provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "main" {
  name     = "rg-myapp-prod"
  location = "Switzerland North"  # Zurich
}

# GCP — RĂ©gion Suisse
provider "google" {
  project = var.project_id
  region  = "europe-west6"  # Zurich
}

resource "google_compute_instance" "app" {
  name         = "app-server"
  machine_type = "e2-standard-2"
  zone         = "europe-west6-a"  # Zurich
  # ...
}

Audit Trail — TraçabilitĂ© complĂšte

La nLPD exige de pouvoir retracer qui a accédé à quelles données et quand.

# Python — Middleware d'audit trail
import json
import datetime
from functools import wraps

class AuditLogger:
    def __init__(self, logger):
        self.logger = logger

    def log_access(self, user_id, action, resource, details=None):
        audit_entry = {
            "timestamp": datetime.datetime.utcnow().isoformat() + "Z",
            "user_id": user_id,
            "action": action,
            "resource": resource,
            "details": details,
            "ip_address": request.remote_addr if request else None,
            "user_agent": request.headers.get("User-Agent") if request else None,
        }
        # Log structurĂ© — sera capturĂ© par Loki/ELK
        self.logger.info(json.dumps(audit_entry))

    def log_data_access(self, user_id, data_subject_id, purpose):
        """Log spĂ©cifique nLPD — accĂšs aux donnĂ©es personnelles"""
        self.log_access(
            user_id=user_id,
            action="PERSONAL_DATA_ACCESS",
            resource=f"data_subject:{data_subject_id}",
            details={
                "purpose": purpose,
                "legal_basis": "consent",  # ou "contract", "legal_obligation"
            }
        )

# Décorateur pour les endpoints sensibles
def audit_personal_data(purpose):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            audit.log_data_access(
                user_id=current_user.id,
                data_subject_id=kwargs.get('user_id'),
                purpose=purpose
            )
            return f(*args, **kwargs)
        return wrapper
    return decorator

# Usage
@app.route('/api/users/<user_id>/profile')
@login_required
@audit_personal_data(purpose="profile_consultation")
def get_user_profile(user_id):
    return db.get_user_profile(user_id)

Checklist conformité nLPD pour un projet tech

# nlpd-checklist.yaml — À vĂ©rifier pour chaque projet
conformité_nlpd:
  documentation:
    - registre_activites_traitement: true
    - politique_confidentialite: true
    - analyse_impact: "si traitement à risque élevé"
    - contrats_sous_traitants: true

  technique:
    - chiffrement_transit: "TLS 1.3 minimum"
    - chiffrement_repos: "AES-256"
    - pseudonymisation: "quand possible"
    - hébergement: "Suisse ou pays adéquat"
    - backup_chiffré: true
    - retention_définie: true
    - suppression_automatique: true

  accĂšs:
    - authentification_forte: "MFA obligatoire"
    - moindre_privilĂšge: true
    - audit_trail: "tous les accÚs aux données personnelles"
    - revue_accÚs_périodique: "trimestrielle"

  droits_personnes:
    - droit_accĂšs: "endpoint /api/my-data"
    - droit_rectification: "endpoint /api/my-data (PATCH)"
    - droit_suppression: "endpoint /api/my-data (DELETE)"
    - droit_portabilité: "export JSON/CSV"
    - délai_réponse: "30 jours max"

  incident:
    - procédure_notification: true
    - délai_notification_pfpdt: "dÚs que possible"
    - délai_notification_personnes: "si risque élevé"
    - registre_violations: true

  international:
    - transfert_hors_suisse: "vérifier liste pays adéquats"
    - clauses_contractuelles: "si pays non adéquat"
    - information_personnes: "si transfert hors CH"

Checklist sĂ©curitĂ© — RĂ©cap

Infrastructure

  • SSH durci (clĂ©s uniquement, port custom, fail2ban)
  • Firewall configurĂ© (deny by default)
  • Mises Ă  jour automatiques de sĂ©curitĂ©
  • Audit systĂšme actif (auditd)
  • AppArmor/SELinux en mode enforce

Conteneurs

  • Images scannĂ©es (Trivy) dans le CI
  • Conteneurs non-root
  • Filesystem read-only
  • Pod Security Standards = restricted
  • Network Policies (deny all par dĂ©faut)
  • Runtime security (Falco)

Secrets

  • Aucun secret dans Git (jamais)
  • Secrets dans Vault / ESO / Sealed Secrets
  • Rotation automatique
  • AccĂšs auditables

CI/CD

  • SAST (Semgrep, CodeQL)
  • SCA (npm audit, Trivy fs)
  • DAST (ZAP)
  • Secret scanning (TruffleHog)
  • Images signĂ©es (Cosign)
  • Policies (Kyverno/OPA)

Conformité

  • Registre des traitements
  • Audit trail sur les donnĂ©es personnelles
  • HĂ©bergement conforme (Suisse/pays adĂ©quat)
  • ProcĂ©dure de notification d’incidents
  • Droits des personnes implĂ©mentĂ©s

Pour aller plus loin


La sécurité est un processus, pas un produit. Reviens réguliÚrement sur cette checklist et fais évoluer ta posture de sécurité avec ton infrastructure. Consulte aussi nos formations sur le CI/CD et le Monitoring.