20. Sécurité par conception#

La sécurité n’est pas une couche qu’on applique sur une architecture terminée. C’est une propriété qui se construit dès la conception, se valide à chaque étape du cycle de développement, et se maintient dans le temps. Un système sécurisé par conception résiste mieux aux menaces futures parce que ses principes fondamentaux limitent structurellement la surface d’attaque.

Security by design#

Shift left#

Le mouvement « shift left » en sécurité signifie déplacer les activités de sécurité vers le début du cycle de développement — conception, spécification, code — plutôt que de les concentrer en fin de projet (tests de pénétration avant la mise en production).

La raison est économique : corriger une vulnérabilité au stade de la conception coûte 1×. En développement : 10×. En test : 100×. En production : 1000× ou plus, sans compter l’impact réputationnel.

Principe du moindre privilège#

Chaque composant, chaque service, chaque utilisateur ne doit avoir que les permissions strictement nécessaires à sa fonction. Un service de lecture ne doit pas avoir de droits en écriture. Un microservice de commandes ne doit pas avoir accès à la base de données des utilisateurs. Une clé API ne doit pas avoir plus de portée que nécessaire.

Ce principe limite mécaniquement le blast radius d’une compromission : si un attaquant prend le contrôle d’un service, il ne peut pas accéder à l’ensemble du système.

Security by design vs security by obscurity

La sécurité par l’obscurité (cacher les détails d’implémentation pour décourager les attaquants) est une fausse protection. Un attaquant suffisamment motivé découvre les détails. La sécurité par conception suppose que l’attaquant connaît tout de l’architecture — et le système reste sécurisé quand même.

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# Coût de correction d'une vulnérabilité selon la phase (relatif)
phases = ['Conception\n& Design', 'Développement', 'Tests\nQA', 'Intégration\n/ Staging', 'Production']
costs_relative = [1, 10, 100, 300, 1000]  # coût relatif de correction

colors = ['#27ae60', '#2980b9', '#f39c12', '#e67e22', '#e74c3c']

fig, ax = plt.subplots(figsize=(12, 6))

bars = ax.bar(phases, costs_relative, color=colors, edgecolor='white', linewidth=1.5, width=0.6)
ax.set_yscale('log')
ax.set_ylabel("Coût relatif de correction (échelle log)")
ax.set_title("Coût de correction d'une vulnérabilité selon la phase\n(Principe Shift Left)", fontsize=13)

for bar, cost in zip(bars, costs_relative):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() * 1.3,
            f'{cost}×', ha='center', va='bottom', fontweight='bold', fontsize=12)

# Flèche "shift left"
ax.annotate('', xy=(0.1, 500), xytext=(3.5, 500),
            arrowprops=dict(arrowstyle='<-', color='navy', lw=2.5))
ax.text(1.8, 700, '← Shift Left', ha='center', fontsize=11, color='navy', fontweight='bold')

ax.set_ylim(0.5, 5000)
plt.savefig("shift_left_cost.png", dpi=100, bbox_inches='tight')
plt.show()
_images/4caec79fda41afed3afb0df6a0ebbe0e26097059119e3b1d1787fab013859586.png

Threat modeling STRIDE#

Le threat modeling est un processus structuré pour identifier, classer et traiter les menaces sur un système avant de les implémenter. STRIDE est la taxonomie créée par Microsoft :

S — Spoofing : usurpation d’identité. Un attaquant se fait passer pour un utilisateur légitime. Contre-mesures : authentification forte, MFA, tokens signés.

T — Tampering : altération des données. Un attaquant modifie des données en transit ou au repos. Contre-mesures : chiffrement, signatures numériques, intégrité des messages (HMAC).

R — Repudiation : déni d’une action. Un utilisateur nie avoir effectué une opération. Contre-mesures : logs d’audit signés, non-répudiation via signatures cryptographiques.

I — Information disclosure : fuite d’informations. Un attaquant accède à des données confidentielles. Contre-mesures : chiffrement au repos et en transit, principe du moindre privilège, masquage des erreurs.

D — Denial of Service : indisponibilité du service. Un attaquant empêche les utilisateurs légitimes d’accéder au service. Contre-mesures : rate limiting, autoscaling, CDN, WAF.

