Nginx में mTLS client certificate कैसे सेटअप करें
Nginx admin panel को mTLS से सुरक्षित करना सीखें। OpenSSL के साथ private CA बनाएं, client certificates जारी करें और ssl_verify_client directive का उपयोग करके अनधिकृत एक्सेस को रोकें।
mTLS क्या करता है
Mutual TLS, जिसे आमतौर पर mTLS लिखा जाता है, Nginx को प्रत्येक client से certificate मांगने के लिए बाध्य करता है। यदि certificate अनुपस्थित हो या आपके द्वारा नियंत्रित certificate authority (CA) द्वारा जारी न किया गया हो, तो Nginx अनुरोध को अस्वीकार कर देता है। यह जांच TLS (transport layer security) handshake के दौरान ही हो जाती है, इसलिए वैध client certificate के बिना कोई भी caller आपकी application तक कभी नहीं पहुँच पाता। यही इसकी मुख्य विशेषता है: एक admin panel या metrics endpoint बिना किसी login page के public internet पर रह सकता है, जहाँ किसी bot के लिए अनुमान लगाने जैसा कुछ नहीं होता।
इसका निर्माण सरल है। openssl के साथ एक private CA, प्रति व्यक्ति एक certificate, और Nginx server block में तीन directives की आवश्यकता होती है। यह व्यवस्था एक वर्ष से अधिक समय तक प्रभावी रहेगी या नहीं, यह इसके संचालन पर निर्भर करता है। इसलिए, इस guide का अधिकांश भाग lifetimes, revocation, प्रति-व्यक्ति certificates, और उस स्थिति पर केंद्रित है जब किसी client को अस्वीकार कर दिया जाता है और कारण स्पष्ट नहीं होता।
दो चेन, एक नहीं
mTLS सेटअप में दो सर्टिफिकेट चेन होती हैं और इनका आपस में कोई संबंध नहीं होता। इन्हें एक ही समझना सबसे पहली गलती है जो लगभग हर कोई करता है।
पहली चेन सर्वर की होती है। आपका VPS admin.example.com के लिए एक सर्टिफिकेट प्रस्तुत करता है जिसे Let's Encrypt जैसे पब्लिक CA द्वारा जारी किया जाता है, और ब्राउज़र ऑपरेटिंग सिस्टम के साथ आने वाले रूट स्टोर के आधार पर इसकी जाँच करता है। mTLS का इस हिस्से पर कोई प्रभाव नहीं पड़ता। यदि certbot आज आपके लिए वह सर्टिफिकेट जारी करता है, तो उसे वैसा ही रहने दें: देखें nginx के लिए certbot से Let's Encrypt सर्टिफिकेट जारी करना।
दूसरी चेन क्लाइंट की होती है। आप अपना एक छोटा CA बनाते हैं, प्रत्येक व्यक्ति के लिए एक सर्टिफिकेट साइन करते हैं जिसे एक्सेस की आवश्यकता है, और आप nginx को निर्देश देते हैं कि क्लाइंट्स की जाँच करते समय वह केवल उस CA पर भरोसा करे। किसी भी पब्लिक रूट स्टोर को आपके CA के बारे में जानने की आवश्यकता नहीं है। एकमात्र इकाई जिसे इस पर भरोसा करना है वह nginx है, और यह ssl_client_certificate फ़ाइल के माध्यम से होता है।
इसलिए ssl_client_certificate कभी भी उस सर्टिफिकेट को प्रभावित नहीं करता जिसे nginx प्रस्तुत करता है, और Let's Encrypt चेन कभी भी यह प्रभावित नहीं करती कि किन क्लाइंट्स को अंदर आने की अनुमति है। ssl_client_certificate को fullchain.pem की ओर पॉइंट करना वैसा काम नहीं करता जैसा यह दिखता है: वह डायरेक्टिव उन जारीकर्ताओं (issuers) के नाम बताता है जिनसे क्लाइंट सर्टिफिकेट आ सकता है, जो कनेक्शन का दूसरा सिरा है। सर्वर को अपने स्वयं के आउटबाउंड कार्यों के लिए अपने CA पर भरोसा दिलाना एक अलग काम है, जिसे Ubuntu ट्रस्ट स्टोर में अपना CA जोड़ना में कवर किया गया है, और सिस्टम ट्रस्ट स्टोर वह नहीं है जिसे nginx क्लाइंट को सत्यापित करते समय पढ़ता है।
अपना स्वयं का client CA, openssl के साथ बनाएँ
CA को web server के अलावा किसी अन्य स्थान पर बनाएँ। nginx को केवल CA के public certificate की आवश्यकता होती है। CA private key नए client certificates को sign करती है, इसलिए इसे internet-facing box पर छोड़ने का अर्थ है कि एक बार breach होने पर हमलावर अपनी इच्छा से वैध clients बना सकता है।
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 और crlnumber, CA database हैं। openssl ca इनके बिना चलने से मना कर देता है। ये ही बाद में revocation को संभव बनाते हैं, क्योंकि revocation list में serial numbers होते हैं, इसलिए CA को यह याद रखना पड़ता है कि कौन सा serial किसे दिया गया था।
~/client-ca/openssl.cnf लिखें। dir को उस directory के वास्तविक path पर set करें, क्योंकि openssl ca, ~ को expand नहीं करता है।
[ 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अब CA key और उसका self-signed certificate बनाएँ:
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, CA key पर एक passphrase लगाता है, इसलिए हर signing run के दौरान यह उसे पूछेगा। यही इसका उद्देश्य है। जाँचें कि आपने क्या बनाया है:
openssl x509 -in ca.crt -noout -subject -dates -ext basicConstraintsSubject आपका CA होना चाहिए और validity दस वर्ष की होनी चाहिए। Extension line को CA:TRUE, pathlen:0 पढ़ना चाहिए। pathlen:0 का अर्थ है कि यह CA end certificates को sign कर सकता है और किसी अन्य CA को sign नहीं कर सकता है, जो chain को ठीक एक स्तर गहरा रखता है और आपको ssl_verify_depth को वैसे ही छोड़ने देता है।
प्रत्येक व्यक्ति के लिए एक क्लाइंट सर्टिफिकेट जारी करें
प्रत्येक व्यक्ति के लिए एक सर्टिफिकेट। टीम के लिए कभी भी साझा सर्टिफिकेट का उपयोग न करें, क्योंकि साझा सर्टिफिकेट को रद्द करने पर सभी का एक्सेस बंद हो जाएगा, और इससे यह भी पता नहीं चलेगा कि किसने कॉल किया है।
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 उस सर्टिफिकेट को प्रिंट करता है जिसे वह साइन करने वाला है, CA पासफ्रेज मांगता है, पुष्टि के लिए दो बार पूछता है, और फिर index.txt में एक लाइन जोड़ देता है। जब आप इसे स्क्रिप्ट करते हैं तो -batch जोड़ें। client_ext सेक्शन महत्वपूर्ण है क्योंकि इसमें एक लाइन है: extendedKeyUsage = clientAuth। ऐसा सर्टिफिकेट जिसमें केवल serverAuth की विस्तारित कुंजी उपयोग (extended key usage) सूची है, उसे क्लाइंट प्रमाणीकरण के लिए अनुपयुक्त मानकर अस्वीकार कर दिया जाता है, इसलिए उम्मीद करने के बजाय उद्देश्य स्पष्ट रूप से बताएं।
किसी को कुछ भी सौंपने से पहले CA के विरुद्ध जोड़ी (pair) को सत्यापित करें:
openssl verify -CAfile ca.crt certs/alice.crtयह certs/alice.crt: OK प्रिंट करता है। कोई अन्य आउटपुट का मतलब है कि सर्टिफिकेट और CA मेल नहीं खाते हैं, और कोई भी nginx कॉन्फ़िगरेशन इसे ठीक नहीं कर पाएगा।
की (key) और सर्टिफिकेट को एक ऐसी फाइल में बंडल करें जिसे ब्राउज़र इम्पोर्ट कर सके:
openssl pkcs12 -export -inkey private/alice.key -in certs/alice.crt \
-name "alice at example ops" -out alice.p12एक्सपोर्ट के दौरान एक पासवर्ड मांगा जाता है, जो फाइल को ट्रांजिट के दौरान सुरक्षित रखता है। फाइल और पासवर्ड को अलग-अलग माध्यमों से भेजें, और लोगों को केवल एक खाली .key के बजाय .p12 दें। आप बंडल में CA को शामिल करने के लिए -certfile ca.crt जोड़ सकते हैं, लेकिन nginx को इसकी आवश्यकता नहीं है: nginx के पास पहले से ही ca.crt मौजूद है, इसलिए उस CA द्वारा सीधे साइन किया गया सर्टिफिकेट अपने आप सत्यापित हो जाता है।
OpenSSL 3, जो Ubuntu 24.04 में आता है, PKCS#12 फाइलों को वर्तमान एन्क्रिप्शन के साथ लिखता है, और अगस्त 2026 तक उपयोग में आने वाले ब्राउज़र और ऑपरेटिंग सिस्टम उन्हें पढ़ सकते हैं। यदि कोई पुराना इम्पोर्टर फाइल को स्वीकार करने से मना कर दे, तो -legacy जोड़कर पुनः एक्सपोर्ट करें, जो उन पुराने एल्गोरिदम पर वापस चला जाता है जिनकी वह इम्पोर्टर अपेक्षा करता है। उस फ्लैग का उपयोग करने से पहले इम्पोर्टर द्वारा दिए गए संदेश को पढ़ें।
ssl_client_certificate और ssl_verify_client के साथ nginx को कॉन्फ़िगर करना
CA certificate को, और केवल CA certificate को ही, सर्वर पर कॉपी करें।
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'यहाँ 644 मोड सही है। CA certificate सार्वजनिक जानकारी होती है। CA key आपके वर्कस्टेशन पर ही रहनी चाहिए।
इसके बाद, उस सर्वर ब्लॉक में तीन directives जोड़ें जो पहले से ही TLS termination कर रहा है:
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 nginx का डिफ़ॉल्ट है, और यह बताता है कि client certificate को सीधे उस फ़ाइल में मौजूद CA द्वारा हस्ताक्षरित (signed) होना चाहिए। इसे केवल तभी बढ़ाएं यदि आप कोई intermediate जोड़ते हैं। nginx हैंडशेक के दौरान क्लाइंट को ssl_client_certificate से subject names भी भेजता है, जिससे ब्राउज़र को पता चलता है कि उसे अपने कौन से certificates पेश करने हैं। यही कारण है कि ssl_trusted_certificate के बजाय ssl_client_certificate का उपयोग किया जाता है, जो समान तरीके से सत्यापित तो करता है लेकिन कोई सूची नहीं भेजता है।
Ubuntu 24.04 में nginx 1.24 आता है, जहाँ HTTP/2 को listen लाइन पर listen 443 ssl http2; के रूप में लिखा जाता है। nginx 1.25.1 और उसके बाद के संस्करणों में वह प्रारूप deprecated हो गया है और HTTP/2 के लिए अलग directive, http2 on; का उपयोग होता है। इनमें से कोई भी विकल्प certificate जाँच को प्रभावित नहीं करता है।
Reload करें और परिणाम देखें:
sudo nginx -t && sudo systemctl reload nginx
curl -i https://admin.example.com/nginx -t, syntax is ok और test is successful प्रिंट करता है। curl कॉल में कोई certificate नहीं होता है, इसलिए इसे 400 Bad Request और बॉडी No required SSL certificate was sent के साथ वापस आना चाहिए। यह nginx का अपने गेट पर ही मना करना है, जिसका अर्थ है कि कॉन्फ़िगरेशन सक्रिय है और एप्लिकेशन से कभी पूछा ही नहीं गया। अब इसे सही तरीके से आज़माएँ:
curl --cert certs/alice.crt --key private/alice.key https://admin.example.com/यह वही परिणाम देना चाहिए जो आपका एप्लिकेशन सर्व करता है।
सर्वर ब्लॉक में गेट क्यों होना चाहिए
TLS handshake के दौरान certificate का आदान-प्रदान होता है, इससे पहले कि nginx ने request line को पढ़ा हो। उस क्षण, nginx को यह नहीं पता होता कि request किस location में जाएगी। ssl_verify_client on; को location के अंदर रखने का अर्थ है client से connection के बीच में ही renegotiate करने के लिए कहना। TLS 1.3 ने renegotiation को हटा दिया है और HTTP/2 इसे वर्जित करता है, इसलिए वर्तमान stack पर वह pattern prompt करने के बजाय विफल हो जाता है।
scoping आप स्वयं करें। server स्तर पर certificate मांगें, फिर प्रत्येक 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 में SUCCESS होता है, या यदि client ने कुछ नहीं भेजा तो NONE, या कारण के साथ FAILED: होता है। optional के साथ, nginx certificate का अनुरोध करता है और केवल तभी verify करता है यदि वह प्राप्त हो। यही वह तरीका है जिससे ऊपर दिया गया सार्वजनिक /healthz path काम करता है जबकि /metrics बंद रहता है। यदि कोई certificate भेजा जाता है और verification विफल हो जाता है, तो nginx उसे उसी बिंदु पर अस्वीकार कर देता है। यदि आप इसके बजाय विफल होने वाले certificate की स्वयं जांच करना चाहते हैं, तो वह optional_no_ca है, और फिर आपके स्वयं के test को SUCCESS के अलावा हर मान को अस्वीकृति के रूप में मानना होगा।
nginx के पास इसके लिए non-standard status codes हैं, और error_page उन्हें catch कर सकता है ताकि अस्वीकृत visitor को केवल एक खाली 400 के बजाय स्पष्टीकरण मिल सके:
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 का अर्थ है कि client certificate का verification विफल रहा। 496 का अर्थ है कि client ने कोई certificate प्रस्तुत नहीं किया। उस page को plain text में रखें, क्योंकि उसे पढ़ने वाले व्यक्ति के पास कोई session या account नहीं होता है।
मैं ब्राउज़र में client certificate कैसे install करूँ?
Firefox अपना स्वयं का certificate store रखता है: Settings में जाएँ, फिर Privacy and Security, उसके बाद View Certificates, फिर Your Certificates tab, और अंत में Import चुनें। इसके बाद .p12 को चुनें और उसका password दर्ज करें।
Chrome और Edge, Windows और macOS पर operating system के store का उपयोग करते हैं, इसलिए .p12 file को खोलने पर system import wizard शुरू हो जाता है। Linux पर, Chrome आपके home directory में एक अलग NSS (network security services) database को पढ़ता है, और command line tool ही सबसे विश्वसनीय तरीका है:
sudo apt install -y libnss3-tools
pk12util -d sql:$HOME/.pki/nssdb -i alice.p12इसके बाद site को load करें, तो browser आपसे पूछेगा कि कौन सा certificate भेजना है। Chrome उस विकल्प को browser session के बाकी समय के लिए याद रखता है, इसलिए यदि आप दोबारा पूछना चाहते हैं तो browser को restart करें। Certificate एक machine पर एक browser profile में रहता है, इसलिए Firefox में import किया गया certificate Chrome को दिखाई नहीं देगा, और दोनों ही आपके phone के लिए अदृश्य रहेंगे।
curl --cert के साथ परीक्षण
curl के साथ debug करें, क्योंकि यह अपनी गतिविधियों की रिपोर्ट देता है।
curl -v --cert certs/alice.crt --key private/alice.key https://admin.example.com/आप certificate और key को एक PEM file में जोड़ सकते हैं और उसे --cert alice.pem के रूप में पास कर सकते हैं। यदि key में passphrase है, तो curl उसे मांगेगा। यह --cert alice.pem:passphrase को भी स्वीकार करता है, जो आपके shell history में रह जाता है, इसलिए prompt का उपयोग करना बेहतर है।
nginx को दोष देने से पहले दो जाँच करना उचित है। पहला, certificate और key का एक जोड़ा होना आवश्यक है:
openssl x509 -noout -pubkey -in certs/alice.crt | openssl sha256
openssl pkey -pubout -in private/alice.key | openssl sha256दो समान hashes का अर्थ है कि फाइलें एक-दूसरे से संबंधित हैं। दो अलग hashes का अर्थ है कि आपने दो अलग-अलग फाइलों को मिला दिया है, और कोई भी client आपको यह कारण नहीं बताएगा।
दूसरा, सर्वर को आपके CA के लिए अनुरोध करना चाहिए:
openssl s_client -connect admin.example.com:443 -servername admin.example.com </dev/nullआउटपुट में Acceptable client certificate CA names ब्लॉक को देखें, और उसके भीतर अपने CA का subject खोजें। यदि वह ब्लॉक पूरी तरह से गायब है, तो nginx उस server block पर certificate का अनुरोध नहीं कर रहा है जिसने उत्तर दिया है, जिसका अर्थ है कि आपके directives किसी अन्य, अक्सर default server, में चले गए हैं।
क्लाइंट CN को एप्लिकेशन तक पहुँचाना
Certificate यह बताता है कि कॉल किसने किया है, लेकिन प्रॉक्सी के पीछे मौजूद एप्लिकेशन TLS लेयर को नहीं देख सकता, इसलिए nginx को यह नाम आगे पास करना पड़ता है।
map $ssl_client_s_dn $client_cn {
default "";
"~,?CN=(?<cn>[^,]+)" $cn;
}$ssl_client_s_dn में subject distinguished name, RFC 2253 प्रारूप में होता है, जो CN=alice,O=Example Ops जैसा दिखता है। यह map, CN फ़ील्ड को $client_cn में ले जाता है। CN को एक साधारण username ही रखें, क्योंकि उस प्रारूप में CN के अंदर का कॉमा escape किया जाता है और ऊपर दिया गया छोटा regular expression उस escape को हैंडल नहीं करता है।
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 उस नाम के किसी भी हेडर को बदल देता है जिसे कॉलर ने भेजा था, ताकि कोई भी इस लोकेशन के माध्यम से X-Client-Cert-CN को forge न कर सके। दो शर्तें इसे सुनिश्चित करती हैं। nginx, proxy_set_header को बाहरी स्तर से केवल तभी inherit करता है जब आंतरिक स्तर पर अपना कोई हेडर परिभाषित न हो, इसलिए एक दूसरी लोकेशन जिसमें एक proxy_set_header लाइन हो, वह चुपचाप अपने ऊपर सेट किए गए हर हेडर को खो देती है, जिसमें यह भी शामिल है। और एप्लिकेशन को nginx के अलावा कहीं और से एक्सेस नहीं किया जाना चाहिए, जिसका अर्थ है इसे 0.0.0.0 के बजाय 127.0.0.1 पर bind करना, क्योंकि public port पर मौजूद ऐप सीधे इंटरनेट से forged हेडर पढ़ लेगा। इसका प्रॉक्सी पक्ष an nginx reverse proxy configuration explained line by line में कवर किया गया है। यदि एप्लिकेशन को नाम के बजाय पूरा certificate चाहिए, तो $ssl_client_escaped_cert इसे URL-encoded और सुरक्षित रूप से एक हेडर के अंदर ले जाता है।
मैं एक client certificate को revoke कैसे करूँ?
कोई व्यक्ति नौकरी छोड़ देता है, या कोई laptop खो जाता है। आप उस एक certificate को revoke करते हैं और बाकी सभी का काम चलता रहता है, यही कारण है कि प्रति व्यक्ति एक certificate जारी किया जाता है।
cd ~/client-ca
openssl ca -config openssl.cnf -revoke certs/alice.crt
openssl ca -config openssl.cnf -gencrl -out crl.pemपहली command index.txt में उस serial की line को V से बदलकर R कर देती है। दूसरी command एक certificate revocation list (CRL) लिखती है, जो revoked serial numbers की एक signed file होती है। इसे deploy करें और अन्य directives के साथ ssl_crl /etc/nginx/client-ca.crl; का उपयोग करके nginx को इसकी ओर point करें।
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'यहाँ वह trap है जो सभी के लिए रास्ता बंद कर देता है। एक CRL में nextUpdate date होती है, जिसे default_crl_days द्वारा set किया जाता है, जो ऊपर दिए गए config में 30 है। एक बार वह date निकल जाने पर, OpenSSL उस list को पुराना (stale) मान लेता है और केवल revoked certificate के लिए ही नहीं, बल्कि हर client certificate के लिए verification को CRL has expired error के साथ fail कर देता है। nginx अपनी configuration load करते समय file को पढ़ता है, इसलिए disk पर नई CRL होने से तब तक कुछ नहीं बदलता जब तक आप reload न करें। 30 दिनों की window के भीतर, जैसे कि साप्ताहिक आधार पर, इसे regenerate और reload करें, और copy करने से पहले dates की जाँच करें:
openssl crl -in crl.pem -noout -lastupdate -nextupdateकुछ ही users के लिए एक छोटा विकल्प भी है। CA आपका है, इसलिए nginx सीधे एक serial को मना (refuse) कर सकता है और CRL की प्रक्रिया को छोड़ सकता है:
map $ssl_client_serial $revoked {
default 0;
"1002" 1;
}इसे location में if ($revoked) { return 403; } के साथ जोड़ें। इसमें भूलने के लिए कोई expiry date नहीं होती। यह कहीं और transfer भी नहीं होती, इसलिए आपके CA पर भरोसा करने वाली कोई अन्य चीज़ इसके बारे में नहीं जानती। एक application के सामने एक nginx के लिए यह सबसे सरल और सही तरीका है। जब एक से अधिक gate हों, तब CRL का उपयोग करें।
Client certificates की अवधि कितनी होनी चाहिए?
Client certificates को एक वर्ष की अवधि दें, या यदि आप बार-बार reissue करने का काम कर सकते हैं तो इससे कम अवधि रखें। यहाँ expiry एक शांत विफलता (quiet failure) है, क्योंकि धारक को पहले से कोई चेतावनी नहीं मिलती है। वे एक सुबह पैनल खोलते हैं, Nginx connection को अस्वीकार कर देता है, और ब्राउज़र अपनी भाषा में इस अस्वीकृति का वर्णन करता है, जिसमें शायद ही कभी 'expired' शब्द का उल्लेख होता है। CA को दस वर्षों के लिए रखें और इसकी expiry date को ऐसी जगह लिखें जिसे आप वास्तव में देख सकें, क्योंकि जब CA certificate expire होता है, तो इसके अंतर्गत आने वाले सभी certificates उसी दिन verify होना बंद हो जाते हैं।
दो commands आपको इससे आगे रहने में मदद करती हैं:
openssl x509 -in certs/alice.crt -noout -subject -serial -enddate
awk -F'\t' '{print $1, $2, $4}' ~/client-ca/index.txtindex.txt का पहला कॉलम status है: V वैध के लिए, R रद्द (revoked) के लिए, E expired के लिए। दूसरा कॉलम YYMMDDHHMMSSZ प्रारूप में expiry है और चौथा serial है। वह फ़ाइल ही आपका एकमात्र रिकॉर्ड है कि किसके पास क्या है, इसलिए इसे CA key के साथ backup करें और दोनों को secrets के रूप में सुरक्षित रखें।
Renewal एक नया certificate है, न कि विस्तार (extension)। एक नई key और CSR (certificate signing request) generate करें, उसे sign करें, उसे सौंपें, और फिर जब व्यक्ति यह पुष्टि कर ले कि नया certificate काम कर रहा है, तो पुराने वाले को revoke कर दें।
mTLS किन खतरों से सुरक्षा प्रदान करता है और किनसे नहीं
यह अनधिकृत पहुंच (unauthenticated reach) को समाप्त करता है। यदि कोई स्कैनर आपके hostname को ढूंढ भी लेता है, तो handshake के दौरान ही उसे अस्वीकार कर दिया जाता है। इसलिए वह न तो कोई HTTP request भेज पाता है, न ही login form देख पाता है, और न ही चोरी किए गए password का उपयोग कर पाता है। Credential stuffing के लिए वहां कुछ भी नहीं होता। एप्लिकेशन के login flow में मौजूद किसी भी vulnerability तक बिना certificate वाला कोई भी व्यक्ति नहीं पहुंच सकता। यह उस shared secret को भी हटा देता है जिसे लोग अक्सर chat में पेस्ट कर देते हैं, क्योंकि private key एक ऐसी फाइल है जिसे गलती से कॉपी करना कठिन होता है।
यह compromised client के मामले में कोई सुरक्षा नहीं देता। लैपटॉप पर मौजूद malware के पास key file होती है, और जैसे ही मालिक passphrase टाइप करता है, वह उसे भी प्राप्त कर लेता है। सर्वर के लिए वह हमलावर बिल्कुल एक वैध उपयोगकर्ता जैसा दिखता है, क्योंकि certificate यह साबित करता है कि फाइल मौजूद है, न कि यह कि कोई व्यक्ति उसे इस्तेमाल कर रहा है। .p12 पासवर्ड और full disk encryption का महत्व अभी भी बना हुआ है।
यह authorization भी नहीं है। जब तक आप $client_cn की जांच करके उसके मान (value) पर कार्रवाई नहीं करते, तब तक हर वैध certificate वाला व्यक्ति उस सर्वर ब्लॉक द्वारा परोसी जाने वाली हर चीज तक पहुंच सकता है। डिफ़ॉल्ट रूप से, दो certificate धारकों के पास समान पहुंच होती है।
यह केवल Nginx के माध्यम से आने वाले रास्ते की सुरक्षा करता है। यदि एप्लिकेशन किसी public port पर भी listen कर रहा है, तो उसके सामने mTLS केवल दिखावा है: एप्लिकेशन को 127.0.0.1 पर bind करें और उसके port पर firewall बंद रखें। उसी बॉक्स में प्रवेश करने का दूसरा रास्ता SSH है, और उसे भी समान ध्यान देने की आवश्यकता है, जिसे hardening SSH access on your VPS में कवर किया गया है।
एक अंतिम सीमा, जो इसे चालू करते ही सामने आती है। जो कुछ भी certificate प्रस्तुत नहीं कर सकता, वह काम करना बंद कर देता है: जैसे uptime monitor, payment provider से आने वाला webhook, RSS reader, या ऐसा mobile app जिसमें आप certificate store तक नहीं पहुंच सकते। ssl_verify_client on सेट करने से पहले इनके बारे में निर्णय लें, क्योंकि विफलता पूर्ण होती है और उनकी तरफ से कोई संकेत भी नहीं मिलता।
जब क्लाइंट को अस्वीकार किया जाता है, तो क्लाइंट की रिपोर्ट पढ़ें
अस्वीकृत क्लाइंट द्वारा दिखाया गया संदेश ब्राउज़र, curl के वर्ज़न और उसके नीचे काम करने वाली TLS लाइब्रेरी पर निर्भर करता है। इसलिए, कहीं और लिखे गए संदेश से मिलान करने के बजाय यह पढ़ें कि आपका अपना क्लाइंट क्या प्रिंट कर रहा है। उपयोगी विवरण सर्वर पर मौजूद होता है।
sudo tail -n 50 /var/log/nginx/error.logएक अस्वीकृत सर्टिफिकेट में एक लाइन होती है जिसमें client SSL certificate verify error होता है, जिसके बाद OpenSSL द्वारा दिया गया कारण लिखा होता है। वह कारण ही वह तथ्य है जिस पर आपको कार्रवाई करनी है। आमतौर पर यह कुछ चुनिंदा चीजों में से एक होता है। सर्टिफिकेट ssl_client_certificate में नामित फाइल से अलग CA से आया है। सर्टिफिकेट अपनी वैधता तिथियों (validity dates) से बाहर है। सर्वर पर मौजूद CRL अपनी nextUpdate पार कर चुका है, इसलिए अब यह किसी एक क्लाइंट के बजाय हर क्लाइंट को फेल कर देता है।
जब ब्राउज़र कोई सर्टिफिकेट ऑफर ही नहीं करता, तो समस्या वेरिफिकेशन से पहले की होती है। nginx हैंडशेक के दौरान स्वीकार्य issuer के नाम भेजता है, और ब्राउज़र को अपने स्टोर में ऐसा कुछ नहीं मिला जो मेल खाता हो, इसलिए उसके पास आपको ऑफर करने के लिए कुछ नहीं था। .p12 को उस प्रोफाइल में फिर से इम्पोर्ट करें जिसका उपयोग आप वास्तव में ब्राउज़िंग के लिए कर रहे हैं।
एक और मामला जिसका उल्लेख करना उचित है। यदि आपने किसी ऐसे सर्टिफिकेट के बजाय जो आपके CA द्वारा हस्ताक्षरित (signed) है, एक अकेले self-signed क्लाइंट सर्टिफिकेट के साथ परीक्षण किया है, तो वेरिफिकेशन पास नहीं हो सकता। इसका कारण यह है कि nginx CA फाइल के खिलाफ सिग्नेचर की जांच करता है और एक self-signed सर्टिफिकेट उसमें नहीं होता है। सर्टिफिकेट बनाने की प्रक्रिया वही है जो Ubuntu पर self-signed सर्टिफिकेट जनरेट करना में दी गई है। mTLS के लिए बस उस अतिरिक्त चरण की आवश्यकता होती है जहाँ आपका CA इसे साइन करता है।
FAQ
क्या mTLS का उपयोग करने पर भी मुझे Let's Encrypt certificate की आवश्यकता है?
हाँ। दोनों certificates का आपस में कोई संबंध नहीं है। आपका सर्वर अपना स्वयं का certificate प्रस्तुत करता है ताकि ब्राउज़र hostname पर भरोसा कर सके, और वह certificate अभी भी किसी ऐसे CA से आना चाहिए जिसे ब्राउज़र पहले से जानता हो। आपका client CA एक अलग private chain है, जिसका उपयोग केवल यह जाँचने के लिए किया जाता है कि कौन connect कर रहा है। ssl_client_certificate को set करने से nginx द्वारा प्रस्तुत certificate में कोई बदलाव नहीं आता है, और इसे आपके Let's Encrypt chain की ओर point नहीं करना चाहिए।
मेरा ब्राउज़र मुझसे certificate चुनने के लिए क्यों नहीं कहता?
nginx handshake के दौरान स्वीकार्य issuers की एक सूची भेजता है, जिसे ssl_client_certificate में मौजूद file से बनाया जाता है। ब्राउज़र केवल उन्हीं certificates को offer करता है जिनका issuer उस सूची में दिखाई देता है। इसलिए, prompt न आने का मतलब है कि ब्राउज़र के पास आपके CA का कोई certificate नहीं है: import किसी अलग browser profile में हुआ है, या certificate सर्वर पर installed CA के बजाय किसी अन्य CA द्वारा sign किया गया था। openssl s_client -connect admin.example.com:443 चलाएँ और output में स्वीकार्य client certificate CA नामों को देखें ताकि यह पता चल सके कि सर्वर वास्तव में किस CA के लिए पूछ रहा है।
क्या मैं केवल एक URL पर client certificate अनिवार्य कर सकता हूँ?
location के अंदर ssl_verify_client on के साथ ऐसा संभव नहीं है। certificate का आदान-प्रदान handshake के दौरान होता है, इससे पहले कि nginx को request path का पता चले, और वह renegotiation जो इसे हल कर सकता था, वह TLS 1.3 से हटा दिया गया है और HTTP/2 में प्रतिबंधित है। server block में ssl_verify_client optional; set करें, फिर प्रत्येक protected location में $ssl_client_verify का परीक्षण करें और जब यह SUCCESS न हो तो 403 return करें।
मैं किसी एक व्यक्ति के लिए access कैसे रद्द करूँ?
उस certificate को openssl ca -revoke के साथ revoke करें, openssl ca -gencrl के साथ सूची को पुन: उत्पन्न करें, इसे सर्वर पर copy करें, और nginx को reload करें ताकि वह नई file को पढ़ सके। बाकी सभी लोग अप्रभावित रहते हैं, जो केवल तभी काम करता है जब प्रत्येक व्यक्ति के पास साझा certificate के बजाय अपना स्वयं का certificate हो। CRL की nextUpdate date पर नज़र रखें, क्योंकि एक expired CRL केवल revoke किए गए clients के लिए ही नहीं, बल्कि हर client के लिए verification को विफल कर देता है।
क्या mTLS login page की जगह ले सकता है?
पहुँच के लिए, हाँ: certificate के बिना application तक कुछ भी नहीं पहुँचता, इसलिए हमला करने के लिए कोई form नहीं है और अनुमान लगाने के लिए कोई password नहीं है। application के भीतर पहचान के लिए, नहीं। एक certificate यह साबित करता है कि caller के पास key file है, इसलिए चोरी हुआ laptop एक वैध user है। CN को upstream पास करें, application के पास पहले से मौजूद accounts और permissions को बनाए रखें, और certificate को उनके सामने एक gate की तरह मानें।