đ 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.
Programme
Zero Trust : ne fais confiance Ă personne Gratuit
Comprends les principes du Zero Trust, pourquoi le modÚle périmétrique est mort, l'architecture ZTA en détail et le modÚle BeyondCorp de Google.
mTLS et Service Mesh en pratique
Implémente mTLS, déploie un Service Mesh (Istio/Linkerd), utilise Tailscale, et mets en place le Zero Trust progressivement sur Kubernetes.
HashiCorp Vault : les fondamentaux
Pourquoi les secrets sont critiques, les principes d'une bonne gestion, et l'architecture complĂšte de HashiCorp Vault (seal/unseal, auth, policies, secrets engines).
Rotation et gestion avancée des secrets
AWS Secrets Manager, SOPS + age pour le GitOps, architecture de référence, rotation automatique et prévention des fuites de secrets.
Supply Chain Security : SLSA et SBOM
Comprends les menaces sur la supply chain logicielle, le framework SLSA, la signature avec Sigstore/cosign et la génération de SBOM.
Scanning et pipeline sécurisé
Scanning de vulnérabilités avec Trivy et Grype, GitHub Advanced Security, pipeline sécurisé bout en bout et protection contre la dependency confusion.
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
- OWASP â Top 10
- CIS Benchmarks â Hardening guides
- NIST Cybersecurity Framework
- Falco â Runtime Security
- Kyverno â Policy Engine
- nLPD â Texte officiel
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.