Aller au contenu principal

Pas de serveur d'authentification : jetons vérifiables hors ligne, rôles et portées dans Zaris

· 12 minutes de lecture
Clustron Team
Ingénierie des systèmes distribués

Jetons vérifiables hors ligne, rôles et portées dans Zaris

La plupart des façons de placer de l'authentification devant un store de données distribué finissent par ajouter un second système distribué à faire tourner : un service d'authentification à appeler, une base de données de sessions à répliquer, un point de terminaison d'introspection de jetons que chaque nœud doit atteindre sur le chemin chaud. Vous avez sécurisé le store et vous vous êtes acheté une nouvelle dépendance de disponibilité et une nouvelle source de latence.

Zaris prend une autre route, et c'est délibérément une route ennuyeuse : une clé de signature par cluster, et des jetons que chaque nœud peut vérifier par lui-même. Un jeton est un JWT signé avec la clé privée du cluster ; chaque manager et chaque nœud détiennent la moitié publique et vérifient la signature hors ligne — aucun appel à un serveur d'authentification, aucun quorum, aucune recherche en base de données. La sécurité est désactivée par défaut, et un cluster avec elle désactivée se comporte exactement comme un cluster non sécurisé. Cet article traite de ce qui s'active quand vous l'activez : la forme du jeton, le modèle de rôle-et-portée, les deux plans où l'application se produit réellement, et — car prétendre autrement est la façon dont les gens se donnent un faux sentiment de sécurité — les limites opérationnelles faciles à rater.

Le jeton est toute l'histoire​

Chaque jeton Zaris est un JWT. Il nomme un sujet — un utilisateur ou une application — et porte une ou plusieurs attributions role @ scope. Il est signé par la clé privée RS256 du cluster, qui ne vit que sur les managers ; tout ce qui a besoin de vérifier un jeton ne détient que la clé publique.

Cette seule décision de conception est ce qui retire le serveur d'authentification du tableau :

  • N'importe quel manager peut émettre un jeton. Il n'y a pas de nœud d'authentification primaire, pas d'élection, pas d'état de session mutable partagé à coordonner. L'émission est une opération de signature locale.
  • Chaque manager et chaque nœud peuvent vérifier un jeton avec seulement la clé publique. La vérification est une vérification de signature plus une vérification d'expiration et de portée — du pur CPU, aucun réseau. Le jeton est autonome : le rôle et la portée y sont embarqués dedans, de sorte qu'un nœud autorisant une connexion n'a besoin de rien d'autre que la clé publique. Il n'y a pas de base de données de politique sur le nœud à maintenir synchronisée.

Le gain pratique apparaît sur le chemin de données. Un client présente son jeton une fois, quand la connexion est établie — le « Hello ». Le nœud le vérifie, autorise la connexion pour le store, puis fait confiance à cette connexion pour sa durée de vie. Les jetons ne sont jamais revérifiés sur le chemin chaud par opération, de sorte qu'activer la sécurité ne vous coûte rien par Get ou Set. Vous payez l'authentification au moment de la connexion, une fois, et plus jamais pour cette connexion.

Rôles et portées​

Les permissions sont un petit catalogue prédéfini de rôles, chacun accordé à une portée qui dit où il s'applique.

RôleNiveau de portéeCe qu'il accorde
ClusterAdminclustertout — toutes les permissions du plan de contrôle et du plan de données
StoreOperatorstoredémarrer/arrêter le store, modifier la config, démarrer/arrêter les nœuds, diagnostics, métriques
Observerstorediagnostics et métriques, lecture seule
DataWriterstorelecture / écriture / suppression / scan de données
DataReaderstorelecture / scan de données

Une portée est hiérarchique, et une portée plus large couvre tout ce qui est en dessous :

  • zaris:cluster — tout le cluster, pour l'administration à l'échelle du cluster.
  • zaris:store:<name> — un store unique, par ex. zaris:store:orders.
  • root (la portée vide) — couvre tout ; réservée à l'administrateur d'amorçage.

Le confinement est root ⊃ cluster ⊃ store, de sorte qu'un DataWriter accordé à zaris:cluster peut écrire dans chaque store, tandis qu'un accordé à zaris:store:orders ne peut écrire que là. Vous composez le moindre privilège à partir de ces deux axes : choisissez le plus petit rôle qui fait le travail, et épinglez-le à la portée la plus étroite dont l'identité a besoin.

L'activer sans vous verrouiller dehors​

La seule chose qu'un système de sécurité ne doit jamais faire, c'est de vous laisser bloqué hors de votre propre cluster. Zaris gère cela en faisant de l'initialisation et de l'activation une seule étape : l'application ne s'active qu'une fois que le premier administrateur existe.

Connect-ZrManager -Managers mgr-01:7801, mgr-02:7801, mgr-03:7801
Initialize-ZrSecurity -AdminSubject admin -AdminPassword (Read-Host -AsSecureString)
# creates the 'admin' login, generates the shared RS256 key on the first manager,
# copies the SAME key to the rest, turns enforcement on, and signs this session in.

