Cloud

Azure Functions avec Storage privé : garder le runtime, le déploiement et le diagnostic sous contrôle

Une approche opérationnelle pour sécuriser le compte Storage d’Azure Functions avec Private Endpoint sans casser le runtime, les déploiements, la résolution DNS, les triggers et les contrôles d’exploitation.

02 juin 2026 azure-functionsstorage-accountprivate-endpointdnsnetworkingdeployment

Sécuriser une Azure Function avec un Private Endpoint sur son entrée HTTP ne suffit pas à rendre l’architecture privée. Une Function dépend aussi d’un compte Storage pour son runtime, ses triggers, certains états internes, ses packages et parfois ses journaux. Quand ce compte Storage est fermé trop vite ou mal résolu, le symptôme côté application peut être déroutant : la Function démarre mal, les triggers ne se synchronisent plus, le déploiement échoue, ou le portail affiche une erreur qui ressemble à un problème applicatif.

Le scénario traité ici est courant. Une équipe exploite une Function App dans Azure, intégrée à un VNet, avec un objectif de réduction de l’exposition publique. L’entrée applicative peut déjà passer par APIM, Application Gateway ou un Private Endpoint. Le compte Storage associé à la Function doit maintenant être placé derrière Private Endpoint, avec accès public limité ou désactivé. L’objectif n’est pas seulement de cocher une case réseau, mais de garder le runtime, les déploiements et les diagnostics opérables.

La difficulté vient du fait que le compte Storage n’est pas un simple backend métier. Il est utilisé par la plateforme Functions elle-même. Le fermer sans préparer les flux de plateforme revient parfois à couper la branche sur laquelle le runtime est assis.

Décrire les dépendances avant de fermer le réseau

Avant de modifier le compte Storage, il faut écrire à quoi il sert réellement. Une Function App peut utiliser le stockage pour AzureWebJobsStorage, pour les triggers Storage, pour Durable Functions, pour le montage de package, pour des files ou tables internes, ou pour des dépendances applicatives supplémentaires. Toutes ces utilisations ne portent pas le même risque.

text function-storage-dependencies.txt
Function App
Nom : func-orders-prod
Plan : Premium ou Dedicated avec integration VNet
Entree applicative : APIM interne ou Private Endpoint selon le design

Compte Storage plateforme
AzureWebJobsStorage
Blob : packages, host state, receipts selon le runtime
Queue : triggers ou messages internes selon les usages
Table : etats ou metadata selon les extensions
File : contenu applicatif selon le mode de deploiement

Objectif reseau
Acces public limite ou desactive
Private Endpoints pour les sous-services utilises
DNS prive prouve depuis le chemin de sortie de la Function
Deploiement encore possible par un chemin controle

Ce cadrage évite une erreur fréquente : créer uniquement un Private Endpoint blob parce que le compte Storage semble être utilisé pour des packages, alors qu’un trigger queue ou une extension Durable Functions a aussi besoin de queue et parfois table. Le bon périmètre dépend de l’usage réel, pas d’un modèle générique.

Séparer l’entrée privée de la sortie privée

Une Function peut être privée en entrée tout en sortant encore par un chemin public vers son compte Storage. L’inverse est aussi possible : elle peut accéder à un Storage privé tout en restant exposée publiquement. Ces deux dimensions doivent être traitées séparément.

L’entrée concerne les clients qui appellent la Function. La sortie concerne ce que la Function et son runtime doivent joindre. Pour atteindre un compte Storage privé, la Function doit disposer d’un chemin sortant cohérent : intégration VNet, DNS privé, routes éventuelles et sous-services Storage exposés par Private Endpoint.

text inbound-outbound-model.txt
Flux entrant
Client interne ou passerelle
-> Nom applicatif
-> Function App ou passerelle devant la Function

Flux sortant runtime
Function App
-> Integration VNet
-> Resolution mystorage.blob.core.windows.net vers IP privee
-> Private Endpoint blob