E — Elevation of privilege : élévation de droits. Un attaquant obtient des permissions supérieures à celles qui lui sont accordées. Contre-mesures : validation des autorisations côté serveur, principe du moindre privilège.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
from matplotlib.patches import FancyArrowPatch

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, axes = plt.subplots(1, 2, figsize=(16, 7))

# --- DFD simplifié e-commerce avec menaces STRIDE ---
ax = axes[0]
ax.set_xlim(0, 10)
ax.set_ylim(0, 8)
ax.axis('off')

# Composants
components = {
    'Browser\nUtilisateur':    (1.5, 6.5, '#3498db'),
    'API\nGateway':            (5.0, 6.5, '#2ecc71'),
    'Service\nCommandes':      (3.0, 3.5, '#e74c3c'),
    'Service\nPaiement':       (7.0, 3.5, '#9b59b6'),
    'Base de\ndonnées':        (5.0, 1.2, '#e67e22'),
    'Service\nNotif.':         (8.5, 5.5, '#1abc9c'),
}

boxes = {}
for name, (x, y, color) in components.items():
    box = mpatches.FancyBboxPatch((x-1, y-0.5), 2, 1,
                                   boxstyle="round,pad=0.1",
                                   facecolor=color, edgecolor='white',
                                   linewidth=2, alpha=0.85)
    ax.add_patch(box)
    ax.text(x, y, name, ha='center', va='center', fontsize=8,
            fontweight='bold', color='white', wrap=True)
    boxes[name] = (x, y)

# Flux de données
flows = [
    ('Browser\nUtilisateur', 'API\nGateway', 'HTTPS'),
    ('API\nGateway', 'Service\nCommandes', 'mTLS'),
    ('API\nGateway', 'Service\nPaiement', 'mTLS'),
    ('Service\nCommandes', 'Base de\ndonnées', 'SQL/TLS'),
    ('Service\nPaiement', 'Base de\ndonnées', 'SQL/TLS'),
    ('Service\nCommandes', 'Service\nNotif.', 'Event'),
]

for src, dst, label in flows:
    x1, y1 = boxes[src]
    x2, y2 = boxes[dst]
    ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
                arrowprops=dict(arrowstyle='->', color='#555', lw=1.5,
                                connectionstyle='arc3,rad=0.1'))
    mx, my = (x1+x2)/2, (y1+y2)/2
    ax.text(mx, my + 0.15, label, ha='center', fontsize=6.5, color='#333',
            style='italic')

# Menaces annotées
threats = [
    (3.0, 7.2, 'S: Spoofing session', 'red'),
    (5.0, 5.7, 'T: Tampering requête', 'orange'),
    (1.5, 5.2, 'I: Info disclosure (logs)', 'purple'),
    (7.0, 2.2, 'D: DoS paiement', 'brown'),
    (5.0, 0.4, 'E: SQL injection → priv.', 'darkred'),
]

for tx, ty, label, color in threats:
    ax.text(tx, ty, f'⚠ {label}', ha='center', fontsize=7.5,
            color=color, fontweight='bold',
            bbox=dict(boxstyle='round,pad=0.2', facecolor='lightyellow',
                      edgecolor=color, alpha=0.9))

ax.set_title("DFD e-commerce simplifié\navec menaces STRIDE", fontsize=11, fontweight='bold')

# --- Matrice STRIDE par composant ---
ax2 = axes[1]

stride_labels = ['Spoofing', 'Tampering', 'Repudiation', 'Info Disc.', 'DoS', 'Elev. Priv.']
comp_labels = ['API Gateway', 'Cmd Service', 'Pay Service', 'Database', 'Notif. Svc']

# Scores de risque (0=faible, 3=critique)
risk_matrix = np.array([
    [3, 2, 1, 2, 3, 1],  # API Gateway
    [2, 2, 2, 1, 2, 2],  # Cmd Service
    [3, 3, 3, 3, 2, 3],  # Pay Service (plus critique)
    [1, 3, 2, 3, 1, 3],  # Database
    [1, 1, 1, 2, 2, 1],  # Notif. Svc
])

cmap = plt.cm.RdYlGn_r
im = ax2.imshow(risk_matrix, cmap=cmap, vmin=0, vmax=3, aspect='auto')

