HashiCorp Vault Secure Storage Backend dans l'écosystème Apache APISIX
API7.ai
January 24, 2022
Avec l'essor de l'architecture basée sur les microservices, la sécurisation des systèmes est devenue bien plus complexe qu'auparavant. Nous avons dépassé le stade où 100 instances de serveurs backend accédaient à notre serveur de base de données avec une seule paire d'identifiants statiques, car en cas de fuite de ces identifiants, l'ensemble du système est compromis, et la révocation de ces identifiants entraîne une interruption massive du service (personne ne peut plus accéder à quoi que ce soit tant que les instances ne sont pas reconfigurées). Nous ne pouvons pas éliminer la possibilité d'une faille de sécurité, car l'inattendu peut toujours se produire. En revanche, il est de notre responsabilité de contrôler l'impact de ces situations. Pour faire face à de tels scénarios, une solution populaire comme HashiCorp Vault entre en jeu dans un environnement de production pour agir comme un système de gestion des secrets et du chiffrement basé sur l'identité. Dans cet article, je démontre comment intégrer Vault avec Apache APISIX (une passerelle API cloud-native) via le plugin jwt-auth pour tirer parti des avantages des deux mondes.
Qu'est-ce que Vault ?
HashiCorp Vault est conçu pour aider les organisations à gérer l'accès aux secrets et à les transmettre en toute sécurité au sein de l'organisation. Les secrets sont définis comme toute forme d'identifiants sensibles qui doivent être strictement contrôlés et surveillés, et qui peuvent être utilisés pour déverrouiller des informations sensibles. Les secrets peuvent prendre la forme de mots de passe, de clés API, de clés SSH, de jetons RSA ou de OTP. Dans le monde réel, il est très courant de voir une prolifération de secrets stockés dans des fichiers de configuration ou sous forme de variables dans le code source, ce qui peut parfois même aboutir à leur inclusion dans des systèmes de contrôle de version comme GitHub, BitBucket ou GitLab, posant ainsi une menace majeure pour la sécurité. Vault résout ce problème en centralisant les secrets. Il fournit un stockage chiffré pour les secrets statiques, la génération de secrets dynamiques avec une durée de vie limitée (TTL), l'authentification des utilisateurs (machines ou humains) pour s'assurer qu'ils sont autorisés à accéder à un secret particulier, et bien plus encore. Ainsi, même en cas de faille de sécurité, l'impact est limité et maîtrisé.
Vault facilite grandement le contrôle et la gestion des accès en nous fournissant une interface unifiée pour gérer chaque secret de votre infrastructure. Non seulement cela, il offre également la flexibilité de créer des journaux d'audit détaillés et de suivre qui a accédé à quoi.
À propos du plugin APISIX jwt-auth
Il s'agit d'un plugin d'authentification qui peut être attaché à n'importe quelle route APISIX pour effectuer une authentification JWT (JSON Web Token, en savoir plus) avant que la requête ne soit transmise à l'URI en amont. En bref, il s'agit d'un mécanisme d'authentification sécurisé qui conduit à l'autorisation d'accès à des ressources critiques. Typiquement, une clé privée ou un secret textuel est utilisé par l'émetteur pour signer le JWT. Le récepteur du JWT vérifiera la signature pour s'assurer que le jeton n'a pas été altéré après sa signature par l'émetteur. L'intégrité de l'ensemble du mécanisme JWT dépend du secret de signature (qu'il s'agisse d'un secret textuel ou de paires de clés RSA). Cela rend difficile pour des sources non authentifiées de deviner la clé de signature et de tenter de modifier les revendications contenues dans le JWT.
Ainsi, le stockage de ces clés dans un environnement sécurisé est extrêmement crucial. Si elles tombent entre de mauvaises mains, cela pourrait compromettre la sécurité de l'ensemble de l'infrastructure. Bien que nous, du côté d'APISIX, prenions toutes les mesures pour suivre les pratiques standard de SecOps, il est tout à fait naturel dans un environnement de production d'avoir une solution centralisée de gestion des clés comme HashiCorp Vault pour avoir des pistes d'audit détaillées, une rotation périodique des clés, une révocation des clés, etc. Et ce serait un problème assez gênant si vous deviez à chaque fois mettre à jour la configuration d'Apache APISIX chaque fois qu'une rotation de clé se produit dans l'infrastructure.
Étapes pour utiliser Vault avec Apache APISIX
Pour l'intégration avec Vault, Apache APISIX doit être configuré avec les paramètres de Vault dans le fichier config.yaml.
En interne, APISIX communique avec le serveur Vault via les API HTTP du moteur de secrets KV version 1. Comme la plupart des solutions d'entreprise préfèrent s'en tenir au moteur de secrets KV - Version 1 dans leur environnement de production, lors de la phase initiale de support d'APISIX-Vault, nous avons opté uniquement pour la version 1. Dans les versions ultérieures, nous ajouterons le support de K/V version 2.
L'idée principale d'utiliser Vault, au lieu du backend etcd d'APISIX, est la préoccupation de sécurité dans un environnement à faible confiance. Nous, les développeurs d'APISIX, prenons vos priorités au sérieux. C'est pourquoi nous recommandons d'utiliser des jetons d'accès Vault à portée limitée qui peuvent accorder un accès limité au serveur APISIX.
Configurer Vault
Si vous avez déjà une instance Vault en cours d'exécution avec les privilèges nécessaires, n'hésitez pas à sauter cette section. Cette section partage les meilleures pratiques pour utiliser Vault dans l'écosystème Apache APISIX. Veuillez suivre les étapes mentionnées ci-dessous.
Étape 1 : Démarrer un serveur Vault
Ici, vous avez plusieurs options, n'hésitez pas à choisir entre Docker, un binaire précompilé ou une compilation à partir des sources. Comme pour communiquer avec le serveur Vault, vous avez besoin d'un client CLI Vault, je préfère opter pour un binaire précompilé plutôt que l'approche Docker. Quoi qu'il en soit, c'est à vous de décider (n'hésitez pas à consulter la documentation officielle d'installation de Vault). Pour démarrer un serveur de développement, veuillez exécuter la commande suivante.
$ vault server -dev -dev-root-token-id=root
…
AVERTISSEMENT ! Le mode dev est activé ! Dans ce mode, Vault fonctionne entièrement en mémoire
et démarre déverrouillé avec une seule clé de déverrouillage. Le jeton root est déjà
authentifié pour le CLI, vous pouvez donc immédiatement commencer à utiliser Vault.
Vous devrez peut-être définir la variable d'environnement suivante :
export VAULT_ADDR='http://127.0.0.1:8200'
La clé de déverrouillage et le jeton root sont affichés ci-dessous au cas où vous souhaiteriez
verrouiller/déverrouiller Vault ou vous ré-authentifier.
Clé de déverrouillage : 12hURx2eDPKK1tzK+8TkgH9pPhPNJFpyfc/imCLgJKY=
Jeton root : root
Le mode développement ne doit PAS être utilisé dans les installations de production !
Définissez votre CLI actuel avec les variables d'environnement correctes.
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='root'
Activez le moteur de secrets k/v version 1 de Vault avec un préfixe de chemin approprié. Dans cette démo, nous allons choisir le chemin kv
pour éviter toute collision avec le chemin secret par défaut de Vault pour la version 2 de kv.
$ vault secrets enable -path=kv -version=1 kv
Succès ! Le moteur de secrets kv a été activé à : kv/
# Pour confirmer le statut, exécutez
$ vault secrets list
Chemin Type Accesseur Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_4eeb394c stockage secret privé par jeton
identity/ identity identity_5ca6201e magasin d'identité
kv/ kv kv_92cd6d37 n/a
secret/ kv kv_6dd46a53 stockage secret clé/valeur
sys/ system system_2045ddb1 points de terminaison système utilisés pour le contrôle, la politique et le débogage
Étape 2 : Générer un jeton d'accès Vault pour APISIX
Cet article concerne l'utilisation de Vault dans le contexte du plugin jwt-auth
. Ainsi, pour un consommateur APISIX (si vous n'êtes pas familier avec les consommateurs dans l'écosystème APISIX, veuillez lire la documentation sur Apache APISIX Consumer) avec le nom d'utilisateur jack
, le plugin jwt-auth
recherche (s'il est activé avec la configuration Vault) les secrets à l'emplacement <vault.prefix dans config.yaml>/consumer/<consumer.username>/jwt-auth
dans le stockage kv de Vault. Dans ce contexte, si vous attribuez l'espace de noms kv/apisix
(chemin Vault) comme vault.prefix
dans config.yaml pour toutes les récupérations de données liées à APISIX, nous vous suggérons de créer une politique pour le chemin kv/apisix/consumer/
. L'astérisque supplémentaire () à la fin garantit que la politique permet la lecture pour tout chemin ayant le préfixe kv/apisix/consumer
.
Créez un fichier de politique en langage de configuration HashiCorp (HCL).
$ tee apisix-policy.hcl << EOF
path "kv/apisix/consumer/*" {
capabilities = ["read"]
}
EOF
Appliquez la politique à l'instance Vault.
$ vault policy write apisix-policy apisix-policy.hcl
Succès ! Politique téléchargée : apisix-policy
Générez un jeton avec la nouvelle politique définie qui a été configurée avec une portée d'accès limitée.
$ vault token create -policy="apisix-policy"
Clé Valeur
--- -----
jeton s.KUWFVhIXgoRuQbbp3j1eMVGa
accesseur_jeton nPXT3q0mfZkLmhshfioOyx8L
durée_jeton 768h
renouvelable_jeton true
politiques_jeton ["apisix-policy" "default"]
politiques_identité []
politiques ["apisix-policy" "default"]
Dans cette démonstration, s.KUWFVhIXgoRuQbbp3j1eMVGa
est votre jeton d'accès.
Ajouter la configuration Vault dans Apache APISIX
Comme discuté précédemment, Apache APISIX communique avec l'instance Vault via les API HTTP de Vault. La configuration nécessaire doit être ajoutée dans config.yaml. Voici un bref aperçu des différents champs que vous pouvez utiliser :
- host : L'adresse hôte où le serveur Vault est en cours d'exécution.
- timeout : Le délai d'attente HTTP pour chaque requête.
- token : Le jeton généré à partir de l'instance Vault qui peut accorder l'accès pour lire les données de Vault.
- prefix : L'activation d'un préfixe vous permet de mieux appliquer les politiques, de générer des jetons à portée limitée et de contrôler étroitement les données accessibles par APISIX. Les préfixes valides sont (
kv/apisix
,secret
, etc.)
vault:
host: 'http://0.0.0.0:8200'
timeout: 10
token: 's.KUWFVhIXgoRuQbbp3j1eMVGa'
prefix: 'kv/apisix'
Créer un consommateur APISIX
APISIX dispose d'une abstraction au niveau du consommateur qui va de pair avec les scénarios d'authentification. Pour activer l'authentification pour n'importe quelle route APISIX, un consommateur est nécessaire avec une configuration adaptée à ce type spécifique de service d'authentification. Ensuite seulement, APISIX peut transmettre la requête à l'URI en amont en effectuant avec succès l'authentification par rapport à la configuration du consommateur. Un consommateur APISIX a deux champs : un username
(obligatoire) pour identifier un consommateur parmi les autres, et un autre plugins
qui contient les configurations spécifiques au plugin du consommateur.
Ici, dans cet article, nous allons créer un consommateur avec le plugin jwt-auth
. Il effectue l'authentification JWT pour la ou les routes ou services concernés.
Pour activer jwt-auth
avec la configuration Vault, faites une requête à :
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"username": "jack",
"plugins": {
"jwt-auth": {
"key": "test-key",
"vault": {}
}
}
}'
Ici, le plugin recherche le secret de la clé dans le chemin Vault (<vault.prefix depuis conf.yaml>/consumer/jack/jwt-auth
) pour le consommateur jack
mentionné dans la configuration du consommateur et l'utilise pour les signatures et vérifications JWT ultérieures. Si la clé n'est pas trouvée dans ce chemin, le plugin enregistre une erreur et échoue à effectuer l'authentification JWT.
Configurer un serveur en amont de test
Pour tester le comportement, vous pouvez créer une route pour un serveur en amont (un simple gestionnaire ping qui renvoie pong). Vous pouvez le configurer avec un simple serveur HTTP en Go.
// serveur en amont simple
package main
import "net/http"
func ping(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("secure/pong\n"))
}
func main() {
http.HandleFunc("/secure/ping", ping)
http.ListenAndServe(":9999", nil)
}
Créer une route APISIX avec authentification activée
Créez une route APISIX avec ce serveur HTTP sécurisé ping et le plugin d'authentification jwt-auth
activé.
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"plugins": {
"jwt-auth": {}
},
"upstream": {
"nodes": {
"127.0.0.1:9999": 1
},
"type": "roundrobin"
},
"uri": "/secure/ping"
}'
Générer un jeton à partir du plugin jwt-auth
Maintenant, signez un secret JWT à partir d'APISIX qui peut être utilisé et transmis pour faire des requêtes à la route proxy [http://localhost:9080/secure/ping](http://localhost:9080/secure/ping)
vers le serveur APISIX.
$ curl http://127.0.0.1:9080/apisix/plugin/jwt/sign\?key\=test-key -i
HTTP/1.1 200 OK
Date: Tue, 18 Jan 2022 07:50:57 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.11.0
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODY1N30.nkyev1_KUapVgY_QVYETsSApA6gEkDWS8tsHFV1EpD8
Dans l'étape précédente, si vous voyez un message comme failed to sign jwt
, assurez-vous que vous avez stocké une clé secrète dans le chemin Vault kv/apisix/consumers/jack/jwt-auth
.
# exemple
$ vault kv put kv/apisix/consumer/jack/jwt-auth secret=$ecr3t-c0d3
Succès ! Données écrites à : kv/apisix/consumer/jack/jwt-auth
Faire une requête au serveur APISIX
Maintenant, faites une requête au proxy APISIX pour la route /secure/ping
. Après une validation réussie, il transmettra la requête à notre serveur HTTP en Go.
$ curl http://127.0.0.1:9080/secure/ping -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODU5M30.IYudBr7FTgRme70u4rEBoYNtGmGByzgfGlt8hctI__Q' -i
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Connection: keep-alive
Date: Tue, 18 Jan 2022 08:00:04 GMT
Server: APISIX/2.11.0
secure/pong
Toute requête sans un JWT valide renverra une erreur HTTP 401 Unauthorized
.
$ curl http://127.0.0.1:9080/secure/ping -i
HTTP/1.1 401 Unauthorized
Date: Tue, 18 Jan 2022 08:00:33 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.11.0
{"message":"Missing JWT token in request"}
Différents cas d'utilisation où Vault peut être intégré avec le plugin APISIX jwt-auth
Le plugin jwt-auth
d'Apache APISIX peut être configuré pour récupérer des clés secrètes textuelles simples ainsi que des paires de clés publiques-privées RS256 à partir du stockage Vault.
:::note Pour la version initiale de cette intégration, le plugin s'attend à ce que le nom de la clé des secrets stockés dans le chemin Vault soit parmi [ secret
, public_key
, private_key
] pour utiliser la clé avec succès. Dans les versions futures, nous ajouterons le support de références à des clés nommées de manière personnalisée. :::
-
Vous avez stocké un secret de signature HS256 dans Vault et vous souhaitez l'utiliser pour la signature et la vérification JWT.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "jack", "plugins": { "jwt-auth": { "key": "key-1", "vault": {} } } }'
Ici, le plugin recherche la clé
secret
dans le chemin Vault (<vault.prefix depuis conf.yaml>/consumer/jack/jwt-auth
) pour le consommateur jack mentionné dans la configuration du consommateur et l'utilise pour les signatures et vérifications JWT ultérieures. Si la clé n'est pas trouvée dans ce chemin, le plugin enregistre une erreur et échoue à effectuer l'authentification JWT. -
Les paires de clés RSA RS256, à la fois publiques et privées, sont stockées dans Vault.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "jim", "plugins": { "jwt-auth": { "key": "rsa-keypair", "algorithm": "RS256", "vault": {} } } }'
Le plugin recherche les clés
public_key
etprivate_key
dans le chemin kv Vault (<vault.prefix depuis conf.yaml>/consumer/jim/jwt-auth
) pourjim
mentionné dans la configuration du plugin Vault. Si elles ne sont pas trouvées, l'authentification échoue.Si vous n'êtes pas sûr de la manière de stocker les clés publiques et privées dans le stockage kv de Vault, utilisez cette commande :
# à condition que votre répertoire actuel contienne les fichiers nommés "public.pem" et "private.pem" $ vault kv put kv/apisix/consumer/jim/jwt-auth public_key=@public.pem private_key=@private.pem Succès ! Données écrites à : kv/apisix/consumer/jim/jwt-auth
-
Clé publique dans la configuration du consommateur, tandis que la clé privée est dans Vault.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "john", "plugins": { "jwt-auth": { "key": "user-key", "algorithm": "RS256", "public_key": "-----BEGIN PUBLIC KEY-----\n……\n-----END PUBLIC KEY-----" "vault": {} } } }'
Ce plugin utilise la clé publique RSA de la configuration du consommateur et utilise la clé privée directement récupérée de Vault.
Désactiver Vault du plugin
Maintenant, pour désactiver la recherche Vault dans le plugin jwt-auth
, supprimez simplement l'objet Vault vide de la configuration du plugin du consommateur (dans ce cas, c'est jack
). Cela fera que le plugin JWT recherchera les secrets de signature (à la fois HS256/HS512 ou les paires de clés RS512) dans la configuration du plugin pour les requêtes ultérieures à la route URI où la configuration jwt-auth
a été activée. Même si vous avez la configuration Vault activée dans APISIX config.yaml
, aucune requête ne sera envoyée au serveur Vault.
Les plugins APISIX sont rechargés à chaud, il n'est donc pas nécessaire de redémarrer APISIX.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"username": "jack",
"plugins": {
"jwt-auth": {
"key": "test-key",
"secret": "my-secret-key"
}
}
}'
Résumé
Cet article vous présente la prochaine version de l'intégration Vault-Apache APISIX et les détails associés.
N'hésitez pas à démarrer une discussion dans GitHub Discussions ou à communiquer via la liste de diffusion.