SSD Nodes Learn 🎉 VPS dari $5.50/bln
Panduan Matt ConnorOleh Matt Connor

Cara Konfigurasi mTLS Client Certificate pada Nginx

Lindungi panel admin dengan mTLS menggunakan OpenSSL. Panduan ini menunjukkan cara menjana CA peribadi, mengeluarkan sijil klien, dan menetapkan arahan ssl_verify_client.

Fungsi mTLS

Mutual TLS, yang biasanya ditulis sebagai mTLS, memaksa nginx meminta sijil daripada setiap klien dan menolak permintaan jika sijil tersebut tiada atau tidak dikeluarkan oleh pihak berkuasa sijil (CA) yang anda kawal. Pemeriksaan ini berlaku di dalam jabat tangan TLS (transport layer security), jadi pemanggil tanpa sijil klien yang sah tidak akan sampai ke aplikasi anda sama sekali. Inilah kelebihannya: panel pentadbir atau titik akhir metrik boleh diletakkan di internet awam tanpa halaman log masuk dan tiada apa-apa untuk diteka oleh bot.

Pembinaannya ringkas. Satu CA peribadi yang dibuat dengan openssl, satu sijil bagi setiap orang, dan tiga arahan dalam blok pelayan nginx. Kerja yang menentukan sama ada sistem ini bertahan selama setahun adalah aspek operasi, jadi kebanyakan panduan ini merangkumi tempoh hayat, pembatalan sijil, sijil bagi setiap individu, dan tindakan yang perlu diambil apabila klien ditolak dan tiada sesiapa yang tahu puncanya.

Dua rantaian, bukan satu

Terdapat dua rantaian sijil dalam persediaan mTLS dan kedua-duanya tidak mempunyai kaitan antara satu sama lain. Menggabungkan kedua-duanya adalah kesilapan pertama yang hampir dilakukan oleh semua orang.

Rantaian pertama adalah milik pelayan. VPS anda membentangkan sijil untuk admin.example.com yang dikeluarkan oleh CA awam seperti Let's Encrypt, dan pelayar akan menyemaknya berdasarkan stor akar (root store) yang disertakan bersama sistem pengendalian. Tiada apa-apa tentang mTLS yang mengubah bahagian tersebut. Jika certbot mengeluarkan sijil itu untuk anda hari ini, kekalkan ia seperti sedia ada: lihat mengeluarkan sijil Let's Encrypt untuk nginx dengan certbot.

Rantaian kedua adalah milik klien. Anda mencipta CA kecil anda sendiri, anda menandatangani satu sijil untuk setiap individu yang perlu masuk, dan anda memberitahu nginx untuk mempercayai CA tersebut dan hanya CA tersebut semasa memeriksa klien. Tiada stor akar awam yang mengenali CA anda dan tiada satu pun yang perlu berbuat demikian. Satu-satunya pihak yang perlu mempercayainya ialah nginx, melalui fail ssl_client_certificate.

Oleh itu, ssl_client_certificate tidak akan menjejaskan sijil yang dibentangkan oleh nginx, dan rantaian Let's Encrypt tidak akan menjejaskan klien mana yang dibenarkan masuk. Menghalakan ssl_client_certificate kepada fullchain.pem tidak melakukan apa yang kelihatan seperti yang anda sangka: arahan tersebut menamakan pengeluar yang mungkin mengeluarkan sijil klien, iaitu hujung sambungan yang satu lagi. Menjadikan pelayan itu sendiri mempercayai CA anda untuk kerja keluarannya sendiri adalah tugas berasingan, yang diliputi dalam menambah CA anda sendiri ke dalam stor kepercayaan Ubuntu, dan stor kepercayaan sistem bukanlah apa yang dibaca oleh nginx apabila ia mengesahkan klien.

Membina CA klien anda sendiri dengan openssl

Bina CA di lokasi selain daripada pelayan web. nginx hanya memerlukan sijil awam CA tersebut. Kunci peribadi CA digunakan untuk menandatangani sijil klien baharu, jadi meninggalkannya pada mesin yang terdedah kepada internet bermakna satu pencerobohan akan memberi penyerang kuasa untuk mencipta klien yang sah bagi dirinya sendiri pada bila-bila masa.

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 > crlnumber

