SSD Nodes Learn 🎉 VPS $5.50/মাস থেকে
নির্দেশিকা Matt Connorদ্বারা Matt Connor

nginx-এ mTLS দিয়ে client certificate চালু করার নিয়ম

openssl দিয়ে private CA ও client certificate তৈরি করে nginx admin panel সুরক্ষিত করুন। certificate না থাকলে nginx কীভাবে request প্রত্যাখ্যান করে, তা দেখুন।

mTLS কী করে

Mutual TLS, সাধারণত mTLS নামে লেখা হয়, nginx-কে প্রতিটি client-এর কাছে certificate চাইতে বাধ্য করে। certificate অনুপস্থিত হলে, অথবা আপনার নিয়ন্ত্রণাধীন কোনো certificate authority (CA) certificate-টি issue না করলে nginx request প্রত্যাখ্যান করে। এই পরীক্ষা TLS (transport layer security) handshake-এর মধ্যেই হয়। তাই বৈধ client certificate ছাড়া কোনো caller আপনার application পর্যন্ত পৌঁছাতে পারে না। এটাই এর সুবিধা: একটি admin panel বা metrics endpoint public internet-এ login page ছাড়াই রাখা যায়, এবং কোনো bot-এর অনুমান করার মতো কিছু থাকে না।

এই setup ছোট। openssl দিয়ে তৈরি একটি private CA, প্রতিজনের জন্য একটি certificate, এবং nginx server block-এ তিনটি directive। এটি এক বছর পরেও কার্যকর থাকবে কি না, তা operational কাজের ওপর নির্ভর করে। তাই এই guide-এর বেশিরভাগ অংশে certificate-এর lifetime, revocation, ব্যক্তি-ভিত্তিক certificate এবং কোনো client প্রত্যাখ্যাত হলে অথচ কারণ দেখা না গেলে কী করতে হবে—এসব আলোচনা করা হয়েছে।

একটি নয়, দুটি chain

mTLS সেটআপে দুটি certificate chain থাকে এবং এগুলোর মধ্যে কোনো সম্পর্ক নেই। এগুলোকে একত্র করা প্রায় সবাই যে প্রথম ভুলটি করে।

প্রথম chain-টি server-এর। আপনার VPS একটি public CA, যেমন Let's Encrypt, কর্তৃক জারি করা admin.example.com-এর জন্য একটি certificate উপস্থাপন করে। Browser operating system-এর সঙ্গে থাকা root store-এর ভিত্তিতে এটি যাচাই করে। mTLS এই অংশে কোনো পরিবর্তন আনে না। certbot আজ আপনার জন্য certificate জারি করলে সেটিকে ঠিক একইভাবে রাখুন: certbot দিয়ে nginx-এর জন্য একটি Let's Encrypt certificate জারি করা দেখুন।

দ্বিতীয় chain-টি client-এর। আপনি নিজস্ব একটি ছোট CA তৈরি করবেন, যাদের প্রবেশাধিকার প্রয়োজন তাদের প্রত্যেকের জন্য একটি করে certificate sign করবেন, এবং client যাচাই করার সময় nginx-কে সেই CA-টিকেই শুধু trust করতে বলবেন। কোনো public root store আপনার CA-কে চেনে না এবং তা চেনার প্রয়োজনও নেই। একমাত্র nginx-কেই ssl_client_certificate file-এর মাধ্যমে এটিকে trust করতে হবে।

