Sécurité des Applications Serverless
Dans le cadre de notre cours "Maîtriser les Architectures Serverless : Développer des Applications Scalables et Économiques", il est impératif d'aborder un aspect souvent sous-estimé mais fondamental : la sécurité. Les architectures serverless, bien qu'elles délèguent une partie significative de la gestion de l'infrastructure au fournisseur de cloud, introduisent de nouvelles considérations et un modèle de responsabilité partagée qui redéfinit les enjeux de sécurité.
Introduction : La Sécurité en Contexte Serverless
Historiquement, la sécurité des applications impliquait de sécuriser les serveurs, les systèmes d'exploitation, les réseaux et les applications elles-mêmes. Avec le serverless, la donne change. Vous n'avez plus à vous soucier des correctifs de sécurité des systèmes d'exploitation ou de la configuration des pare-feu au niveau de l'hôte. Cependant, cela ne signifie pas que la sécurité disparaît, mais qu'elle se déplace.
Le modèle serverless met l'accent sur le code, les configurations des fonctions, les autorisations d'accès, la gestion des secrets et l'intégration avec d'autres services cloud. La granuralité des fonctions et la nature éphémère de leurs exécutions modifient la surface d'attaque et nécessitent une approche de sécurité adaptée.
Objectifs de cette leçon :
- Comprendre le modèle de responsabilité partagée en matière de sécurité serverless.
- Identifier les menaces et vulnérabilités spécifiques aux architectures serverless.
- Mettre en œuvre les meilleures pratiques pour sécuriser vos applications serverless.
- Appréhender l'importance de la gestion des identités et des accès (IAM) et de la validation des entrées.
Le Modèle de Responsabilité Partagée en Serverless
Le concept fondamental en cloud computing, et plus particulièrement en serverless, est le modèle de responsabilité partagée. Il définit clairement qui est responsable de quoi en matière de sécurité :
- Le fournisseur de cloud (ex: AWS, Azure, Google Cloud) est responsable de la sécurité du cloud. Cela inclut la protection de l'infrastructure sous-jacente qui exécute tous les services cloud, des régions, des zones de disponibilité, des serveurs physiques, du réseau et de la virtualisation.
- Vous (l'utilisateur) êtes responsable de la sécurité dans le cloud. Cela concerne tout ce que vous déployez et configurez :
- Votre code de fonction (vulnérabilités, dépendances).
- Les configurations de vos fonctions (permissions, environnement).
- La gestion des identités et des accès (IAM).
- La sécurité des données (chiffrement, classification).
- La configuration réseau de vos fonctions (VPC, groupes de sécurité).
- La gestion des secrets et des clés.
- Le logging et la surveillance.
En serverless, votre "zone de responsabilité" est considérablement étendue au niveau du contrôle d'accès, du code et de la configuration.
Menaces et Vulnérabilités Courantes dans les Applications Serverless
Bien que le serverless apporte des avantages en termes de sécurité en réduisant la surface d'attaque liée à l'infrastructure, il expose les applications à de nouvelles catégories de risques ou amplifie des risques existants :
1. Configurations de Fonction Insecure (IAM sur-permissif)
C'est probablement la vulnérabilité la plus fréquente et la plus critique. Accorder aux fonctions des permissions trop larges (ex: accès à toutes les tables DynamoDB, permissions d'administrateur) ouvre la porte à des escalades de privilèges si la fonction est compromise.
2. Attaques par Injection (OWASP Top 10 persistant)
Les injections SQL, NoSQL, de commandes (OS command injection) ou Cross-Site Scripting (XSS) restent des menaces pertinentes si les entrées utilisateur ne sont pas correctement validées et assainies. Une fonction serverless est souvent un point d'entrée pour les données utilisateur.
3. Gestion des Dépendances Insécurisée
Les applications serverless s'appuient fortement sur des bibliothèques tierces. Des dépendances obsolètes ou contenant des vulnérabilités connues (CVE) peuvent être exploitées par des attaquants (attaques de la chaîne d'approvisionnement logicielle).
4. Attaques par Déni de Service (DoS) et Épuisement Financier
Une fonction mal conçue ou non protégée peut être ciblée par des attaques DoS, non seulement pour rendre le service indisponible, mais aussi pour entraîner des coûts d'exécution excessifs en raison de la nature "pay-per-execution" du serverless.
5. Fuite de Données Sensibles
Des données sensibles (clés API, identifiants de base de données, secrets) peuvent être exposées si elles sont mal gérées, par exemple codées en dur dans le code, stockées dans des variables d'environnement non sécurisées ou imprimées dans les logs.
6. Rupture d'Authentification et d'Autorisation
Si l'authentification et l'autorisation entre les services ou pour les utilisateurs finaux sont mal implémentées, un attaquant peut usurper des identités ou accéder à des ressources non autorisées.
7. Erreurs de Configuration API Gateway
L'API Gateway est souvent la porte d'entrée de vos fonctions. Des configurations incorrectes (absence de throttling, CORS trop permissif, absence de WAF) peuvent rendre votre application vulnérable.
Bonnes Pratiques pour Sécuriser les Applications Serverless
La sécurité serverless nécessite une approche proactive et multicouche. Voici les pratiques essentielles :
1. Appliquer le Principe du Moindre Privilège (PoLP)
C'est la règle d'or de la sécurité cloud. Chaque fonction serverless (Lambda, Azure Function, Google Cloud Function) doit avoir uniquement les permissions nécessaires pour exécuter sa tâche. Pas plus.
Exemple de Politique IAM (AWS) :
Imaginons une fonction Lambda qui doit lire et écrire uniquement dans une table DynamoDB spécifique nommée MaTableUtilisateurs.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem"
],
"Resource": "arn:aws:dynamodb:eu-west-1:123456789012:table/MaTableUtilisateurs"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:eu-west-1:123456789012:log-group:/aws/lambda/MaFonctionUtilisateurs:*"
}
]
}
Explication du code : Cette politique IAM est attachée au rôle d'exécution de votre fonction Lambda.
- Le premier bloc
Statementautorise uniquement les actionsGetItem,PutItemetUpdateItemsur la ressource spécifiqueMaTableUtilisateurs. Il n'y a pas d'accès à d'autres tables ni d'autres actions DynamoDB (commeDeleteItemouScan). - Le second bloc
Statementautorise la fonction à écrire ses logs dans CloudWatch Logs, mais uniquement dans son propre groupe de logs.
À Éviter absolument : Utiliser des jokers (*) pour les actions ou les ressources, sauf si c'est absolument nécessaire et que vous avez une justification de sécurité solide. Par exemple, dynamodb:* ou Resource: "*" donnerait à votre fonction un accès illimité, ce qui est une vulnérabilité majeure.
2. Valider et Assainir Toutes les Entrées
Toutes les entrées qui parviennent à votre fonction (requêtes HTTP, événements de SQS, messages Kinesis, etc.) doivent être considérées comme non fiables. Appliquez une validation stricte des types de données, des formats, des plages de valeurs et assainissez les données pour prévenir les injections et le XSS.
Exemple de Validation d'Entrée (Node.js/Lambda) : Considérons une fonction Lambda déclenchée par une API Gateway recevant un corps JSON.
// handler.js
const Joi = require('joi'); // Utilisation d'une bibliothèque de validation robuste
const userSchema = Joi.object({
id: Joi.string().guid({ version: ['uuidv4'] }).required(),
name: Joi.string().min(3).max(50).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(1).max(120).optional()
});
exports.createUser = async (event) => {
try {
const body = JSON.parse(event.body);
// Validation des données d'entrée
const { error, value } = userSchema.validate(body);
if (error) {
console.error('Validation error:', error.details[0].message);
return {
statusCode: 400,
body: JSON.stringify({ message: `Invalid input: ${error.details[0].message}` }),
};
}
// Si la validation réussit, 'value' contient les données validées et assainies
const { id, name, email, age } = value;
// Ici, vous pouvez traiter les données en toute sécurité
// Ex: enregistrer dans une base de données
console.log(`User created: ${name} (${email})`);
return {
statusCode: 201,
body: JSON.stringify({ message: 'User created successfully', user: value }),
};
} catch (parseError) {
console.error('JSON Parse Error:', parseError);
return {
statusCode: 400,
body: JSON.stringify({ message: 'Invalid JSON body' }),
};
}
};
Explication du code :
- Nous utilisons la bibliothèque
Joipour définir un schéma de validation rigoureux pour les données utilisateur (id,name,email,age). C'est une pratique beaucoup plus robuste que des vérifications manuelles simples. JSON.parse(event.body)tente de parser le corps de la requête. Un bloctry-catchest utilisé pour gérer les erreurs de parsing JSON invalides.userSchema.validate(body)exécute la validation. Si des erreurs sont détectées, un code d'état 400 (Bad Request) est retourné avec un message d'erreur explicite.- Si la validation est réussie, les données dans
valuesont considérées comme sûres et peuvent être utilisées pour le traitement métier.
3. Gérer les Secrets en Toute Sécurité
Les secrets (clés API, identifiants de base de données, jetons OAuth) ne doivent jamais être codés en dur dans votre code ou stockés directement dans les variables d'environnement de la fonction. Utilisez des services dédiés :
- AWS Secrets Manager ou AWS Systems Manager Parameter Store
- Azure Key Vault
- Google Secret Manager
Ces services permettent de stocker, de gérer et de récupérer des secrets de manière sécurisée et auditable. Vos fonctions devraient récupérer ces secrets au moment de l'exécution, via les SDK des fournisseurs cloud.
4. Sécuriser l'API Gateway
Si votre fonction est exposée via une API Gateway, assurez-vous de configurer :
- Authentification et Autorisation : Utilisez des méthodes d'autorisation robustes (Lambda Authorizers, Cognito User Pools, IAM).
- Throttling et Quotas : Limitez le nombre de requêtes pour prévenir les DoS et contrôler les coûts.
- Pare-feu d'Application Web (WAF) : Intégrez AWS WAF, Azure Application Gateway WAF, ou Google Cloud Armor pour filtrer le trafic malveillant.
- CORS (Cross-Origin Resource Sharing) : Configurez CORS de manière restrictive pour autoriser uniquement les domaines de confiance.
5. Gérer les Dépendances avec Vigilance
- Mises à jour régulières : Maintenez vos bibliothèques et frameworks à jour pour bénéficier des derniers correctifs de sécurité.
- Analyse de vulnérabilités : Utilisez des outils comme
npm audit,pip-audit, ou des scanners de dépendances (ex: Snyk, Dependabot) pour identifier les vulnérabilités connues dans vos dépendances. - Minimisation des dépendances : N'incluez que les dépendances absolument nécessaires pour réduire la surface d'attaque.
6. Implémenter une Journalisation et une Surveillance Robustes
- Centralisation des logs : Assurez-vous que toutes vos fonctions envoient leurs logs à un service centralisé (CloudWatch Logs, Azure Monitor, Google Cloud Logging).
- Surveillance proactive : Configurez des alertes pour les activités suspectes (erreurs inattendues, pics d'invocation, accès non autorisés).
- Attention aux données sensibles : Ne jamais logger d'informations sensibles (mots de passe, numéros de carte de crédit) en clair.
7. Sécurité au Niveau du Réseau (VPC pour AWS Lambda)
Pour les fonctions Lambda accédant à des ressources dans un VPC (bases de données RDS, clusters ElastiCache), configurez la fonction pour qu'elle s'exécute dans le VPC. Utilisez des groupes de sécurité et des listes de contrôle d'accès réseau (ACL) pour restreindre le trafic vers et depuis la fonction.
8. Chiffrement des Données
- Données au repos : Chiffrez les données stockées dans les bases de données (DynamoDB, S3, RDS) à l'aide de clés gérées par le fournisseur de cloud (KMS, Azure Key Vault, Google Cloud KMS).
- Données en transit : Utilisez HTTPS/TLS pour toutes les communications entre services et avec les clients. L'API Gateway le fait par défaut, assurez-vous que vos fonctions aussi pour les appels sortants.
9. Intégrer la Sécurité dans le Cycle de Vie de Développement (DevSecOps)
- SAST (Static Application Security Testing) : Analysez votre code source pour les vulnérabilités avant le déploiement.
- DAST (Dynamic Application Security Testing) : Testez la sécurité de votre application en cours d'exécution.
- Tests d'intrusion : Organisez des tests d'intrusion réguliers pour identifier les failles.
- Code Reviews : Intégrez la sécurité comme un pilier des revues de code.
Conclusion
La sécurité des applications serverless n'est pas une préoccupation moindre, mais une préoccupation différente. En comprenant le modèle de responsabilité partagée et en adoptant une approche de sécurité rigoureuse centrée sur le code, les configurations et les identités, vous pouvez construire des applications serverless robustes et résilientes.
Les principes clés à retenir sont : le principe du moindre privilège, la validation et l'assainissement systématiques des entrées, la gestion sécurisée des secrets, et l'intégration de la sécurité tout au long du cycle de vie de développement. La nature dynamique et événementielle du serverless exige une vigilance continue et une automatisation poussée des contrôles de sécurité.