JIT
← Notebook
Field notes SecurityIdentityHomelab

Niemand heeft hier nog een SSH-key, ikzelf ook niet

Een private CA als de enige SSH trust root: short-lived certs voor mensen (SSO+MFA), 10-minuten voor machines, en host certs die known_hosts TOFU killen.

Altijd diezelfde saaie vraag van de auditor in mij, deze keer over SSH: wie geraakt er nu op die box, en moest het moeten, krijg ik dat vandaag nog teruggedraaid? Voor de meeste homelabs, de mijne tot voor kort incluis, is dat antwoord om te wenen. Op elke host staat een public key in ~/.ssh/authorized_keys. Ooit met de hand of via Ansible erop gezet, en sindsdien heeft niemand er nog naar omgekeken. Niemand weet nog welke key van wie is, of op hoeveel hosts hij intussen staat. En terugdraaien? Dan mag je op elke box gaan inloggen om een regeltje te schrappen, áls je nog weet welke boxes er allemaal zijn.

De key overleeft de server, het project, soms de mens. Dát is de bug. Geen zwak algoritme, geen ontbrekende passphrase, de standing-ness zelf. Dus heb ik de keys buitengedaan. Bij de agents, bij de automation, en bij mezelf. Hier is de vorm op één bladzijde, dan de walk-through.

step-ca, de enige trust root elke host trust 'm · tekent short-lived certificates A · User CA wie logt in mensen → SSO + MFA → ~8 u cert machines → JWK → ≤10 min cert principal = identity, niet wat je vraagt B · Host CA welke host je bereikt de host toont een host certificate clients trusten de Host CA één keer, geen TOFU overleeft golden-template rebuilds C · trust, verdeeld Ansible, elke host hosts trusten de User CA TrustedUserCAKeys hosts tonen een Host cert clients pinnen de Host CA @cert-authority Nergens nog standing keys. Elke credential is short-lived en on-demand gemint, en vervalt dan.
Eén private CA, drie pijlers: ze tekent wie je bent (User CA) en welke host je bereikt (Host CA); Ansible verdeelt de trust. Niks dat blijft staan.

De standing key is de echte bug

Loop de failure modes van dat nederige authorized_keys-regeltje af en ze komen allemaal op één eigenschap uit: hij gaat nooit vanzelf weg.

  • Sprawl. Diezelfde key kopieert zichzelf over elke host, shadow-clone-jutsu-gewijs, alleen roept niemand de clones ooit nog terug. Eén gestolen private key is een master key, en je weet niet eens hoeveel deuren hij opendoet, want je bent al twee rebuilds geleden gestopt met hosts tellen.
  • Geen identity. Een key is geen mens. ssh-rsa AAAA… in een log zegt dat een key authenticeerde, niet wie het deed, en zeker niet in wiens naam.
  • Geen expiry. Hij werkt vandaag, volgend jaar, en nog altijd nadat je al lang vergeten bent dat hij bestaat. Een credential die nooit vervalt is geen credential, ‘t is een liability met een commentveld.
  • Hij overleeft rebuilds. Golden-template een box en je plakt braaf de key terug, want het alternatief is buitenstaan. Die standing access is load-bearing geworden.
  • Revocation is archeologie. Toegang terugdraaien betekent elke host afgaan. Aan de client-kant is het ‘t spiegelbeeld: known_hosts doet trust-on-first-use (TOFU), roept “the authenticity of host can’t be established” elke keer dat een box herbouwd is, en zo train je jezelf om yes te typen zonder te lezen.

Deel geen keys uit. Deel trust uit, en laat die vervallen.

Eén CA, drie pijlers

Draai het model om: stop met keys uitdelen en begin met trust uitdelen. Zet een kleine private CA op, ik gebruik step-ca (Smallstep, open source), en maak daarvan het enige dat elke host trust. Ze doet drie jobs (zie de diagram hierboven).

Pijler A, de User CA tekent short-lived login-certificates. Elke cert draagt een principal (wie je bent) en een expiry in minuten of uren. De host trust de User CA via één TrustedUserCAKeys-regel, een geldige cert logt je in, en nergens nog een per-user key.

Pijler B, de Host CA tekent de eigen key van elke host in een host certificate. Clients trusten de Host CA één keer, en de TOFU-prompt is voorgoed weg, zelfs na een rebuild, want de herbouwde host krijgt een verse cert van diezelfde CA. Het plezantste neveneffect van heel dit project: geen known_hosts-warnings meer.

Pijler C, distributie is niks meer dan Ansible: elke host trust de User CA en toont een host cert, elke client pint de Host CA. De trust is configuratie, geen stapel gekopieerde keys.

Mensen: SSO + MFA, dan een cert dat tegen het avondeten vervalt