Flux sortant supplementaire selon usage
Function App
-> mystorage.queue.core.windows.net
-> mystorage.table.core.windows.net
-> mystorage.file.core.windows.net si necessaire

Si cette séparation n’est pas faite, les diagnostics deviennent confus. Un test HTTP réussi contre la Function ne prouve pas que le runtime atteint son Storage. Un compte Storage privé correctement résolu depuis une VM du hub ne prouve pas que la Function utilise le même chemin DNS.

Identifier les sous-services Storage vraiment nécessaires

Storage expose plusieurs sous-services avec des noms DNS différents : Blob, Queue, Table et File. Les Private Endpoints sont créés par sous-service. Il ne faut donc pas supposer qu’un endpoint blob rend tout le compte Storage privé et joignable pour tous les usages.

bash 01-identify-storage-usage.sh
az functionapp config appsettings list -g rg-app-prod -n func-orders-prod --query "[?name=='AzureWebJobsStorage' || contains(name, 'Storage') || contains(name, 'WEBSITE_RUN_FROM_PACKAGE')].{name:name, value:value}"

az functionapp show -g rg-app-prod -n func-orders-prod --query '{name:name, state:state, kind:kind, serverFarmId:serverFarmId}'

Cette lecture ne suffit pas toujours, mais elle donne les premiers indices. Un AzureWebJobsStorage basé sur une chaîne de connexion pointe vers un compte. Un WEBSITE_RUN_FROM_PACKAGE peut indiquer un package distant. Un usage Durable Functions nécessite une attention particulière aux files, tables et blobs selon la configuration.

Le résultat attendu n’est pas une liste abstraite, mais une décision explicite.

text storage-private-endpoint-scope.txt
Sous-services requis pour func-orders-prod
blob  : requis pour runtime et packages
queue : requis car trigger queue utilise le meme compte
table : requis car extension Durable Functions active
file  : non requis si aucun contenu monte depuis Azure Files

Decision
Creer Private Endpoint blob, queue et table
Ne pas creer file tant que le mode de deploiement ne l'exige pas
Documenter cette decision dans la note de changement

Cette décision rend le changement lisible. Elle évite aussi de créer des endpoints partout sans comprendre les flux, ou au contraire d’en manquer un et de diagnostiquer ensuite un runtime instable.

Créer les Private Endpoints avec des zones DNS séparées

Chaque sous-service Storage utilise sa zone privée. Pour Blob, la zone est privatelink.blob.core.windows.net. Pour Queue, privatelink.queue.core.windows.net. Pour Table, privatelink.table.core.windows.net. Pour File, privatelink.file.core.windows.net.

bash 02-create-storage-private-endpoints.sh
export RG_NET=rg-network-prod
export RG_APP=rg-app-prod
export LOCATION=westeurope
export VNET=vnet-app-prod
export PE_SUBNET=snet-private-endpoints
export STORAGE=stfuncordersprod

STORAGE_ID=$(az storage account show -g "$RG_APP" -n "$STORAGE" --query id -o tsv)

for SERVICE in blob queue table; do
az network private-dns zone create   -g "$RG_NET"   -n "privatelink.$SERVICE.core.windows.net"

az network private-dns link vnet create   -g "$RG_NET"   -n "link-$VNET-$SERVICE"   -z "privatelink.$SERVICE.core.windows.net"   -v "$VNET"   -e false

az network private-endpoint create   -g "$RG_NET"   -n "pe-$STORAGE-$SERVICE"   -l "$LOCATION"   --vnet-name "$VNET"   --subnet "$PE_SUBNET"   --private-connection-resource-id "$STORAGE_ID"   --group-id "$SERVICE"   --connection-name "cn-$STORAGE-$SERVICE"

az network private-endpoint dns-zone-group create   -g "$RG_NET"   --endpoint-name "pe-$STORAGE-$SERVICE"   -n "dzg-$SERVICE"   --private-dns-zone "privatelink.$SERVICE.core.windows.net"   --zone-name "privatelink.$SERVICE.core.windows.net"
done