ax2.set_xticks(range(len(stride_labels)))
ax2.set_xticklabels(stride_labels, rotation=35, ha='right', fontsize=9)
ax2.set_yticks(range(len(comp_labels)))
ax2.set_yticklabels(comp_labels, fontsize=9)
ax2.set_title("Matrice de risque STRIDE\npar composant (0=faible → 3=critique)", fontsize=11, fontweight='bold')

for i in range(len(comp_labels)):
    for j in range(len(stride_labels)):
        val = risk_matrix[i, j]
        label = ['Faible', 'Moyen', 'Élevé', 'Critique'][val]
        ax2.text(j, i, label, ha='center', va='center', fontsize=7.5,
                 color='white' if val >= 2 else 'black', fontweight='bold')

plt.colorbar(im, ax=ax2, label='Niveau de risque', shrink=0.8)
plt.suptitle("Threat Modeling STRIDE — Système e-commerce", fontsize=13, fontweight='bold')
plt.savefig("stride_analysis.png", dpi=100, bbox_inches='tight')
plt.show()
_images/47a9929203b83d17662988ba96c540b12988f5e03204c89adcfb12986dbd6fff.png

OWASP Top 10 — vue architecturale#

L’OWASP Top 10 est souvent présenté comme une liste de vulnérabilités de code. Mais chaque item a une dimension architecturale :

A01 — Broken Access Control : absence de contrôle d’accès centralisé. Chaque service doit valider les autorisations, pas seulement l’API Gateway.

A02 — Cryptographic Failures : chiffrement absent ou insuffisant au repos et en transit. Décision architecturale : quel niveau de chiffrement pour quelles données.

A03 — Injection : SQL, LDAP, OS command injection. L’architecture doit séparer les données du code — ORM, requêtes paramétrées, validation stricte.

A04 — Insecure Design : absence de threat modeling, de défense en profondeur. C’est la catégorie la plus architecturale du Top 10.

A05 — Security Misconfiguration : permissions par défaut trop larges, services non nécessaires exposés, headers de sécurité manquants.

A06 — Vulnerable Components : dépendances avec vulnérabilités connues. L’architecture doit inclure un processus de mise à jour des dépendances (SCA).

A09 — Security Logging & Monitoring Failures : absence de logs d’audit, d’alertes, de détection d’intrusion.

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# OWASP Top 10 2021 : impact × probabilité (scores estimatifs)
owasp_items = [
    ('A01\nAccess Control',    0.88, 0.94),
    ('A02\nCrypto Failures',   0.75, 0.72),
    ('A03\nInjection',         0.80, 0.65),
    ('A04\nInsecure Design',   0.90, 0.55),
    ('A05\nMisconfiguration',  0.70, 0.88),
    ('A06\nVuln. Components',  0.65, 0.78),
    ('A07\nAuthn Failures',    0.85, 0.70),
    ('A08\nData Integrity',    0.75, 0.50),
    ('A09\nLog Failures',      0.60, 0.45),
    ('A10\nSSRF',              0.72, 0.40),
]

labels = [item[0] for item in owasp_items]
impacts = [item[1] for item in owasp_items]
probs = [item[2] for item in owasp_items]
risk_scores = [i * p for i, p in zip(impacts, probs)]

# Taille des bulles proportionnelle au score de risque
sizes = [r * 3000 for r in risk_scores]

fig, ax = plt.subplots(figsize=(12, 8))

colors = sns.color_palette("RdYlGn_r", len(owasp_items))
# Trier par score de risque pour la couleur
sorted_idx = np.argsort(risk_scores)[::-1]
color_map = {i: colors[rank] for rank, i in enumerate(sorted_idx)}

scatter_colors = [color_map[i] for i in range(len(owasp_items))]
scatter = ax.scatter(probs, impacts, s=sizes, c=risk_scores,
                     cmap='RdYlGn_r', edgecolors='white', linewidths=1.5,
                     alpha=0.85, vmin=min(risk_scores), vmax=max(risk_scores))

for i, label in enumerate(labels):
    ax.annotate(label, (probs[i], impacts[i]),
                xytext=(5, 5), textcoords='offset points',
                fontsize=8, fontweight='bold')

