mTLS instellen met Nginx en clientcertificaten
Beveilig uw admin-paneel met mTLS. Leer hoe u een private CA maakt met openssl, clientcertificaten uitgeeft en Nginx configureert om ongeautoriseerde verzoeken direct te weigeren.
Wat mTLS doet
Mutual TLS, meestal geschreven als mTLS, zorgt ervoor dat Nginx elke client om een certificaat vraagt en het verzoek weigert wanneer dat certificaat ontbreekt of niet is uitgegeven door een certificaatautoriteit (CA) die u beheert. De controle vindt plaats tijdens de TLS (transport layer security) handshake, waardoor een aanroeper zonder geldig clientcertificaat uw applicatie nooit bereikt. Dat is het voordeel: een beheerderspaneel of een metrics-endpoint kan op het publieke internet staan zonder inlogpagina en zonder dat een bot iets kan raden.
De opzet is klein. Eén private CA gemaakt met openssl, één certificaat per persoon, drie directives in het Nginx server-blok. Het werk dat bepaalt of dit een jaar standhoudt is operationeel, dus het grootste deel van deze handleiding behandelt levensduur, intrekking, certificaten per persoon en wat u moet doen wanneer een client wordt geweigerd en niemand kan zien waarom.
Twee ketens, niet één
In een mTLS-opstelling zijn er twee certificaatketens die los van elkaar staan. Het samenvoegen van deze ketens is de meest gemaakte fout.
De eerste keten is die van de server. Uw VPS presenteert een certificaat voor admin.example.com, uitgegeven door een publieke CA zoals Let's Encrypt. De browser controleert dit certificaat aan de hand van de root store die bij het besturingssysteem wordt geleverd. mTLS verandert niets aan dit proces. Als certbot dit certificaat momenteel voor u uitgeeft, behoud dit dan exact zoals het is: zie het uitgeven van een Let's Encrypt-certificaat voor nginx met certbot.
De tweede keten is die van de client. U creëert uw eigen kleine CA, u ondertekent één certificaat voor elke persoon die toegang nodig heeft, en u instrueert nginx om bij het controleren van clients alleen die CA te vertrouwen. Geen enkele publieke root store kent uw CA en dat is ook niet nodig. De enige partij die de CA moet vertrouwen is nginx, via het bestand ssl_client_certificate.
Daarom heeft ssl_client_certificate nooit invloed op het certificaat dat nginx presenteert, en de Let's Encrypt-keten heeft nooit invloed op welke clients toegang krijgen. Het verwijzen van ssl_client_certificate naar fullchain.pem doet niet wat het lijkt te doen: die richtlijn benoemt de uitgevers waar een client-certificaat vandaan mag komen, wat de andere kant van de verbinding betreft. Het laten vertrouwen van uw CA door de server zelf voor uitgaand verkeer is een aparte taak, beschreven in het toevoegen van uw eigen CA aan de Ubuntu trust store. De systeem-trust store is niet de bron die nginx raadpleegt bij het verifiëren van een client.
Uw eigen client-CA bouwen met openssl
Bouw de CA op een andere locatie dan de webserver. Nginx heeft alleen het publieke certificaat van de CA nodig. De private sleutel van de CA ondertekent nieuwe clientcertificaten; als u deze op een server met internettoegang laat staan, kan een aanvaller bij een inbreuk zelf naar believen geldige clients aanmaken.
mkdir -p ~/client-ca/certs ~/client-ca/newcerts ~/client-ca/private ~/client-ca/csr
cd ~/client-ca
chmod 700 private
touch index.txt
echo 1000 > serial
echo 1000 > crlnumberindex.txt, serial en crlnumber vormen de CA-database. openssl ca weigert te werken zonder deze bestanden. Ze maken latere intrekking mogelijk, omdat een intrekkingslijst serienummers bevat; de CA moet dus bijhouden welk serienummer aan wie is uitgegeven.
Schrijf ~/client-ca/openssl.cnf. Stel dir in op het werkelijke pad van die map, aangezien openssl ca de variabele ~ niet uitbreidt.
[ ca ]
default_ca = client_ca
[ client_ca ]
dir = /home/you/client-ca
database = $dir/index.txt
new_certs_dir = $dir/newcerts
certificate = $dir/ca.crt
private_key = $dir/private/ca.key
serial = $dir/serial
crlnumber = $dir/crlnumber
default_md = sha256
default_days = 365
default_crl_days = 30
policy = policy_loose
rand_serial = no
unique_subject = no
email_in_dn = no
[ policy_loose ]
commonName = supplied
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
emailAddress = optional
[ client_ext ]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuerNu volgen de CA-sleutel en het zelfondertekende certificaat:
openssl genrsa -aes256 -out private/ca.key 4096
chmod 600 private/ca.key
openssl req -x509 -new -key private/ca.key -sha256 -days 3650 \
-subj "/O=Example Ops/CN=Example Ops Client CA" \
-addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-out ca.crt-aes256 plaatst een wachtwoordzin op de CA-sleutel, waardoor er bij elke ondertekening om wordt gevraagd. Dat is precies de bedoeling. Controleer wat u heeft gemaakt:
openssl x509 -in ca.crt -noout -subject -dates -ext basicConstraintsHet onderwerp (subject) moet uw CA zijn en de geldigheid moet tien jaar bedragen. De extensieregel moet CA:TRUE, pathlen:0 bevatten. pathlen:0 betekent dat deze CA eindcertificaten mag ondertekenen, maar geen andere CA mag ondertekenen. Hierdoor blijft de keten precies één niveau diep en kunt u ssl_verify_depth ongewijzigd laten.
Geef per persoon een clientcertificaat uit
Eén certificaat per persoon. Gebruik nooit een gedeeld certificaat voor een team, omdat een gedeeld certificaat niet kan worden ingetrokken zonder iedereen de toegang te ontzeggen, en het biedt geen inzicht in wie de aanroep heeft gedaan.
openssl genrsa -out private/alice.key 2048
openssl req -new -key private/alice.key -out csr/alice.csr \
-subj "/O=Example Ops/CN=alice"
openssl ca -config openssl.cnf -extensions client_ext \
-days 365 -notext -in csr/alice.csr -out certs/alice.crtopenssl ca toont het certificaat dat het gaat ondertekenen, vraagt om de CA-wachtwoordzin, vraagt tweemaal om bevestiging en voegt vervolgens een regel toe aan index.txt. Voeg -batch toe wanneer u dit script. De sectie client_ext is van belang vanwege één regel daarin: extendedKeyUsage = clientAuth. Een certificaat dat een extended key usage bevat met alleen serverAuth wordt afgewezen als ongeschikt voor clientauthenticatie; specificeer daarom het doel in plaats van te hopen op een goede afloop.
Controleer het paar tegen de CA voordat u iets overhandigt:
openssl verify -CAfile ca.crt certs/alice.crtDit geeft certs/alice.crt: OK weer. Elke andere uitvoer betekent dat het certificaat en de CA niet overeenkomen, en geen enkele nginx-configuratie zal dit herstellen.
Bundel de sleutel en het certificaat in één bestand dat een browser kan importeren:
openssl pkcs12 -export -inkey private/alice.key -in certs/alice.crt \
-name "alice at example ops" -out alice.p12De export vraagt om een wachtwoord, dat het bestand tijdens het transport beveiligt. Verstuur het bestand en het wachtwoord via verschillende kanalen en overhandig mensen het .p12 in plaats van een kaal .key. U kunt -certfile ca.crt toevoegen om de CA in de bundel op te nemen, maar nginx heeft dit niet nodig: nginx beschikt al over ca.crt, dus een certificaat dat direct door die CA is ondertekend, verifieert zichzelf.
OpenSSL 3, dat wordt meegeleverd met Ubuntu 24.04, schrijft PKCS#12-bestanden met moderne encryptie, en browsers en besturingssystemen die in augustus 2026 in gebruik zijn, kunnen deze lezen. Als een oude importeur het bestand weigert, exporteer het dan opnieuw met -legacy toegevoegd; dit valt terug op de oudere algoritmen die de importeur verwacht. Lees het bericht van de importeur voordat u naar die vlag grijpt.
Nginx configureren met ssl_client_certificate en ssl_verify_client
Kopieer het CA-certificaat, en uitsluitend het CA-certificaat, naar de server.
scp ca.crt user@admin.example.com:/tmp/client-ca.crt
ssh user@admin.example.com \
'sudo install -o root -g root -m 644 /tmp/client-ca.crt /etc/nginx/client-ca.crt'Mode 644 is hier correct. Een CA-certificaat is openbare informatie. De CA-sleutel blijft op uw werkstation.
Voeg vervolgens drie richtlijnen toe aan het server-blok dat de TLS-verbinding afhandelt:
server {
listen 443 ssl;
server_name admin.example.com;
ssl_certificate /etc/letsencrypt/live/admin.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.example.com/privkey.pem;
ssl_client_certificate /etc/nginx/client-ca.crt;
ssl_verify_client on;
ssl_verify_depth 1;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}ssl_verify_depth 1 is de standaardwaarde van nginx; deze geeft aan dat het clientcertificaat direct moet zijn ondertekend door de CA in dat bestand. Verhoog deze waarde alleen als u een intermediate certificaat toevoegt. Nginx stuurt tijdens de handshake ook de subject-namen uit ssl_client_certificate naar de client; op basis hiervan weet een browser welk certificaat moet worden aangeboden. Dit gedrag is de reden om ssl_client_certificate te gebruiken in plaats van ssl_trusted_certificate, die op dezelfde wijze verifieert maar geen lijst meestuurt.
Ubuntu 24.04 levert nginx 1.24, waarbij HTTP/2 op de listen-regel wordt geplaatst als listen 443 ssl http2;. Vanaf nginx 1.25.1 en later is die notatie verouderd en is HTTP/2 een eigen richtlijn: http2 on;. Geen van beide keuzes heeft invloed op de certificaatcontrole.
Herlaad de configuratie en controleer het resultaat:
sudo nginx -t && sudo systemctl reload nginx
curl -i https://admin.example.com/nginx -t toont syntax is ok en test is successful. De curl-aanroep bevat geen certificaat, dus deze zou moeten terugkeren met 400 Bad Request en de body No required SSL certificate was sent. Dit betekent dat nginx de toegang weigert bij de poort; de configuratie is dus actief en de applicatie is niet eens benaderd. Probeer het nu op de juiste wijze:
curl --cert certs/alice.crt --key private/alice.key https://admin.example.com/Dit zou de output moeten retourneren die uw applicatie serveert.
Waarom de gate in het server-blok thuishoort
Het certificaat wordt uitgewisseld tijdens de TLS-handshake, voordat nginx een request-regel heeft gelezen. Op dat moment weet nginx dus nog niet in welk location het verzoek terecht zal komen. Het plaatsen van ssl_verify_client on; binnen een location dwingt de client om opnieuw te onderhandelen midden in de verbinding. TLS 1.3 heeft heronderhandeling verwijderd en HTTP/2 verbiedt het, waardoor dat patroon op een moderne stack faalt in plaats van om een certificaat te vragen.
Voer de scoping zelf uit. Vraag om een certificaat op server-niveau en beslis vervolgens per locatie:
ssl_verify_client optional;
location /metrics {
if ($ssl_client_verify != SUCCESS) { return 403; }
proxy_pass http://127.0.0.1:9090;
}
location /healthz {
proxy_pass http://127.0.0.1:8080;
}$ssl_client_verify bevat SUCCESS, of NONE wanneer de client niets heeft verzonden, of FAILED: gevolgd door een reden. Met optional vraagt nginx om een certificaat en verifieert dit alleen als er een aankomt. Dit zorgt ervoor dat het publieke /healthz-pad hierboven werkt terwijl /metrics gesloten blijft. Een certificaat dat wordt verzonden maar niet door de verificatie komt, wordt op dat punt alsnog door nginx geweigerd. Als u een falend certificaat liever zelf inspecteert, gebruikt u optional_no_ca; uw eigen test moet dan elke waarde anders dan SUCCESS als een weigering behandelen.
nginx heeft hiervoor niet-standaard statuscodes en error_page kan deze opvangen, zodat een geweigerde bezoeker een uitleg krijgt in plaats van een kale 400-foutmelding:
error_page 495 496 = @needcert;
location @needcert {
default_type text/plain;
return 200 "This host requires a client certificate. Ask ops for one.\n";
}495 betekent dat het clientcertificaat niet door de verificatie kwam. 496 betekent dat de client geen certificaat presenteerde. Houd die pagina in platte tekst, aangezien de persoon die deze leest geen sessie en geen account heeft.
Hoe installeer ik het clientcertificaat in een browser?
Firefox beheert een eigen certificaatopslag: ga naar Instellingen, vervolgens naar Privacy en beveiliging, kies Certificaten bekijken, open het tabblad Uw certificaten, klik op Importeren en selecteer het .p12-bestand en voer het bijbehorende wachtwoord in.
Chrome en Edge gebruiken de certificaatopslag van het besturingssysteem op Windows en macOS. Het openen van het .p12-bestand start daarom de importwizard van het systeem. Op Linux leest Chrome een afzonderlijke NSS-database (Network Security Services) in uw thuismap. De betrouwbaarste methode is het gebruik van de command-line tool:
sudo apt install -y libnss3-tools
pk12util -d sql:$HOME/.pki/nssdb -i alice.p12Laad daarna de website opnieuw; de browser vraagt vervolgens welk certificaat moet worden verzonden. Chrome onthoudt deze keuze voor de rest van de browsersessie. Start de browser opnieuw op als u wilt dat er opnieuw om een keuze wordt gevraagd. Het certificaat bevindt zich in één browserprofiel op één machine. Een certificaat dat in Firefox is geïmporteerd, is daarom niet zichtbaar in Chrome, en beide zijn niet zichtbaar op uw telefoon.
Testen met curl --cert
Debug met curl, omdat dit rapporteert wat het heeft uitgevoerd.
curl -v --cert certs/alice.crt --key private/alice.key https://admin.example.com/U kunt het certificaat en de sleutel samenvoegen tot één PEM-bestand en dit doorgeven als --cert alice.pem. Als de sleutel een wachtwoordzin heeft, vraagt curl hierom. Het accepteert ook --cert alice.pem:passphrase, maar dit komt in uw shell-geschiedenis terecht; gebruik daarom de prompt.
Voer twee controles uit voordat u Nginx de schuld geeft. Ten eerste moeten het certificaat en de sleutel een paar vormen:
openssl x509 -noout -pubkey -in certs/alice.crt | openssl sha256
openssl pkey -pubout -in private/alice.key | openssl sha256Twee identieke hashes betekenen dat de bestanden bij elkaar horen. Twee verschillende hashes betekenen dat u de bestanden van twee verschillende entiteiten heeft verwisseld; geen enkele client zal deze specifieke oorzaak voor u benoemen.
Ten tweede hoort de server om uw CA te vragen:
openssl s_client -connect admin.example.com:443 -servername admin.example.com </dev/nullZoek naar het Acceptable client certificate CA names-blok in de uitvoer en naar de subject-naam van uw CA daarbinnen. Als dit blok volledig ontbreekt, vraagt Nginx niet om een certificaat in het server-blok dat antwoordde. Uw richtlijnen zijn dan in een ander blok terechtgekomen, vaak de default server.
Doorgeven van de client-CN aan de applicatie
Het certificaat identificeert de aanroeper, maar de applicatie achter de proxy kan de TLS-laag niet inzien. Daarom moet nginx de naam doorgeven.
map $ssl_client_s_dn $client_cn {
default "";
"~,?CN=(?<cn>[^,]+)" $cn;
}$ssl_client_s_dn bevat de subject distinguished name in RFC 2253-formaat, wat eruitziet als CN=alice,O=Example Ops. De map-instructie haalt het CN-veld naar $client_cn. Houd de CN als een eenvoudige gebruikersnaam; een komma in een CN wordt in dat formaat geëscaped en de bovenstaande reguliere expressie kan niet met die escape omgaan.
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Client-Cert-CN $client_cn;
proxy_set_header X-Client-Cert-Serial $ssl_client_serial;
}proxy_set_header vervangt elke header met die naam die de aanroeper heeft verzonden, zodat niemand X-Client-Cert-CN via deze locatie kan vervalsen. Twee voorwaarden garanderen dit. nginx erft proxy_set_header alleen van het buitenste niveau als het binnenste niveau er zelf geen definieert. Een tweede locatie met één proxy_set_header-regel verliest daarom stilletjes elke header die erboven is ingesteld, inclusief deze. Bovendien moet de applicatie onbereikbaar zijn behalve via nginx. Dit betekent dat u deze moet binden aan 127.0.0.1 in plaats van 0.0.0.0, aangezien een app op een publieke poort de vervalste header direct vanaf het internet zal lezen. De proxy-kant hiervan wordt behandeld in een nginx reverse proxy-configuratie regel voor regel uitgelegd. Als de applicatie het volledige certificaat in plaats van een naam vereist, vervoert $ssl_client_escaped_cert dit URL-geëncodeerd en veilig binnen een header.
Hoe trek ik één clientcertificaat in?
Iemand vertrekt of een laptop raakt vermist. U trekt dat ene certificaat in en alle anderen kunnen blijven werken; dat is precies de reden waarom u per persoon één certificaat uitgeeft.
cd ~/client-ca
openssl ca -config openssl.cnf -revoke certs/alice.crt
openssl ca -config openssl.cnf -gencrl -out crl.pemHet eerste commando wijzigt de regel van dat serienummer in index.txt van V naar R. Het tweede commando schrijft een certificate revocation list (CRL), een ondertekend bestand met de serienummers die zijn ingetrokken. Verspreid dit bestand en verwijs ernaar in nginx met ssl_crl /etc/nginx/client-ca.crl;, naast de andere richtlijnen.
scp crl.pem user@admin.example.com:/tmp/client-ca.crl
ssh user@admin.example.com 'sudo install -m 644 /tmp/client-ca.crl /etc/nginx/client-ca.crl && sudo nginx -t && sudo systemctl reload nginx'Hier is de valkuil die de toegang voor iedereen blokkeert. Een CRL bevat een nextUpdate-datum, ingesteld door default_crl_days, wat in de bovenstaande configuratie op 30 staat. Zodra die datum is verstreken, beschouwt OpenSSL de lijst als verouderd en mislukt de verificatie voor elk clientcertificaat met CRL has expired, niet alleen voor het ingetrokken certificaat. nginx leest het bestand wanneer de configuratie wordt geladen, dus een nieuw CRL-bestand op de schijf verandert niets totdat er een reload plaatsvindt. Genereer en herlaad de lijst volgens een schema dat ruim binnen de termijn valt, bijvoorbeeld wekelijks bij een termijn van 30 dagen, en controleer de data voordat u het bestand kopieert:
openssl crl -in crl.pem -noout -lastupdate -nextupdateVoor een klein aantal gebruikers is er een eenvoudigere optie. De CA is van u, dus nginx kan een serienummer direct weigeren en het CRL-mechanisme overslaan:
map $ssl_client_serial $revoked {
default 0;
"1002" 1;
}Combineer dit met if ($revoked) { return 403; } in de location-blok. Deze methode heeft geen vervaldatum die u kunt vergeten. Het is echter niet overdraagbaar, dus andere systemen die uw CA vertrouwen, weten hier niets van. Voor één nginx-server voor één applicatie is dit de meest eenvoudige en eerlijke oplossing. Stap over op een CRL zodra er meer dan één toegangspoort is.
Hoe lang moeten clientcertificaten geldig zijn?
Geef clientcertificaten een geldigheidsduur van een jaar, of korter als u de werklast van het opnieuw uitgeven kunt dragen. De verloopdatum is hier het punt waar het ongemerkt misgaat, omdat er geen waarschuwing vooraf naar de houder gaat. Men opent op een ochtend het paneel, Nginx weigert de verbinding en de browser beschrijft de weigering in eigen bewoordingen, waarin het woord 'verlopen' zelden voorkomt. Houd de CA op tien jaar en noteer de verloopdatum op een plek waar u deze ook daadwerkelijk zult lezen; wanneer het CA-certificaat verloopt, stopt de verificatie van alle onderliggende certificaten op diezelfde dag.
Twee commando's zorgen ervoor dat u dit voorblijft:
openssl x509 -in certs/alice.crt -noout -subject -serial -enddate
awk -F'\t' '{print $1, $2, $4}' ~/client-ca/index.txtDe eerste kolom van index.txt is de status: V voor geldig, R voor ingetrokken, E voor verlopen. De tweede kolom is de verloopdatum in YYMMDDHHMMSSZ-formaat en de vierde is het serienummer. Dat bestand is uw enige registratie van wie wat in bezit heeft, dus maak er een back-up van samen met de CA-key en behandel beide als geheimen.
Vernieuwing is een nieuw certificaat, geen verlenging. Genereer een nieuwe key en CSR (certificate signing request), onderteken deze, overhandig het certificaat en trek het oude in zodra de persoon bevestigt dat het nieuwe werkt.
Waartegen mTLS beschermt en waartegen niet
Wat het wegneemt, is ongeautoriseerde toegang. Een scanner die uw hostnaam vindt, wordt geweigerd tijdens de handshake. Hierdoor verstuurt deze nooit een HTTP-verzoek, ziet nooit een inlogformulier en kan nooit een gestolen wachtwoord proberen. Credential stuffing heeft geen doelwit. Een kwetsbaarheid in het inlogproces van de applicatie is onbereikbaar voor iedereen zonder certificaat. Het elimineert ook het delen van gedeelde geheimen via chat, omdat een private key een bestand is dat niet per ongeluk gekopieerd kan worden.
Wat het niet oplost, is een gecompromitteerde client. Malware op een laptop beschikt over het sleutelbestand en heeft de passphrase op het moment dat de eigenaar deze intypt. Voor de server ziet die aanvaller er precies zo uit als een legitieme gebruiker, omdat een certificaat het bezit van een bestand bewijst, niet de aanwezigheid van een persoon. Het .p12 wachtwoord en volledige schijfversleuteling blijven daarom van belang.
Het is ook geen autorisatie. Elk geldig certificaat krijgt toegang tot alles wat dat serverblok serveert, tenzij u $client_cn controleert en actie onderneemt op basis van de waarde. Standaard hebben twee certificaathouders identieke toegang.
Bovendien beveiligt het alleen het pad via Nginx. Als de applicatie ook op een publieke poort luistert, is mTLS ervoor slechts decoratie: bind de applicatie aan 127.0.0.1 en houd de firewall gesloten voor die poort. De andere ingang tot dezelfde server is SSH, en die verdient dezelfde aandacht, zoals beschreven in SSH-toegang op uw VPS beveiligen.
Eén laatste beperking, die direct merkbaar is zodra u het inschakelt: alles wat geen certificaat kan presenteren, stopt met werken. Denk aan een uptime-monitor, een webhook van een betaalprovider, een RSS-lezer of een mobiele app zonder toegankelijke certificaatopslag. Beslis hierover voordat u ssl_verify_client on instelt, want de uitval is volledig en blijft aan hun kant vaak onopgemerkt.
Wanneer een client wordt geweigerd, lees wat de client rapporteert
De melding die een geweigerde client toont, hangt af van de browser, de versie van curl en de onderliggende TLS-bibliotheek. Lees daarom wat uw eigen client weergeeft in plaats van dit te vergelijken met een melding die elders is gedocumenteerd. De relevante details staan op de server.
sudo tail -n 50 /var/log/nginx/error.logEen geweigerd certificaat laat een regel achter met client SSL certificate verify error, gevolgd door de reden die OpenSSL heeft opgegeven. Die reden is het feit waarop u moet handelen. Meestal is er sprake van een van de volgende zaken. Het certificaat is afkomstig van een andere CA dan het bestand dat in ssl_client_certificate is benoemd. De geldigheidsduur van het certificaat is verstreken. De CRL op de server heeft zijn nextUpdate overschreden, waardoor nu elke client wordt geweigerd in plaats van slechts één.
Wanneer de browser helemaal geen certificaat aanbiedt, ligt het probleem vóór de verificatie. nginx verstuurt de namen van de geaccepteerde uitgevers tijdens de handshake, en de browser heeft niets in zijn opslag gevonden dat overeenkomt; daarom had de browser niets aan te bieden. Importeer de .p12 opnieuw in het profiel waarmee u daadwerkelijk surft.
Nog een geval dat het benoemen waard is. Als u heeft getest met een enkel, zelfondertekend clientcertificaat in plaats van een certificaat dat door uw CA is ondertekend, kan de verificatie niet slagen. nginx controleert de handtekening namelijk tegen het CA-bestand en een zelfondertekend certificaat staat daar niet in. De procedure voor het maken van het certificaat is hetzelfde als in het genereren van een zelfondertekend certificaat op Ubuntu. mTLS vereist enkel de extra stap waarbij uw CA het certificaat ondertekent.
FAQ
Heb ik nog steeds een Let's Encrypt-certificaat nodig als ik mTLS gebruik?
Ja. Deze twee certificaten staan los van elkaar. Uw server presenteert zijn eigen certificaat zodat de browser de hostnaam vertrouwt; dit certificaat moet nog steeds afkomstig zijn van een CA die de browser al kent. Uw client-CA is een afzonderlijke private keten die alleen wordt gebruikt om te controleren wie er verbinding maakt. Het instellen van ssl_client_certificate verandert niets aan het certificaat dat nginx presenteert, en dit mag niet verwijzen naar uw Let's Encrypt-keten.
Waarom vraagt mijn browser nooit om een certificaat te kiezen?
nginx stuurt tijdens de handshake een lijst met geaccepteerde uitgevers, opgebouwd uit het bestand in ssl_client_certificate. Een browser biedt alleen certificaten aan waarvan de uitgever op die lijst voorkomt. Geen prompt betekent dus dat de browser niets bevat van uw CA: het importbestand is in een ander browserprofiel terechtgekomen, of het certificaat is ondertekend door een andere CA dan degene die op de server is geïnstalleerd. Voer openssl s_client -connect admin.example.com:443 uit en zoek in de uitvoer naar de namen van de geaccepteerde client-certificaat-CA's om te zien om welke CA de server daadwerkelijk vraagt.
Kan ik een client-certificaat voor slechts één URL verplichten?
Niet met ssl_verify_client on binnen een location. Het certificaat wordt uitgewisseld tijdens de handshake, nog voordat nginx het aanvraagpad kent. De heronderhandeling die dit zou kunnen omzeilen, is verwijderd uit TLS 1.3 en verboden in HTTP/2. Stel ssl_verify_client optional; in het server-blok in, test vervolgens in elke beveiligde locatie $ssl_client_verify en retourneer 403 wanneer dit niet SUCCESS is.
Hoe trek ik de toegang voor één persoon in?
Trek dat certificaat in met openssl ca -revoke, genereer de lijst opnieuw met openssl ca -gencrl, kopieer deze naar de server en herlaad nginx zodat het nieuwe bestand wordt ingelezen. Iedereen anders blijft ongemoeid; dit werkt alleen als elke persoon een eigen certificaat heeft in plaats van een gedeeld exemplaar. Houd de nextUpdate-datum van de CRL in de gaten, want een verlopen CRL zorgt ervoor dat de verificatie voor elke client mislukt, niet alleen voor de ingetrokken certificaten.
Vervangt mTLS een inlogpagina?
Voor de bereikbaarheid wel: zonder certificaat komt er niets bij de applicatie terecht, dus is er geen formulier om aan te vallen en geen wachtwoord om te raden. Voor identiteit binnen de applicatie niet. Een certificaat bewijst alleen dat de aanroeper een sleutelbestand bezit; een gestolen laptop is dus een geldige gebruiker. Geef de CN door aan de upstream, behoud de accounts en rechten die de applicatie al heeft, en behandel het certificaat als de poortwachter daarvoor.