L'administrateur est une vraie identité de connexion — un nom d'utilisateur et un mot de passe, comme un compte root cloud — pas un jeton flottant que vous devez garder pour toujours. Après cela, les opérateurs se connectent avec des identifiants et obtiennent un jeton de session à courte durée de vie. L'application elle-même est une bascule d'exécution, persistée par manager, qui s'active sans redémarrage ni modification de config :

Enable-ZrSecurity     # control-plane enforcement ON  (requires an initialized admin)
Disable-ZrSecurity # OFF — keeps the key and policy, so you can re-enable instantly

Les deux se diffusent à chaque manager de l'espace de travail, de sorte que le cluster reste cohérent.

Deux sortes d'identité : personnes et applications​

Suivant le même motif qu'AWS IAM, Kubernetes et Vault, chaque jeton appartient à une identité enregistrée — vous ne pouvez pas frapper un jeton pour un nom arbitraire. Il y en a exactement deux sortes, plus l'unique administrateur d'amorçage.

Les utilisateurs sont des personnes. Ils sont créés avec un mot de passe (stocké uniquement comme un hachage PBKDF2 salé dans users.json sur chaque manager — jamais en clair, aucune base de données externe) et ils se connectent pour obtenir un jeton de session :

New-ZrUser -Name alice -Password (Read-Host -AsSecureString) `
-Role StoreOperator -Scope zaris:store:orders

Les comptes de service sont des applications. Pas de mot de passe ; en créer un vous remet un jeton autonome que l'application présente et renouvelle :

New-ZrServiceAccount -Name orders-svc -Role DataWriter `
-Scope zaris:store:orders -LifetimeMinutes 1440
# prints the application token once — store it in the app's secret manager.

New-ZrToken -Subject <name> existe toujours pour émettre ou faire tourner un jeton, mais le sujet doit déjà être un utilisateur ou un compte de service enregistré. Il n'y a pas de jetons ad hoc pour des noms qui n'existent pas — c'est la propriété qui fait de « qui peut toucher à ce store ? » une question à laquelle on peut répondre.

Utiliser un jeton​

Sur le plan de données, le jeton s'intègre directement dans la chaîne de connexion — et vous n'avez jamais à y intégrer un secret littéral, car l'indirection env: et file: est de première classe :

Connect-ZrStore -ConnectionString "zaris://node-a:7861,node-b:7861/orders?token=env:ZARIS_TOKEN"
Set-ZrItem -Key customer:42 -Value '{"name":"Ada"}'

Depuis .NET, c'est la même chaîne, ou un TokenProvider dans le code quand vous voulez la rotation :

var services = new ServiceCollection()
.AddClustronZarisConnectionString("orders",
"zaris://node-a:7861,node-b:7861/orders?token=env:ZARIS_TOKEN")
.BuildServiceProvider();

var client = await services
.GetRequiredService<IZarisClientProvider>()
.GetAsync("orders"); // token presented once, at the connection Hello

Si le jeton est manquant, expiré, invalide ou non limité à ce store, la connexion est refusée avec un statut Unauthorized — vous l'apprenez à la connexion, pas trois opérations plus loin dans une transaction. Si vous préférez ne pas frapper et coller un jeton à la main, Connect-ZrStore peut aussi échanger un identifiant nom d'utilisateur/mot de passe contre un jeton auprès du Management Service et le renouveler en arrière-plan.

Un renouvellement qui ne coupe jamais une connexion​

C'est ici que « vérifié seulement à la connexion » paie deux fois. Parce qu'un nœud vérifie le jeton seulement quand la connexion est ouverte, une connexion en cours n'est jamais interrompue quand son jeton expire. Le renouvellement n'a qu'à s'assurer que le client a un jeton actuellement valide la prochaine fois qu'il ouvre une connexion — lors d'une reconnexion, d'un nouveau nœud, d'un redémarrage de nœud.

Le client lit donc son jeton via un fournisseur à chaque (re)connexion. Gardez ce fournisseur renvoyant des jetons frais et le renouvellement est complètement transparent :

var renewer = new ZarisRenewingTokenProvider(initialToken, "http://mgr-01:7801");
renewer.Start(); // renews itself at 80% of lifetime, before expiry

var options = new ZarisClientOptions {
StoreName = "orders",
Mode = ZarisClientMode.Remote,
TokenProvider = renewer.Token, // read on every (re)connect
};