plt.colorbar(scatter, ax=ax, label='Score de risque (impact × probabilité)', shrink=0.8)

# Quadrants
ax.axvline(0.65, color='gray', linestyle='--', alpha=0.4, linewidth=1)
ax.axhline(0.70, color='gray', linestyle='--', alpha=0.4, linewidth=1)
ax.text(0.35, 0.95, 'Impact élevé\nProbabilité faible', ha='center', fontsize=8,
        color='gray', style='italic')
ax.text(0.85, 0.95, 'PRIORITÉ\nMAXIMALE', ha='center', fontsize=9,
        color='red', fontweight='bold')
ax.text(0.35, 0.55, 'Risque faible', ha='center', fontsize=8, color='gray', style='italic')
ax.text(0.85, 0.55, 'Probabilité haute\nImpact modéré', ha='center', fontsize=8,
        color='darkorange', style='italic')

ax.set_xlabel("Probabilité d'occurrence (estimée)")
ax.set_ylabel("Impact potentiel (estimé)")
ax.set_title("OWASP Top 10 (2021) — Vue architecturale\nBubble chart : taille = score de risque", fontsize=13)
ax.set_xlim(0.25, 1.05)
ax.set_ylim(0.50, 1.05)

plt.savefig("owasp_bubble.png", dpi=100, bbox_inches='tight')
plt.show()
_images/53ac3f951e3d3cb8db9151505d5111d3d428e4da9e09a241553b665a62427505.png

Zero Trust#

L’architecture Zero Trust abandonne le modèle du périmètre : l’idée que tout ce qui est à l’intérieur du réseau de l’entreprise est de confiance. Dans un monde où les workloads sont dans le cloud, les employés en remote, et les attaques latérales courantes, le périmètre n’existe plus.

« Never trust, always verify » : chaque requête est authentifiée et autorisée, quel que soit son origine — réseau interne ou externe. Un service qui appelle un autre service doit prouver son identité à chaque appel.

Identité comme nouveau périmètre : l’identité (de l’utilisateur, du service, du device) remplace l’adresse IP comme base du contrôle d’accès.

Micro-segmentation : le réseau est découpé en segments aussi petits que possible. Une compromission ne peut pas se propager latéralement sans re-authentification.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, axes = plt.subplots(1, 2, figsize=(16, 7))

# Modèle périmètre classique
ax1 = axes[0]
ax1.set_xlim(0, 10)
ax1.set_ylim(0, 10)
ax1.axis('off')
ax1.set_title("Modèle périmètre classique\n(\"castle and moat\")", fontsize=11, fontweight='bold')

# Périmètre externe (firewall)
perimeter = plt.Circle((5, 5), 4.2, fill=False, edgecolor='red',
                         linewidth=3, linestyle='--')
ax1.add_patch(perimeter)
ax1.text(5, 9.4, 'Firewall / DMZ', ha='center', fontsize=9, color='red', fontweight='bold')

# Zone "de confiance"
trust_zone = plt.Circle((5, 5), 3.5, color='#d5f5e3', alpha=0.5)
ax1.add_patch(trust_zone)
ax1.text(5, 8.2, '✓ Zone de confiance implicite', ha='center', fontsize=8.5, color='#27ae60')

# Services internes
internal = [
    (3.5, 6.5, 'App\nServer', '#2ecc71'),
    (6.5, 6.5, 'DB\nPrimaire', '#e74c3c'),
    (5.0, 4.0, 'Service\nInterne', '#3498db'),
    (3.0, 3.0, 'Admin\nPanel', '#9b59b6'),
]
for x, y, label, color in internal:
    circle = plt.Circle((x, y), 0.6, color=color, alpha=0.85)
    ax1.add_patch(circle)
    ax1.text(x, y, label, ha='center', va='center', fontsize=7, color='white', fontweight='bold')

# Attaquant qui a traversé le périmètre
ax1.plot(5, 5.8, 'r*', markersize=18, zorder=10)
ax1.text(5.7, 5.9, 'Attaquant\n(déjà dedans!)', fontsize=8, color='red', fontweight='bold')
ax1.annotate('', xy=(3.0, 3.6), xytext=(5, 5.8),
             arrowprops=dict(arrowstyle='->', color='red', lw=2))
