Авторизация с помощью сертификата ssl на nginx + Let's Encrypt

Добрый день, вечер или ночь, все зависит от времени суток в который вам довелось прочитать мою статью.

В связи с ростом количества корпоративных клиентов, было принято решение дать доступ к учетной системе внешним пользователям. Для самостоятельного оформления заказов и отслеживания их состояний. Реализация была создан web интерфейс с необходимым функционалом и доступом. Тут же стал вопрос безопасности, кроме стандартных пользователь-пароль было решено еще усилить безопасность, для этого применили OpenVPN, но появились клиенты, для которых нельзя применять OpenVPN (политики безопасности, нежелания и.д.), тут на глаза попались статьи про доступ по ssl сертификату.



Исходные данные:

Сервер с учетной программой + web интерфейс находятся в DMZ;
WEB-server на nginx, на него проброшены порты http(80) и https(443);
На web-server настроен proxy_pass на сервер с учетной программой, доступ только по порту 8080 и только с IP web-server, большего доступа с серверу нет(обычная безопасность);
На сайт для доступа установлен сертификат от Let's Encrypt.

Переходим к самому процессу создания сертификата пользователя:

Для сертификатов будем использовать каталог "/etc/ssl/crm.example.ru"

Создаём структуру каталогов:

# mkdir /etc/ssl/crm.example.ru
# cd /etc/ssl/crm.example.ru
# mkdir db
# mkdir db/certs
# mkdir db/newcerts
# touch db/index.txt
# echo "01" > db/serial
# chmod 700 ./

Создаем конфигурационный файл для подписи сертификатов.

/etc/ssl/crm.example.ru/ca.conf"
[ ca ]
default_ca             		= CA_CITENAME          	# Секция по умолчанию для подписи сертификатов

[ CA_CITENAME ]
droot                  		= /etc/ssl/crm.example.ru # Корневой каталог хранилища
dir                    		= $droot/db            	# Каталог базы хранилища
certs                  		= $dir/certs           	# Каталог сертификатов
new_certs_dir          		= $dir/newcerts        	# Каталог для новых сертификатов (pem)

database               		= $dir/index.txt       	# Файл базы сертификатов
serial                 		= $dir/serial          	# Файл серийного номера

# Файл доверенного сертификата
certificate            		= $droot/ca.crt
# Закрытый ключ доверенного сертификата
private_key            		= $droot/ca.key

default_days           		= 365                  	# Срок действия нового сертификата (дни)
default_crl_days       		= 7                    	# Срок действия списка отозванных сертификатов
default_md             		= md5                  	# Использовать алгоритм MD5

policy                 		= policy_citename      	# Политика секции

[ policy_citename ]
countryName            		= optional             	# Необязательный параметр
stateOrProvinceName    		= optional             	# .......................
localityName           		= optional             	# .......................
organizationName       		= optional             	# .......................
organizationalUnitName 		= optional             	# .......................
commonName             		= supplied             	# обязательный параметр
emailAddress           		= supplied             	# .....................

[ req_distinguished_name ]
countryName                     = Название страны (2-буквенный код)
countryName_default             = RU
countryName_min                 = 2
countryName_max                 = 2

stateOrProvinceName             = Название области (полное название)
stateOrProvinceName_default     = Tyumen region

localityName                    = Название местности (например, город)
localityName_default            = Tyumen

0.organizationName              = Название организации
0.organizationName_default      = EXAMPLE

organizationalUnitName          = Название организационной единицы (например, отдел)

commonName                      = Ваше имя
commonName_max                  = 64

emailAddress                    = Email адрес
emailAddress_max                = 64

Создаем самоподписанный сертификат и новый ключ сервера без пароля:

# openssl req -new -newkey rsa:2048 -nodes -keyout ca.key -x509 -days 365 \
-subj "/C=RU/ST=Tyumen region/L=Tyumen/O=EXAMPLE/OU=CRM/CN=crm.example.ru/emailAddress=crm@example.ru" \
-out ca.crt

Либо, если хотите всё вводить вручную.

# openssl req -new -newkey rsa:2048 -nodes -keyout ca.key -x509 -days 365 -out ca.crt

Просмотреть данные закрытого ключа и сертификата вы можете с помощью команд:

