Sécurité Informatique : Les Fondamentaux pour DevOps et SRE
La sécurité n'est plus l'affaire d'une équipe dédiée. Dans un monde DevOps, chaque ingénieur est responsable de la sécurité. Ce guide couvre les fondamentaux que tout professionnel IT doit maîtriser.
Les principes fondamentaux
La triade CIA
┌─────────────────────────────────────────────────────────────────┐
│ TRIADE CIA │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ │
│ │ CONFIDENTIALITÉ│ │
│ │ (Secrecy) │ │
│ └───────┬───────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌────────────┐ ┌─────────────────┐ │
│ │ Chiffrement │ │ Contrôle │ │ Gestion des │ │
│ │ des données │ │ d'accès │ │ secrets │ │
│ └──────────────┘ └────────────┘ └─────────────────┘ │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ INTÉGRITÉ │ │ DISPONIBILITÉ │ │
│ │ (Integrity) │ │(Availability) │ │
│ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │
│ ┌─────┴─────┐ ┌─────┴─────┐ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Checksums│ │Signatures│ │Redondance│ │ Backups │ │
│ │ /Hash │ │numériques│ │ HA │ │ DR │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Principe du moindre privilège
# ❌ MAUVAIS : Accès admin complet
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dev-team-admin
subjects:
- kind: Group
name: developers
roleRef:
kind: ClusterRole
name: cluster-admin # Trop de privilèges !
---
# ✅ BON : Accès limité au namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-team-access
namespace: development # Limité à un namespace
subjects:
- kind: Group
name: developers
roleRef:
kind: Role
name: developer-role
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer-role
namespace: development
rules:
# Lecture des pods et logs
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
# Déploiements limités
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "create", "update"]
# Pas de "delete" !
# Pas d'accès aux secrets !
Defense in Depth
┌─────────────────────────────────────────────────────────────────┐
│ DEFENSE IN DEPTH │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Internet │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────┐ ← Couche 1 │
│ │ WAF / DDoS Protection │ Périmètre │
│ └───────────────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────┐ ← Couche 2 │
│ │ Firewall / Load Balancer │ Réseau │
│ └───────────────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────┐ ← Couche 3 │
│ │ Network Policies / Service Mesh │ Micro-seg │
│ └───────────────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────┐ ← Couche 4 │
│ │ Application Security (OWASP) │ Application │
│ └───────────────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────┐ ← Couche 5 │
│ │ Authentication / Authorization │ Identité │
│ └───────────────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────┐ ← Couche 6 │
│ │ Data Encryption (at rest) │ Données │
│ └───────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
OWASP Top 10 (2021)
Les 10 vulnérabilités les plus critiques
| Rang | Vulnérabilité | Description | Impact |
|---|---|---|---|
| A01 | Broken Access Control | Contrôle d'accès défaillant | Élévation de privilèges |
| A02 | Cryptographic Failures | Chiffrement insuffisant | Fuite de données |
| A03 | Injection | SQL, NoSQL, OS, LDAP injection | Exécution de code |
| A04 | Insecure Design | Architecture non sécurisée | Vulnérabilités structurelles |
| A05 | Security Misconfiguration | Configuration par défaut | Exposition de données |
| A06 | Vulnerable Components | Dépendances vulnérables | Supply chain attack |
| A07 | Auth Failures | Authentification faible | Usurpation d'identité |
| A08 | Software Integrity | Intégrité du code non vérifiée | Code malveillant |
| A09 | Logging Failures | Logs insuffisants | Attaques non détectées |
| A10 | SSRF | Server-Side Request Forgery | Accès réseau interne |
Exemples et remediations
A03 - Injection SQL
# ❌ VULNÉRABLE : Injection SQL possible
def get_user_vulnerable(username: str):
query = f"SELECT * FROM users WHERE username = '{username}'"
# Si username = "admin' OR '1'='1" → Retourne TOUS les users !
return db.execute(query)
# ✅ SÉCURISÉ : Requête paramétrée
def get_user_secure(username: str):
query = "SELECT * FROM users WHERE username = %s"
return db.execute(query, (username,))
# ✅ ENCORE MIEUX : ORM avec validation
from sqlalchemy import select
from pydantic import BaseModel, validator
class UserQuery(BaseModel):
username: str
@validator('username')
def validate_username(cls, v):
if not v.isalnum():
raise ValueError('Username must be alphanumeric')
return v
def get_user_orm(query: UserQuery):
stmt = select(User).where(User.username == query.username)
return session.execute(stmt).scalar()
A05 - Security Misconfiguration
# ❌ MAUVAIS : Pod avec privilèges root
apiVersion: v1
kind: Pod
metadata:
name: vulnerable-pod
spec:
containers:
- name: app
image: myapp:latest
# Pas de securityContext = root par défaut !
---
# ✅ BON : Pod durci avec security context
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# Ajouter uniquement ce qui est nécessaire
# capabilities:
# add:
# - NET_BIND_SERVICE
# Ressources limitées
resources:
limits:
memory: "256Mi"
cpu: "500m"
requests:
memory: "128Mi"
cpu: "100m"
# Volumes en lecture seule sauf nécessité
volumeMounts:
- name: tmp
mountPath: /tmp
- name: config
mountPath: /etc/app
readOnly: true
volumes:
- name: tmp
emptyDir: {}
- name: config
configMap:
name: app-config
Hardening système Linux
Checklist de sécurisation
#!/bin/bash
# hardening-linux.sh - Script de sécurisation basique
set -euo pipefail
echo "=== HARDENING LINUX ==="
# 1. Mises à jour de sécurité
echo "[1/10] Mises à jour..."
apt update && apt upgrade -y
apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
# 2. Désactiver les services inutiles
echo "[2/10] Désactivation services inutiles..."
systemctl disable --now cups avahi-daemon bluetooth 2>/dev/null || true
# 3. Configuration SSH sécurisée
echo "[3/10] Hardening SSH..."
cat > /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
# Authentification
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
LoginGraceTime 20
# Sécurité
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
# Algorithmes sécurisés
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Timeouts
ClientAliveInterval 300
ClientAliveCountMax 2
EOF
systemctl restart sshd
# 4. Configuration du firewall
echo "[4/10] Configuration UFW..."
apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
# 5. Paramètres sysctl sécurisés
echo "[5/10] Hardening kernel..."
cat > /etc/sysctl.d/99-hardening.conf << 'EOF'
# Protection réseau
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.tcp_syncookies = 1
# Protection mémoire
kernel.randomize_va_space = 2
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.perf_event_paranoid = 3
# Limiter core dumps
fs.suid_dumpable = 0
EOF
sysctl -p /etc/sysctl.d/99-hardening.conf
# 6. Permissions fichiers critiques
echo "[6/10] Permissions fichiers..."
chmod 600 /etc/shadow
chmod 644 /etc/passwd
chmod 600 /etc/gshadow
chmod 644 /etc/group
chmod 700 /root
# 7. Configuration auditd
echo "[7/10] Configuration audit..."
apt install -y auditd
cat > /etc/audit/rules.d/hardening.rules << 'EOF'
# Suppression des règles existantes
-D
# Buffer size
-b 8192
# Surveillance des fichiers sensibles
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/sudoers -p wa -k sudo_changes
-w /etc/ssh/sshd_config -p wa -k ssh_config
# Surveillance des commandes privilégiées
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
# Surveillance des modifications système
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=4294967295 -k root_commands
EOF
systemctl restart auditd
# 8. Fail2ban
echo "[8/10] Installation Fail2ban..."
apt install -y fail2ban
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
EOF
systemctl enable --now fail2ban
# 9. Limites utilisateurs
echo "[9/10] Configuration limites..."
cat >> /etc/security/limits.conf << 'EOF'
* hard core 0
* hard maxlogins 10
* soft nofile 65535
* hard nofile 65535
EOF
# 10. Banner de connexion
echo "[10/10] Banner légal..."
cat > /etc/issue.net << 'EOF'
***************************************************************************
AUTHORIZED ACCESS ONLY
***************************************************************************
This system is for authorized use only. All activities are monitored and
logged. Unauthorized access will be prosecuted to the full extent of the law.
***************************************************************************
EOF
echo "=== HARDENING TERMINÉ ==="
echo "Redémarrage recommandé pour appliquer tous les changements."
Gestion des secrets
HashiCorp Vault : Configuration de base
# Configuration Vault pour production
storage "raft" {
path = "/opt/vault/data"
node_id = "vault-1"
retry_join {
leader_api_addr = "https://vault-2.internal:8200"
}
retry_join {
leader_api_addr = "https://vault-3.internal:8200"
}
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/opt/vault/certs/vault.crt"
tls_key_file = "/opt/vault/certs/vault.key"
# TLS 1.3 uniquement
tls_min_version = "tls13"
}
# Auto-unseal avec AWS KMS
seal "awskms" {
region = "eu-west-1"
kms_key_id = "alias/vault-unseal"
}
api_addr = "https://vault.internal:8200"
cluster_addr = "https://vault-1.internal:8201"
ui = true
# Audit logging
telemetry {
prometheus_retention_time = "30s"
disable_hostname = true
}
# Activer le moteur de secrets KV v2
vault secrets enable -path=secret kv-v2
# Stocker un secret
vault kv put secret/myapp/config \
database_url="postgres://user:pass@db:5432/app" \
api_key="sk-xxx-secret-key"
# Configurer l'auth Kubernetes
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc:443" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# Créer une policy pour l'application
vault policy write myapp-policy - <<EOF
path "secret/data/myapp/*" {
capabilities = ["read"]
}
EOF
# Créer un rôle Kubernetes
vault write auth/kubernetes/role/myapp \
bound_service_account_names=myapp-sa \
bound_service_account_namespaces=production \
policies=myapp-policy \
ttl=1h
# Injection automatique de secrets via Vault Agent
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
template:
metadata:
annotations:
# Activer l'injection Vault
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "myapp"
# Injecter les secrets dans des fichiers
vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp/config"
vault.hashicorp.com/agent-inject-template-config: |
{{- with secret "secret/data/myapp/config" -}}
DATABASE_URL={{ .Data.data.database_url }}
API_KEY={{ .Data.data.api_key }}
{{- end }}
spec:
serviceAccountName: myapp-sa
containers:
- name: app
image: myapp:latest
env:
# Utiliser le fichier injecté
- name: CONFIG_FILE
value: /vault/secrets/config
volumeMounts:
- name: vault-secrets
mountPath: /vault/secrets
readOnly: true
Secrets Kubernetes avec External Secrets Operator
# ExternalSecret qui synchronise depuis Vault
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: myapp-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: myapp-secrets
creationPolicy: Owner
template:
type: Opaque
data:
# Template Go pour formater les secrets
DATABASE_URL: "{{ .database_url }}"
API_KEY: "{{ .api_key }}"
data:
- secretKey: database_url
remoteRef:
key: secret/data/myapp/config
property: database_url
- secretKey: api_key
remoteRef:
key: secret/data/myapp/config
property: api_key
---
# ClusterSecretStore pour Vault
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.internal:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "external-secrets"
serviceAccountRef:
name: external-secrets-sa
namespace: external-secrets
Network Security
Network Policies Kubernetes
# Politique par défaut : deny all
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # Tous les pods
policyTypes:
- Ingress
- Egress
---
# Autoriser uniquement le trafic nécessaire
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-network-policy
namespace: production
spec:
podSelector:
matchLabels:
app: api-backend
policyTypes:
- Ingress
- Egress
ingress:
# Autoriser depuis l'ingress controller
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
ports:
- protocol: TCP
port: 8080
# Autoriser depuis le frontend
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
egress:
# Autoriser vers la base de données
- to:
- podSelector:
matchLabels:
app: postgresql
ports:
- protocol: TCP
port: 5432
# Autoriser vers Redis
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
# Autoriser DNS
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
mTLS avec Istio
# Activer mTLS strict pour tout le mesh
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT
---
# Authorization Policy : Zero Trust
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: api-authz
namespace: production
spec:
selector:
matchLabels:
app: api-backend
# Deny par défaut, puis allow explicite
action: ALLOW
rules:
# Autoriser le frontend
- from:
- source:
principals:
- "cluster.local/ns/production/sa/frontend-sa"
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/*"]
# Autoriser les health checks depuis Kubernetes
- from:
- source:
namespaces: ["kube-system"]
to:
- operation:
methods: ["GET"]
paths: ["/health/*"]
Scan de vulnérabilités
Pipeline CI/CD sécurisé
stages:
- build
- security-scan
- deploy
variables:
DOCKER_IMAGE: registry.company.com/myapp
# Scan des dépendances
dependency-scan:
stage: security-scan
image: aquasec/trivy:latest
script:
# Scan du code source
- trivy fs --severity HIGH,CRITICAL --exit-code 1 .
# Générer rapport SBOM
- trivy fs --format spdx-json -o sbom.json .
artifacts:
reports:
cyclonedx: sbom.json
# Scan SAST (Static Application Security Testing)
sast-scan:
stage: security-scan
image: semgrep/semgrep:latest
script:
- semgrep scan --config=auto --error --json -o sast-report.json .
artifacts:
reports:
sast: sast-report.json
allow_failure: false
# Scan de l'image Docker
container-scan:
stage: security-scan
image: aquasec/trivy:latest
needs: ["build"]
script:
- trivy image --severity HIGH,CRITICAL --exit-code 1 ${DOCKER_IMAGE}:${CI_COMMIT_SHA}
allow_failure: false
# Scan des secrets accidentels
secrets-scan:
stage: security-scan
image: trufflesecurity/trufflehog:latest
script:
- trufflehog git file://. --since-commit HEAD~10 --fail
allow_failure: false
# Scan IaC (Terraform, Kubernetes)
iac-scan:
stage: security-scan
image: bridgecrew/checkov:latest
script:
- checkov -d . --framework terraform,kubernetes --output junitxml > checkov-report.xml
artifacts:
reports:
junit: checkov-report.xml
# Scan DAST en staging
dast-scan:
stage: security-scan
image: owasp/zap2docker-stable:latest
needs: ["deploy-staging"]
script:
- zap-baseline.py -t https://staging.company.com -r zap-report.html
artifacts:
paths:
- zap-report.html
when: manual
Dockerfile sécurisé
# Étape 1 : Build
FROM golang:1.21-alpine AS builder
# Utilisateur non-root pour le build
RUN adduser -D -g '' appuser
WORKDIR /build
# Copier uniquement les fichiers de dépendances d'abord (cache)
COPY go.mod go.sum ./
RUN go mod download
# Copier le code source
COPY . .
# Build statique avec flags de sécurité
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s -X main.version=${VERSION}" \
-o /app ./cmd/server
# Étape 2 : Image finale minimale
FROM scratch
# Métadonnées
LABEL org.opencontainers.image.source="https://github.com/company/app"
LABEL org.opencontainers.image.vendor="Company"
# Copier les certificats CA pour HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copier l'utilisateur non-root
COPY --from=builder /etc/passwd /etc/passwd
# Copier le binaire
COPY --from=builder /app /app
# Utiliser l'utilisateur non-root
USER appuser
# Port non privilégié
EXPOSE 8080
# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/app", "healthcheck"]
# Point d'entrée
ENTRYPOINT ["/app"]
Checklist sécurité DevOps
## Infrastructure
- [ ] Firewall configuré (deny by default)
- [ ] SSH durci (clés uniquement, no root)
- [ ] Updates automatiques activées
- [ ] Audit logging activé
- [ ] Fail2ban ou équivalent
## Kubernetes
- [ ] RBAC configuré (least privilege)
- [ ] Network Policies en place
- [ ] Pod Security Standards appliqués
- [ ] Secrets chiffrés (Vault, Sealed Secrets)
- [ ] Images scannées et signées
- [ ] Resource limits sur tous les pods
## Application
- [ ] Validation des inputs
- [ ] Requêtes SQL paramétrées
- [ ] Headers de sécurité (CSP, HSTS)
- [ ] Rate limiting
- [ ] Logs d'audit
## CI/CD
- [ ] Scan SAST dans le pipeline
- [ ] Scan des dépendances
- [ ] Scan des images Docker
- [ ] Scan des secrets
- [ ] Scan IaC
## Secrets
- [ ] Pas de secrets en clair dans le code
- [ ] Rotation automatique
- [ ] Accès audité
- [ ] Secrets scope minimal
Conclusion
La sécurité en DevOps repose sur trois piliers :
- Shift Left : Intégrer la sécurité dès le développement
- Automatisation : Scans automatiques dans les pipelines
- Zero Trust : Ne faire confiance à rien par défaut
:::warning Rappel important : La sécurité n'est jamais "terminée". C'est un processus continu d'amélioration, de veille et d'adaptation aux nouvelles menaces. :::