তাই ssl_client_certificate nginx যে certificate উপস্থাপন করে, তাতে কখনো প্রভাব ফেলে না। একইভাবে, Let's Encrypt chain কোন client-কে প্রবেশের অনুমতি দেওয়া হবে, তাতেও প্রভাব ফেলে না। ssl_client_certificate-কে fullchain.pem-এর দিকে নির্দেশ করলে দেখতে যেমন মনে হয়, তেমন কাজ হয় না। ওই directive নির্ধারণ করে একটি client certificate কোন issuer থেকে আসতে পারে। এটি connection-এর অপর প্রান্তের বিষয়। nginx-এর নিজস্ব outbound কাজের জন্য আপনার CA-কে trust করানো আলাদা কাজ। এটি Ubuntu trust store-এ আপনার নিজস্ব CA যোগ করা অংশে ব্যাখ্যা করা হয়েছে। Client যাচাই করার সময় nginx system trust store পড়ে না।

openssl দিয়ে নিজের client CA তৈরি করুন

web server-এর বাইরে অন্য একটি স্থানে CA তৈরি করুন। nginx-এর শুধু CA-এর public certificate প্রয়োজন। CA private key নতুন client certificate-এ স্বাক্ষর করে। তাই এটি Internet-facing box-এ রাখলে একটি break-in-ই আক্রমণকারীকে ইচ্ছামতো নিজের জন্য বৈধ client তৈরি করার ক্ষমতা দিতে পারে।

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 এবং crlnumber হলো CA database। এগুলো ছাড়া openssl ca চালু হয় না। পরে certificate revocation সম্ভব করার ক্ষেত্রেও এগুলোর ভূমিকা আছে। কারণ revocation list-এ serial number উল্লেখ থাকে। তাই কোন serial number কোন ব্যক্তিকে দেওয়া হয়েছে, CA-কে তা মনে রাখতে হয়।

~/client-ca/openssl.cnf লিখুন। dir-এ ওই directory-এর প্রকৃত path সেট করুন, কারণ 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 চালালে passphrase চাওয়া হবে। এটাই এর উদ্দেশ্য। তৈরি করা জিনিস যাচাই করুন:

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

subject-এ আপনার CA-এর নাম থাকা উচিত এবং validity দশ বছর হওয়া উচিত। extension line-এ CA:TRUE, pathlen:0 লেখা থাকা উচিত। pathlen:0 বোঝায়, এই CA end certificate-এ স্বাক্ষর করতে পারবে, কিন্তু অন্য কোনো CA-তে স্বাক্ষর করতে পারবে না। এতে chain ঠিক এক স্তর গভীর থাকে এবং ssl_verify_depth অপরিবর্তিত রাখা যায়।

প্রতি ব্যক্তির জন্য একটি client certificate ইস্যু করুন

প্রতি ব্যক্তির জন্য একটি certificate ব্যবহার করুন। একটি team-এর জন্য কখনো একটি shared certificate ব্যবহার করবেন না। কারণ shared certificate revoke করলে সবাই access থেকে বঞ্চিত হবে। এ ছাড়া কে request পাঠিয়েছে, সেটিও বোঝা যাবে না।

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 sign করার আগে যে certificate তৈরি করতে যাচ্ছে তা দেখায়, CA passphrase চায়, দুবার confirmation চায়, তারপর index.txt-এ একটি line যোগ করে। Script-এ ব্যবহার করলে -batch যোগ করুন। client_ext section গুরুত্বপূর্ণ, কারণ এতে একটি line আছে: extendedKeyUsage = clientAuth। শুধু serverAuth-এর তালিকা থাকা extended key usage-সহ certificate client authentication-এর জন্য উপযুক্ত নয় বলে প্রত্যাখ্যাত হয়। তাই অনুমান না করে certificate-এর উদ্দেশ্য স্পষ্টভাবে উল্লেখ করুন।

কাউকে দেওয়ার আগে CA-এর সঙ্গে certificate ও key-এর জোড়াটি যাচাই করুন:

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

এটি certs/alice.crt: OK দেখায়। অন্য কোনো output-এর অর্থ হলো certificate এবং CA একে অপরের সঙ্গে মেলে না। কোনো nginx configuration দিয়েই এই সমস্যা সমাধান করা যাবে না।

একটি file-এ key এবং certificate একত্র করুন, যাতে browser সেটি import করতে পারে:

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

Export করার সময় একটি password চাওয়া হয়। এটি স্থানান্তরের সময় file-টিকে সুরক্ষিত রাখে। File এবং password আলাদা channel-এ পাঠান। কাউকে bare .key না দিয়ে .p12 দিন। CA-কে bundle-এ অন্তর্ভুক্ত করতে -certfile ca.crt যোগ করতে পারেন। তবে nginx-এর এটি প্রয়োজন নেই। nginx-এর কাছে ইতিমধ্যেই ca.crt আছে। তাই ওই CA দ্বারা সরাসরি sign করা certificate নিজেই verify হয়।

Ubuntu 24.04-এর সঙ্গে আসা OpenSSL 3 বর্তমান encryption ব্যবহার করে PKCS#12 file তৈরি করে। August 2026 পর্যন্ত ব্যবহৃত browser এবং operating system-গুলো এগুলো পড়তে পারে। কোনো পুরোনো importer file-টি গ্রহণ না করলে -legacy যোগ করে আবার export করুন। এতে importer যে পুরোনো algorithm প্রত্যাশা করে, সেটি ব্যবহার করা হবে। ওই flag ব্যবহার করার আগে importer-এর দেওয়া message পড়ুন।

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'

এখানে Mode 644 সঠিক। CA certificate public information। CA key আপনার workstation-এ থাকবে।

এরপর যে server block-এ TLS termination হয়, সেখানে তিনটি directive যোগ করুন:

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-এর default। এটি নির্ধারণ করে যে client certificate সরাসরি ওই ফাইলে থাকা CA দ্বারা signed হতে হবে। Intermediate যোগ করলে তবেই এটি বাড়ান। Handshake-এর সময় nginx ssl_client_certificate থেকে subject name-গুলোও client-কে পাঠায়। এর ফলে browser বুঝতে পারে, তার কোন certificate offer করতে হবে। এই আচরণের জন্য ssl_client_certificate ব্যবহার করুন, ssl_trusted_certificate নয়। উভয়টি একইভাবে verification করে, তবে ssl_trusted_certificate কোনো তালিকা পাঠায় না।

Ubuntu 24.04-এ nginx 1.24 থাকে। এই version-এ HTTP/2 listen line-এ listen 443 ssl http2; হিসেবে দেওয়া হয়। nginx 1.25.1 এবং পরবর্তী version-এ এই form deprecated। সেখানে HTTP/2-এর জন্য আলাদা directive, http2 on;, ব্যবহার করতে হয়। কোনোটিই certificate check পরিবর্তন করে না।

Reload করে ফলাফল দেখুন:

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

nginx -t, syntax is ok এবং test is successful প্রদর্শন করে। curl call-এ কোনো certificate নেই। তাই এটি 400 Bad Request status এবং No required SSL certificate was sent body নিয়ে ফিরে আসার কথা। nginx নিজস্ব gate-এ request প্রত্যাখ্যান করছে। এর অর্থ configuration কার্যকর হয়েছে এবং application-কে কোনো request পাঠানো হয়নি। এবার সঠিকভাবে চেষ্টা করুন:

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

এতে আপনার application যে response দেয়, সেটিই ফেরত আসার কথা।

সার্ভার ব্লকে gate রাখার কারণ

TLS handshake-এর সময় certificate বিনিময় হয়। nginx request line পড়ার আগেই এটি ঘটে। তাই সেই মুহূর্তে অনুরোধটি কোন location-এ যাবে, nginx তা জানে না। কোনো location-এর ভিতরে ssl_verify_client on; রাখলে client-কে connection চলাকালীন পুনরায় negotiation করতে বলা হয়। TLS 1.3 renegotiation সরিয়ে দিয়েছে এবং HTTP/2 এটি নিষিদ্ধ করে। তাই বর্তমান stack-এ এই পদ্ধতি prompt দেখানোর পরিবর্তে ব্যর্থ হয়।

Scope নিজেই নির্ধারণ করুন। server level-এ 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 চায় এবং certificate এলে তবেই সেটি যাচাই করে। এর ফলে উপরের public /healthz path চালু রাখা যায়, কিন্তু /metrics বন্ধ থাকে। Client certificate পাঠানো হলেও verification ব্যর্থ হলে nginx সেই পর্যায়েই তা প্রত্যাখ্যান করে। ব্যর্থ certificate নিজে পরীক্ষা করতে চাইলে সেটি optional_no_ca। সে ক্ষেত্রে আপনার test-এ SUCCESS ছাড়া অন্য প্রতিটি মানকে প্রত্যাখ্যান হিসেবে গণ্য করতে হবে।

nginx এ জন্য non-standard status code ব্যবহার করে। error_page এগুলো ধরে রাখতে পারে, যাতে প্রত্যাখ্যাত visitor শুধু bare 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-টি সাধারণ text হিসেবে রাখুন, কারণ এটি পড়ছেন এমন ব্যক্তির কোনো session বা account নেই।

ব্রাউজারে client certificate কীভাবে ইনস্টল করব?

Firefox নিজস্ব certificate store ব্যবহার করে: Settings, তারপর Privacy and Security, তারপর View Certificates, এরপর Your Certificates tab, তারপর Import নির্বাচন করুন। এরপর .p12 ফাইলটি বেছে নিয়ে তার password দিন।

Windows এবং macOS-এ Chrome ও Edge operating system store ব্যবহার করে। তাই .p12 ফাইলটি খুললে 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-টি খুললে browser কোন certificate পাঠাবে তা জানতে চায়। Chrome browser session শেষ না হওয়া পর্যন্ত এই নির্বাচন মনে রাখে। তাই আবার নির্বাচন করতে বলানোর জন্য browser restart করুন। Certificate একটি মেশিনের একটি browser profile-এ সংরক্ষিত থাকে। তাই Firefox-এ import করা certificate Chrome-এ দেখা যায় না। একইভাবে, দুটির কোনোটিতেই আপনার phone-এ থাকা certificate দেখা যায় না।

curl --cert দিয়ে পরীক্ষা

curl দিয়ে ডিবাগ করুন, কারণ এটি কী করেছে তা জানায়।

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

দুটি অভিন্ন hash হলে file দুটি পরস্পরের সঙ্গে সম্পর্কিত। দুটি ভিন্ন hash হলে আপনি দুই ব্যক্তির file গুলিয়ে ফেলেছেন। কোনো client এই কারণটি আপনার হয়ে নির্দিষ্ট করে জানাবে না।

দ্বিতীয়ত, server-এর আপনার CA চাওয়া উচিত:

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

output-এ Acceptable client certificate CA names block এবং তার ভেতরে আপনার CA-এর subject খুঁজুন। পুরো block-টি অনুপস্থিত হলে যে server block উত্তর দিয়েছে, nginx সেখানে certificate চাইছে না। অর্থাৎ আপনার directive অন্য কোনো server block-এ প্রয়োগ হয়েছে, যা প্রায়ই default server হয়।

অ্যাপ্লিকেশনে client CN পাঠানো

Certificate-এ কে অনুরোধ করেছে তা উল্লেখ থাকে। কিন্তু proxy-এর পেছনের application TLS layer দেখতে পারে না। তাই nginx-কে সেই নামটি application-এ পাঠাতে হবে।

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

$ssl_client_s_dn RFC 2253 format-এ subject distinguished name ধারণ করে। এটি CN=alice,O=Example Ops-এর মতো দেখায়। map directive-টি CN field-কে $client_cn-এ নিয়ে আসে। CN-এ সাধারণ username ব্যবহার করুন। কারণ ওই format-এ CN-এর ভেতরের comma 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 caller পাঠানো একই নামের যেকোনো header প্রতিস্থাপন করে। তাই এই location-এর মাধ্যমে কেউ X-Client-Cert-CN জাল করতে পারে না। এটি নিশ্চিত রাখতে দুটি শর্ত প্রযোজ্য। nginx কেবল তখনই বাইরের স্তর থেকে proxy_set_header inherit করে, যখন ভেতরের স্তরে নিজস্ব কোনো proxy_set_header সংজ্ঞায়িত থাকে না। ফলে দ্বিতীয় কোনো location-এ একটি proxy_set_header line থাকলে তার ওপরের স্তরে সেট করা সব header, এটিও, নীরবে হারিয়ে যায়। এছাড়া application-এ nginx ছাড়া অন্য কোনো পথ থেকে পৌঁছানো যাবে না। তাই application-কে 0.0.0.0-এর বদলে 127.0.0.1-এ bind করতে হবে। Public port-এ চলা application Internet থেকে সরাসরি জাল header পড়বে। Proxy-সংক্রান্ত অংশটি nginx reverse proxy configuration-এর line-by-line ব্যাখ্যায় দেখানো হয়েছে। Application-এর নামের বদলে পুরো certificate প্রয়োজন হলে $ssl_client_escaped_cert সেটিকে URL-encoded করে header-এর ভেতরে নিরাপদভাবে বহন করে।

একটি 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-এর status V থেকে R-এ পরিবর্তন করে। দ্বিতীয়টি একটি certificate revocation list (CRL) লিখে, যা revoked serial number-গুলোর নাম থাকা signed file। এটি deploy করুন এবং অন্য directive-গুলোর পাশে ssl_crl /etc/nginx/client-ca.crl; দিয়ে nginx-কে ওই file দেখান।

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'

এখানে এমন একটি সমস্যা আছে, যা সবার access বন্ধ করে দিতে পারে। CRL-এ একটি nextUpdate date থাকে, যা default_crl_days নির্ধারণ করে। উপরের configuration-এ এটি 30। ওই date পার হয়ে গেলে OpenSSL list-টিকে stale হিসেবে গণ্য করে এবং CRL has expired-সহ প্রতিটি client certificate-এর verification ব্যর্থ করে। শুধু revoked certificate-এর verification ব্যর্থ হয় না। nginx configuration load করার সময় file পড়ে। তাই disk-এ নতুন CRL রাখলেই পরিবর্তন কার্যকর হয় না; reload করতে হয়। এমন schedule-এ regenerate ও reload করুন, যাতে এই window-এর অনেক আগেই কাজটি সম্পন্ন হয়। 30 দিনের window হলে weekly schedule ব্যবহার করুন। Copy করার আগে date পরীক্ষা করুন:

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

অল্প কয়েকজন user-এর ক্ষেত্রে আরও ছোট একটি বিকল্প আছে। CA আপনার নিয়ন্ত্রণে, তাই nginx সরাসরি কোনো serial প্রত্যাখ্যান করতে পারে এবং CRL ব্যবস্থাপনা বাদ দিতে পারে:

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

Location-এ এর সঙ্গে if ($revoked) { return 403; } ব্যবহার করুন। এর কোনো expiry date নেই, যা ভুলে যাওয়ার আশঙ্কা থাকে। তবে এটি অন্য কোথাও প্রযোজ্য হয় না। আপনার CA-কে trust করা অন্য কোনো system এ সম্পর্কে কিছু জানবে না। একটি application-এর সামনে একটি nginx থাকলে এটিই সরল ও যথাযথ সমাধান। একাধিক gate হলে CRL ব্যবহার করুন।

ক্লায়েন্ট certificate কতদিন কার্যকর রাখা উচিত?

ক্লায়েন্ট certificate এক বছর কার্যকর রাখুন। পুনরায় issue করার কাজ সামলাতে পারলে এর চেয়েও কম সময় নির্ধারণ করুন। এখানে নীরব ব্যর্থতার কারণ হলো expiry। certificate holder-কে আগে থেকে কোনো সতর্কতা দেওয়া হয় না। এক সকালে তিনি panel খুললে nginx connection প্রত্যাখ্যান করে। browser নিজের ভাষায় সেই প্রত্যাখ্যানের কারণ দেখায়, যেখানে expired শব্দটি সাধারণত থাকে না। CA দশ বছরের জন্য কার্যকর রাখুন এবং এর expiry date এমন জায়গায় লিখে রাখুন যা আপনি নিয়মিত দেখবেন। কারণ CA certificate-এর মেয়াদ শেষ হলে এর অধীনে থাকা সব certificate একই দিনে verification ব্যর্থ করবে।

নিচের দুটি command ব্যবহার করলে আপনি এই বিষয়টি আগেই শনাক্ত করতে পারবেন:

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

index.txt-এর প্রথম column-এ status থাকে: বৈধ certificate-এর জন্য V, revoked certificate-এর জন্য R, এবং expired certificate-এর জন্য E। দ্বিতীয় column-এ YYMMDDHHMMSSZ format-এ expiry date থাকে এবং চতুর্থ column-এ serial থাকে। এই file-টিই কার কাছে কোন certificate আছে তার একমাত্র record। তাই CA key-এর সঙ্গে এটিও backup করুন এবং উভয়কেই secret হিসেবে সংরক্ষণ করুন।

Renewal হলো নতুন certificate তৈরি করা; এটি পুরোনো certificate-এর মেয়াদ বাড়ানো নয়। নতুন key এবং CSR (certificate signing request) তৈরি করুন, certificate sign করুন, সংশ্লিষ্ট ব্যক্তির কাছে হস্তান্তর করুন, তারপর তিনি নতুন certificate কাজ করছে নিশ্চিত করলে পুরোনো certificate revoke করুন।

mTLS কী থেকে সুরক্ষা দেয় এবং কী থেকে দেয় না

এটি যে বিষয়টি দূর করে, তা হলো authentication ছাড়া প্রবেশের সুযোগ। কোনো scanner আপনার hostname খুঁজে পেলেও handshake-এর সময় প্রত্যাখ্যাত হবে। ফলে এটি কখনো HTTP request পাঠাতে, login form দেখতে বা চুরি করা password দিয়ে login করার চেষ্টা করতে পারবে না। Credential stuffing করার মতো কোনো credential-ও থাকবে না। Certificate ছাড়া কোনো ব্যক্তি application-এর login flow-এর vulnerability-তে পৌঁছাতে পারবে না। এটি chat-এ কপি-পেস্ট করা shared secret-এর প্রয়োজনও দূর করে, কারণ private key এমন একটি file যা ভুল করে কপি করা সহজ নয়।

Compromised client-এর ক্ষেত্রে এটি কিছুই করে না। কোনো laptop-এ থাকা malware-এর কাছে key file থাকে। মালিক passphrase লিখলেই সেটিও malware-এর কাছে চলে যায়। server-এর কাছে সেই attacker-কে বৈধ user-এর মতোই দেখায়, কারণ certificate কোনো ব্যক্তির উপস্থিতি নয়, একটি file দখলে থাকার প্রমাণ দেয়। .p12 password এবং full disk encryption-এর গুরুত্ব তাই এখনও রয়েছে।

এটি authorization-ও নয়। $client_cn পরীক্ষা করে সেই মানের ভিত্তিতে ব্যবস্থা না নিলে, বৈধ প্রতিটি certificate ওই server block যে সবকিছু serve করে, সেগুলোতে পৌঁছাতে পারে। Default হিসেবে দুই certificate holder-এর access একই থাকে।

এটি শুধু nginx-এর মধ্য দিয়ে যাওয়া পথটি সুরক্ষিত করে। Application যদি public port-এও listen করে, তাহলে তার সামনে mTLS রাখা শুধু আনুষ্ঠানিকতা। Application-কে 127.0.0.1-এ bind করুন এবং তার port-এ firewall বন্ধ রাখুন। একই box-এ ঢোকার অন্য পথ হলো SSH। সেটিকেও একই গুরুত্ব দিতে হবে। এ বিষয়ে আপনার VPS-এ SSH access hardening করা অংশে আলোচনা করা হয়েছে।

আরেকটি সীমাবদ্ধতা আছে, যা mTLS চালু করার দিনই সমস্যায় ফেলতে পারে। যে কোনো কিছু certificate উপস্থাপন করতে না পারলে কাজ বন্ধ করবে। এর মধ্যে uptime monitor, payment provider-এর webhook, RSS reader এবং এমন mobile app রয়েছে যার certificate store-এ আপনার প্রবেশাধিকার নেই। ssl_verify_client on সেট করার আগে এগুলোর বিষয়ে সিদ্ধান্ত নিন। কারণ ব্যর্থতা সম্পূর্ণ হবে এবং তাদের দিক থেকে নীরব থাকবে।

কোনো client প্রত্যাখ্যাত হলে client যে বার্তা দেখায় তা পড়ুন

প্রত্যাখ্যাত client যে বার্তা দেখায়, তা browser, curl-এর version এবং নিচে ব্যবহৃত TLS library-এর ওপর নির্ভর করে। তাই অন্য কোথাও লেখা বার্তার সঙ্গে মিলিয়ে দেখার বদলে আপনার নিজের client কী দেখাচ্ছে তা পড়ুন। প্রয়োজনীয় তথ্য server-এ থাকে।

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

প্রত্যাখ্যাত certificate-এর ক্ষেত্রে client SSL certificate verify error-যুক্ত একটি line দেখা যায়, যার পরে OpenSSL প্রদত্ত কারণ থাকে। কাজের জন্য এই কারণটিই বিবেচনা করুন। সাধারণত কারণ কয়েকটির একটিই হয়। ssl_client_certificate-তে উল্লেখ করা file-এর চেয়ে certificate-টি অন্য CA থেকে এসেছে। Certificate-এর validity date-এর বাইরে রয়েছে। Server-এর CRL তার nextUpdate অতিক্রম করেছে। ফলে এটি শুধু একটি client নয়, সব client-এর ক্ষেত্রেই ব্যর্থ হচ্ছে।

Browser যদি কখনো certificate পাঠাতেই না চায়, তাহলে সমস্যা verification-এর আগের ধাপে রয়েছে। Handshake-এর সময় nginx গ্রহণযোগ্য issuer-এর নাম পাঠায়। Browser তার store-এ মিলে যায় এমন কিছু পায়নি, তাই পাঠানোর মতো কোনো certificate ছিল না। আপনি যে profile দিয়ে সত্যিই browse করছেন, সেই profile-এ .p12 আবার import করুন।

আরেকটি পরিস্থিতি উল্লেখ করা দরকার। CA-স্বাক্ষরিত client certificate-এর পরিবর্তে যদি একটি একক self-signed client certificate দিয়ে পরীক্ষা করে থাকেন, verification সফল হবে না। কারণ nginx CA file-এর বিরুদ্ধে signature যাচাই করে, আর self-signed certificate সেখানে থাকে না। Certificate তৈরির পদ্ধতি Ubuntu-তে self-signed certificate তৈরি করা অংশের মতোই। mTLS-এর জন্য শুধু অতিরিক্ত একটি ধাপ দরকার, যেখানে আপনার CA certificate-টিতে signature দেয়।

FAQ

mTLS ব্যবহার করলে কি আমার এখনও Let's Encrypt certificate প্রয়োজন?

হ্যাঁ। দুটি certificate-এর মধ্যে কোনো সম্পর্ক নেই। browser যাতে hostname-কে বিশ্বাস করে, সে জন্য server নিজের certificate উপস্থাপন করে। সেই certificate এমন একটি CA থেকে আসতে হবে, যেটিকে browser আগে থেকেই চেনে। আপনার client CA একটি আলাদা private chain, যা শুধু কে সংযোগ করছে তা যাচাই করতে ব্যবহৃত হয়। ssl_client_certificate সেট করলে nginx যে certificate উপস্থাপন করে তাতে কোনো পরিবর্তন হয় না। এটি কখনও আপনার Let's Encrypt chain-কে নির্দেশ করা উচিত নয়।

আমার browser কখনও certificate বেছে নিতে বলে না কেন?

handshake-এর সময় nginx গ্রহণযোগ্য issuer-এর একটি তালিকা পাঠায়। এই তালিকাটি ssl_client_certificate-এর file থেকে তৈরি হয়। browser কেবল সেই certificate-গুলোই প্রস্তাব করে, যেগুলোর issuer ওই তালিকায় আছে। তাই কোনো prompt না দেখানোর অর্থ হলো browser-এ আপনার CA-এর কোনো certificate নেই। Certificate অন্য browser profile-এ import করা হয়েছে, অথবা server-এ install করা CA-এর বদলে অন্য CA দিয়ে certificate-টি sign করা হয়েছে। openssl s_client -connect admin.example.com:443 চালান এবং output-এ গ্রহণযোগ্য client certificate CA name খুঁজুন। এতে বোঝা যাবে server আসলে কোন CA চাইছে।

আমি কি শুধু একটি URL-এ client certificate বাধ্যতামূলক করতে পারি?

একটি location-এর ভিতরে ssl_verify_client on ব্যবহার করে তা করা যায় না। handshake-এর সময় certificate আদান-প্রদান হয়। তখন nginx এখনও request path জানে না। এই সীমাবদ্ধতা কাটাতে যে renegotiation প্রয়োজন, TLS 1.3-এ তা বাদ দেওয়া হয়েছে এবং HTTP/2-এ নিষিদ্ধ। server block-এ ssl_verify_client optional; সেট করুন। এরপর প্রতিটি সুরক্ষিত location-এ $ssl_client_verify পরীক্ষা করুন এবং সেটি SUCCESS না হলে 403 return করুন।

একজন ব্যক্তির access কীভাবে বাতিল করব?

openssl ca -revoke দিয়ে ওই certificate revoke করুন। openssl ca -gencrl দিয়ে তালিকাটি আবার তৈরি করুন। এটি server-এ copy করুন এবং nginx reload করুন, যাতে nginx নতুন file পড়ে। অন্য সবার access অপরিবর্তিত থাকবে। তবে এটি তখনই সম্ভব, যখন প্রত্যেক ব্যক্তির নিজস্ব certificate থাকবে, shared certificate নয়। CRL-এর nextUpdate date পর্যবেক্ষণ করুন। CRL-এর মেয়াদ শেষ হলে শুধু revoked client নয়, সব client-এর verification ব্যর্থ হয়।

mTLS কি login page-এর বিকল্প?

Application-এ পৌঁছানোর দিক থেকে হ্যাঁ। Certificate ছাড়া কোনো request application-এ পৌঁছায় না। তাই attack করার মতো form থাকে না এবং অনুমান করার মতো password-ও থাকে না। তবে application-এর ভেতরের identity-এর ক্ষেত্রে উত্তরটি না। Certificate প্রমাণ করে যে caller-এর কাছে একটি key file আছে। তাই চুরি হওয়া laptop-ও একটি বৈধ user হিসেবে গণ্য হবে। CN upstream-এ পাঠান, application-এর বিদ্যমান account ও permission ব্যবস্থা বজায় রাখুন, এবং certificate-কে সেই ব্যবস্থার সামনে থাকা gate হিসেবে বিবেচনা করুন।

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