Published by

Il y a 3 ans -

Temps de lecture 18 minutes

Gérer ses secrets en serverless : plus aucun secret ne vous résistera

Introduction

Si vous avez déjà touché à du serverless sur AWS, vous avez sûrement déjà été confronté à gérer vos secrets. Cette petite chaîne de caractères qui donne accès à des données sensibles comme un accès à une base de données ou à une API privée.

L’importance de gérer correctement ses secrets est primordiale, mais à trop vouloir tout contrôler, on ne sait plus comment allier simplicité avec efficacité surtout dans un écosystème aussi vaste que le serverless.

Alors comment faire pour que mon secret reste secret ? Qu’il ne soit pas divulgué à n’importe qui ? Comment faire pour gérer plusieurs dizaines voire centaines de secrets ? Et comment répercuter un changement de secret dans toute mon infrastructure Serverless ?

Je vais vous donner les clés pour gérer vos secrets en partant de la solution la plus simple jusqu’à la solution la plus riche.

La solution la plus simple mais la moins pratique

En utilisant le service lambda

AWS propose déjà de chiffrer les variables d’environnement et donc des données sensibles de bout en bout, sans effort, avec seulement une clé KMS. C’est une façon très simple de chiffrer les secrets dont votre lambda pourrait avoir besoin pour, par exemple, accéder à une base de données, une API ou n’importe quelle variable d’environnement de votre choix.

Chiffrement des variables d’environnement dans le service Lambda depuis la console AWS

Cette solution, bien que rapide, est assez simpliste et n’est dans bien des cas pas taillée pour la production de projets d’envergures.

Elle pose en effet plusieurs problèmes :

  • Tout d’abord, toute personne ayant la possibilité de configurer la lambda doit avoir accès à la valeur du secret puisque le chiffrement intervient directement au niveau d’AWS, après le déploiement.
    Si, par exemple, une lambda doit accéder à une base de donnée, l’équipe qui développe cette lambda doit impérativement connaître le mot de passe pour le configurer en variable d’environnement. L’équipe est donc responsable de la lambda ainsi que du mot de passe d’accès à la base. Cela casse le principe du least privilege : une personne ayant une responsabilité dans une équipe n’a pas à endosser la responsabilité des autres équipes.
  • Dans une architecture serverless, chaque lambda ayant un périmètre réduit, il arrive que plusieurs fonctions accèdent à la même base de données et bien souvent elles vont donc toutes avoir les mêmes accès pour s’y connecter (identifiant et mot de passe dans leur variable d’environnement). Et comme je ne doute pas que vous changez régulièrement le mot de passe, toutes les lambda vont devoir également renouveler leurs variables d’environnement à ce moment là. Si l’on revient sur l’exemple précédent, cela veut dire que l’équipe va devoir de nouveau redéployer toutes les lambda pour prendre en compte le nouveau mot de passe. Ce n’est pas du tout scalable et donc pas vraiment envisageable en production.

Avec le secret chiffré en amont

Pour palier au problème de responsabilités multiples, et donc ne pas avoir à connaître à l’avance le secret, la solution consiste à le chiffrer en amont.

En effet, si l’équipe responsable du secret est en mesure de fournir à l’équipe de développement ce secret déjà chiffré, ainsi que la clé qui permet de le déchiffrer alors c’est presque gagné !

Le principe de least privilege est respecté puisque les développeurs n’ont accès qu’au secret chiffré.

L’équipe devops donne le secret chiffré ainsi que la clé associée à l’équipe de dev pour ne pas être responsable du secret.

Je vous l’admets, cela rajoute des processus en plus entre les différentes équipes pour s’échanger les secrets chiffrés et les clés. Mais dans le cas d’une startup où les petites équipes sont assez proches, la solution est viable. Le problème de scalabilité n’est cependant toujours pas résolu puisque à chaque rotation il faudra répéter la même procédure. Voyons donc une gestion des secrets un peu plus avancée.

Gestion plus avancée des secrets : SSM

Présentation