Dit is het stuk dat mijn eigen dag veranderd heeft. Ik hou geen homelab-SSH-key meer bij. Om binnen te geraken authenticeer ik bij de CA via Authentik, de identity provider die ik toch al draai, in de browser, met MFA. De CA geeft een certificate terug, goed voor een werkdag, rechtstreeks in mijn ssh-agent. Er belandt niks op disk.

Jij · je laptop geen SSH-key op disk ssh een host → geen cert → step ssh login Authentik · je IdP browser-login · SSO + MFA (TOTP) id_token · wie je bent + je groups step-ca · User CA template mapt je group → principal geeft een ~8 u certificate cert → ssh-agent · niks op disk Target host trust de User CA · principal == login user ✓ jij trust de Host CA → geen host-key-prompt ✓ Vervalt tegen het avondeten. Revoke = disable de mens in de IdP, geen box-per-box cleanup.
Eén step ssh login per dag: SSO + MFA → een paar uur cert in de agent → SSH overal waar het getrust is. Disable me in Authentik en de toegang is weg.

Twee details maken dit veilig in plaats van enkel handig. De principal van de cert komt uit mijn identity en group membership, beslist door een template op de CA, ik kan dus geen login vragen waar ik geen recht op heb. En revocation is geen archeologie meer: disable de account in Authentik en er wordt geen nieuwe cert meer gemint, die in mijn agent vervalt tegen het avondeten. ’s Anderendaags respawn ik: één keer SSO + MFA en ik sta er weer met een verse cert, de vorige is dood en hoeft niet eens opgeruimd. Niemand die nog één enkele host moet afgaan.

Machines: een tien-minuten-key die zichzelf mint

Agents en automation krijgen dezelfde behandeling, maar zonder mens in de loop. In plaats van een statische key in een of andere .env authenticeert elk met een provisioner-credential, mint een ≤10-minuten-certificate per connectie, gebruikt het, en laat het vervallen. This cert will self-destruct in ten minutes, alleen ontploft er niks en springt er geen Tom Cruise voor van een gebouw. Geen standing key bij de caller, geen authorized_keys op de target. Die tien-minuten-cap wordt afgedwongen aan de CA zelf, vraag een uur en ze zegt nee.

Van een key die nooit weggaat → een certificate dat amper blijft VROEGER · standing keys één public key gekopieerd op elke host geen expiry · niet aan een persoon gebonden overleeft elke rebuild revoke = op elke box inloggen, een regel schrappen NU · één CA, short-lived certs mensen: SSO + MFA → ~8 u cert machines: ≤10 min cert per connectie hosts: geïdentificeerd door een host cert revoke = al vervallen
Heel het project in één swap: een key die voor altijd op elke host leeft wordt een certificate dat de volgende tien minuten bestaat (machines) of de rest van de dag (mensen).

‘t Is dezelfde reflex als de blast radius van elke agent klein houden, de SSH-laag-tegenhanger van het confused-deputy-stuk, dat inperkt wat een token mag tussen agents. Hier perken we in wat een SSH-credential mag, en voor hoe lang, tussen machines.

Wat het je kost (de eerlijke voetnoot)

Dit is niet gratis, en de auditor in mij laat het stuk niet eindigen op het glossy deel. De CA is nu load-bearing. Je kent die xkcd wel, heel de moderne infrastructuur die balanceert op één klein blokje dat ergens door één onbekende gast onderhouden wordt, en dat blokje ben ik nu zelf. Ligt ze plat, dan geraakt niemand nog aan een nieuwe cert, wat de veilige kant is (fail-closed is beter dan fail-open), maar je voelt het. Dus moet je het recht verdienen om die oude keys te schrappen:

  • Hou één verzegelde break-glass-key offline, voor de dag dat de CA én de IdP allebei tegelijk plat liggen. Test ‘m.
  • Back-up de CA, en zet die back-up niet op iets dat samen met de CA onderuit gaat.
  • Hou de root key offline, enkel de signing key staat online.
  • Draai NTP overal, short-lived certs zijn onverbiddelijk over clock skew.

Niks daarvan is exotisch. ‘t Is de prijs om “een key, voor altijd” om te zetten in “een certificate, voor de volgende tien minuten.”

Dus, terug naar die vraag van de auditor. Wie geraakt er nu op die box? Wie een niet-vervallen, CA-getekende cert heeft, en ik kan ze bij naam noemen, want elke uitgifte wordt gelogd met een principal. Krijg ik dat vandaag nog teruggedraaid? Moet niet. Het heeft zichzelf al teruggedraaid. De standing SSH-key heeft een goeie rit gehad, hij mag met pensioen.

(De SSH-laag-tegenhanger van the confused deputy comes for your AI agents, dezelfde reflex, scope en time-box elke credential, deze keer toegepast op wie er op de box geraakt.)