NFS mit Identitäten aus Zertifikaten
NFSv3 allein authentifiziert niemanden, weshalb eine Freigabe, die benennt, wer sie
nutzen darf, nicht über NFS exportiert wird – es sei denn, etwas kann
Personen unterscheiden. Ein kerberos-Block ist eine Antwort. Seit v0.12.0 ist ein
Client-Zertifikat, das eine Person benennt, eine weitere:
serve "nfs" {
tls = true
client_ca_file = "/etc/fileshare/bridge-x509-ca.pem"
identity = "certificate"
crl_url = "https://bridge.example.org/x509/crl" # or crl_file; required
crl_state_file = "/var/lib/fileshare/nfs.crl.state" # the order, across a restart
}Was das Zertifikat enthält
Nach einer Anmeldung beim Identitätsanbieter stellt go-authn/bridge ein kurzlebiges
X.509-Client-Zertifikat aus, das die Person so benennt, wie rpc.tlsservd -u von FreeBSD
sie liest:
- die Person: der SubjectAltName otherName
1.3.6.1.4.1.2238.1.1.1, ein UTF8Stringuser@domain(draft-cel-nfsv4-rpc-tls-othername beschreibt ihn; seine OIDs sind noch nicht vergeben); - ihre Gruppen: je eine SubjectAltName-URI,
tag:go-authn.github.io,2026:group:<group>– eine Tag-URI (RFC 4151: keine Registrierung, genau dafür gemacht).rpc.tlsservdvon FreeBSD liest den ersten Identitäts-otherName und überspringt jeden anderen SAN-Eintrag.
Warum eine Tag-URI und keine eigene Erweiterung
Eine wurde entworfen, unter einem aus einer UUID abgeleiteten Bogen (2.25.<128-bit number>), und
gemessen: x509.ParseCertificate von Go lehnt das ganze Zertifikat ab –
malformed extension OID field –, weil ein Bogen eines ASN.1-Objektbezeichners dort
ein int ist. Jeder TLS-Server in Go hätte das Zertifikat zurückgewiesen.
Ein URI-SAN braucht keine OID, und jeder Parser liest ihn. Auch nicht urn:x-…:
RFC 8141 hat die experimentellen URN-Namensräume abgeschafft, und ein strenger Parser
lehnt einen solchen ab.
Was bei jedem Aufruf geprüft wird
Mit identity = "certificate" wird eine Freigabe, die Personen benennt, über NFS
ohne Kerberos bereitgestellt. Jeder Aufruf darauf muss über TLS ankommen, mit einem
Zertifikat, das:
- nicht in der CRL steht;
- genau eine Person benennt;
- jemanden benennt, den dieser Server so zulässt, wie er ein Token zulässt – eine Regel,
trust_all,domains, siehe Personen, die der Identitätsanbieter benennt; - jemanden benennt, den die Freigabe erlaubt.
Und mit einem ssf-Block muss das Zertifikat
nach jedem Widerruf dieser Person ausgestellt worden sein (sein NotBefore).
Widerruf: die CRL
Eine Sperrliste (CRL) ist erforderlich – crl_url oder crl_file. Das Zertifikat einer
Person, das nichts widerrufen kann, überdauert ihre Entfernung um seine ganze Lebensdauer.
| Feld | Standard | |
|---|---|---|
crl_url | nur HTTPS | |
crl_file | oder ein absoluter Pfad – das eine oder das andere | |
crl_ca_file | die des Systems | legt die Zertifizierungsstellen des HTTPS-Servers der CRL fest |
crl_refresh | 1m | wie oft sie abgerufen wird |
crl_max_age | 1h | wie alt die letzte gute Kopie sein darf |
crl_state_file | wo die letzte geprüfte CRL aufbewahrt wird, damit sich ein Neustart erinnert, welche neuer ist |
Sie wird wie die KRL abgerufen und aufbewahrt und ist auf
dieselbe Weise ausfallsicher geschlossen (fail closed); eine CRL jenseits ihres eigenen
NextUpdate gilt als unbekannt, was auch immer crl_max_age erlaubt. Eine CRL muss
von der Client-CA signiert sein – eine CRL, die jeder hätte schreiben können, widerruft,
was immer er will, und, schlimmer noch, hebt den Widerruf wieder auf. Ist die CRL erreichbar,
wird der nächste Aufruf nach ihrer Änderung abgelehnt; die Lebensdauer des Zertifikats
begrenzt einen Widerruf nur, wenn die CRL nicht erreichbar ist.
Seit v0.16.2 wird die Signatur über go-authn/revocation
an den Rohbytes geprüft, bevor die CRL geparst wird: Wurde zuerst geparst, kostete eine große,
von irgendjemandem signierte CRL Speicher und Sekunden, bevor sie abgelehnt wurde. Was dieser
Server nicht als vollständige Liste lesen kann, wird ebenfalls abgelehnt: eine Delta-CRL,
eine unbekannte kritische Erweiterung oder Eintragserweiterung, keine CRL-Nummer, kein
NextUpdate. Eine CRL mit einer niedrigeren Nummer als die gehaltene oder mit derselben
Nummer, früher ausgestellt, wird abgelehnt wie eine ältere
KRL.
Ein CRL-Feld ohne identity = "certificate" wird abgelehnt, ebenso
identity = "certificate" ohne tls = true und client_ca_file: Die
Identität steht in dem Zertifikat, das der Client vorlegt.
Was sie nicht versprechen kann
Das Zertifikat eines Linux-Clients gehört zu einem MOUNT, nicht zu einer Person
Der Server sieht das Zertifikat einer Verbindung, und ein Linux-Client verknüpft
eines mit einem Mount (tlshd, die Keyring-Seriennummer bei
mount -o xprtsec=mtls,...). Jeder Benutzer dieses Mounts handelt als die Person,
die das Zertifikat benennt. Auf einem Arbeitsplatzrechner, den eine Person nutzt, ist das
genau sie; auf einem Rechner, an dem sich mehrere Personen anmelden, ist es, wer auch immer
eingehängt hat – verwenden Sie dort sec=krb5.
Deshalb lehnt es RFC 9289 allein ab, eine Benutzerauthentifizierung zu versprechen, und deshalb weist einfaches NFS über TLS weiterhin nur den Rechner nach.
Gemessen mit einem echten Linux-Client
Kernel 6.17, ktls-utils 0.9, in der CI von go-filesystems/nfs:
MNTvon MOUNT kommt unverschlüsselt an, was auch immerxprtsec=sagt: Der Mount-Client des Kernels hat kein TLS. Er wird beantwortet, und jeder NFS-Aufruf ohne TLS wird danach abgelehnt – eine Person, die die Freigabe nicht erlaubt, sieht daher access denied beim ersten Zugriff statt beimount. Nichts von einer Freigabe geht unverschlüsselt über die Leitung.tlshdprüft das Zertifikat dieses Servers nur gegen den Vertrauensspeicher des Systems – es ignoriertx509.truststore– und verlangt, dass Zertifikat und Schlüssel des Clients root gehören, der Schlüssel mit Modus 600. Andernfalls schlägt es mit gnutls: Error in the certificate (-43) fehl, ohne eines von beiden zu nennen.
Erstellt am von tannevaled
Geändert am von tannevaled