Настройка mTLS

Допустим нужно настроить 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

Оставьте комментарий

Добавить комментарий