# openssl rsa -noout -text -in ca.key              (для ключа)
# openssl x509 -noout -text -in ca.crt             (для сертификата)

Создание клиентского закрытого ключа и запроса на сертификат (CSR):

# openssl req -new -newkey rsa:2048 -nodes -keyout client01.key \
-subj "/C=RU/ST=Tyumen region/L=Tyumen/O=EXAMPLE/OU=CRM/CN=User example1/emailAddress=user@example1.ru" \
-out client01.csr

Либо, если хотите всё вводить вручную.

#openssl req -new -newkey rsa:2048 -nodes -keyout client01.key -out client01.csr

Заместо User example1 можно указать почту клиента, а за место EXAMPLE компанию клиента, это поможет отслеживать сертификаты.

В результате выполнения команды появятся два файла client01.key и client01.csr. Просмотреть данные закрытого ключа и запроса на сертификат (CSR) вы можете с помощью команд:
# openssl rsa -noout -text -in client01.key             (для ключа)
# openssl req -noout -text -in client01.csr             (для запроса)


Подпись запроса на сертификат (CSR) с помощью доверенного сертификата (CA). При подписи запроса используются параметры заданные в файле ca.config

# openssl ca -config ca.config -in client01.csr -out client01.crt -batch

Подготовка данных для передачи клиенту. Для передачи полученных в результате предыдущих операций файлов клиенту, обычно используется файл в формате PKCS#12. В этот файл упаковывается и защищается паролем вся информация необходимая клиенту для инсталляции сертификата в броузер.

# openssl pkcs12 -export -in client01.crt -inkey client01.key \
-certfile ca.crt -out client01.p12 -passout pass:123ewqasdcxz

Выставляем права доступа на ключи.

# chmod 600 /etc/ssl/crm.example.ru/client*.crt
# chmod 600 /etc/ssl/crm.example.ru/client*.key

Переместим все созданные файлы в каталог db/certs на хранение.

# mv ./client01.* db/certs/

В nginx надо установить:

            ssl_client_certificate      /etc/ssl/crm.example.ru/ca.crt;
            ssl_verify_client           on;
            ssl_verify_depth            1;

Для того чтобы клиент смог подключиться по сертификату ему необходимо отправить файл client01.p12 и ca.crt, а так же сообщить пароль для установки сертификата. ca.crt необходим, так как мы не используем его для сертификации сервера, для этомо используеться Let's Encrypt.

Процесс выдачи сертификатов можно автоматизировать, написать просто скрипт не составит труда. У нас таких клиентов не много, около 15, так что вбить всё руками не составило проблем.

Мой рабочий пример:

Окно выбора сертификата:



Сам сертификат:



Работоспособность Let's Encrypt:



В подготовке материала помогли статьи:

«Авторизация клиентов в nginx посредством SSL сертификатов»
«Авторизация по SSL сертификатам»
«Авторизация с помощью клиентских SSL сертификатов. (ssl crypt mod_ssl apache)»
«Великий и могучий Google»

P.S. Проверка проводилась на Google Chrome.
Поделиться публикацией

Похожие публикации