ax1.annotate('', xy=(6.5, 6.0), xytext=(5, 5.8),
             arrowprops=dict(arrowstyle='->', color='red', lw=2))
ax1.text(3.0, 1.8, 'Mouvement latéral libre\n(tout est "de confiance")',
         ha='center', fontsize=8, color='red',
         bbox=dict(boxstyle='round', facecolor='lightyellow', edgecolor='red'))

# Modèle Zero Trust
ax2 = axes[1]
ax2.set_xlim(0, 10)
ax2.set_ylim(0, 10)
ax2.axis('off')
ax2.set_title("Architecture Zero Trust\n(\"never trust, always verify\")", fontsize=11, fontweight='bold')

zt_services = [
    (2.0, 7.5, 'Browser\nUser', '#3498db'),
    (8.0, 7.5, 'Service\nA', '#2ecc71'),
    (2.0, 3.5, 'Service\nB', '#e67e22'),
    (8.0, 3.5, 'Database', '#e74c3c'),
    (5.0, 5.5, 'Identity\nProvider\n(IdP)', '#9b59b6'),
]

for x, y, label, color in zt_services:
    box = mpatches.FancyBboxPatch((x-0.85, y-0.6), 1.7, 1.2,
                                   boxstyle="round,pad=0.1",
                                   facecolor=color, edgecolor='white', linewidth=2, alpha=0.85)
    ax2.add_patch(box)
    ax2.text(x, y, label, ha='center', va='center', fontsize=8,
             color='white', fontweight='bold')

# Chaque connexion passe par l'IdP
connections = [
    ((2.0, 7.5), (5.0, 5.5)),
    ((8.0, 7.5), (5.0, 5.5)),
    ((2.0, 3.5), (5.0, 5.5)),
    ((8.0, 3.5), (5.0, 5.5)),
    ((2.0, 7.5), (8.0, 7.5)),
    ((2.0, 7.5), (8.0, 3.5)),
]

for (x1, y1), (x2, y2) in connections:
    ax2.annotate('', xy=(x2, y2), xytext=(x1, y1),
                 arrowprops=dict(arrowstyle='->', color='#555', lw=1.5,
                                 connectionstyle='arc3,rad=0.1'))

# Badges d'authentification
auth_points = [(3.5, 7.0), (7.0, 7.0), (3.5, 4.0), (7.0, 4.0)]
for px, py in auth_points:
    ax2.text(px, py, '🔐 mTLS\n+ JWT', ha='center', fontsize=6.5,
             color='darkgreen', fontweight='bold',
             bbox=dict(boxstyle='round,pad=0.2', facecolor='#eafaf1', edgecolor='green', alpha=0.9))

ax2.text(5.0, 1.5, 'Chaque appel = authentification + autorisation\n'
         'Réseau = non fiable par défaut',
         ha='center', fontsize=8.5, color='#2c3e50',
         bbox=dict(boxstyle='round', facecolor='#eaf2ff', edgecolor='#3498db'))

plt.suptitle("Périmètre classique vs Zero Trust", fontsize=13, fontweight='bold')
plt.savefig("zero_trust.png", dpi=100, bbox_inches='tight')
plt.show()
/tmp/ipykernel_83542/1233282724.py:102: UserWarning: Glyph 128272 (\N{CLOSED LOCK WITH KEY}) missing from font(s) DejaVu Sans.
  plt.savefig("zero_trust.png", dpi=100, bbox_inches='tight')