Dans une vraie landing zone, les zones privées peuvent déjà exister dans un hub DNS. Dans ce cas, il ne faut pas les recréer dans un spoke au hasard. Le point important est que la Function résolve les noms Storage via les zones privées effectivement utilisées par son VNet ou par son resolver.

Tester la résolution depuis le chemin de la Function

Le contrôle DNS doit être effectué depuis un contexte qui représente la Function. Une VM dans le même VNet peut donner un premier signal, mais elle ne remplace pas un test depuis le contexte applicatif quand l’intégration VNet, les routes ou les resolvers ne sont pas identiques.

bash 03-check-storage-dns.sh
nslookup stfuncordersprod.blob.core.windows.net
nslookup stfuncordersprod.queue.core.windows.net
nslookup stfuncordersprod.table.core.windows.net

# Resultat attendu
# stfuncordersprod.blob.core.windows.net
# -> stfuncordersprod.privatelink.blob.core.windows.net
# -> 10.50.20.11
#
# stfuncordersprod.queue.core.windows.net
# -> stfuncordersprod.privatelink.queue.core.windows.net
# -> 10.50.20.12

Un résultat public n’est pas un détail. Tant que la résolution ne renvoie pas l’adresse privée depuis le bon chemin, le runtime ne teste pas le design attendu. Il peut encore fonctionner par exception réseau, par accès public résiduel ou par un chemin différent de celui documenté.

Ne pas désactiver l’accès public avant une preuve de runtime

La fermeture du compte Storage doit arriver après une preuve fonctionnelle. Il faut vérifier que la Function démarre, que les triggers sont synchronisés, que les logs de host ne montrent pas d’échec Storage, et que le déploiement prévu dispose encore d’un chemin valide.

bash 04-runtime-checks-before-closure.sh
az functionapp show -g rg-app-prod -n func-orders-prod --query '{state:state, hostNames:hostNames, outboundIpAddresses:outboundIpAddresses}'

az functionapp function list -g rg-app-prod -n func-orders-prod --query '[].{name:name, trigger:config.bindings[0].type}'

az webapp log tail -g rg-app-prod -n func-orders-prod

Les messages importants ne sont pas toujours spectaculaires. Une erreur de type connexion Storage, synchronisation de triggers impossible, package inaccessible ou host lock non obtenu doit être traitée avant de fermer l’accès public. Sinon, le changement réseau devient le suspect unique alors que plusieurs dépendances étaient déjà fragiles.

Prévoir le chemin de déploiement

Le déploiement est souvent oublié dans les architectures privées. Une Function peut être privée et fonctionner, mais ne plus recevoir de package depuis l’agent CI/CD si celui-ci n’a pas de chemin réseau vers le site SCM ou vers le Storage qui héberge le package. Il faut donc décider comment les déploiements seront faits après fermeture.

text deployment-path-options.txt
Option 1 : agent CI/CD dans le reseau prive
L'agent resout les noms prives
Il atteint SCM ou le point de publication autorise
Les secrets de deploiement restent limites

Option 2 : package prepare puis recupere par la Function
Le Storage du package est joignable par chemin prive
Le runtime peut lire le package au demarrage
L'URL ou la reference de package ne depend pas d'un acces public oublie

Option 3 : fenetre de deploiement controlee
Exception temporaire documentee
Plage source limitee
Retrait de l'exception verifie apres publication

Le bon choix dépend de l’organisation. Ce qui n’est pas acceptable, c’est de découvrir après coup que la seule méthode de déploiement utilisait un accès public supprimé sans alternative.

Fermer le compte Storage progressivement

Quand les preuves sont réunies, la fermeture peut être appliquée. Le changement doit être lisible : état réseau du compte, règles restantes, Private Endpoints, DNS, puis test runtime après modification.

bash 05-restrict-storage-network.sh
az storage account update -g rg-app-prod -n stfuncordersprod --public-network-access Disabled

