Допустим нужно настроить mTLS для связи между двумя серверами, например для запросов с сайта на API сервер.
Шаг 1. CA
Допустим сервер с API будет основным, доступ к нему будет только с доверенных клиентов, на нем будем генерировать сертификаты.
Создадим директорию для сертификатов:
mkdir mtls-certs && cd mtls-certs
Генерируем главный ключ нашего CA (он нужен будет для выпуска всех новых сертификатов, после генерации которых, этот ключ нужно убрать с сервера в целях безопасности и хранить в шифрованном менеджере паролей, его также можно использовать в другом месте для выпуска сертификатов):
openssl genrsa -out ca.key 4096
Создаем корневой сертификат CA (на 10 лет):
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/CN=ixnfo.com-Private-CA"
Шаг 2. Сертификат для сервера
Для удобства сервер API будет разделен на два сервера разными доменами или IP адресами, один тестовый для разработчиков, второй production.
Ключ для тестового сервера API:
openssl genrsa -out test_server.key 2048
Запрос на сертификат (CSR):
openssl req -new -key test_server.key -out test_server.csr -subj "/CN=test.api"
Подписываем сертификат сервера нашим CA:
openssl x509 -req -days 3650 -in test_server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out test_server.crt -extfile <(echo "subjectAltName=IP:192.168.10.20")
Подобным образом создаем сертификат для production сервера.
Шаг 3. Сертификат для клиента
Аналогично будут тестовый и production клиенты, которые будут делать запросы к API серверу, тестовый клиент (сайт) к тестовому API, production клиент (сайт) к production API.
Ключ тестового клиента:
openssl genrsa -out test_client.key 2048
Запрос на сертификат:
openssl req -new -key test_client.key -out test_client.csr -subj "/CN=test.client"
Подписываем сертификат клиента:
openssl x509 -req -days 3650 -in test_client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out test_client.crt
Шаг 4: Настройка Nginx на сервере
В /etc/nginx/sites-available/ создадим две конфигурации Nginx для API теста и production, например с proxy_pass если API в docker:
server {
listen 192.168.10.20:443 ssl;
server_name 192.168.10.20;
ssl_certificate /etc/nginx/ssl/test_server.crt;
ssl_certificate_key /etc/nginx/ssl/test_server.key;
# mTLS
ssl_client_certificate /etc/nginx/ssl/ca.crt; # Who do we trust?
ssl_verify_client on;
location / {
# If the check fails, Nginx will return a 400 Bad Request.
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-SSL-Client-S-DN $ssl_client_s_dn;
}
}
Или так если API в /var/www/:
server {
listen 192.168.10.20:443 ssl;
server_name 192.168.10.20;
root /var/www/test;
index index.php index.html;
ssl_certificate /etc/nginx/ssl/test_server.crt;
ssl_certificate_key /etc/nginx/ssl/test_server.key;
# mTLS
ssl_client_certificate /etc/nginx/ssl/ca.crt;
ssl_verify_client on;
location / {
# If mTLS fails, we terminate the connection.
if ($ssl_client_verify != SUCCESS) {
return 403;
}
if ($ssl_client_s_dn !~ "CN=test.client") {
return 403;
}
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
# Passing certificate data to PHP (optional)
fastcgi_param SSL_CLIENT_S_DN $ssl_client_s_dn;
fastcgi_param SSL_CLIENT_VERIFY $ssl_client_verify;
}
# Disabling access to hidden files (.env, .git)
location ~ /\. {
deny all;
}
}
Как это использовать в коде (PHP пример):
$ch = curl_init("https://192.168.10.20/api");
curl_setopt($ch, CURLOPT_SSLCERT, "/path/to/test_client.crt");
curl_setopt($ch, CURLOPT_SSLKEY, "/path/to/test_client.key");
curl_setopt($ch, CURLOPT_CAINFO, "/path/to/ca.crt");
// If you use self-signed certificates, this is necessary to verify the hostname:
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // Because we are contacting by IP, not by domain.
$result = curl_exec($ch);
Файлы ключей, что на сервере, что у клиента должны иметь права 600 и принадлежать только тому пользователю, от которого запущен сайт/nginx.
Скопируем файлы test_client.crt, test_client.key и ca.crt на тестовый сайт и проверим связь обычным curl, сначала без сертификата, чтобы увидеть ошибку:
curl -k https://192.168.10.20/
Тест с mTLS сертификатами (выполним в директории с сертификатами):
curl --cacert ca.crt --cert test_client.crt --key test_client.key https://192.168.10.20/
Пример для сверки целостности файлов на серверах:
openssl x509 -noout -modulus -in test_client.crt | openssl md5
openssl rsa -noout -modulus -in test_client.key | openssl md5
Пример просмотра IP сервера при запросе:
echo | openssl s_client -connect 192.168.10.20:443 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
nginx -T | grep -E "server_name|ssl_certificate "
Я привел лишь пример настройки, по которому у вас будет успешно работать mTLS соединение, но смотря по ситуации, могут быть разные конфигурации, например можно улучшить конфигурацию nginx, добавить заголовки безопасности, ограничить доступ к порту 443 для доверенных клиентов, и т.д.
Смотрите также мою статью:
Настройка open_basedir