Sécuriser une API dans Azure : six couches, une raison chacune

Je suis un solution architecte dans le cloud Azure, avec des multiples expériences dans le monde du consulting. Je parcours l'univers informatique depuis déjà plus de 15 ans, m'amuse à jouer les magiciens du code et les alchimistes du cloud. J'ai été apprenti sorcier en conception des applications, et je fais partie des super-héros de développement backend, web et mobile, toujours prêt à sauver le monde du chaos informatique !
Mon pouvoir spécial réside dans l'art de construire des architectures gigantesques et de rendre les sites invincibles. Imaginez-moi, tel un maître des nuages, déployant des serveurs en un claquement de doigts et écrivant des sorts magiques pour des logiciels d'application éblouissants.
N'hésitez pas à me contacter si vous avez besoin d'aide ! Je suis toujours prêt à défendre l'Internet des méchants bugs et des pannes maléfiques ! :)
Une API bien protégée dans Azure ne repose jamais sur un seul contrôle : elle empile six couches, chacune conçue pour tenir quand la précédente cède.
Dans les audits que je mène, le scénario de compromission est presque toujours le même. Ce n'est pas un 0-day spectaculaire. C'est un backend encore joignable en direct sur *.azurewebsites.net alors qu'un WAF a été posé devant, une API qui accepte n'importe quel jeton valide du tenant, ou un identifiant d'objet qu'on incrémente pour lire les données du client voisin.
Ce dernier cas a un nom : BOLA (Broken Object Level Authorization). Il occupe la première place du OWASP API Security Top 10 2023, et aucun WAF ni gateway ne le bloque : seul le code applicatif peut le faire.
D'où la règle qui structure cet article : chaque couche a une raison d'exister, c'est-à-dire un risque précis qu'elle seule traite bien. Si une couche ne traite aucun risque que les autres ne couvrent déjà, elle n'est que du coût. Si un risque n'est traité par aucune couche, c'est votre prochain incident.
L'architecture de référence, pour une API exposée sur Internet :
Edge : Front Door Premium ou Application Gateway, avec WAF.
Gateway : Azure API Management, point unique d'application des politiques.
Identité : Microsoft Entra ID, pour chaque appelant et chaque saut.
Backend : sans exposition publique, et seul responsable de l'autorisation fine.
Détection : Defender for Cloud et Microsoft Sentinel.
DevSecOps et gouvernance : une configuration sécurisée qui est la seule déployable.
Couche 1 : l'edge, absorber avant de traiter
Pourquoi. L'edge filtre le trafic hostile avant qu'il ne consomme la moindre ressource stateful : volumétrie, scans, charges OWASP classiques, bots. C'est aussi le seul point d'entrée public de toute l'architecture.
Comment.
Choisir l'edge selon la géographie de vos consommateurs.
Front Door Premium pour une audience multi-régions. Il atteint ses origines par Private Link, et API Management comme Application Gateway font partie des origines supportées.
Application Gateway WAF v2 pour un modèle hub-and-spoke régional devant un APIM interne.
Politique WAF : le rule set DRS 2.2, recommandé par Microsoft pour toute nouvelle politique, plus Bot Manager 1.1. Les deux sont activés en mode Detection par défaut. Laissez-les en Detection deux à quatre semaines, réglez les exclusions par règle et par chemin, puis passez en Prevention.
Règles custom, évaluées avant les règles managées : géo-filtrage si vos consommateurs sont européens, blocage des méthodes HTTP inutilisées, et rate limiting par IP. Derrière un proxy ou un CDN, regroupez par
ClientAddrXFFHeaderplutôt que parClientAddr.TLS 1.2 minimum. Stockez les certificats dans Key Vault ; la gateway les lit via son identité managée.
Verrouiller l'origine pour qu'elle n'accepte que l'edge. Avec Front Door : Private Link, ou au minimum le service tag
AzureFrontDoor.Backendcombiné à la validation de l'en-têteX-Azure-FDID.
Le piège terrain. Un WAF devant une origine encore publique ne sert à rien : l'attaquant appelle directement l'origine sur son hostname *.azure-api.net ou *.azurewebsites.net. C'est le constat que je fais le plus souvent. Second classique : une exclusion globale (« ignorer tous les corps de requête ») ajoutée pour corriger un faux positif, qui désactive de fait l'inspection.
À noter. Le rate limiting du WAF n'est pas exact : Microsoft précise que chaque instance compte indépendamment et déconseille de l'utiliser pour un contrôle fin. C'est un filet anti-abus. Le quota métier par consommateur se fait à la couche 2.
Couche 2 : la gateway, une politique pour toutes les APIs
Pourquoi. Sans gateway, chaque équipe réimplémente l'authentification, le throttling et la validation, chacune à sa façon. API Management applique la même politique à toutes les APIs, rejette le trafic invalide avant qu'il n'atteigne le backend, et centralise les logs.
Comment.
Choisir le mode réseau en fonction du tier. Les options diffèrent selon le tier (concepts réseau APIM) :
Developer et Premium classiques : injection VNet en mode interne, le data plane n'est joignable que depuis le VNet.
Premium v2 : injection VNet du gateway.
Standard v2 : intégration VNet sortante seulement ; gateway, plan de gestion et portail restent publics. Pour fermer l'entrée, ajoutez un private endpoint.
Contrainte à connaître : en tiers classiques, un private endpoint est incompatible avec l'injection VNet.
Authentifier avec
validate-azure-ad-token. La policy existe dans tous les tiers. Elle doit contrôler le tenant, l'audience et la liste blanche desclient-application-ids, et ajouterrequired-claimssur les rôles au niveau de chaque opération. Pour un fournisseur d'identité autre qu'Entra, utilisezvalidate-jwt.Throttler par application, jamais par IP. Utilisez
rate-limit-by-keyavec pour clé le claimazpdu jeton. Trois réserves documentées :le comptage est approximatif ;
il se fait par gateway, donc par région en multi-région ;
un
increment-conditionécrit en expression décale le 429 d'un appel.
Faire respecter le contrat OpenAPI. Utilisez
validate-contentavecallow-additional-properties="false", qui est la réponse gateway au mass assignment, etvalidate-parameters. Ces policies ne valent que ce que vaut le schéma : sansmaxLength,enumniadditionalProperties: false, elles ne bloquent presque rien.Construire la hiérarchie des policies. Les invariants vont au niveau global : suppression de
ServeretX-Powered-By, CORS strict, ID de corrélation. Imposez<base />à tous les niveaux : il existe une Azure Policy built-in pour cela.Clés d'abonnement : métrologie, pas authentification. Une clé est un secret statique partagé ; le jeton Entra reste le contrôle d'accès.
<validate-azure-ad-token tenant-id="{{tenant-id}}" output-token-variable-name="jwt">
<client-application-ids>
<application-id>{{partner-client-id}}</application-id>
</client-application-ids>
<audiences><audience>api://{{orders-api-app-id}}</audience></audiences>
</validate-azure-ad-token>
<rate-limit-by-key calls="100" renewal-period="60"
counter-key="@(((Jwt)context.Variables["jwt"]).Claims.GetValueOrDefault("azp", "unknown"))" />
Le piège terrain. Une opération ajoutée plus tard qui surcharge la policy sans <base /> et se retrouve donc sans validation de jeton. Autre point : l'API de gestion directe et le dépôt Git de configuration ont été retirés en mars 2025. Si un runbook interne y fait encore référence, il est obsolète.
Couche 3 : l'identité, zéro secret et zéro confiance implicite
Pourquoi. Le réseau réduit la surface d'attaque ; l'identité décide qui peut faire quoi. C'est la couche qui survit au déplacement du périmètre : télétravail, partenaires, workloads multi-cloud.
Comment.
Une app registration par API. Elle expose :
des app roles pour les appels applicatifs (claim
roles) ;des scopes pour les appels délégués (claim
scp).
Microsoft recommande des rôles distincts pour les utilisateurs et pour les applications, sinon l'un peut se faire passer pour l'autre.
Activer Assignment required sur le service principal de l'API. Avec ce réglage, un utilisateur ou un service non assigné ne peut plus obtenir de jeton. Le contrôle se fait à l'émission, avant même que la requête n'existe.
Vérifier quand même les rôles côté API. Entra peut émettre un jeton applicatif sans claim
roles: Microsoft documente d'ailleurs un modèle d'autorisation par ACL pour ce cas. Un backend qui accepte « tout jeton valide du tenant » est ouvert à toutes les applications du tenant.Identifiants clients, par ordre de préférence :
Identité managée, pour les workloads hébergés dans Azure.
Workload Identity Federation, pour GitHub Actions, AKS ou un IdP externe : aucun secret.
Certificat stocké dans Key Vault.
Secret client, en dernier recours. Les app management policies permettent de bloquer l'ajout de secrets à l'échelle du tenant (
passwordAddition) ou d'en plafonner la durée.
Gouverner l'octroi des rôles. Donner
Orders.Writeà un partenaire est une action privilégiée : faites-la passer par une revue ou un package d'accès, et générez une alerte sur l'événement d'audit correspondant (couche 5).Conditional Access pour workload identities. Bloque un service principal hors de vos plages IP ou en cas de risque détecté. Trois limites documentées :
il faut une licence Workload Identities Premium ;
seuls les service principals single-tenant enregistrés dans votre tenant sont couverts, pas les apps multi-tenant ni les identités managées ;
le seul contrôle disponible est Block.
Le piège terrain. Les apps multi-tenant de partenaires échappent à Conditional Access. Pour un partenaire critique, une app single-tenant dans votre tenant, authentifiée par certificat, reste plus gouvernable, même si elle vous impose de gérer son identifiant.
Couche 4 : le backend, la seule couche qui connaît la donnée
Pourquoi. APIM sait que l'appelant détient Orders.Read. Seul le backend sait si la commande 4711 lui appartient. L'autorisation au niveau objet (BOLA, n°1 OWASP), au niveau propriété (n°3) et la logique métier ne peuvent vivre qu'ici.
Comment.
Aucune exposition publique. Un private endpoint en entrée et l'accès public désactivé ; sur AKS, un ingress interne et des NetworkPolicies.
Le backend joint ses données par le VNet. Intégration VNet sortante de l'App Service, puis private endpoint vers SQL, Storage ou Cosmos DB.
Re-valider le jeton dans le backend, avec Microsoft.Identity.Web ou l'authentification App Service. Le principe : ne jamais présumer qu'une requête vient d'APIM. Le jour où quelqu'un ouvre un accès réseau « temporaire », c'est la dernière ligne de défense.
Choisir explicitement le modèle d'appel entre APIM et le backend :
transmission du jeton de l'appelant, que le backend re-valide ;
identité managée d'APIM, avec un backend qui n'accepte que cet
azp;On-Behalf-Of, quand le backend appelle d'autres APIs au nom de l'utilisateur.
Le filtre de propriété dans la requête SQL, et 404 plutôt que 403. Une ressource d'un autre client doit être indiscernable d'une ressource inexistante, pour ne pas permettre l'énumération.
DTO explicites en entrée et en sortie. Ne sérialisez jamais l'entité de persistance : c'est l'exposition excessive de données en réception, et le mass assignment en écriture.
Base de données sans mot de passe. Azure SQL en mode Microsoft Entra-only. Identité managée mappée sur un utilisateur contenu dans la base, avec des
GRANTobjet par objet plutôt quedb_datareader/db_datawriter.Contrôler la sortie. Routez l'egress du backend via Azure Firewall avec une liste blanche de FQDN : c'est la mitigation SSRF (OWASP API7), qu'aucune gateway en entrée ne couvre.
var order = await db.QuerySingleOrDefaultAsync<OrderDto>(
"SELECT ... FROM dbo.Orders WHERE Id = @id AND OwnerId = @owner",
new { id, owner = user.GetObjectId() });
return order is null ? Results.NotFound() : Results.Ok(order);
Le piège terrain. Un private endpoint ajouté sans couper l'accès public : la ressource est alors joignable des deux côtés, et le schéma d'architecture ment.
Couche 5 : la détection, savoir ce qui existe et ce qui se passe
Pourquoi. Les quatre premières couches réduisent la probabilité d'une attaque ; la détection réduit sa durée. Elle répond aussi à une question que l'architecture seule ne règle pas : quelles APIs existent vraiment, y compris celles qui ne passent pas par APIM ?
Comment.
L'inventaire d'abord. Azure API Center sert de catalogue de référence, avec un propriétaire, un cycle de vie et une classification par API. Une API sans propriétaire est une alerte qui n'atterrit nulle part.
Defender for APIs couvre aujourd'hui les APIs publiées dans API Management uniquement. Il apporte :
un inventaire agrégé ;
des recommandations de posture, par exemple sur les APIs externes, inutilisées ou non authentifiées ;
la classification des données sensibles dans les payloads ;
la détection d'anomalies à l'exécution, alignée sur l'OWASP API Top 10.
Des logs exploitables. Envoyez les logs APIM, WAF, App Service et SQL vers Log Analytics en tables dédiées (
ApiManagementGatewayLogs,AGWFirewallLogs, etc.), avec les logs Entra (AuditLogs,AADServicePrincipalSignInLogs).Journaliser l'identité de l'appelant et sa vraie IP. APIM n'enregistre pas les claims du jeton. Loggez un en-tête interne (l'
azpinjecté par la policy) etX-Forwarded-Fordans les paramètres de diagnostic. Sans cela, derrière une Application Gateway, toutes les requêtes semblent venir de la même IP.Détections Sentinel à écrire en priorité :
rafales de 401/403 par IP réelle ;
énumération d'identifiants (beaucoup d'ID distincts, beaucoup de 404) par application ;
succès APIM venant d'une IP bloquée par le WAF ;
nouveau rôle ou nouveau secret sur les applications de l'API.
ApiManagementGatewayLogs
| where TimeGenerated > ago(1h) and ResponseCode in (401, 403)
| extend ClientIp = tostring(split(tostring(RequestHeaders["X-Forwarded-For"]), ",")[0])
| summarize Failures = count(), Ops = dcount(OperationId) by ClientIp, bin(TimeGenerated, 5m)
| where Failures > 50 or Ops > 15
Le piège terrain. Des détections calées sur l'IP d'APIM ou de l'Application Gateway, qui ne déclenchent jamais, ou toujours.
Couche 6 : DevSecOps et gouvernance, rendre la bonne configuration inévitable
Pourquoi. Une architecture sécurisée le jour J dérive dès le premier hotfix fait dans le portail. Cette couche garantit que la configuration sécurisée est la seule déployable, et qu'un écart se voit.
Comment.
APIOps. Toute la configuration APIM (APIs, policies, produits, named values) vit dans Git. Elle est extraite et publiée par pipeline (APIOps) ou en Bicep/Terraform. Le portail reste en lecture seule en production, et toute modification de policy passe en PR avec un relecteur sécurité.
Le contrat comme contrôle de sécurité. Lintez chaque spec OpenAPI en CI avec Spectral et le ruleset OWASP. Une opération sans schéma de sécurité ou un objet sans
additionalProperties: falsefait échouer le build. C'est ce qui donne ses dents àvalidate-contenten couche 2.La chaîne de pipeline : lint de la spec, SAST et dépendances, scan IaC, déploiement en recette, DAST authentifié piloté par la spec, puis porte manuelle avant la production.
Azure Policy au niveau Management Group, en Deny. Les built-ins couvrent l'essentiel :
APIM dans un VNet ;
named values secrets dans Key Vault ;
appels backend authentifiés et validation de certificat ;
héritage
<base />;accès public désactivé sur App Service et SQL ;
logs déployés automatiquement en DeployIfNotExists.
Le cycle de vie. Versionnez explicitement, fixez une date de fin dans API Center, et décommissionnez réellement. Une v1 oubliée avec la posture de sécurité de 2021 est un chemin d'intrusion classique (OWASP API9).
La traçabilité réglementaire. Associez chaque contrôle à vos référentiels (DORA, NIS2, exigences internes) : l'audit devient alors un export de preuves, pas un projet.
Le piège terrain. Des policies en Audit « en attendant » depuis dix-huit mois, et un DAST lancé sans jeton qui ne teste que la page 401.
En pratique : landing zone, périmètre et écart à la cible
Ces six couches sont la bonne cible, mais en pratique elles vivent rarement seules dans un resource group. Trois réalités changent leur mise en œuvre.
1. Elles s'appuient sur une landing zone et sur des briques mutualisées par l'équipe plateforme.
Réseau en hub-and-spoke ou Virtual WAN. Application Gateway, Azure Firewall et DDoS Protection sont dans le hub ; chaque application occupe un spoke.
DNS privé centralisé. Les zones
privatelink.*sont hébergées dans l'abonnement de connectivité. UnDeployIfNotExistsy crée les enregistrements de chaque private endpoint, et une policy Deny empêche les équipes de créer leurs propres zones (Private Link et DNS à l'échelle). Sans cela, le DNS devient ingérable au-delà de quelques dizaines d'applications.APIM mutualisé avec des workspaces. C'est la gestion fédérée : les équipes produit publient leurs APIs dans leur workspace, l'équipe plateforme garde la gouvernance, les policies globales et la supervision.
SOC et Sentinel centralisés. Les détections API sont maintenues par l'équipe sécurité, pas projet par projet.
Le reste du socle. Agents CI/CD dans le VNet, puisque plus rien n'est joignable depuis Internet, et PIM pour tout accès privilégié : APIM, Key Vault, admins SQL, rôles Entra.
2. Toutes les APIs ne traversent pas les six couches.
La plupart du temps, les APIs sont internes. Elles sont consommées depuis d'autres spokes ou depuis l'on-premise via ExpressRoute. Pas d'edge public : les couches 2 à 6 restent, la couche 1 disparaît.
Les partenaires sur Azure passent par Private Link sans toucher Internet.
Front Door et Application Gateway ne s'empilent que si l'on a besoin des deux : de la présence globale et d'un WAF régional aux règles différentes.
3. Dans la réalité, l'écart entre cible et existant est la norme. Le cas typique : edge et APIM bien en place, mais des backends encore joignables en direct, des APIs historiques hors APIM et des policies en Audit depuis des mois. C'est cet écart que DORA et NIS2 rendent visible. Le sujet n'est pas d'avoir un WAF, mais de prouver que chaque API est inventoriée, protégée et surveillée de façon cohérente.