index.txt, serial dan crlnumber merupakan pangkalan data CA. openssl ca enggan berjalan tanpanya. Fail-fail ini juga membolehkan pembatalan sijil dilakukan kemudian, kerana senarai pembatalan (revocation list) menyenaraikan nombor siri, jadi CA perlu menyimpan rekod nombor siri yang diberikan kepada siapa.

Tulis ~/client-ca/openssl.cnf. Tetapkan dir kepada laluan sebenar direktori tersebut, kerana openssl ca tidak mengembangkan ~.

[ 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,issuer

Sekarang, kunci CA dan sijil yang ditandatangani sendiri:

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 meletakkan frasa laluan pada kunci CA, jadi setiap proses penandatanganan akan memintanya. Itulah tujuan utamanya. Semak apa yang telah anda buat:

openssl x509 -in ca.crt -noout -subject -dates -ext basicConstraints

Subjek haruslah CA anda dan tempoh sah laku harus berjalan selama sepuluh tahun. Baris sambungan (extension) harus membaca CA:TRUE, pathlen:0. pathlen:0 bermaksud CA ini boleh menandatangani sijil akhir dan tidak boleh menandatangani CA lain, yang mengekalkan rantaian tepat pada satu tahap kedalaman dan membolehkan anda membiarkan ssl_verify_depth tanpa diubah.

Mengeluarkan sijil klien bagi setiap individu

Satu sijil untuk setiap individu. Jangan sekali-kali menggunakan satu sijil kongsi untuk satu pasukan, kerana sijil kongsi tidak boleh dibatalkan tanpa menyekat akses semua orang, dan ia tidak memberikan maklumat tentang siapa yang membuat panggilan tersebut.

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.crt

openssl ca mencetak sijil yang bakal ditandatangani, meminta frasa laluan CA, meminta pengesahan sebanyak dua kali, kemudian menambah satu baris ke dalam index.txt. Tambahkan -batch apabila anda membuat skrip untuknya. Bahagian client_ext adalah penting kerana satu baris di dalamnya: extendedKeyUsage = clientAuth. Sijil yang membawa penyenaraian penggunaan kunci lanjutan (extended key usage) yang hanya mengandungi serverAuth akan ditolak kerana tidak sesuai untuk pengesahan klien, jadi nyatakan tujuannya dan jangan hanya berharap ia berfungsi.

Sahkan pasangan tersebut dengan CA sebelum anda menyerahkan apa-apa:

openssl verify -CAfile ca.crt certs/alice.crt

Perintah itu akan mencetak certs/alice.crt: OK. Sebarang output lain bermakna sijil dan CA tidak sepadan, dan tiada konfigurasi nginx yang dapat membaikinya.

Gabungkan kunci dan sijil ke dalam satu fail yang boleh diimport oleh pelayar:

openssl pkcs12 -export -inkey private/alice.key -in certs/alice.crt \
  -name "alice at example ops" -out alice.p12

Proses eksport akan meminta kata laluan, yang melindungi fail tersebut semasa dalam transit. Hantar fail dan kata laluan melalui saluran yang berbeza, dan berikan pengguna .p12 dan bukannya .key kosong. Anda boleh menambah -certfile ca.crt untuk menyertakan CA dalam pakej tersebut, tetapi nginx tidak memerlukannya: nginx sudah menyimpan ca.crt, jadi sijil yang ditandatangani secara terus oleh CA tersebut akan disahkan dengan sendirinya.

OpenSSL 3, yang disertakan dalam Ubuntu 24.04, menulis fail PKCS#12 dengan penyulitan semasa, dan pelayar serta sistem pengendalian yang digunakan setakat Ogos 2026 boleh membacanya. Jika pengimport lama menolak fail tersebut, eksport semula dengan menambah -legacy, yang akan kembali menggunakan algoritma lama yang dijangkakan oleh pengimport tersebut. Baca mesej yang diberikan oleh pengimport sebelum anda menggunakan flag tersebut.

Konfigurasi nginx dengan ssl_client_certificate dan ssl_verify_client

Salin sijil CA, dan hanya sijil CA tersebut, ke pelayan.

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'

Mod 644 adalah tepat di sini. Sijil CA merupakan maklumat awam. Kunci CA kekal pada stesen kerja anda.

Kemudian, tambahkan tiga direktif ke blok server yang sudah menamatkan TLS:

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 ialah tetapan lalai nginx, dan ia menyatakan bahawa sijil klien mesti ditandatangani oleh CA dalam fail tersebut secara terus. Tingkatkan nilainya hanya jika anda menambah perantara (intermediate). nginx juga menghantar nama subjek daripada ssl_client_certificate kepada klien semasa jabat tangan (handshake), yang membolehkan pelayar mengetahui sijil mana yang perlu ditawarkan. Gelagat tersebut adalah sebab untuk menggunakan ssl_client_certificate berbanding ssl_trusted_certificate, yang melakukan pengesahan dengan cara yang sama tetapi tidak menghantar sebarang senarai.

Ubuntu 24.04 membekalkan nginx 1.24, di mana HTTP/2 diletakkan pada baris listen sebagai listen 443 ssl http2;. Pada nginx 1.25.1 dan versi terkemudian, format tersebut telah ditamatkan (deprecated) dan HTTP/2 mempunyai direktifnya sendiri, iaitu http2 on;. Tiada satu pun pilihan ini mengubah pemeriksaan sijil.

Muat semula dan baca hasilnya:

sudo nginx -t && sudo systemctl reload nginx
curl -i https://admin.example.com/

nginx -t mencetak syntax is ok dan test is successful. Panggilan curl tidak membawa sebarang sijil, jadi ia sepatutnya kembali dengan 400 Bad Request berserta badan No required SSL certificate was sent. Ini bermakna nginx menolak di pintu masuknya sendiri, yang menunjukkan konfigurasi telah aktif dan aplikasi tidak pernah diminta untuk memprosesnya. Sekarang, cuba dengan cara yang betul:

curl --cert certs/alice.crt --key private/alice.key https://admin.example.com/

Perintah tersebut sepatutnya memaparkan apa sahaja yang dihidangkan oleh aplikasi anda.

Mengapa gate perlu berada dalam blok server

Sijil ditukar semasa jabat tangan TLS, sebelum nginx membaca baris permintaan, jadi pada saat itu nginx tidak tahu location mana yang akan menerima permintaan tersebut. Meletakkan ssl_verify_client on; di dalam location meminta klien untuk melakukan rundingan semula di tengah-tengah sambungan. TLS 1.3 telah membuang rundingan semula dan HTTP/2 melarangnya, jadi pada stack semasa, corak tersebut gagal dan bukannya memaparkan gesaan.

Lakukan skop sendiri. Minta sijil pada peringkat server, kemudian tentukan bagi setiap location:

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 menyimpan SUCCESS, atau NONE apabila klien tidak menghantar apa-apa, atau FAILED: diikuti dengan sebab. Dengan optional, nginx meminta sijil dan mengesahkannya hanya jika sijil diterima, inilah yang membolehkan laluan /healthz awam di atas berfungsi sementara /metrics kekal tertutup. Sijil yang dihantar dan gagal disahkan masih akan ditolak oleh nginx pada tahap tersebut. Jika anda ingin memeriksa sendiri sijil yang gagal, gunakan optional_no_ca, dan kemudian ujian anda sendiri perlu menganggap setiap nilai selain SUCCESS sebagai penolakan.

nginx mempunyai kod status bukan standard untuk perkara ini, dan error_page boleh menangkapnya supaya pelawat yang ditolak mendapat penjelasan dan bukannya ralat 400 kosong:

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 bermaksud sijil klien gagal disahkan. 496 bermaksud klien tidak mengemukakan sijil. Pastikan halaman tersebut dalam bentuk teks biasa, kerana orang yang membacanya tidak mempunyai sesi dan tidak mempunyai akaun.

Bagaimanakah cara memasang sijil klien dalam pelayar?

Firefox menggunakan stor sijilnya sendiri: pergi ke Settings, kemudian Privacy and Security, seterusnya View Certificates, pilih tab Your Certificates, klik Import, kemudian pilih .p12 dan masukkan kata laluan sijil tersebut.

Chrome dan Edge menggunakan stor sistem pengendalian pada Windows dan macOS, jadi membuka fail .p12 akan memulakan wizard import sistem. Pada Linux, Chrome membaca pangkalan data NSS (network security services) yang berasingan dalam direktori home anda, dan menggunakan alat baris perintah adalah cara yang paling boleh dipercayai:

sudo apt install -y libnss3-tools
pk12util -d sql:$HOME/.pki/nssdb -i alice.p12

Muatkan laman web tersebut selepas itu dan pelayar akan meminta sijil mana yang perlu dihantar. Chrome akan mengingati pilihan tersebut sepanjang sesi pelayar, jadi mulakan semula pelayar jika anda mahu diminta memilih sijil sekali lagi. Sijil tersebut disimpan dalam satu profil pelayar pada satu mesin, jadi sijil yang diimport ke dalam Firefox tidak dapat dikesan oleh Chrome, dan kedua-duanya tidak dapat dikesan oleh telefon anda.

Menguji dengan curl --cert

Lakukan penyahpepijatan menggunakan curl, kerana ia melaporkan tindakan yang telah dilakukan.

curl -v --cert certs/alice.crt --key private/alice.key https://admin.example.com/

Anda boleh menggabungkan sijil dan kunci ke dalam satu fail PEM dan menyalurkannya sebagai --cert alice.pem. Jika kunci mempunyai frasa laluan, curl akan memintanya. Ia juga menerima --cert alice.pem:passphrase, namun ini akan disimpan dalam sejarah shell anda, jadi pilihlah untuk menjawab gesaan tersebut.

Dua semakan perlu dijalankan sebelum anda menyalahkan nginx. Pertama, sijil dan kunci mestilah pasangan yang sepadan:

openssl x509 -noout -pubkey -in certs/alice.crt | openssl sha256
openssl pkey -pubout -in private/alice.key | openssl sha256

Dua hash yang serupa bermakna fail tersebut adalah pasangan. Dua hash yang berbeza bermakna anda telah mencampurkan fail milik pihak lain, dan tiada klien yang akan menyatakan punca tersebut untuk anda.

Kedua, pelayan sepatutnya meminta CA anda:

openssl s_client -connect admin.example.com:443 -servername admin.example.com </dev/null

Cari blok Acceptable client certificate CA names dalam output, dan cari subjek CA anda di dalamnya. Jika blok tersebut tiada langsung, nginx tidak meminta sijil pada blok pelayan yang menjawab permintaan tersebut, yang bermakna arahan anda telah diletakkan di blok lain, selalunya pada pelayan lalai (default server).

Menghantar CN klien kepada aplikasi

Sijil menyatakan siapa yang membuat panggilan, tetapi aplikasi di sebalik proksi tidak dapat melihat lapisan TLS, jadi nginx perlu menghantar nama tersebut.

map $ssl_client_s_dn $client_cn {
    default              "";
    "~,?CN=(?<cn>[^,]+)" $cn;
}

$ssl_client_s_dn menyimpan subject distinguished name dalam format RFC 2253, yang kelihatan seperti CN=alice,O=Example Ops. Peta tersebut mengambil medan CN ke dalam $client_cn. Pastikan CN kekal sebagai nama pengguna biasa, kerana koma di dalam CN akan di-escape dalam format tersebut dan ungkapan nalar (regular expression) kecil di atas tidak mengendalikan escape tersebut.

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 menggantikan mana-mana pengepala (header) dengan nama tersebut yang dihantar oleh pemanggil, supaya tiada sesiapa boleh memalsukan X-Client-Cert-CN melalui lokasi ini. Dua syarat memastikan perkara itu benar. nginx mewarisi proxy_set_header daripada tahap luar hanya apabila tahap dalam tidak mentakrifkan sebarang pengepala sendiri, jadi lokasi kedua dengan satu baris proxy_set_header akan kehilangan setiap pengepala yang ditetapkan di atasnya secara senyap, termasuk yang ini. Dan aplikasi mestilah tidak boleh dicapai kecuali melalui nginx, yang bermaksud mengikatnya kepada 127.0.0.1 dan bukannya 0.0.0.0, memandangkan aplikasi pada port awam akan membaca pengepala palsu terus daripada internet. Bahagian proksi bagi perkara itu diliputi dalam penjelasan baris demi baris konfigurasi reverse proxy nginx. Jika aplikasi mahukan keseluruhan sijil dan bukannya nama, $ssl_client_escaped_cert membawanya dalam bentuk URL-encoded dan selamat di dalam pengepala.

Bagaimanakah cara untuk membatalkan satu sijil klien?

Seseorang berhenti kerja, atau komputer riba hilang. Anda membatalkan sijil tersebut dan pengguna lain boleh terus bekerja; inilah sebab utama mengapa satu sijil dikeluarkan bagi setiap individu.

cd ~/client-ca
openssl ca -config openssl.cnf -revoke certs/alice.crt
openssl ca -config openssl.cnf -gencrl -out crl.pem

Perintah pertama menukar baris siri tersebut dalam index.txt daripada V kepada R. Perintah kedua menulis senarai pembatalan sijil (CRL), iaitu fail bertandatangan yang menyenaraikan nombor siri yang dibatalkan. Hantarkan fail tersebut dan arahkan nginx kepadanya menggunakan ssl_crl /etc/nginx/client-ca.crl; di samping arahan-arahan lain.

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'

Berikut adalah perangkap yang akan menyekat akses semua orang. CRL membawa tarikh nextUpdate, yang ditetapkan oleh default_crl_days, iaitu 30 dalam konfigurasi di atas. Sebaik sahaja tarikh tersebut berlalu, OpenSSL menganggap senarai itu sudah lapuk dan gagal melakukan pengesahan untuk setiap sijil klien dengan CRL has expired, bukan hanya untuk sijil yang dibatalkan sahaja. nginx membaca fail tersebut apabila ia memuatkan konfigurasinya, jadi CRL baharu pada cakera tidak akan mengubah apa-apa sehingga proses muat semula (reload) dilakukan. Jana semula dan muat semula mengikut jadual yang selesa dalam tempoh tersebut, contohnya mingguan untuk tempoh 30 hari, dan semak tarikh sebelum anda menyalinnya:

openssl crl -in crl.pem -noout -lastupdate -nextupdate

Untuk sebilangan kecil pengguna, terdapat pilihan yang lebih ringkas. CA tersebut adalah milik anda, jadi nginx boleh menolak sesuatu siri secara terus dan melangkau mekanisme CRL:

map $ssl_client_serial $revoked {
    default 0;
    "1002"  1;
}

Gandingkan perkara itu dengan if ($revoked) { return 403; } dalam blok location. Ia tidak mempunyai tarikh luput yang perlu diingati. Ia juga tidak berpindah, jadi mana-mana sistem lain yang mempercayai CA anda tidak akan mengetahui tentang pembatalan tersebut. Bagi satu nginx yang berada di hadapan satu aplikasi, ini adalah jawapan yang jujur dan mudah. Beralihlah kepada CRL sebaik sahaja terdapat lebih daripada satu pintu masuk.

Berapa lama sijil klien harus bertahan?

Berikan sijil klien tempoh setahun, atau kurang jika anda sanggup melakukan kerja pengeluaran semula. Tarikh luput merupakan kegagalan senyap dalam hal ini, kerana tiada amaran awal diberikan kepada pemegang sijil. Mereka membuka panel pada suatu pagi, nginx menolak sambungan tersebut, dan pelayar web menerangkan penolakan itu dengan bahasanya sendiri, yang jarang sekali mengandungi perkataan tamat tempoh. Kekalkan CA pada sepuluh tahun dan letakkan tarikh luputnya di tempat yang anda benar-benar akan membacanya, kerana apabila sijil CA tamat tempoh, setiap sijil di bawahnya akan berhenti disahkan pada hari yang sama.

Dua arahan ini membantu anda mendahului perkara tersebut:

openssl x509 -in certs/alice.crt -noout -subject -serial -enddate
awk -F'\t' '{print $1, $2, $4}' ~/client-ca/index.txt

Lajur pertama index.txt ialah status: V untuk sah, R untuk dibatalkan, E untuk tamat tempoh. Lajur kedua ialah tarikh luput dalam format YYMMDDHHMMSSZ dan lajur keempat ialah siri. Fail tersebut merupakan satu-satunya rekod anda tentang siapa yang memegang sijil apa, jadi buat sandaran bersama-sama dengan kunci CA dan anggap kedua-duanya sebagai rahsia.

Pembaharuan bermaksud sijil baharu, bukan pelanjutan. Jana kunci dan CSR (certificate signing request) yang baharu, tandatanganinya, serahkan kepada pengguna, kemudian batalkan sijil lama sebaik sahaja individu tersebut mengesahkan sijil baharu berfungsi.

Perkara yang dilindungi oleh mTLS, dan perkara yang tidak dilindunginya

Apa yang dihapuskannya ialah capaian tanpa pengesahan. Pengimbas yang menemui nama hos anda akan ditolak semasa jabat tangan (handshake), jadi ia tidak akan menghantar permintaan HTTP, tidak akan melihat borang log masuk, dan tidak akan berpeluang mencuba kata laluan yang dicuri. Serangan credential stuffing tidak mempunyai sasaran untuk diserang. Kerentanan dalam aliran log masuk aplikasi tidak boleh dicapai oleh sesiapa yang tidak mempunyai sijil. Ia juga menghapuskan rahsia dikongsi (shared secret) yang sering disalin orang ke dalam ruang sembang, kerana kunci peribadi adalah fail yang sukar disalin secara tidak sengaja.

Apa yang tidak dilindunginya ialah klien yang telah diceroboh. Perisian hasad pada komputer riba memiliki fail kunci tersebut, dan ia mendapat frasa laluan sebaik sahaja pemiliknya menaipnya. Bagi pelayan, penyerang tersebut kelihatan sama seperti pengguna yang sah, kerana sijil membuktikan pemilikan fail, bukan kehadiran seseorang. Kata laluan .p12 dan penyulitan cakera penuh masih memainkan peranan penting.

Ia juga bukan kebenaran (authorization). Setiap sijil yang sah boleh mencapai segala-galanya yang dihidangkan oleh blok pelayan tersebut melainkan anda menyemak $client_cn dan bertindak berdasarkan nilainya. Secara lalai, dua pemegang sijil mempunyai akses yang sama.

Dan ia hanya mengawal laluan melalui Nginx. Jika aplikasi tersebut juga mendengar pada port awam, mTLS di hadapannya hanyalah hiasan: ikat (bind) aplikasi tersebut kepada 127.0.0.1 dan pastikan firewall ditutup pada portnya. Pintu lain ke dalam kotak yang sama ialah SSH, dan ia memerlukan perhatian yang sama, yang dibincangkan dalam mengeraskan akses SSH pada VPS anda.

Satu lagi had, dan ia akan memberi kesan pada hari anda mengaktifkannya. Apa-apa sahaja yang tidak dapat mengemukakan sijil akan berhenti berfungsi: pemantau masa aktif (uptime monitor), webhook daripada penyedia pembayaran, pembaca RSS, aplikasi mudah alih tanpa stor sijil yang boleh anda capai. Buat keputusan mengenai perkara tersebut sebelum anda menetapkan ssl_verify_client on, kerana kegagalannya adalah menyeluruh dan, dari pihak mereka, tidak disedari.

Apabila klien ditolak, baca laporan klien tersebut

Mesej yang dipaparkan oleh klien yang ditolak bergantung pada pelayar, versi curl dan pustaka TLS yang digunakan. Oleh itu, baca apa yang dipaparkan oleh klien anda sendiri dan jangan membandingkannya dengan mesej yang ditulis di tempat lain. Butiran yang berguna ada pada pelayan.

sudo tail -n 50 /var/log/nginx/error.log

Sijil yang ditolak akan meninggalkan baris yang mengandungi client SSL certificate verify error diikuti dengan sebab yang diberikan oleh OpenSSL. Sebab itulah perkara yang perlu ditangani. Biasanya, ia berpunca daripada beberapa perkara. Sijil tersebut datang daripada CA yang berbeza daripada fail yang dinamakan dalam ssl_client_certificate. Sijil tersebut berada di luar tarikh sahnya. CRL pada pelayan telah melepasi nextUpdate, jadi ia kini menolak setiap klien dan bukan hanya satu.

Apabila pelayar tidak menawarkan sijil langsung, masalahnya berlaku lebih awal daripada pengesahan. nginx menghantar nama pengeluar (issuer) yang boleh diterima semasa jabat tangan (handshake), dan pelayar tidak menemui apa-apa dalam stornya yang sepadan, jadi ia tidak mempunyai apa-apa untuk ditawarkan kepada anda. Import semula .p12 ke dalam profil yang anda gunakan untuk melayari.

Satu lagi kes yang perlu dinyatakan. Jika anda menguji dengan sijil klien yang ditandatangani sendiri (self-signed) dan bukannya sijil yang ditandatangani oleh CA anda, pengesahan tidak akan berjaya kerana nginx menyemak tandatangan tersebut terhadap fail CA, manakala sijil yang ditandatangani sendiri tidak terkandung di dalamnya. Mekanisme untuk membuat sijil adalah sama seperti dalam menjana sijil yang ditandatangani sendiri pada Ubuntu. mTLS hanya memerlukan langkah tambahan di mana CA anda menandatanganinya.

FAQ

Adakah saya masih memerlukan sijil Let's Encrypt jika saya menggunakan mTLS?

Ya. Kedua-dua sijil tersebut tidak berkaitan. Pelayan anda mempersembahkan sijilnya sendiri supaya pelayar mempercayai nama hos, dan sijil itu masih perlu datang daripada CA yang sudah dikenali oleh pelayar. CA klien anda ialah rantaian peribadi yang berasingan, digunakan hanya untuk menyemak siapa yang sedang membuat sambungan. Menetapkan ssl_client_certificate tidak mengubah apa-apa tentang sijil yang dipersembahkan oleh nginx, dan ia tidak boleh menghala ke rantaian Let's Encrypt anda.

Mengapa pelayar saya tidak pernah meminta saya memilih sijil?

nginx menghantar senarai pengeluar yang boleh diterima semasa jabat tangan (handshake), yang dibina daripada fail dalam ssl_client_certificate. Pelayar hanya menawarkan sijil yang pengeluarnya muncul dalam senarai tersebut. Oleh itu, tiada gesaan bermakna pelayar tidak memegang apa-apa daripada CA anda: import telah dilakukan ke dalam profil pelayar yang berbeza, atau sijil tersebut ditandatangani oleh CA yang berbeza daripada yang dipasang pada pelayan. Jalankan openssl s_client -connect admin.example.com:443 dan cari nama CA sijil klien yang boleh diterima dalam output untuk melihat CA mana yang sebenarnya diminta oleh pelayan.

Bolehkah saya mewajibkan sijil klien pada satu URL sahaja?

Tidak boleh dengan ssl_verify_client on di dalam location. Sijil ditukar semasa jabat tangan, sebelum nginx mengetahui laluan permintaan, dan rundingan semula (renegotiation) yang boleh mengatasi perkara itu telah tiada dalam TLS 1.3 dan dilarang dalam HTTP/2. Tetapkan ssl_verify_client optional; dalam blok pelayan, kemudian dalam setiap lokasi yang dilindungi, uji $ssl_client_verify dan kembalikan 403 apabila ia bukan SUCCESS.

Bagaimanakah cara saya membatalkan akses untuk seseorang?

Batalkan sijil tersebut dengan openssl ca -revoke, jana semula senarai dengan openssl ca -gencrl, salin ke pelayan, dan muat semula nginx supaya ia membaca fail baharu tersebut. Orang lain tidak terjejas, yang hanya berfungsi jika setiap orang memegang sijil mereka sendiri dan bukannya sijil yang dikongsi. Pantau tarikh nextUpdate pada CRL, kerana CRL yang tamat tempoh akan menyebabkan pengesahan gagal untuk setiap klien, bukan hanya untuk mereka yang telah dibatalkan aksesnya.

Adakah mTLS menggantikan halaman log masuk?

Untuk capaian, ya: tanpa sijil, tiada apa-apa yang sampai ke aplikasi, jadi tiada borang untuk diserang dan tiada kata laluan untuk diteka. Untuk identiti di dalam aplikasi, tidak. Sijil membuktikan pemanggil memegang fail kunci, jadi komputer riba yang dicuri dianggap sebagai pengguna yang sah. Hantar CN ke hulu (upstream), kekalkan apa jua akaun dan kebenaran yang sudah dimiliki oleh aplikasi, dan anggap sijil tersebut sebagai pintu masuk di hadapannya.

#tls#mtls#nginx#openssl#access-control