/home/loc/legacy_workspace/notebooks/.venv/lib/python3.13/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 128272 (\N{CLOSED LOCK WITH KEY}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
_images/ff8fdb9350c3123be5e2da11f8474d7b348ca9e6078f293e20d41c50a7f889ff.png

Défense en profondeur#

La défense en profondeur (defence in depth) empile plusieurs couches de sécurité. Aucune couche n’est parfaite — la sécurité réside dans leur combinaison.

Couche réseau : firewall, segmentation, WAF (Web Application Firewall), DDoS protection.

Couche application : authentification, autorisation, validation des entrées, rate limiting.

Couche données : chiffrement au repos, chiffrement en transit (TLS), masquage des données sensibles, contrôle d’accès aux colonnes.

Couche opérationnelle : logs d’audit, alerting, détection d’anomalies (SIEM), rotation régulière des credentials.

L’objectif n’est pas de rendre le système inattaquable — c’est de rendre l’attaque suffisamment coûteuse que l’attaquant choisisse une cible plus facile, et de détecter les intrusions qui réussissent malgré tout.

Secrets management#

Les secrets (clés API, mots de passe de base de données, certificats TLS, tokens) sont des données particulièrement sensibles. Quelques règles absolues :

Jamais dans le code source ni dans git : un secret pushé dans git, même brièvement, doit être considéré compromis. Les scanners de secrets (git-secrets, truffleHog, GitLeaks) détectent et alertent.

Variables d’environnement : insuffisant seul : les variables d’environnement sont lisibles par tous les processus du système, visibles dans les logs de déploiement, et souvent mal gérées en rotation.

Secrets stores dédiés : HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager. Ces outils centralisent la gestion, permettent la rotation automatique, auditent chaque accès, et permettent la révocation immédiate.

Rotation régulière : les secrets doivent avoir une durée de vie limitée. La rotation automatique élimine le risque lié aux secrets qui n’ont jamais été changés.

Le principe de non-répudiation pour les secrets

Chaque accès à un secret doit être loggé : qui a accédé, depuis quel service, à quelle heure. Si un secret est compromis, ces logs permettent de reconstituer la timeline de l’intrusion et d’évaluer l’impact.

Audit et non-répudiation#

Un log d’audit est différent d’un log applicatif. Il enregistre les actions des utilisateurs et des systèmes avec une intention de non-répudiation : prouver qu’une action a bien eu lieu, par qui, quand.

Propriétés d’un log d’audit solide#

Immutabilité : les logs d’audit ne peuvent pas être modifiés ou supprimés, même par un administrateur. On écrit, on n’efface pas. Les solutions cloud (CloudTrail, GCS Audit Logs) offrent cette garantie.

Intégrité : les logs sont signés ou chainés (hash du log précédent inclus dans le suivant) pour détecter toute altération.

Complétude : toute action significative est loggée — connexions, modifications de données sensibles, accès aux ressources critiques, changements de permissions.

Event sourcing comme log d’audit naturel#

Un système en event sourcing stocke toutes les mutations comme une séquence d’événements immuables. Par construction, il dispose d’un log d’audit complet : l’état actuel est le résultat de l’application séquentielle de tous les événements depuis l’origine. Il est possible de rejouer l’historique, de répondre à des questions comme « quel était l’état du compte à 14h37 le 12 mars ? ».

RGPD et logs d’audit

Les logs d’audit contiennent des données personnelles (qui a fait quoi). Ils sont soumis au RGPD : durée de conservation limitée, droit d’accès, parfois droit à l’effacement (en tension avec la non-répudiation). L’architecture doit adresser cette tension dès la conception, par exemple en pseudonymisant les identifiants dans les logs.

Résumé#

La sécurité par conception réduit structurellement la surface d’attaque et le coût des incidents.

Principe

Menace adressée

Mise en œuvre

Moindre privilège

Élévation, mouvement latéral

IAM granulaire, tokens scoped

Shift left

Vulnérabilités coûteuses

SAST, threat modeling en design

STRIDE

Toutes catégories de menaces

Sessions de threat modeling

Zero Trust

Mouvement latéral, compromission interne

mTLS, IdP centralisé

Défense en profondeur

Contournement d’une couche

WAF + authn + chiffrement + audit

Secrets management

Fuite de credentials

Vault, rotation automatique

Logs d’audit immutables

Déni d’une action

Event sourcing, CloudTrail

Règles pratiques :

  • Aucun secret dans git. Jamais. Sans exception.

  • Valider les autorisations côté serveur, pas seulement en frontend ou à l’API Gateway.

  • Le threat modeling doit être fait en équipe, pas délégué à un spécialiste seul.

  • Zero Trust n’est pas un produit — c’est un principe architectural qui nécessite un IdP solide et du mTLS.

  • Les logs d’audit sont inutiles s’ils ne sont pas monitorés et alertés.