Привет уважаемые, хабровчане!

Иногда возникает потребность выдать доступ пользователям только к одному веб ресурсу в компании. Самый очевидный вариант сделать это через урезанный VPN, но тут возникают препятствия в виде девайсов, с которых подключаются пользователи, и качество интернета, которым они пользуются. OpenVPN, который мы пробовали использовать для этих целей, не дал желаемого результата, подключение было медленным и нестабильным.

В поисках более стабильного решения для нашей проблемы мы наткнулись на статью Авторизация с помощью сертификата ssl на nginx + Let's Encrypt.

Попробовали реализовать ее на нашей инфре и столкнулись с проблемой, что сертификаты для пользователей создаются, но при попытке подключиться к целевому веб ресурсу, созданный сертификат не проходит аутентификацию и падает с 404 ошибкой.

После анализа обнаружили проблему, что алгоритм шифрования который используется в статье, не подходит для нашей конфигурации nginx 1.18.0 + Ubuntu 20.04.4 LTS + Let`s Encrypt, он считается небезопасным. Решением этой ситуации было использование шифрования на эллиптических кривых.

Собственно сама настройка

  1. Перед началом настройки стоит создать следующую структуру конфиг-файлов:

Лучше ограничить доступ к папке с конфигами, чтобы кто угодно не мог сгенерировать сертификат, поломать настроенный функционал

mkdir /etc/nginx/example.com #создаем папку, в которой будут лежать наши кофиги и сертификаты
cd /etc/nginx/example.com #переходим в нее
mkdir users_certs certs newcerts #создаем необходимые поддиректории
touch index.txt root.config serial #создаем необходимые кофиг-файлы
echo "01" > serial #записываем первое значение в serial от него система начнет отсчет номеров сертификатов
chmod 700 ./
Итоговая файловая структура
Итоговая файловая структура
  1. Создаем собственный самоподписанный доверенный сертификат (в командах отмечены поля которые нужно заполнить), с алгоритмом шифрования на эллиптических кривых:

openssl ecparam -out /etc/nginx/example.com/certs/root.key -name secp384r1 -genkey
openssl req -new -x509 -days 500 -sha512 -key /etc/nginx/example.com/certs/root.key -subj /O={Название вашей компании}/emailAddress={email создателя сертификата} -out /etc/nginx/example.com/certs/root.crt
  1. В nginx добавляем строки с корневым сертификатом для агентов:

Строки добавляются в конфигурацию nginx целевого ресурса, которая обычно лежит в папке /etc/nginx/sites‑enabled/имя_веб_ресурса.
Данные добавляются в раздел server, где слушается 443 https порт.

ssl_client_certificate /etc/nginx/example.com/certs/root.crt; #путь до корневого сертификата 
ssl_verify_client on;
keepalive_timeout 70;
#ниже необязательные параметры, можно их не указывать
fastcgi_param SSL_VERIFIED $ssl_client_verify; 
fastcgi_param SSL_CLIENT_SERIAL $ssl_client_serial;
fastcgi_param SSL_CLIENT_CERT $ssl_client_cert;
fastcgi_param SSL_DN $ssl_client_s_dn;
  1. В папке /etc/nginx/ssl/ открываем root.config, созданный ранее с необходимыми параметрами для автоматического выпуска сертификатов:

[ ca ]
        default_ca             = CA_CLIENT       
        
        
        [ CA_CLIENT ]
        dir                    = /etc/nginx/example.com/            
        certs                  = /etc/nginx/example.com/certs      
        new_certs_dir          = /etc/nginx/example.com/newcerts   

        database               = $dir/index.txt  
        
        serial                 = $dir/serial     
        
        
        certificate            = $dir/certs/root.crt        
        private_key            = $dir/certs/root.key        

        default_days           = 365 #срок действия пользовательских сертификатов            
        
        default_crl_days       = 7               
        default_md             = md5             

        policy                 = policy_anything 
        
        

        [ policy_anything ]
        countryName            = optional        
        stateOrProvinceName    = optional        
        localityName           = optional        
        organizationName       = optional        
        organizationalUnitName = supplied        
        commonName             = supplied        
        emailAddress           = supplied
  1. На этом настройка аутентификации закончена.

Создание пользовательского сертификата

  1. Создаем закрытый ключ пользователя (нужно заполнить параметры в скобках):

openssl ecparam -out /etc/nginx/example.com/users_certs/{email пользователя}.key -name secp384r1 -genkey
  1. Создаем CSR сертификат пользователя (нужно заполнить параметры в скобках):

openssl req -new -key /etc/nginx/example.com/users_certs/{email пользователя}.key -subj /C={аббревиатура вашей страны}/ST={название вашего региона}/L={название вашего города}/OU={имя фамилия пользователя}/CN={название компании}/emailAddress={email пользователя} -out /etc/nginx/example.com/users_certs/{email пользователя}.csr
  1. Создаем CRT сертификат пользователя (нужно заполнить параметры в скобках):

openssl ca -config /etc/nginx/example.com/root.config -in /etc/nginx/example.com/users_certs/{email пользователя}.csr -out /etc/nginx/example.com/users_certs/{email пользователя}.crt -batch
  1. Создаем P12 сертификат, который непосредственно будем передавать пользователю ( (нужно заполнить параметры в скобках и задать пароль, который будет использоваться для установки сертификата пользователем):

Если не переживаете, что кто-то случайно удалит сертификаты пользователей, то вывод можно настроить сразу в общую папку, из которой будете их забирать по SFTP. Для этого можете подкорректировать следующий параметр:

-out /etc/nginx/example.com/agent/{email пользователя}.p12

openssl pkcs12 -export -clcerts -in /etc/nginx/example.com/users_certs/{email пользователя}.crt -inkey /etc/nginx/example.com/users_certs/{email пользователя}.key -out /etc/nginx/example.com/users_certs/{email пользователя}.p12 -passout pass:{пароль который хотите задать для сертификата}
  1. Готово. Теперь подключаемся к серверу любым SFTP клиентом и скачиваем сертификат с расширением .p12 и передаем его и пароль пользователю.

Чтобы было проще вот весь этот функционал в одном bash-скрипте, который нужно положить в директорию /etc/nginx/example.com:

#!/bin/bash


echo "Please, enter user name:"
read A 

Q=$(grep -rn $A index.txt | awk '{print $5}' | awk -F/ '{print $6}' | sed  's/OU=//g')

echo "Please, enter user email:"
read B

echo "Please, enter key pass:"
read P

if [[ "$A" = "$Q" ]]
then
echo "this user is already exist"
else 
openssl ecparam -out /etc/nginx/example.com/users_certs/$B.key -name secp384r1 -genkey &&

openssl req -new -key /etc/nginx/example.com/users_certs/{email пользователя}.key -subj /C={аббревиатура вашей страны}/ST={название вашего региона}/L={название вашего города}/OU={имя фамилия пользователя}/CN={название компании}/emailAddress={email пользователя} -out /etc/nginx/example.com/users_certs/{email пользователя}.csr &&

openssl ca -config /etc/nginx/example.com/root.config -in /etc/nginx/example.com/users_certs/$B.csr -out /etc/nginx/example.com/users_certs/$B.crt -batch &&

openssl pkcs12 -export -clcerts -in /etc/nginx/example.com/users_certs/$B.crt -inkey /etc/nginx/example.com/users_certs/$B.key -out /etc/nginx/example.com/users_certs/$B.p12 -passout pass:$P

fi

В итоге схема создания сертификата выглядит так:

Установка сертификата

Покажу на примере Google Chrome

  1. Переходим по следующим скриншотам и добавляем сертификат:

  1. Вводим пароль и нажимаем ОК:

  1. Переходим на целевой сайт, выбираем нужный сертификат в выпадающем окне и нажимаем ОК:

  1. Проверяем что все открылось:

Удаление сертификата

  1. Удалите созданные 4 сертификата пользователя из папки /etc/nginx/example.com/users_certs.

  2. Удалите строчку пользователя из /etc/nginx/example.com/index.txt.

  3. Удалите последний номер из файла /etc/nginx/example.com/serial.

Эти действия тоже можно зашить в скрипт, но данный функционал я не реализовывал.

Итог

Аутентификация готова, главное не забывать отзывать сертификаты, если пользователю они больше не нужны.

Комментарии (11)


  1. dronmaxman
    21.09.2023 19:55
    +2

    Где проверка списка отзыва сертификатов ?


    1. nikshav Автор
      21.09.2023 19:55

      Согласен, этот функционал здесь не отразил


  1. AlexGluck
    21.09.2023 19:55

    Быстрый способ запустить локально нджинкс:
    docker run --name nginx -d --rm -v $PWD:/etc/letsencrypt/live/test-mtls.example.com:Z -v $PWD/nginx.conf:/etc/nginx/nginx.conf:Z --network host nginx

    Быстрый способ проверить курлом из консоли:
    curl -vvv --resolve test-mtls.example.com:8443:127.0.0.1 --http1.1 -E user.crt --key decrypted.key.pem "https://test-mtls.example.com:8443/"


  1. kirb
    21.09.2023 19:55
    +1

    Вы не предусмотрели возможность отзыва сертификатов. И по-хорошему, ключи нужно генерить пользователям самим, а не администратору.


    1. nikshav Автор
      21.09.2023 19:55
      +1

      Отзыв есть в конце статьи, но он костылный, согласен, что нужно сделать изящней.

      Для кейса, который был в моем опыте, генерировать стоит администратору, поскольку конечные пользователи недостаточно технически подкованы и были требования безопасности, по которым пользователь не должен иметь возможности генерации сертификатов, поскольку в таком случае любой человек может сделать доступ к системе, если у него есть исходные данные для генерации сертификата


      1. kirb
        21.09.2023 19:55

        Если Вы про раздел "Удаление сертификата", то это не отзыв, это вы просто из "базы CA" сертификат удалили, а для nginx, как и для любого кто будет проверять сертификат он останется валидным. Отзыв нужно через CRL/OCSP реализовывать.


  1. saipr
    21.09.2023 19:55
    +2

    Аутентификация готова, главное не забывать отзывать сертификаты, если пользователю они больше не нужны.

    Но самое главное не забывать отзывать сертификаты пользователя, если был скомпрометирован (в том числе и утерян носитель закрытого ключа) закрытый ключ сертификата.


  1. anzay911
    21.09.2023 19:55

    Во времена пользования webmoney, помню, проходишь по специальной ссылке, и тебе устанавливается твой личный сертификат прямо в браузер.


  1. mmaks17
    21.09.2023 19:55

    а для мобильных клиентов работает ? когда я последний раз пробовал были проблемы


    1. AlexGluck
      21.09.2023 19:55

      Работает, проверено андройд 11,12,13 браузеры хром,фарефокс.


    1. nikshav Автор
      21.09.2023 19:55
      +1

      Да, у нас коллеги ставили как на смартфоны, так и на планшеты