Комментарии 17
    0
    Автор, вопрос. Если nginx в качестве фронтенда, то как сделать проксирование, чтобы по сертификату авторизовались на бэкенде (apache)?
      0
      Ответ на ваш вопрос: Github Gist
        0
        Такой задачи не было, поэтому не занимался данным вопросом
          0
          Я тоже планирую реализовать подобную аутентификацию.
          Хочу заметить, что метод автора для генерации *.p12 файла включает в результирующий файл самоподписанный CA сертификат. По хорошему, клиентам нет необходимости устанавливать (а иногда и нет возможности) «левый» CA в систему, который для нужд аутентификации не требуется.

          Чтобы сгенерировать файл для передачи клиенту не содержащий информацию о родительском самоподписанном сертификате, нужно выполнить следующую команду:
          openssl pkcs12 -export -in client01.crt -inkey client01.key -clcerts \
          -out client01.p12 -passout pass:PASSWORD_HERE
          

          В результате получаем файлик, который клиент/пользователь может установить сам. Для винды достаточно дважды кликнуть и далее-далее-далее-готово.

          UPD:
          Так же хочу дополнить что данная команда выдаст хоть и зашифрованный .p12 файл, но этот файл будет зашифрован слабыми алгоритмами вроде 40-битного RC2 и 3DES(для приватного ключа). Такой файл нельзя передавать по открытым каналам.
          Я не являюсь спецом в ИБ, но как мне кажется, лучше явно указать чтобы приватный ключ был зашифрован другим алгоритмом, например, AES256. Сделать это можно с помощью параметра командной строки -aes256
            0

            UPD2: Ключ -aes256 не даст желаемого результата.
            Помимо этого, в формате PKCS#12 ограниченная поддержка алгоритмов шифрования: вики.
            Теоретически, через флаги -certpbe aes-256-cbc -keypbe aes-256-cbc можно заставить openssl шифровать сертификат и ключ с помощью aes. Но винда данные файлы поддерживать не будет, увы :(

          0
          Для упрощения работы с openssl есть коллекция скриптов github.com/OpenVPN/easy-rsa
          Но вы, навероное, об это уже знали)
            0
            Я так понимаю на location авторизацию повесить нельзя?
              0
              Да, нельзя. В документации nginx указан контекст: http, server. location нету.
              +1
              Создание клиентского закрытого ключа и запроса на сертификат (CSR):

              Почему из мануала в мануал тянется генерация закрытого ключа клиента? Этот ключ должен быть только у клиента и никого более.
                0
                Смотря какова цель данного действия. Выше в комментариях автор пишет, что сертификаты используются лишь для предварительной авторизации. То есть, чтобы back end сервер не сканировали все боты мира, его закрыли за прокси-сервером с пре-авторизацией.

                Создание ключа на клиентском компьютере даёт следующие выгоды:
                1. Нет необходимости передавать ключ по безопасному каналу
                2. Есть возможность аутентификации пользователя по сертификату

                Пункт №2 автору не нужен.
                Вместо пункта №1 можно обезопасить канал передачи ключа. Во многих случаях это достаточно просто: можно передать ключ непосредственно пользователю при визите в офис. Есть ещё вариант с пересылкой запароленного ключа по e-mail, а (сложный) пароль передать, например, по телефону. Конечно, есть теоретическая вероятность перехвата обоих каналов, но что получит атакующий — возможность просканировать back end сервер?

                Всегда полезно делать анализ угроз и понимать, как и для чего мы собираемся использовать некий инструмент, и что нам грозит в случае взлома. Выдать пользователю инструкцию по генерации ключа и отсылке вам запроса не всегда возможно — слишком сложные действия на которые у пользователя может не быть прав. Установить готовый ключ — несколько кликов.
                  0
                  появились клиенты, для которых нельзя применять OpenVPN (политики безопасности, нежелания и.д.),

                  Если у клиента такая политика безопасности, то маловероятно, что им подойдет вариант, когда их ключ у кого-то еще есть.
                  Мне самому в свое время пришлось разбираться с похожей схемой с авторизацией по ключам. И junior-админу предыдущими админами просто была вручена инструкция «генеришь ключ, отдаешь клиенту». И тут вдруг банк сказал «ключ генерим мы сами», и у этого junior'а внезапно все встало.
                  Ну и еще если вдруг что-то сломается, клиент всегда может сказать «ну и что что моя запись в логах, ключ-то у вас, вы сами все и сломали».
                  Выдать пользователю инструкцию по генерации ключа и отсылке вам запроса не всегда возможно — слишком сложные действия на которые у пользователя может не быть прав.
                  Если нет прав, значит у пользователя есть админы. Раз есть админы, значит они должны знать, что у пользователя есть какая-то кастомная схема с авторизацией по ключам. Потому что когда у пользователя что-то сломается, он пойдет к своим админам в первую очередь, а тем придется ковыряться по всей цепочке.
                    0
                    Вы предполагаете, что у автора статьи ничего не работает, и он всё выдумал? Таких «их ключей» от вашего самоподписанного центра вы и без ведома клиента можете сгенерировать миллионы. Если клиенты довольны и угрозы безопасности нет, то в чём дело?

                    Ещё раз: аутентификация по ключу не производится, только пре-авторизация. Задача обеспечить plausible deniability по ключу у автора статьи не стоит. Если нужна p.d. — автоматическая смена пароля при первом заходе снимет вопрос (насколько вообще можно снять этот вопрос в условиях наличия контроля над серверной инфраструктурой).

                    Типовая ситуация в крупных компаниях: у пользователя нет и не может быть прав администратора. Скорее всего, скрипт, присланный по e-mail, запустить тоже не удастся. То есть ни mmc.exe не запустить, ни с certreq автоматически никаких манипуляций не сделать. А дабл-клик на .pfx никаких прав не требует и обрабатывается rundll32.exe, которая даже при наличии белого списка, скорее всего, в нём будет.

                    В компании из 100 человек можно «пойти к админам». В крупном банке на любые операции с правами администратора будете год получать разрешение от всех отделов безопасности по цепочке.
                      0
                      Я добавлю один момент, почему ключи делаем сами. У большинства клиентов, которым выдаем ключи, договор без пролонгации, а порой и вообще сроком на 2-3 мес. Менеджеры часто забивают забывают об этом, но когда доступ у клиента резко обрывается им напоминают)) При запросе ключа, обязательно запрашиваем на какой срок необходим сертификат.
                        0
                        Если клиенты довольны и угрозы безопасности нет, то в чём дело?
                        Клиент не понимает, что он делает/получает, поэтому он и доволен.
                        В крупном банке на любые операции с правами администратора будете год получать разрешение от всех отделов безопасности по цепочке.
                        А если не пошли и не получили разрешения, то получать по шапке позже, когда все эти отделы выяснят, что что-то тут сделано без их разрешения.
                        Да и не нужны админские права для генерации ключа в большинстве случаев.

                        Еще раз — есть корректный способ (генерация ключа у клиента); есть не очень корректный (генерим сами). Тот, кто будет читать эту статью должен сам решать, как же ему делать. И это стоило отобразить в статье
                          0
                          Можно ссылку на авторитетный источник, где один из двух способов обозначен как единственно «корректный», а другой, как, соответственно «некорректный»?

                          Я вам пояснил, почему нет разницы с точки зрения безопасности в заданных условиях между этими двумя способами. Вы вчистую проигнорировали всё обсуждение по сути, настаиваете на надуманной «корректности» одного из них.

                          Да и не нужны админские права для генерации ключа в большинстве случаев.

                          Если вы знаете способ, как без прав администратора, запуска присланных по e-mail скриптов, сторонних программ и набирания команд в консоли сгенерировать запрос к стороннему удостоверяющему центру, стоит написать об этом статью на Хабр, многим будет полезно.
                  0
                  Спасибо, что затронули эту тему. Конечно, до двухфакторной аутентификации такой схеме ещё далеко, но, насколько я сталкивался, многие вообще не в курсе возможности всем браузерам прозрачно и просто аутентифицироваться/авторизоваться по сертификату.

                  Для того чтобы клиент смог подключиться по сертификату ему необходимо отправить файл client01.p12 и ca.crt, а так же сообщить пароль для установки сертификата. ca.crt необходим, так как мы не используем его для сертификации сервера, для этомо используеться Let's Encrypt.

                  И что вы предлагаете клиентам делать с вашим корневым сертификатом — ставить его в Trusted Root, чтоб вы могли подменять собой любые сайты?
                  Как раз потому и есть смысл использования серверного сертификата от доверенного центра (Let's Encrypt в данном случае), чтобы не надо было отсылать пользователю ca.crt, а тому его ставить в систему. Для того, чтобы использовать клиентский сертификат, доверять центру, его выдавшему, не обязательно.

                  Отдельно хочу порекомендовать добавить в конфигурацию настройки Extended Key Usage — нет необходимости раздавать всем сертификаты, доступные для любых типов использования (например, подпись кода). Вкупе с установкой ca.crt в Trusted Root это может потенциально создать проблемы. Подробнее можно поискать по:
                  extendedKeyUsage = clientAuth
                    0
                    Спасибо большое за полезный комментарий! Надо будет добавить Extended Key Usage в свой конфиг.

                    Меня тоже смутило что необходимо добавлять «левый» CA сертификат в систему. Поэтому я нашел и описал в соседнем комменте способ как создать клиентский *.p12 файл без встроенного корневого сертификата.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое