SSD Nodes Learn 🎉 VPS від $5.50/міс
Посібники Matt ConnorВід Matt Connor

mTLS у nginx: клієнтські сертифікати для захисту

Захистіть адмінпанель через mTLS: створіть приватний CA в openssl, видайте клієнтські сертифікати та налаштуйте nginx відхиляти запити без них.

Що робить mTLS

Взаємний TLS, зазвичай позначуваний як mTLS, змушує nginx запитувати сертифікат у кожного клієнта та відхиляти запит, якщо сертифікат відсутній або його не видано центром сертифікації (CA), яким ви керуєте. Перевірка відбувається під час рукостискання TLS (захист транспортного рівня), тому викликач із недійсним сертифікатом клієнта взагалі не досягає вашого застосунку. У цьому перевага: панель адміністратора або кінцева точка метрик може бути доступною в публічному інтернеті без сторінки входу та без даних, які бот міг би підбирати.

Конфігурація невелика. Один приватний CA, створений за допомогою openssl, один сертифікат для кожної особи та три директиви в блоці server nginx. Те, що визначає, чи працюватиме ця схема протягом року, належить до операційних процесів. Тому більша частина цього посібника присвячена термінам дії, відкликанню, сертифікатам для окремих осіб і діям у випадку, коли клієнту відмовлено, але ніхто не бачить причини.

Два ланцюжки, а не один

У схемі mTLS є два ланцюжки сертифікатів, і вони не пов’язані між собою. Об’єднати їх — перша помилка, якої припускається майже кожен.

Перший ланцюжок належить серверу. Ваш VPS пред’являє сертифікат для admin.example.com, виданий публічним CA, наприклад Let’s Encrypt, а браузер перевіряє його за сховищем кореневих сертифікатів, яке постачається з операційною системою. mTLS нічого не змінює в цій частині. Якщо certbot сьогодні видає цей сертифікат для вас, залиште його без змін: див. як видати сертифікат Let’s Encrypt для nginx за допомогою certbot.

Другий ланцюжок належить клієнту. Ви створюєте власний невеликий CA, підписуєте окремий сертифікат для кожної особи, якій потрібен доступ, і вказуєте nginx довіряти цьому CA та лише цьому CA під час перевірки клієнтів. Жодне публічне сховище кореневих сертифікатів не знає про ваш CA, і це не потрібно. Єдиний компонент, який має йому довіряти, — nginx, через файл ssl_client_certificate.

Отже, ssl_client_certificate ніколи не впливає на сертифікат, який пред’являє nginx, а ланцюжок Let’s Encrypt не впливає на те, яким клієнтам дозволено доступ. Вказати ssl_client_certificate на fullchain.pem — не означає те, що може здатися: ця директива визначає видавців, від яких може походити клієнтський сертифікат, тобто інший бік з’єднання. Налаштування довіри сервера до вашого CA для власних вихідних операцій — окреме завдання, описане в розділі додавання власного CA до сховища довіри Ubuntu. Системне сховище довіри — не те сховище, яке читає nginx під час перевірки клієнта.

Створення власного клієнтського CA за допомогою openssl

Створюйте CA не на вебсервері. nginx потрібен лише публічний сертифікат CA. Приватний ключ CA підписує нові клієнтські сертифікати, тому його зберігання на сервері, доступному з Інтернету, означає, що після одного злому зловмисник зможе за власним бажанням створювати дійсні клієнтські сертифікати для себе.

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. openssl ca відмовляється запускатися без них. Вони також забезпечують можливість відкликання сертифікатів у майбутньому, оскільки список відкликаних сертифікатів містить серійні номери. Тому CA має пам’ятати, кому передано кожен серійний номер.

Створіть ~/client-ca/openssl.cnf. Установіть dir як фактичний шлях до цього каталогу, оскільки openssl ca не розгортає ~.

[ 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 і його самопідписаний сертифікат:

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 парольною фразою, тому кожен запуск підписування запитує її. Саме для цього він потрібен. Перевірте створені файли:

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

У полі subject має бути зазначено ваш CA, а строк дії має становити десять років. Рядок розширення має мати значення CA:TRUE, pathlen:0. pathlen:0 означає, що цей CA може підписувати кінцеві сертифікати, але не може підписувати інший CA. Це обмежує ланцюжок рівно одним рівнем і дає змогу залишити 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.crt

openssl ca виводить сертифікат, який збирається підписати, запитує парольну фразу CA, двічі запитує підтвердження, а потім додає рядок до index.txt. Додайте -batch, якщо запускаєте це зі скрипту. Секція client_ext важлива через один рядок у ній: extendedKeyUsage = clientAuth. Сертифікат, у якому список extended key usage містить лише serverAuth, відхиляється як непридатний для клієнтської автентифікації. Тому явно вкажіть призначення сертифіката.

Перевірте пару ключа й сертифіката щодо CA, перш ніж передавати її:

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

Команда виводить certs/alice.crt: OK. Будь-який інший результат означає, що сертифікат і CA не відповідають один одному. Жодна конфігурація nginx не виправить цю проблему.

Об’єднайте ключ і сертифікат в один файл, який можна імпортувати в браузер:

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

Під час експорту команда запитує пароль. Він захищає файл під час передавання. Надсилайте файл і пароль різними каналами. Передавайте користувачам .p12, а не необроблений .key. Можна додати -certfile ca.crt, щоб включити CA до пакета, але nginx цього не потребує: nginx уже має ca.crt, тому сертифікат, безпосередньо підписаний цим CA, перевіряється самостійно.

OpenSSL 3, який постачається з Ubuntu 24.04, записує файли PKCS#12 із сучасним шифруванням. Браузери й операційні системи, що використовуються станом на August 2026, їх читають. Якщо старий імпортер відхиляє файл, повторно експортуйте його, додавши -legacy. Це повертає сумісність зі старішими алгоритмами, яких очікує цей імпортер. Спочатку прочитайте повідомлення імпортера і лише потім використовуйте цей прапорець.

Налаштуйте nginx за допомогою ssl_client_certificate і ssl_verify_client

Скопіюйте на сервер сертифікат CA і лише сертифікат CA.

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 є загальнодоступною інформацією. Ключ CA зберігається на вашій робочій станції.

Потім додайте три директиви до server block, який уже завершує 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 є значенням nginx за замовчуванням. Воно означає, що сертифікат клієнта має бути безпосередньо підписаний CA із цього файла. Підвищуйте це значення лише якщо додаєте проміжний сертифікат. nginx також надсилає клієнту під час handshake імена суб’єктів із ssl_client_certificate. Завдяки цьому браузер знає, які зі своїх сертифікатів запропонувати. Саме тому слід використовувати ssl_client_certificate, а не ssl_trusted_certificate. Вони виконують однакову перевірку, але ssl_trusted_certificate не надсилає цей список.

В Ubuntu 24.04 постачається nginx 1.24, де HTTP/2 вказують у рядку listen як listen 443 ssl http2;. У nginx 1.25.1 і новіших версіях цей формат застарілий, а HTTP/2 задають окремою директивою http2 on;. Жоден із варіантів не змінює перевірку сертифіката.

Перезавантажте конфігурацію та перегляньте результат:

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

nginx -t виводить syntax is ok і test is successful. Запит curl не містить сертифіката, тому у відповіді має бути 400 Bad Request з тілом No required SSL certificate was sent. Це означає, що nginx відхилив запит на власному рівні. Отже, конфігурація активна, а застосунок навіть не отримав запит. Тепер виконайте запит належним чином:

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

У відповіді має бути саме те, що повертає ваш застосунок.

Чому обмеження має бути в блоці server

Сертифікат передається під час TLS handshake, до того як nginx прочитає рядок запиту. Тому в цей момент nginx не знає, до якого location потрапить запит. Розміщення ssl_verify_client on; усередині location змушує клієнт повторно узгоджувати параметри посеред з’єднання. TLS 1.3 вилучив renegotiation, а HTTP/2 забороняє його. Тому в сучасному стеку така схема завершується помилкою, а не запитом сертифіката.

Обмеження потрібно налаштувати самостійно. Запитуйте сертифікат на рівні server, а потім визначайте поведінку для кожного 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, або NONE, якщо клієнт нічого не надіслав, або FAILED: із зазначенням причини. За допомогою optional nginx запитує сертифікат і перевіряє його лише за умови, що сертифікат надійшов. Саме це дає змогу залишити загальнодоступний шлях /healthz відкритим, а /metrics — закритим. Сертифікат, який надіслано, але який не пройшов перевірку, nginx у цей момент також відхиляє. Якщо ви хочете самостійно перевіряти сертифікат, що не пройшов перевірку, використовуйте optional_no_ca. Тоді власна перевірка має трактувати будь-яке значення, крім SUCCESS, як відмову.

nginx використовує для цього нестандартні коди стану, а error_page може перехоплювати їх, щоб відвідувач, якому відмовлено, отримав пояснення, а не просто код 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 означає, що сертифікат клієнта не пройшов перевірку. 496 означає, що клієнт не надіслав сертифікат. Ця сторінка має містити лише звичайний текст, оскільки користувач, який її читає, не має сеансу й облікового запису.

Як встановити клієнтський сертифікат у браузері?

Firefox використовує власне сховище сертифікатів: відкрийте Settings, потім Privacy and Security, View Certificates, вкладку Your Certificates, натисніть Import, виберіть файл .p12 і введіть його пароль.

Chrome і Edge у Windows та macOS використовують сховище операційної системи, тому відкриття файлу .p12 запускає системний майстер імпорту. У Linux Chrome читає окрему базу даних NSS (network security services) у вашому домашньому каталозі. Надійніше скористатися командним рядком:

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

Після цього відкрийте сайт. Браузер запитає, який сертифікат потрібно надіслати. Chrome запам’ятовує цей вибір до завершення поточного сеансу браузера. Щоб браузер знову запитав сертифікат, перезапустіть його. Сертифікат зберігається в одному профілі браузера на одному комп’ютері. Тому сертифікат, імпортований у Firefox, недоступний для Chrome. Обидва браузери також не мають доступу до сертифіката на вашому телефоні.

Тестування за допомогою curl --cert

Використовуйте curl для налагодження, оскільки він показує виконані дії.

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

Сертифікат і ключ можна об’єднати в один PEM-файл і передати його через --cert alice.pem. Якщо ключ захищений парольною фразою, curl запитає її. Також можна використати --cert alice.pem:passphrase, але тоді парольна фраза залишиться в історії shell, тому виберіть запит.

Перш ніж звинувачувати nginx, варто виконати дві перевірки. По-перше, сертифікат і ключ мають бути парою:

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

Однакові хеші означають, що файли належать одній парі. Різні хеші означають, що ви переплутали файли, і жоден клієнт не повідомить вам про цю причину.

По-друге, сервер має запитувати сертифікат вашого CA:

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

Знайдіть у виводі блок Acceptable client certificate CA names і subject вашого CA всередині нього. Якщо цього блоку немає взагалі, nginx не запитує сертифікат у server block, який обробив запит. Отже, ваші директиви потрапили в інший server block, часто в default server.

Передавання CN клієнта до застосунку

Сертифікат містить дані про того, хто виконав виклик, але застосунок за проксі не бачить TLS-рівень. Тому nginx має передати це ім’я далі.

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

$ssl_client_s_dn містить distinguished name суб’єкта у форматі RFC 2253. Він має вигляд CN=alice,O=Example Ops. Директива map витягує поле CN у $client_cn. Значення CN має бути простим іменем користувача, оскільки кома всередині CN екранується в цьому форматі, а наведений вище короткий регулярний вираз не обробляє таке екранування.

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 замінює будь-який заголовок із таким іменем, переданий клієнтом. Тому через цей location неможливо підробити X-Client-Cert-CN. Це забезпечують дві умови. nginx успадковує proxy_set_header із зовнішнього рівня лише тоді, коли внутрішній рівень не визначає власних значень. Тому другий location з одним рядком proxy_set_header непомітно втрачає всі заголовки, задані вище, зокрема цей. Застосунок також має бути недоступним в обхід nginx. Для цього його слід прив’язати до 127.0.0.1, а не до 0.0.0.0, оскільки застосунок на публічному порту прочитає підроблений заголовок безпосередньо з інтернету. Налаштування проксі розглянуто в конфігурації зворотного проксі nginx з поясненням кожного рядка. Якщо застосунку потрібен увесь сертифікат, $ssl_client_escaped_cert передає його в URL-кодованому вигляді, безпечному для використання в заголовку.

Як відкликати один сертифікат клієнта?

Хтось залишає компанію або зникає ноутбук. Відкличте сертифікат лише для цього клієнта, а всі інші продовжать працювати. Саме для цього кожному користувачу видають окремий сертифікат.

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

Перша команда змінює рядок із цим серійним номером у index.txt: значення V замінюється на R. Друга команда записує список відкликаних сертифікатів (CRL) — підписаний файл із серійними номерами відкликаних сертифікатів. Передайте цей файл на сервер і вкажіть його в nginx за допомогою ssl_crl /etc/nginx/client-ca.crl; поруч з іншими директивами.

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'

Ось нюанс, через який доступ буде заблоковано для всіх. CRL містить дату nextUpdate, яку задає default_crl_days; у наведеній конфігурації це 30. Після цієї дати OpenSSL вважає список застарілим і завершує перевірку кожного сертифіката клієнта з помилкою CRL has expired, а не лише відкликаного сертифіката. nginx читає файл під час завантаження конфігурації, тому оновлення CRL на диску нічого не змінить до перезавантаження конфігурації. Створюйте новий CRL і виконуйте reload за розкладом із запасом до завершення цього періоду: щотижня для періоду 30 днів. Перед копіюванням перевіряйте дати:

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

Для невеликої кількості користувачів є простіший варіант. CA належить вам, тому nginx може безпосередньо відхиляти серійний номер і не використовувати механізм CRL:

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

Поєднайте це з if ($revoked) { return 403; } у location. Такий підхід не має строку дії, про який можна забути. Але це правило не поширюється на інші системи, тому будь-який інший сервіс, що довіряє вашому CA, нічого про нього не знає. Якщо перед одним застосунком працює один nginx, це просте й коректне рішення. Переходьте на CRL, коли шлюзів стане більше одного.

Який термін дії мають мати клієнтські сертифікати?

Встановлюйте для клієнтських сертифікатів термін дії один рік або менше, якщо ви готові частіше виконувати повторну видачу. Тут основною непомітною причиною збою є завершення терміну дії, оскільки власник не отримує про це попередження. Вранці він відкриває панель, nginx відхиляє з’єднання, а браузер описує відмову власними словами, які рідко містять слово «прострочений». Встановіть для CA термін дії десять років і запишіть дату його завершення там, де ви справді її перевірятимете, оскільки після завершення терміну дії сертифіката CA всі сертифікати, видані ним, перестануть проходити перевірку того самого дня.

Виконуйте ці дві команди, щоб завчасно виявляти проблему:

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

У першому стовпці index.txt міститься статус: V для чинного сертифіката, R для відкликаного, E для простроченого. У другому стовпці вказано дату завершення терміну дії у форматі YYMMDDHHMMSSZ, а в четвертому — серійний номер. Цей файл є єдиним записом про те, кому належить кожен сертифікат, тому створюйте його резервну копію разом із ключем CA і зберігайте обидва як секрети.

Поновлення — це створення нового сертифіката, а не продовження терміну дії наявного. Створіть новий ключ і CSR (запит на підпис сертифіката), підпишіть його, передайте користувачеві, а потім відкличте старий сертифікат, коли користувач підтвердить, що новий працює.

Від чого захищає mTLS, а від чого — ні

Воно усуває неавтентифікований доступ. Сканер, який знаходить ваше hostname, отримує відмову під час handshake. Тому він не надсилає HTTP-запит, не бачить форму входу й не може перевірити вкрадений пароль. Credential stuffing втрачає сенс. Вразливість у потоці входу застосунку недоступна всім, хто не має сертифіката. Також зникає потреба в спільному секреті, який люди пересилають у чаті, оскільки приватний ключ — це файл, який складно випадково скопіювати.

Від скомпрометованого клієнта це не захищає. Шкідливе ПЗ на ноутбуці має файл ключа, а щойно власник вводить passphrase — і саму passphrase. Для сервера такий зловмисник виглядає так само, як легітимний користувач, оскільки сертифікат підтверджує наявність файлу, а не присутність людини. Пароль .p12 і повне шифрування диска й надалі мають значення.

Це також не авторизація. Кожен дійсний сертифікат отримує доступ до всього, що обслуговує цей server block, якщо не перевірити $client_cn і не обробити його значення. За замовчуванням два власники сертифікатів мають однаковий доступ.

Захист діє лише для шляху через nginx. Якщо застосунок також слухає публічний порт, mTLS перед ним є лише формальністю: прив’яжіть застосунок до 127.0.0.1 і закрийте його порт у firewall. Інший шлях до того самого сервера — SSH, і він потребує такої самої уваги. Це описано в зміцненні доступу SSH до вашого VPS.

Є ще одне обмеження, яке проявляється в день увімкнення mTLS. Усе, що не може надати сертифікат, припиняє працювати: uptime monitor, webhook від платіжного провайдера, RSS reader або мобільний застосунок без доступного сховища сертифікатів. Визначтеся з такими випадками до налаштування ssl_verify_client on, оскільки збій буде повним і на їхньому боці — непомітним.

Коли клієнту відмовлено в підключенні, прочитайте, що повідомляє сам клієнт

Повідомлення клієнта про відмову залежить від браузера, версії curl і базової TLS-бібліотеки. Тому читайте виведення саме вашого клієнта, а не порівнюйте його з повідомленням, записаним десь в іншому місці. Потрібні подробиці містяться на сервері.

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

Відхилений сертифікат залишає рядок, що містить client SSL certificate verify error, а після нього — причину, яку повідомив OpenSSL. Саме на цю причину потрібно реагувати. Зазвичай причин небагато. Сертифікат виданий іншим CA, ніж той, що вказаний у файлі в ssl_client_certificate. Сертифікат має термін дії, що вже минув або ще не почався. CRL на сервері досяг свого nextUpdate, тому тепер перевірка не проходить для жодного клієнта, а не лише для одного.

Якщо браузер взагалі не надсилає сертифікат, проблема виникає ще до перевірки. Під час рукостискання nginx надсилає список прийнятних імен видавців. Браузер не знайшов у своєму сховищі відповідного сертифіката, тому йому не було що надсилати. Імпортуйте .p12 ще раз у профіль, з якого ви насправді переглядаєте сторінки.

Варто згадати ще один випадок. Якщо для перевірки ви використали окремий самопідписаний сертифікат клієнта, а не сертифікат, підписаний вашим CA, перевірка не пройде. nginx перевіряє підпис за файлом CA, а самопідписаного сертифіката в ньому немає. Механізм створення сертифіката такий самий, як у створенні самопідписаного сертифіката в Ubuntu. Для mTLS потрібен лише додатковий крок, на якому ваш CA підписує цей сертифікат.

FAQ

Чи потрібен мені сертифікат Let's Encrypt, якщо я використовую mTLS?

Так. Ці два сертифікати не пов’язані між собою. Сервер передає власний сертифікат, щоб браузер довіряв імені хоста, і цей сертифікат все одно має походити від CA, відомого браузеру. Ваш клієнтський CA — це окремий приватний ланцюжок, який використовують лише для перевірки того, хто підключається. Налаштування ssl_client_certificate не змінює сертифікат, який передає nginx, і воно не має вказувати на ваш ланцюжок Let's Encrypt.

Чому браузер ніколи не просить мене вибрати сертифікат?

Під час рукостискання nginx надсилає список прийнятних видавців, сформований на основі файлу в ssl_client_certificate. Браузер пропонує лише сертифікати, видавці яких є в цьому списку. Отже, відсутність запиту означає, що браузер не має сертифіката від вашого CA: імпорт виконано в іншому профілі браузера або сертифікат підписано іншим CA, ніж той, який встановлено на сервері. Виконайте openssl s_client -connect admin.example.com:443 і знайдіть у виведенні назви CA прийнятних клієнтських сертифікатів, щоб побачити, який саме CA запитує сервер.

Чи можна вимагати клієнтський сертифікат лише для одного URL?

Ні, якщо використовувати ssl_verify_client on всередині location. Сертифікат передається під час рукостискання, до того як nginx дізнається шлях запиту, а повторне узгодження, яке могло б це обійти, вилучено з TLS 1.3 і заборонено в HTTP/2. Установіть ssl_verify_client optional; у блоці server, а потім у кожному захищеному location перевіряйте $ssl_client_verify і повертайте 403, якщо його значення не дорівнює SUCCESS.

Як відкликати доступ для однієї особи?

Відкличте цей сертифікат за допомогою openssl ca -revoke, повторно створіть список за допомогою openssl ca -gencrl, скопіюйте його на сервер і перезавантажте nginx, щоб він прочитав новий файл. Інші користувачі не постраждають. Це працює лише за умови, що кожна особа має власний сертифікат, а не спільний. Перевіряйте дату nextUpdate CRL, оскільки прострочений CRL порушує перевірку для всіх клієнтів, а не лише для відкликаних.

Чи замінює mTLS сторінку входу?

Для доступу до застосунку — так: без сертифіката запит узагалі не потрапляє до застосунку, тому немає форми для атаки й пароля, який можна підбирати. Для ідентифікації всередині застосунку — ні. Сертифікат доводить, що викликач має файл із ключем, тому викрадений ноутбук дає змогу автентифікуватися як дійсний користувач. Передавайте CN на upstream, зберігайте наявні облікові записи та дозволи застосунку й розглядайте сертифікат як шлюз перед ними.

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