Votre application Azure a échoué à un audit sécurité faute de CSP et HSTS ? Voici, pas à pas, comment ajouter, tester et déployer ces en‑têtes selon l’hébergement : App Service (Windows/Linux), Front Door/CDN, Application Gateway, Azure Functions et Static Web Apps.
Problématique
Une application hébergée dans Azure sur un domaine personnalisé n’a pas passé une analyse de cybersécurité : les en‑têtes Content‑Security‑Policy (CSP) et HTTP Strict Transport Security (HSTS) sont manquants ou trop permissifs. L’auteur n’a pas trouvé où les définir dans Azure.
Réponse initiale obtenue
La discussion d’origine n’apportait pas de solution technique ; la recommandation était de poser la question sur le forum Microsoft Q&A. Ci‑dessous, vous trouverez au contraire un guide complet et opérationnel pour implémenter CSP et HSTS selon votre scénario d’hébergement Azure.
Où configurer CSP et HSTS dans Azure
Azure offre plusieurs couches possibles pour injecter des en‑têtes HTTP : dans l’application (code, serveur web), à l’edge (Front Door/CDN) ou au niveau du proxy/LB (Application Gateway). La règle d’or : placer HSTS sur la première réponse HTTPS que voit le navigateur, et propager CSP au plus près de la page qui l’applique.
Scénario d’hébergement | Où ajouter ou modifier les en‑têtes |
---|---|
App Service Windows (IIS intégré) | Fichier web.config à la racine du site (IIS) pour définir customHeaders et éventuellement la redirection HTTPS. |
App Service Linux / conteneur Docker | Dans le code (ex. Helmet pour Node.js, middleware ASP.NET), ou côté serveur web du conteneur (Nginx/Apache) via le Dockerfile . |
Front Door, Application Gateway, CDN | Dans une règle de réécriture/ingénierie de règles : action « Ajouter/Modifier un en‑tête de réponse » pour injecter CSP et HSTS à l’edge. |
Azure Functions | Middleware (runtime .NET isolé, Node.js, Python) ou logique commune de réponse. Remarque : host.json ne sert pas à injecter globalement des en‑têtes de réponse ; préférez middleware ou l’edge. |
Static Web Apps | Fichier staticwebapp.config.json (globalHeaders ou headers par route). Front Door peut aussi ajouter ces en‑têtes. |
Bonnes pratiques essentielles
- Forcer HTTPS avant d’activer HSTS pour éviter de « bloquer » des clients encore en HTTP.
- Pour HSTS, visez
max-age
≥ 31536000 (1 an), ajoutezincludeSubDomains
et, si vous êtes prêts,preload
. - Déployez CSP en mode Report‑Only jusqu’à stabilisation, puis passez en mode bloquant.
- Testez systématiquement avec des analyseurs d’en‑têtes (ex. SecurityHeaders, Mozilla Observatory, Qualys SSL Labs).
Mise en œuvre par scénario
App Service Windows (IIS) : via web.config
Placez un fichier web.config
à la racine du site. Il ajoute les en‑têtes et force la redirection HTTPS. Exemple minimal :
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="HTTP to HTTPS" enabled="true" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
```
<httpProtocol>
<customHeaders>
<add name="Strict-Transport-Security"
value="max-age=31536000; includeSubDomains; preload" />
<add name="Content-Security-Policy"
value="default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none';" />
<!-- En‑têtes complémentaires conseillés -->
<add name="X-Content-Type-Options" value="nosniff" />
<add name="Referrer-Policy" value="no-referrer-when-downgrade" />
<add name="Permissions-Policy" value="geolocation=(), microphone=()" />
</customHeaders>
</httpProtocol>
```
Astuce : si vous retournez beaucoup de redirections (301/302/307) depuis IIS, ajoutez l’attribut always
quand c’est possible côté Nginx/Apache (voir plus bas) ; sur IIS, le bloc customHeaders
s’applique aux réponses générées par IIS. HSTS n’est pris en compte par les navigateurs que sur des réponses HTTPS.
Validation rapide
- Déployez
web.config
, rechargez le site en HTTPS et inspectez la réponse initiale (F12 → Réseau) : cherchezStrict-Transport-Security
etContent-Security-Policy
. - Corrigez les éventuels blocages CSP en mode Report‑Only avant de basculer en bloquant.
App Service Linux ou conteneur Docker
Node.js avec Helmet
import express from 'express';
import helmet from 'helmet';
const app = express();
// HSTS (1 an + sous-domaines + précharge)
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true
}));
// CSP stricte (adaptez les directives à votre app)
app.use(helmet.contentSecurityPolicy({
useDefaults: false,
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"], // ajoutez des nonces/hachages si nécessaire
styleSrc: ["'self'"],
imgSrc: ["'self'","data:"],
connectSrc: ["'self'"],
fontSrc: ["'self'","data:"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
frameAncestors: ["'none'"]
}
}));
// redirection HTTPS si vous terminez TLS ailleurs (Front Door/AppGW)
app.use((req, res, next) => {
const proto = req.headers['x-forwarded-proto'];
if (proto && proto !== 'https') {
return res.redirect(308, 'https://' + req.headers.host + req.originalUrl);
}
next();
});
app.get('/', (req, res) => res.send('OK'));
app.listen(process.env.PORT || 3000);
ASP.NET Core (hébergement Linux)
// Program.cs (.NET 7/8)
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseHsts(); // Configurez si besoin: app.UseHsts(new HstsOptions{ MaxAge = TimeSpan.FromDays(365), IncludeSubDomains = true, Preload = true });
}
// Redirection HTTPS si nécessaire
app.UseHttpsRedirection();
// CSP via middleware simple (ou via librairie spécialisée)
app.Use(async (ctx, next) =>
{
ctx.Response.Headers["Content-Security-Policy"] =
"default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'";
// HSTS est déjà géré par UseHsts sur HTTPS
await next();
});
app.MapGet("/", () => Results.Ok("OK"));
app.Run();
Nginx dans un conteneur (ou App Service Linux Custom Container)
Si vous servez l’app via Nginx :
server {
listen 443 ssl;
server_name _;
# ... config TLS ...
# CSP + HSTS (toujours présent, même sur 301/304)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
location / {
proxy_pass [http://app:3000](http://app:3000);
# proxies, headers, etc.
}
}
server {
listen 80;
return 301 https://$host$request_uri;
}
Front Door, Application Gateway et CDN
Ajouter HSTS et CSP à l’edge est idéal lorsque plusieurs back‑ends existent ou que vous souhaitez centraliser la politique.
Front Door (Standard/Premium)
- Activez « Redirection HTTP → HTTPS » dans la route concernée.
- Créez une règle (Rule set) « SecurityHeaders » appliquée à la/aux route(s) : Action → Modifier l’en‑tête de réponse → Ajouter.
- Ajoutez
Strict-Transport-Security
valeur :max-age=31536000; includeSubDomains; preload
. - Ajoutez
Content-Security-Policy
valeur :default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
(puis affinez).
Exemple Bicep (extrait de règle)
resource ruleSet 'Microsoft.Cdn/profiles/securityPolicies@2023-05-01' = {
name: 'afdx-sec-headers' // exemple
// ...
}
resource rule 'Microsoft.Cdn/profiles/ruleSets/rules@2023-05-01' = {
name: 'AddSecurityHeaders'
parent: ruleSet
properties: {
order: 1
actions: [
{
name: 'ModifyResponseHeader'
parameters: {
headerAction: 'Overwrite'
headerName: 'Strict-Transport-Security'
value: 'max-age=31536000; includeSubDomains; preload'
}
}
{
name: 'ModifyResponseHeader'
parameters: {
headerAction: 'Overwrite'
headerName: 'Content-Security-Policy'
value: "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
}
}
]
// conditions éventuelles: forcer HTTPS, chemins, etc.
}
}
Application Gateway (avec ou sans WAF)
- Créez un Rewrite set (Règles de réécriture) et associez‑le au listener HTTPS.
- Ajoutez des actions « Ajouter un en‑tête de réponse » pour
Strict-Transport-Security
etContent-Security-Policy
. - Optionnel : condition « if scheme = https » pour ne pas émettre HSTS sur HTTP.
CDN (Standard Microsoft/Azure CDN classique)
Utilisez le moteur de règles : Action → Modifier l’en‑tête de réponse, ajoutez HSTS et CSP et appliquez‑les au /*
. Assurez la redirection HTTPS au niveau CDN ou back‑end.
Azure Functions
Dans Functions, la méthode fiable est d’injecter les en‑têtes dans le code (middleware ou handler commun), ou à l’edge (Front Door/AppGW). À noter : host.json
ne propose pas une clé universelle « headers » pour toutes les réponses ; utilisez plutôt les approches ci‑dessous.
.NET (processus isolé, v4)
Ajoutez un middleware Worker pour enrichir toutes les réponses HTTP :
// Program.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(worker => worker.UseMiddleware())
.Build();
host.Run();
public sealed class SecurityHeadersMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
await next(context);
```
var response = await context.GetHttpResponseDataAsync();
if (response is not null)
{
response.Headers.Add("Strict-Transport-Security",
"max-age=31536000; includeSubDomains; preload");
response.Headers.Add("Content-Security-Policy",
"default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'");
}
}
```
}
Node.js (modèle de programmation v4)
Centralisez l’ajout d’en‑têtes dans un utilitaire que vous appelez depuis chaque fonction HTTP :
// headers.js
export function withSecurityHeaders(handler) {
return async (request, context) => {
const res = await handler(request, context);
const headers = new Headers(res.headers || {});
headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
headers.set('Content-Security-Policy', "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'");
return new Response(res.body, { status: res.status, headers });
};
}
// index.js
import { app } from '@azure/functions';
import { withSecurityHeaders } from './headers.js';
app.http('hello', {
methods: ['GET'],
authLevel: 'anonymous',
handler: withSecurityHeaders(async (req, ctx) => {
return { status: 200, body: 'OK' };
})
});
Static Web Apps
Pour une SWA, ajoutez un fichier staticwebapp.config.json
à la racine de sortie (output) de votre build. Deux options :
{
"globalHeaders": {
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
"Content-Security-Policy": "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
}
}
ou, si vous avez des politiques différentes selon les chemins :
{
"routes": [
{
"route": "/*",
"headers": {
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
"Content-Security-Policy": "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
}
}
]
}
Exemples de politiques CSP prêtes à adapter
Base stricte pour application SPA/SSR
default-src 'self';
base-uri 'self';
object-src 'none';
frame-ancestors 'none';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self' data:;
connect-src 'self';
Avec Azure AD / Microsoft Entra ID (authentification)
Si vous utilisez des bibliothèques d’authentification Microsoft (MSAL), autorisez leurs hôtes.
default-src 'self';
base-uri 'self';
object-src 'none';
frame-ancestors 'none';
script-src 'self' https://js.monitor.azure.com https://aadcdn.msftauth.net https://aadcdn.msauth.net;
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self' data:;
connect-src 'self' https://login.microsoftonline.com https://graph.microsoft.com;
Avec contenu statique Azure Storage
default-src 'self';
img-src 'self' data: https://*.blob.core.windows.net;
font-src 'self' data: https://*.blob.core.windows.net;
style-src 'self' 'unsafe-inline';
script-src 'self';
connect-src 'self';
Gestion des scripts inline : nonces et hachages
Évitez 'unsafe-inline'
. Préférez des nonces ou des hachages :
- Nonce : générez un nonce cryptographiquement sûr par requête, insérez‑le dans la balise
<script nonce="...">
et dansscript-src 'nonce-...'
. - Hash : calculez un hachage (SHA256/384/512) du contenu exact du script inline, puis ajoutez‑le dans
script-src 'sha256-...'
.
Mode rapport uniquement
Pour tester sans bloquer, utilisez Content-Security-Policy-Report-Only
avec la même politique et stabilisez‑la avant de passer en blocage. Pensez à définir un endpoint de collecte (Reporting API) si vous exploitez les rapports.
HSTS : recommandations et pièges
- Paramètres :
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
- Portée : n’émettez HSTS que sur HTTPS. Le navigateur ignore HSTS reçu via HTTP.
- Précharge : n’ajoutez
preload
que si tous vos sous‑domaines sont servis en HTTPS en permanence. Ensuite, enregistrez votre domaine dans la liste de préchargement HSTS. - Dév/Local : évitez HSTS en local pour ne pas « casser »
http://localhost
. Activez‑le uniquement en environnements d’intégration/staging/production en HTTPS. - Edge vs. Back‑end : émettre HSTS à l’edge (Front Door/CDN) garantit que c’est la première réponse vue par le navigateur, même si votre back‑end fait des redirections.
Plan de déploiement sûr
- Cartographier les origines nécessaires (API, polices, images, telemetry) et supprimer le superflu.
- Appliquer CSP en Report‑Only pendant quelques jours ; collecter les violations.
- Durcir progressivement : remplacez les jokers (
*
) par des hôtes précis, remplacez'unsafe-inline'
par des nonces/hachages. - Activer HSTS après avoir validé la redirection HTTPS partout.
- Basculer CSP en blocage et surveiller les journaux (Application Insights, SIEM).
- Option précharge : ajoutez l’attribut
preload
et inscrivez le domaine au registre HSTS Preload lorsque tout est stable.
Tests et contrôle qualité
Vérifiez vos en‑têtes avec plusieurs méthodes :
- Navigateurs : onglet Réseau, inspectez la première réponse HTML (status 200/304) pour y voir CSP et HSTS.
- Ligne de commande :
curl -I https://votre‑domaine
doit afficherStrict-Transport-Security
etContent-Security-Policy
. - Scanners : utilisez des outils d’audit reconnus (SecurityHeaders, Mozilla Observatory, Qualys SSL Labs).
Dépannage : erreurs courantes et correctifs
Symptôme | Cause probable | Correctif |
---|---|---|
HSTS absent sur la première requête | Redirection HTTP→HTTPS gérée côté back‑end, en‑tête supprimé avant l’edge | Déplacez HSTS à l’edge (Front Door/CDN/AppGW) et activez la redirection HTTPS au même niveau |
CSP trop permissive default-src * | Politique de départ « tout ouvert » | Remplacez * par des origines précises ; séparez connect-src , img-src , etc. |
Fonctionnalité cassée après passage en blocage | Scripts inline bloqués | Implémentez des nonces/hachages, ou refactorez pour éviter l’inline |
Doublon d’en‑têtes | En‑têtes ajoutés à l’edge et dans l’app | Harmonisez : soit edge, soit back‑end. Un doublon n’est pas critique, mais simplifiez la maintenance |
Azure Functions : impossible d’ajouter via host.json | Mauvaise compréhension de host.json | Utilisez un middleware (worker isolé .NET) ou un wrapper de réponse (Node/Python), ou ajoutez à l’edge |
Exemples supplémentaires prêts à copier
Fichier web.config
complet (IIS/App Service Windows)
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="HTTP to HTTPS" enabled="true" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
```
<httpProtocol>
<customHeaders>
<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains; preload" />
<add name="Content-Security-Policy" value="default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" />
<add name="Referrer-Policy" value="no-referrer-when-downgrade" />
<add name="X-Content-Type-Options" value="nosniff" />
<add name="Permissions-Policy" value="geolocation=(), microphone=(), camera=()" />
</customHeaders>
</httpProtocol>
```
Dockerfile Nginx (extrait)
FROM nginx:alpine
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
# Nginx ajoute HSTS + CSP (voir nginx.conf ci‑dessus)
Bicep : règle de sécurité Front Door (variante)
resource fdRuleSet 'Microsoft.Cdn/profiles/ruleSets@2023-05-01' existing = {
name: 'myRuleSet'
scope: resourceGroup()
}
resource addCsp 'Microsoft.Cdn/profiles/ruleSets/rules@2023-05-01' = {
name: 'CspAndHsts'
parent: fdRuleSet
properties: {
order: 1
actions: [
{
name: 'ModifyResponseHeader'
parameters: {
headerAction: 'Overwrite'
headerName: 'Content-Security-Policy'
value: "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
}
}
{
name: 'ModifyResponseHeader'
parameters: {
headerAction: 'Overwrite'
headerName: 'Strict-Transport-Security'
value: 'max-age=31536000; includeSubDomains; preload'
}
}
]
}
}
Checklist finale
- HTTPS forcé au niveau pertinent (edge ou app)
- HSTS actif sur toutes les réponses HTTPS
- CSP en Report‑Only puis en blocage
- Directives CSP affinées (nonces/hachages, origines minimales)
- Tests croisés navigateurs + scanners
- Surveillance (journaux violations CSP, erreurs réseau, télémétrie)
- Option : préchargement HSTS une fois l’écosystème entièrement en HTTPS
Conclusion
Que votre application tourne sur App Service Windows, Linux, un conteneur, Azure Functions ou soit exposée via Front Door/CDN/Application Gateway, vous pouvez implémenter CSP et HSTS sans refonte majeure. Placez HSTS là où le navigateur le voit en premier, durcissez CSP progressivement, et validez par des tests outillés : votre prochaine analyse de sécurité devrait passer haut la main.