az storage account show -g rg-app-prod -n stfuncordersprod --query '{publicNetworkAccess:publicNetworkAccess, defaultAction:networkRuleSet.defaultAction}'

az network private-endpoint-connection list -g rg-app-prod --name stfuncordersprod --type Microsoft.Storage/storageAccounts --query '[].{name:name, status:privateLinkServiceConnectionState.status}'

Après fermeture, il faut refaire les tests qui comptent : démarrage de la Function, exécution d’un trigger, lecture ou écriture Storage attendue, déploiement de test si possible, et vérification depuis un réseau qui ne doit plus accéder au compte.

Diagnostiquer les pannes récurrentes

Les pannes les plus fréquentes suivent quelques motifs. Le Private Endpoint blob existe, mais le trigger queue utilise un sous-service non exposé. La zone privée existe dans le hub, mais le VNet de la Function ne l’utilise pas. Le compte Storage est privé, mais l’agent de déploiement est toujours hors réseau. Une Function Premium a bien une intégration VNet, mais les paramètres de routage ne forcent pas le trafic attendu vers le réseau privé. Une exception firewall laissée pendant les tests donne l’impression que le design privé fonctionne.

text storage-private-troubleshooting.txt
La Function ne demarre plus
Verifier AzureWebJobsStorage
Verifier DNS blob, queue, table selon les extensions
Lire les logs host avant de modifier les permissions

Les triggers ne se synchronisent pas
Verifier le sous-service Storage utilise par le trigger
Controler queue ou table, pas seulement blob
Comparer le test DNS depuis VM et depuis contexte Function

Le deploiement echoue
Identifier si l'agent atteint SCM
Verifier si le package est lu depuis un Storage prive
Documenter l'exception temporaire si elle existe

Tout fonctionne encore depuis l'exterieur
Chercher acces public residuel
Verifier firewall Storage et exceptions trusted services
Tester depuis un reseau explicitement non autorise

Cette grille garde les couches séparées. Elle évite de transformer chaque incident en débat général sur Private Endpoint alors que le problème peut être un sous-service manquant, un resolver incohérent ou un chemin CI/CD oublié.

Conserver une preuve d’exploitation

Un changement privé réussi doit laisser une trace simple. Elle doit pouvoir être relue par une équipe qui n’a pas participé au changement. La preuve doit inclure les noms testés, les adresses privées obtenues, les sous-services couverts, le mode de déploiement retenu et le résultat après fermeture.

text function-storage-private-evidence.txt
Changement : Storage prive pour func-orders-prod
Compte Storage : stfuncordersprod
Sous-services privatisees : blob, queue, table
Zones DNS : privatelink.blob/queue/table.core.windows.net
Test DNS depuis chemin applicatif : IP privees 10.50.20.11, 10.50.20.12, 10.50.20.13
Runtime : host demarre apres fermeture
Trigger queue : execution OK
Deploiement : agent prive confirme
Acces public Storage : desactive
Test externe non autorise : echec attendu

Ce format est plus utile qu’une capture d’écran isolée. Il montre que le design ne repose pas sur une supposition et que le contrôle privé a été validé côté runtime, pas seulement côté réseau.

Conclusion

Le compte Storage d’une Azure Function est une dépendance de plateforme autant qu’une ressource applicative. Le sécuriser avec Private Endpoint demande donc plus qu’un endpoint et une zone DNS. Il faut identifier les sous-services réellement utilisés, prouver la résolution depuis le chemin de la Function, vérifier le démarrage du host, préserver le déploiement et fermer l’accès public seulement après validation.

Cette discipline évite les architectures privées qui ne tiennent que tant qu’une exception publique reste ouverte. Elle produit aussi un diagnostic plus propre : quand la Function échoue, l’équipe sait distinguer DNS, sous-service Storage manquant, routage, déploiement et runtime, au lieu de rouvrir le réseau à l’aveugle.