Содержание

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

Создаем каталог, в котором будем настраивать конфигурацию контейнера и переходим в него.

sudo mkdir /opt/docker && cd $_

Создаем файл 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

Для начала запустим в тестовом режиме с параметром --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

Для начала нам необходимо создать сертификат для центра сертификации. Им мы будем подписывать сертификаты клиентов и проверять их достоверность.

Инфо
Важный момент! При создании сертификационного центра и клиентских сертификатов поля Organization Name должны отличаться! Иначе авторизация клиентов проходить не будет.

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

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

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 и тестируем.

Похожее