Il existe un problème commun auquel la plupart des développeurs backend sont confrontés au moins une fois dans leur carrière : où devons-nous stocker nos secrets ? Cela semble assez simple, nous avons beaucoup de services qui se concentrent sur ce problème, nous avons juste besoin d’en choisir un et de passer à la tâche suivante. Cela semble facile, mais comment pouvons-nous choisir la bonne solution pour nos besoins ? Nous devrions évaluer nos options pour y voir plus clair.
Le test
Pour la démonstration, nous pouvons prendre une simple Botte de printemps application à titre d’exemple. Ce sera parfait pour nous car c’est l’un des choix technologiques les plus populaires sur le backend aujourd’hui. Dans notre exemple, nous supposerons que nous devons utiliser une base de données MySQL sur JDBCName; par conséquent, nos secrets seront l’URL de connexion, le nom de la classe du pilote, le nom d’utilisateur et le mot de passe. Ceci n’est qu’une preuve de concept, toute dépendance ferait l’affaire tant qu’elle utilise des secrets. Nous pouvons facilement générer un tel projet en utilisant Spring Initializr. Nous obtiendrons le DataSource
configuré automatiquement, puis créez un bean qui effectuera le test de connexion. Le test peut ressembler à ceci :
@Component
public class MySqlConnectionCheck {
private final DataSource dataSource;
@Autowired
public MySqlConnectionCheck(DataSource dataSource) {
this.dataSource = dataSource;
}
public void verifyConnectivity() throws SQLException {
try (final Connection connection = dataSource.getConnection()) {
query(connection);
}
}
private void query(Connection connection) throws SQLException {
final String sql = "SELECT CONCAT(@@version_comment, ' - ', VERSION()) FROM DUAL";
try (final ResultSet resultSet = connection.prepareStatement(sql).executeQuery()) {
resultSet.next();
final String value = resultSet.getString(1);
//write something that will be visible on the Gradle output
System.err.println(value);
}
}
}
Cette classe établira une connexion à MySQL et s’assurera que nous utilisons bien MySQL car elle imprimera le commentaire et la version de MySQL. De cette façon, nous remarquerions notre erreur même si une instance H2 configurée automatiquement était utilisée par l’application. De plus, si nous générons un mot de passe aléatoire pour notre MySQL Conteneur Docker, nous pouvons nous assurer que nous utilisons l’instance que nous voulions, en validant l’ensemble de la configuration a fonctionné correctement.
Revenons au problème, d’accord ?
Stocker des secrets
Le Emanière simple
L’option la plus triviale consiste à stocker les secrets avec le code, soit codés en dur, soit en tant que propriété de configuration, en utilisant certains profils pour pouvoir utiliser des environnements séparés (dev/test/staging/prod).
Aussi simple soit-elle, c’est une idée horrible car de nombreux sites populaires ont dû apprendre à leurs dépens au fil des ans. Ces « secrets » sont tout sauf un secret. Dès que quelqu’un aura accès à un référentiel, il disposera des informations d’identification de la base de données de production. Ajoutant l’insulte à l’injure, nous ne le saurons même pas ! Il s’agit de la cause la plus fréquente de violation de données. Un bon indicateur de la gravité de la situation est de voir à quel point les offres de numérisation secrètes sont devenues courantes, par exemple sur GitHub, GitLab, Bitbucket, ou d’autres hébergeant des dépôts git.
Le droit chemin
Maintenant que nous voyons quel est le problème, nous pouvons commencer à chercher de meilleures options. Il y a une chose commune que nous remarquerons dans toutes les solutions que nous pouvons utiliser : ils veulent que nous stockions nos secrets dans un service externe qui les gardera en sécurité. Cela s’accompagne de nombreux avantages que ces services peuvent offrir, tels que :
- Contrôle d’accès solide.
- Secrets chiffrés (et parfois plus, comme des certificats, des clés).
- Journaux d’accès auditables.
- Un moyen de révoquer l’accès/la rotation des secrets en cas de suspicion de violation.
- Séparation naturelle des environnements car ils font partie de la pile (un gestionnaire de secrets par environnement).
Ça a l’air génial, avons-nous tout résolu ? Eh bien, ce n’est pas si simple. Nous avons de nouvelles questions auxquelles nous devons d’abord répondre :
- Qui les hébergera et les maintiendra ?
- Où devons-nous mettre les secrets dont nous avons besoin pour l’authentification lorsque nous voulons accéder au gestionnaire de secrets ?
- Comment allons-nous exécuter notre code localement sur les ordinateurs portables des développeurs ?
- Comment allons-nous exécuter nos tests sur CI ?
- Cela coûtera-t-il quelque chose ?
Celles-ci ne sont pas anodines et leurs réponses dépendent beaucoup de la solution que nous voulons utiliser. Passons-les en revue un par un dans la section suivante.
Exemples de gestionnaires de secrets
Dans tous les cas ci-dessous, nous présenterons le gestionnaire de secrets comme un nouveau composant de notre pile, donc si nous avions une application et une base de données, cela ressemblerait au schéma suivant.
Coffre-fort HashiCorp
Si nous optons pour l’option open-source populaire, Coffre-fort HashiCorpnous pouvons soit nous auto-héberger, soit utiliser leur service géré, Coffre-fort HCP. Selon la variante que nous sélectionnons, nous pouvons ou non avoir déjà un effort de maintenance, mais cela répond à la première question. Répondre au reste devrait également être facile. En ce qui concerne la pièce d’authentification, on peut utiliser, par exemple, la Méthode d’authentification AppRole en utilisant variables d’environnement fournissant les informations d’identification nécessaires à nos instances d’application dans chaque environnement.
En ce qui concerne l’exécution locale et CI, nous pouvons simplement configurer et exécuter une instance de coffre-fort en dmode serveur ev sur la machine sur laquelle l’application doit s’exécuter et transmettre les informations d’identification nécessaires à l’aide de variables d’environnement de la même manière que les instances d’application en direct. Comme il s’agit de coffres-forts locaux, donnant accès à des bases de données de développement jetables, nous ne devons pas trop nous soucier de leur sécurité, car nous devons éviter d’y stocker des données significatives.
Pour éviter de consacrer beaucoup d’efforts à la maintenance de ces instances de coffre-fort local/CI, il peut être judicieux de stocker leur contenu dans un emplacement central et de laisser chaque développeur mettre à jour son coffre-fort à l’aide d’une seule commande de temps en temps. Concernant le coût, cela dépend de plusieurs choses. Si vous pouvez opter pour l’option open source auto-hébergée, vous ne devriez vous soucier que de la VM le coût (et le temps consacré à la maintenance) ; sinon, vous devrez peut-être déterminer comment vous pouvez optimiser le coût de la licence/du support.
Solutions basées sur le cloud
Si nous hébergeons nos services en utilisant les services de l’un des trois grands fournisseurs de cloud, nous avons encore plus d’options. AWS, Azure et Google Cloud proposent tous un service géré pour les gestionnaires de secrets. Probablement en raison de la nature du problème, Gestionnaire de secrets AWS, Coffre de clés Azureet Gestionnaire de secrets Google Cloud partagent de nombreuses similitudes. Veuillez consulter la liste ci-dessous pour des exemples :
- Stocke les secrets versionnés.
- Enregistre l’accès au service et à son contenu.
- Utilise des fonctionnalités d’authentification et d’autorisation solides.
- Bien intégré avec d’autres services gérés du même fournisseur
- Fournit un SDK pour les développeurs de certains langages populaires
Dans le même temps, nous devons garder à l’esprit qu’il s’agit encore de services extrêmement différents. Certaines des différences évidentes sont l’API qu’ils utilisent pour la communication et les fonctionnalités supplémentaires qu’ils fournissent. Par exemple, Azure Key Vault peut stocker des secrets, des clés et des certificats, tandis qu’AWS et GCP fournissent des services gérés distincts pour ces fonctionnalités supplémentaires.
En pensant aux questions auxquelles nous voulions répondre, ils peuvent répondre aux deux premières questions de la même manière. Tous sont des services gérés, et la solution d’identité gérée du fournisseur de cloud auquel ils appartiennent est le moyen le plus pratique et le plus sûr d’y accéder. Grâce à cela, nous n’avons pas besoin de nous soucier de stocker des secrets/tokens dans la configuration de notre application, juste l’URL du gestionnaire de secrets, qui n’est pas considérée comme un secret. En ce qui concerne le coût, AWS et GCP peuvent facturer en fonction du nombre de secrets et du nombre d’appels d’API. En revanche, Azure ne facture que ce dernier. En général, leur prix est très raisonnable et nous pouvons mieux dormir la nuit en sachant que notre posture de sécurité est un peu meilleure.
Les problèmes commencent lorsque nous essayons de répondre aux deux questions restantes concernant les cas d’utilisation locaux et CI. Les trois solutions sont accessibles depuis le monde extérieur (étant donné la configuration réseau appropriée), mais le simple fait de percer des trous sur un pare-feu et de partager les mêmes informations d’identification du gestionnaire de secrets n’est pas une solution idéale. Il y a des situations où cela n’est tout simplement pas pratique, comme les cas suivants :
- Notre équipe est dispersée dans le monde entier dans le bureau à domicile, et nous ne serions pas en mesure d’utiliser de fortes restrictions IP, ou nous aurions besoin d’une connexion VPN constante juste pour construire/tester le code. Avoir besoin d’une connexion Internet pour les tests est déjà assez grave. Cependant, l’utilisation constante d’un VPN au travail peut exercer une pression supplémentaire sur l’infrastructure et l’équipe en même temps.
- Lorsque nos instances CI se reproduisent avec des adresses IP aléatoires d’une plage inconnue, nous ne pouvons pas définir de restrictions IP appropriées. Un cas similaire au précédent.
- Nous ne pouvons pas confier à toute l’équipe les secrets du gestionnaire de secrets partagés. Par exemple, dans le cas de projets open source, nous ne pouvons pas courir et partager une instance de gestionnaire de secrets avec le reste du monde.
- Nous devons changer le contenu du gestionnaire de secrets pendant les tests. Lorsque cela se produit, nous risquons des problèmes d’isolement entre chaque développeur et l’instance CI. Nous ne pouvons pas lancer une instance de gestionnaire de secrets différente pour chaque personne et processus (ou cas de test) car cela ne serait pas très évolutif.
- Nous ne voulons pas payer de supplément pour les gestionnaires de secrets supplémentaires utilisés dans ces cas.
Pouvons-nous faire semblant localement ?
Habituellement, c’est le moment où je commence à chercher un essai double et formuler des plans pour l’utiliser au lieu du service réel localement et sur CI. Qu’attend-on d’un tel doublé test ?
- Comportez-vous comme le ferait le vrai service dans des situations exceptionnelles.
- Soyez activement maintenu pour réduire le risque de retard en cas de changement de version de l’API dans le service réel.
- Avoir un moyen d’initialiser le contenu du gestionnaire de secrets en double au démarrage pour ne pas avoir besoin de code supplémentaire dans l’application.
- Permettez-nous de synchroniser les valeurs secrètes entre l’équipe et les instances CI pour réduire les coûts de maintenance.
- Commençons et lançons le test double simplement, localement et sur…