Настройка аутентификации по SSL сертификатам клиентов на Nginx в Docker

Базовая настройка HTTP сервера в Docker
Создаем каталог, в котором будем настраивать конфигурацию контейнера и переходим в него.
sudo mkdir /opt/docker && cd $_
Конфигурация контейнеров в docker compose
Создаем файл docker-compose.yml
с двумя контейнерам – nginx и certbot для обновления сертификатов от Let’s Encrypt.
services:
nginx:
image: nginx:latest
restart: unless-stopped
# маршрутизируем порты
ports:
- "80:80"
- "443:443"
# монтируем директории из текущего каталога в контейнер
volumes:
- certbot-crt:/etc/letsencrypt:ro
- certbot-tmp:/var/certtmp:ro
- ./nginx:/etc/nginx/conf.d:ro
command: "/bin/sh -c 'while :; do sleep 12h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
image: certbot/certbot:latest
restart: unless-stopped
volumes:
- certbot-crt:/etc/letsencrypt
- certbot-tmp:/var/certtmp
depends_on:
- nginx
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
volumes:
certbot-crt:
certbot-tmp:
Каталог /var/certtmp
в контейнере nginx должен совпадать с тем, что указан в root /var/certtmp;
конфигурационного файла сайта.
Создаем простую конфигурацию для вашего сайта в файле nginx/example.com.conf
. Убедитесь, что ваш домен указывает на
IP данного сервера.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location / {
add_header Content-Type text/plain;
return 200 "SSL Client Verify: $ssl_client_verify\nSSL Serial: $ssl_client_serial\nSSL DN: $ssl_client_s_dn\nSSL Cert: $ssl_client_cert\n";
}
# Нужен для получения сертификата от Let's Encrypt
location ~ /.well-known/acme-challenge {
allow all;
root /var/certtmp;
}
}
Не забываем менять все example.com
на ваш домен. Перезагружаем конфигурацию nginx.
docker compose exec nginx nginx -s reload
Получение сертификата от Let’s Encrypt
Для начала запустим в тестовом режиме с параметром --dry-run
для проверки корректности настроек.
docker compose run --entrypoint "certbot certonly --webroot --webroot-path=/var/certtmp --dry-run -d example.com -d www.example.com" certbot
Если все нормально, то должны получить следующее сообщение
[+] Creating 1/0
✔ Container docker-nginx-1 Running 0.0s
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Account registered.
Simulating a certificate request for example.com
The dry run was successful.
Теперь отправляем запрос на получение сертификата.
docker compose run --entrypoint "certbot certonly --webroot --webroot-path=/var/certtmp -d example.com -d www.example.com" certbot
Соглашаемся с условиями использования и получаем сертификат. Если все успешно, то в выводе будет путь к файлам, куда
сохранился сертификат. Далее обновляем нашу конфигурацию в nginx/example.com.conf
.
Блок с проверкой для Let’s Encrypt оставляем на месте, иначе мы потом не сможем обновить сертификат, поскольку их бот не пройдет нашу защиту. А для всех остальных запросов ставим переадресацию на HTTPS.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location / {
return 301 https://$server_name$request_uri;
}
# Нужен для получения сертификата от Let's Encrypt
location ~ /.well-known/acme-challenge {
allow all;
root /var/certtmp;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
add_header Content-Type text/plain;
return 200 "SSL Client Verify: $ssl_client_verify\nSSL Serial: $ssl_client_serial\nSSL DN: $ssl_client_s_dn\nSSL Cert: $ssl_client_cert\n";
}
}
Перезагружаем конфигурацию nginx и проверяем. Ваш сайт теперь должен открыться по протоколу https.
curl https://example.com
Настройка клиентских сертификатов
Настройка центра сертификации
Для начала нам необходимо создать сертификат для центра сертификации. Им мы будем подписывать сертификаты клиентов и проверять их достоверность.
Создадим отдельный каталог для хранения сертификатов и все операции будем проводить в нем. Для безопасности, этот каталог не нужно хранить внутри контейнера. Создадим его, например, в домашней директории пользователя.
Создаем закрытый ключ центра сертификации и генерируем сертификат.
mkdir ~/mycompanycert && cd $_
openssl ecparam -name secp384r1 -genkey -out companyname.ca.key
openssl req -new -x509 -days 36500 -sha512 -key companyname.ca.key -out companyname.ca.crt
Не забываем указать оригинальный Organization Name для центра сертификации. Желательно заполнить и остальные поля реальными данными.
Скопируем созданный сертификат companyname.ca.crt в каталог с настройками /opt/docker/nginx
.
Добавляем в конфигурацию nginx вашего сайта ваш созданный удостоверяющий центр для проверки клиентских сертификатов
и включаем принудительную проверку сертификатов клиентов.
server {
...
ssl_client_certificate conf.d/companyname.ca.crt;
ssl_verify_client on;
...
}
Перезагружаем конфигурацию nginx. После этой настройки доступ к сайту без сертификата клиента должен быть закрыт.
docker compose -f /opt/docker/docker-compose.yml exec nginx nginx -s reload
Создаем сертификаты пользователей
В идеале, ключи пользователей нужно создавать на их устройствах, чтобы секретный ключ никогда не покидал их и не
передавался по сети. Для подписи достаточно только файла .csr
с запросом на сертификат. Но часто,
пользователи не имеют возможности их создавать и вся эта задача целиком ложится на администратора.
Создаем закрытый ключ пользователя и сертификат. Желательно корректно заполнить поля сертификата для облегчения последующего контроля за ними.
openssl ecparam -name secp384r1 -genkey -out user1.key
openssl req -new -key user1.key -out user1.csr
Создаем сертификат пользователя подписанный нашим центром сертификации.
openssl x509 -req -CA companyname.ca.crt -CAkey companyname.ca.key -CAcreateserial -days 36500 -sha512 -in user1.csr -out user1.crt
Можно проверить корректность созданного сертификата. Не должно быть сообщения о самоподписанном сертификате.
openssl verify -verbose -CAfile companyname.ca.crt user1.crt
Для проверки корректной работы пробуем подключиться к серверу с помощью сертификата.
curl --cert user1.crt --key user1.key https://example.com
Перенос сертификатов на устройства клиентов
Переносить приватный ключ в открытом виде не рекомендуется. Для этого нужно создать защищенный контейнер,
например формата .p12
. Его поддерживают все современные устройства.
Переносить CA-сертификат нашего центра пользователям не нужно. При авторизации клиента он не используется. Его следует устанавливать в систему пользователей только в том случае, если вы хотите самостоятельно выпускать сертификаты для вашего HTTPS-сервера (например, для серверов с локальными именами или без доступа из Интернета). Тогда браузеры пользователей не будут ругаться на них как на недоверенные.
openssl pkcs12 -export -in user1.crt -inkey user1.key -out user1.p12 -passout pass:SuperPassword
Проверяем полученный контейнер.
openssl pkcs12 -info -nodes -in user1.p12
Обратите внимание на строку с PKCS7 Encrypted data – в ней не должно быть никаких DES, 3DES, MD5 и прочих устаревших форматов. Обновите вашу версию openssl, если это произошло.
Теперь можно отправлять данный файл пользователям, а пароль от него – по другому безопасному каналу с инструкциями, как его импортировать в систему.
Скрипт автоматизации создания сертификатов пользователя
Создаем файл для добавления пользователя adduser.sh
. Отредактируйте параметр SUBJ для задания названия вашей
организации, города, региона.
#!/bin/sh
USER=$1
PASSWORD=$2
CACRT=companyname.ca.crt
CAKEY=companyname.ca.key
SUBJ="/C=RU/ST=State/L=City/O=Organization Name/CN=$USER"
DAYS=36500
if [ -z "$1" ]; then
echo $0 \"User Name\" [Password for p12]
exit 1
fi
if [ ! -z "$PASSWORD" ]; then
PASSWORD="-passout pass:$PASSWORD"
fi
openssl ecparam -name secp384r1 -genkey -out "$USER.key"
openssl req -new -subj "$SUBJ" -key "$USER.key" -out "$USER.csr"
openssl x509 -req -CA "$CACRT" -CAkey "$CAKEY" -CAcreateserial -days "$DAYS" -sha512 -in "$USER.csr" -out "$USER.crt"
openssl verify -verbose -CAfile "$CACRT" "$USER.crt"
openssl pkcs12 -export -in "$USER.crt" -inkey "$USER.key" -out "$USER.p12" $PASSWORD
Делаем его исполняемым chmod +x adduser.sh
и создаем пользователя.
./adduser.sh "Ivan Petrov" SuperPassword
Отзыв сертификатов клиентов
Всю информацию об отозванных сертификатах будем хранить в том же каталоге с сертификатами. Для этого создаем подкаталог
demoCA (или другой, если вы изменили файл /etc/ssl/openssl.cnf, посмотреть можно в разделе [ CA_default ]
параметр dir) и необходимые файлы. В них будет храниться реестр отозванных сертификатов, по которому будет
создаваться подписанный файл с отозванными сертификатами crl.pem
для использования, например, в Nginx.
cd ~/mycompanycert
mkdir demoCA
touch demoCA/index.txt
echo 1000 > demoCA/crlnumber
Пока у нас нет отозванных сертификатов, но мы уже можем сформировать файл для них, чтобы добавить его в конфигурацию Nginx. Скопировать его нужно в каталог с настройками для сайта. В нашем случае это /opt/docker/nginx/.
openssl ca -keyfile companyname.ca.key -cert companyname.ca.crt -gencrl -out companyname.crl.pem
sudo cp companyname.crl.pem /opt/docker/nginx/
Теперь добавляем в nginx настройку для проверки отозванных сертификатов.
server {
...
ssl_crl conf.d/companyname.crl.pem;
...
}
Перезагружаем конфигурацию nginx и проверяем работу. Сейчас все сертификаты должны работать, а без них – доступ закрыт.
Теперь отзываем сертификат у пользователя.
openssl ca -revoke user1.crt -keyfile companyname.ca.key -cert companyname.ca.crt
Обновляем файл с отозванными сертификатами.
openssl ca -keyfile companyname.ca.key -cert companyname.ca.crt -gencrl -out companyname.crl.pem
sudo cp companyname.crl.pem /opt/docker/nginx/
Перезагружаем конфигурацию Nginx и тестируем.