Le point de terminaison de renouvellement (POST /security/token/renew) est sans état — il vérifie votre jeton courant hors ligne contre la clé partagée et en renvoie un nouveau avec le même sujet et les mêmes rôles et une expiration fraîche, de sorte que n'importe quel manager peut le servir et qu'il survit aux redémarrages. Un jeton expiré ne peut pas être renouvelé (renouvelez avant l'expiration — le fournisseur intégré le fait), et un jeton ou un sujet révoqué est refusé. La ligne directrice qui en découle est simple : préférez des durées de vie courtes avec renouvellement automatique à un seul jeton à longue durée de vie. Vous obtenez la même transparence avec bien moins d'exposition.

Si vous ne voulez pas du tout de code client dans la boucle, pointez le fournisseur vers une variable d'environnement ou un fichier et laissez un rotateur externe — un gestionnaire de secrets, un sidecar, un cron qui refrappe — le mettre à jour hors bande :

options.TokenProvider = ZarisTokenProviders.FromEnvironment("ZARIS_TOKEN");
// or ZarisTokenProviders.FromFile("/etc/zaris/token");

La partie honnête : deux plans signifie deux interrupteurs​

C'est le détail qui mord les gens, il a donc sa propre section. « La sécurité est activée » n'est pas un seul fait — c'en est deux, parce qu'il y a deux points d'application.

Le plan de contrôle — le Management Service sur le port 7801, où vous créez des stores, démarrez et arrêtez des nœuds, et gérez les attributions — applique dès que vous avez initialisé et activé la sécurité. C'est l'interrupteur que Enable-ZrSecurity actionne.

Le plan de données — les nœuds Zaris réels qui servent vos clés — est un interrupteur séparé. Un nœud n'applique les jetons qu'une fois qu'il a la clé publique partagée dans sa configuration, et vous provisionnez cela par store :

Enable-ZrStoreSecurity -Store orders            # write the public key into every node's config
Enable-ZrStoreSecurity -Store orders -Restart # ...and rolling-restart to activate now

Jusqu'à ce que vous fassiez cela, les nœuds du store exécutent un authentificateur AllowAll et acceptent chaque connexion — avec ou sans jeton. C'est intentionnel : cela vous laisse activer d'abord le plan de contrôle et déployer l'application du plan de données store par store, avec un redémarrage planifié, au lieu de basculer tout le cluster d'un coup. Mais cela signifie qu'activer le plan de contrôle ne sécurise pas, à lui seul, vos données. Les deux pièges correspondants :

  • Get-ZrSecurityStatus rapporte le plan de contrôle. Ses champs Enabled / Initialized / Issuer décrivent l'application du plan de gestion. Un True là-bas ne prouve pas que les nœuds d'un store donné vérifient les jetons — c'est le travail de Enable-ZrStoreSecurity, confirmé séparément. Si un store « sécurisé » accepte toujours des clients sans jeton, la config du nœud n'a pas la clé ou n'a pas été redémarrée.
  • La révocation n'est pas instantanée sur le chemin de données. Parce que les jetons de données sont autonomes et vérifiés seulement à la connexion, Revoke-ZrToken est honoré immédiatement par le plan de contrôle et par le renouvellement (un sujet révoqué ne peut pas se rafraîchir), mais une connexion de données déjà ouverte continue de fonctionner jusqu'à ce qu'elle se ferme. La façon propre de couper une application sur le plan de données est d'émettre des jetons de données avec un -LifetimeMinutes modeste et de laisser le jeton courant expirer, ou de redémarrer ses connexions après révocation. Si vous avez besoin d'une coupure quasi instantanée, les durées de vie courtes sont le levier — pas l'attente que le chemin chaud revérifie.

Aucun de ces deux cas n'est un bug ; ce sont les conséquences directes de « vérifier hors ligne, une fois, sans serveur d'authentification ». C'est simplement la partie que vous devez opérer délibérément. Le déploiement recommandé en découle directement : initialisez et activez le plan de contrôle, vérifiez avec Get-ZrSecurityStatus, émettez des jetons d'opérateur et d'application et confirmez qu'ils fonctionnent, puis Enable-ZrStoreSecurity par store avec un redémarrage planifié, et enfin faites passer les applications sur des jetons à courte durée de vie avec renouvellement automatique.

Où cela se situe dans la pile​

L'authentification par jeton répond à qui peut toucher à ce store et ce qu'il peut y faire. Elle se compose avec les deux autres moitiés d'un Zaris verrouillé et n'en remplace aucune :

  • La confiance TLS enfichable chiffre le fil et authentifie les points de terminaison — c'est le transport zariss:// sous le jeton, orthogonal à qui détient le jeton.
  • La visite guidée Helm sécurisée met les deux ensemble sur Kubernetes de bout en bout, qui est là où les détails de chaîne de connexion et de certificat sont réellement exercés.

La chose à retenir est la forme de l'idée centrale. Une clé RS256, des JWT qui embarquent leur propre autorité, une vérification qui est du pur CPU hors ligne, et un jeton vérifié exactement une fois par connexion. C'est ce qui vous achète l'authentification et l'autorisation limitée par rôle à travers un cluster entier sans monter un serveur d'authentification, un store de session, ou un seul saut réseau supplémentaire sur le chemin que votre application exécute réellement. Le coût est la discipline à laquelle cet article a consacré sa seconde moitié : rappelez-vous que le plan de données est son propre interrupteur, et gardez vos jetons à courte durée de vie. Faites cela, et « sécurisé » devient une propriété que vous pouvez énoncer précisément plutôt qu'espérer.