AWS a mis en place depuis fin 2016 un service qui permet justement de gérer ses configurations et ses secrets. Ce service s’appelle Simple Service Manager (SSM) ou plus communément System Manager. Parmi ses multiples facettes, ce service offre une fonctionnalité qui nous intéresse particulièrement dans cet article : Parameter Store qui est tout simplement un gestionnaire de paramètres.

Parameter Store utilise un système de clé/valeur hiérarchique qui permet de stocker les paramètres sous forme de chemin absolu comme dans une arborescence de fichier. On pourra par exemple stocker tous les secrets liés à une application sous le prefix “/myapp/secret/*”. Ce service s’intègre bien évidemment avec IAM pour gérer les droits sur les paramètres, mais il s’intègre également avec KMS pour chiffrer les secrets. Voyons un peu comment on peut le coupler avec nos lambda.

Un secret stocké dans le Parameter Store.

Fonctionnement et limites

L’utilisation de Parameter Store permet d’avoir une couche d’abstraction supplémentaire entre un secret et une variable d’environnement. C’est l’équivalent de notre chiffrement en amont mais dans un service prévu pour et donc beaucoup plus facile à utiliser et sécurisé.

Dans Parameter Store, le secret est stocké dans un paramètre sous forme de “secureString”. Qui dit “secureString”, dit chiffrement avec KMS.

La lambda, quant à elle, contient en variable d’environnement le chemin complet du secret dans Parameter Store. Elle ne contient donc aucune donnée sensible, seulement des références.

Cela permet de décorréler le cycle de vie des secrets (rotation), des livraisons des lambdas; le secret peut changer autant de fois que nécessaire, à la prochaine instanciation, la lambda ira récupérer la dernière valeur.

Schéma représentant la couche d’abstraction entre le service Lambda et le Parameter Store.

Cerise sur le gâteau, on peut facilement contrôler l’accès à un sous ensemble de paramètre grâce au fonctionnement hiérarchique Parameter Store avec IAM. Gérer finement les accès mais sans le faire au compte-goutte, c’est aussi ça l’avantage d’un gestionnaire de paramètres.

[java]{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Action": ["ssm:GetParameter"],
"Resource": ["arn:aws:ssm:eu-west-1::parameter/myapp/secret/*"]
}
{
"Sid": "2",
"Effect": "Allow",
"Action": ["kms:Decrypt"],
"Resource": ["arn:aws:kms:eu-west-1::key/key-id"]
}
]
}[/java]

Exemple d’une policy IAM pour récupérer la valeur déchiffrée des secrets dans SSM sur les ressources commençant par “/myapp/secret/”.

A l’exécution de la lambda, il n’y a rien de magique. Pour qu’elle puisse lire le secret, le développeur doit d’abord récupérer le chemin stocké dans la variable d’environnement puis ensuite aller lire le secret dans Parameter Store. Dans l’exemple ci-dessus la valeur de la variable d’environnement “API_KEY” est “/myapp/secret/api-key”. Ce chemin correspond au secret stocké dans Parameter Store. Ce secret étant chiffré grâce à KMS il faut indiquer en deuxième paramètre de l’appel de la fonction la valeur “true” pour le récupérer déchiffré. Bien entendu, il faut que la Lambda ait les droits suffisants sur la clé KMS en question. Le secret est utilisable et forcément à jour car le référentiel est bien Parameter Store.

[js]"use strict";
const SSM = require("aws-sdk/clients/ssm");
const ssm = new SSM();

// Step 1 : Retrieve SSM Parameter from process env
const apiKeySSMPath = process.env.API_KEY;

async function handler(event, context) {
// Step 2.1 : Call function to retrieve SSM Parameter
const apiKeyPass = await getSSMParameter(apiKeySSMPath);
// …
return { statusCode: 200 };
}

async function getSSMParameter(parameterName) {
const params = {
Name: parameterName,
WithDecryption: true
};
// Step 2.2 : Call SSM service with parameter name and
// decryption to get associated value
const result = await ssm.getParameter(params).promise();

// Step 3 : Return value retrieved
return result.Parameter.Value;
}

module.exports = handler;
[/js]

Exemple de code d’une lambda en NodeJS pour récupérer un secret depuis un paramètre SSM. On peut imaginer rajouter un cache pour ne pas requêter SSM à chaque invocation de la lambda avec un TTL en bonus.

Venons-en aux limites de cette solution. L’API qui permet de récupérer les paramètres depuis SSM n’offre pas un débit par seconde énorme. Seulement 100 requêtes/sec. L’augmentation est possible jusqu’à 1000 requêtes/sec en faisant une demande à AWS mais la facture sera forcément plus salée. Étant donné qu’un secret ne change pas tous les quatre matins, il est conseillé de résoudre les secrets une fois par instance de lambda au lieu de le faire à chaque requête. Avec cette optimisation, il faudra avoir plus de 1000 lambda instanciées par seconde avant d’avoir des problèmes de scalabilité.

Alternative : lecture au déploiement

Si ce problème de scalabilité est contraignant, il est possible de faire autrement.

En effet, au lieu de lire le secret dans SSM à chaque instanciation d’une lambda, on peut le faire une seule fois au moment de déployer la lambda. L’abstraction entre SSM et la lambda est donc déplacée à la création de la lambda et plus à chaque exécution.

Pour ce faire, il suffit de récupérer le secret chiffré (withDecrypt: false) depuis SSM et de le placer dans une variable d’environnement de la lambda. On a ainsi une chaîne de caractère impossible à lire en l’état mais qui peut être déchiffrée avec la bonne clé KMS.

L’appel à la fonction getParameter() ne se fait plus à l’exécution de la lambda mais au déploiement.

Si l’on stocke le secret chiffré en tant que variable d’environnement, l’appel à la fonction getParameter() se fait une seule fois au déploiement.

Aujourd’hui, début 2020, cette alternative n’est en revanche pas totalement supporté par CloudFormation car pour l’instant il ne gère le déchiffrement des secureString que dans 11 ressources. Les variables d’environnement lambda n’en font pas parties.

[java]Parameters:
ApiKey:
Type: ‘AWS::SSM::Parameter::Value<String>’
Resources:
Lambda:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: ./my-function.zip
Environment:
Variables:
API_KEY: !Ref ApiKey # Not supported by CloudFormation here.
FunctionName: MyFunction
Handler: index.lambda_handler
Role: arn:aws:iam::123456789012:role/service-role/MyFunction-role-xxxxx
Runtime: nodejs12.x[/java]

Avec ce template Cloudformation, vous allez tomber sur ce message d’erreur :

[text]An error occurred (ValidationError) when calling the CreateStack operation: Parameters [/parameter/app/api-key] referenced by template have types not supported by CloudFormation.[/text]

[java]Resources:
Lambda:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: ./my-function.zip
Environment:
Variables:
# SSM Secure Reference is not supported here.
API_KEY: ‘{{resolve:ssm-secure:/parameter/app/api-key:1}}’
FunctionName: MyFunction
Handler: index.lambda_handler
Role: arn:aws:iam::12345678910:role/service-role/MyFunction-role-xxxxx
Runtime: nodejs12.x[/java]

Avec ce deuxième template qui permet de déchiffrer directement le paramètre, vous allez avoir ce message d’erreur :

[text]An error occurred (ValidationError) when calling the CreateStack operation: SSM Secure reference is not supported in: [AWS::Lambda::Function/Properties/Environment/Variables/API_KEY][/text]

Un outil d’infra as Code comme Terraform ou Serverless Framework sait en revanche gérer les secureString de SSM n’importe où.

Quant bien même votre outil d’infra as code gère la récupération de secrets chiffrés, il persiste néanmoins un inconvénient de taille dans cette approche de lecture au déploiement : chaque modification du secret implique un redéploiement des services qui l’utilisent.

Notre avis sur Parameter Store

SSM offre une gestion facilitée et un stockage gratuit pour des secrets dit “standard”. Le rate-limiting d’API de ce service est quant à lui limité. Et cette limite peut être atteinte assez rapidement ! Aussi, une fois le secret stocké, aucun mécanisme out-of-the-box de rotation n’est prévu dans Parameter Store. Il vous faudra développer le vôtre, ce qui augmente grandement le risque de mauvaise manipulation.

Enfin, les secrets sont stockés dans un référentiel distinct qui procure bien une séparation des responsabilités.

Heureusement, il existe encore un service plus poussé pour gérer nos secrets aux petits oignons. Alors passons encore au niveau ultime dans la gestion des secrets.

Le service ultime : Secrets Manager

Présentation

Le service Secrets Manager est le service parfait pour gérer au mieux vos secrets. Le principal atout de ce service est sa capacité à faire la rotation de secret. Très bien intégré avec les services de bases de données d’AWS, il pourra changer vos secrets automatiquement et de manière transparente à une fréquence donnée en jour.

Son fonctionnement est basé sur des lambda pour mettre à jour le secret. Si le secret concerne un service de bases de données d’AWS, alors vous pourrez utiliser le code de la lambda que Amazon vous fournira. Mais si vous voulez mettre à jour un secret qui n’est pas en lien avec un service AWS, vous devez alors fournir vous-même le code de la lambda basé sur des étapes de rotation. Vous pouvez aussi vous aider d’un template que le service vous fournis pour créer cette lambda.

Pour ce qui est du fonctionnement du service, le principe reste le même que le Parameter Store vu précédemment mise à part la rotation des secrets qui peut être à première vue plus complexe à mettre en place. Si le Secrets Manager n’avait pas la rotation des secrets, il deviendrait beaucoup plus futile et surtout beaucoup trop cher. Alors regardons l’atout principal de ce gestionnaire de secret.

Rotation des secrets

Principe des versions et des labels

Lorsqu’une rotation s’effectue, une nouvelle version du secret est créée en plus de devenir active. Afin de gérer ces différentes versions et de pouvoir les différencier, Amazon a mis en place la notion de versions et de labels.

Une version est un état du secret à un instant T. Un secret peut donc avoir plusieurs versions mais seulement une version sera active en même temps. À chaque rotation, une nouvelle version est créée.

Une version est définie par son ID qui est par défaut un uuid, des staging labels que nous allons voir ci-dessous et enfin la valeur du secret qui peut être du JSON ou du texte.

En ce qui concerne les labels, ce sont des étiquettes que l’on colle à une version d’un secret. Mais attention, chaque étiquette est unique, ce qui veut dire que mon étiquette ne peut être liée qu’à une seule version en même temps. On peut cependant déplacer ou échanger les étiquettes entre les versions comme on le souhaite.

Un secret est composé de plusieurs versions qui ont eux-même des labels associés.

Pour savoir quelle est la version du secret active, Amazon utilise les labels suivant AWSCURRENT, AWSPREVIOUS et AWSPENDING qui sont collés sur les versions lorsqu’une rotation est en cours. Si votre version comporte le label AWSCURRENT, alors c’est cette version qui est active en ce moment et c’est la valeur de cette version du secret qui sera retournée.

Le deuxième label indique la version précédente du secret et enfin le dernier label AWSPENDING indique la version qui va bientôt être promue en tant que nouveau secret actif.

À chaque rotation, ces trois labels se fixent sur la bonne version en fonction de l’avancement des étapes de rotations. Dans tous les cas, AWSCURRENT doit forcément être sur une version d’un secret.

Si une version ne comporte pas d’étiquette, le service la supprimera dans les jours à venir sans plus de précision.

Les étapes de rotation

Pour faire une rotation de secret, le service Secrets Manager a mis en place des étapes de rotations qui permettent de s’assurer que le nouveau secret est accepté par l’application à laquelle vous voulez changer son secret ou dans le cas contraire pouvoir faire un rollback sans impacter les services qui utilise la version actuelle du secret.

C’est votre lambda qui va devoir gérer ces 4 étapes. A chaque étape, votre lambda sera invoquée avec le même input, mis à part le nom de l’étape qui changera. Passons en revue chaque étape :

Etape 1: createSecret

Dans cette étape, votre lambda va devoir générer ou récupérer un nouveau secret. Cela peut être juste un mot de passe à générer ou alors demander à une API de générer un nouveau token. A la fin, votre lambda va créer une nouvelle version du secret à changer avec le label AWSPENDING. Pour l’instant, cela n’a aucun impact sur la version du secret actif qui a forcément le label AWSCURRENT.

Etape 2: setSecret

 

C’est pendant cette étape que votre lambda va mettre à jour le secret en créant un nouvel utilisateur dans la base de données pour qu’il ait les mêmes droits mais avec un mot de passe différent. Dans le cas de la mise à jour d’un token d’une API, vous n’avez la plupart du temps rien à faire durant cette étape. En fonction de la stratégie de rotation que vous avez choisie, cette étape peut déjà avoir un impact sur les services qui utilisent le secret en cours de rotation. En effet, si vous n’avez en votre possession qu’un seul utilisateur, vous allez devoir changer son mot de passe actif et donc avoir une indisponibilité le temps que ce nouveau mot de passe soit effectif. D’autres stratégies comme alterner entre deux utilisateurs ou avoir un utilisateur qui supporte plusieurs identifiants permettent de s’assurer de la haute disponibilité du service qui subit la rotation.

Etape 3: testSecret

 

Avant dernière étape qui donne l’occasion de vérifier que le nouveau secret généré fonctionne correctement. AWS recommande de tester toutes les actions que votre application est capable de faire afin de ne pas avoir de mauvaise surprise. Attention cependant, certaines actions sont difficiles à reproduire voire même disruptives.

Etape 4: finishSecret

Cette dernière étape consiste à déplacer le label AWSCURRENT vers la nouvelle version du secret, celle qui a le label AWSPENDING. A la fin de cette étape, ce sera cette nouvelle version qui sera retourné aux services qui en ont besoin. Amazon va automatiquement étiqueter l’ancienne version avec AWSPREVIOUS en cas de rollback.

Si vous utilisez l’une des bases de données gérées par AWS, la gestion de ces étapes de rotation vous est déjà fournie à la création d’un nouveau secret dans une lambda. Vous pouvez bien évidemment modifier cette lambda ou en utiliser une déjà existante.

Activation de la rotation d’un secret avec création d’une lambda pour gérer la rotation depuis la console AWS

Ce qu’on en pense

Le service Secrets Manager a pour ambition de gérer des secrets sensibles sans jamais avoir à les manipuler. La rotation des secrets permet d’automatiser cette manipulation ce qui ajoute une sécurité supplémentaire sans s’en préoccuper. Cependant, le service a un coût élevé de $0.40 par mois pour chaque secret à stocker. C’est un coût non négligeable. Dans bien des cas, l’utilisation du Parameter Store est largement suffisant. Cependant, si vous jugez que certains de vos secrets méritent d’avoir une rotation récurrente, alors le Secrets Manager sera adapté pour ce besoin.

Récap

On vient de passer en revue les différents moyens pour gérer les secrets en Serverless.

Comme vous avez pu le remarquer, il n’y pas de solution miracle parfaite.

Chaque service a des ambitions différentes et en fonction du nombre de secret à gérer et de leurs criticités, le service à utiliser ne sera pas le même.

Le service Lambda est utile pour stocker des secrets dans le cadre d’un POC ou pour accélérer des développements mais il n’est pas recommandé de partir en production tel quel.

Le Parameter Store sera votre choix de prédilection dans bien des cas, car il offre une sécurité supplémentaire pour un coût négligeable. Pour être totalement scalable avec ce service, il faut cependant utiliser une approche différente (lecture au déploiement) et donc avoir des incompatibilités avec CloudFormation par exemple ou ne plus être capable de mettre à jour vos secrets sans redéployer les lambda concernées.

Enfin, Secrets Manager sait faire la même chose que le Parameter Store avec la rotation en bonus et quelques fonctionnalités supplémentaires comme la suppression des secrets retardée de sept jours minimum pour éviter les mauvaises manipulations. Ce service ne souffre d’aucun problème de scalabilité mais est en revanche beaucoup plus onéreux que ses confrères.

Vous voilà maintenant avec toutes les clés en main pour gérer vos secrets en Serverless.

Take away

Published by

Commentaire

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.