Сертификаты в Nginx

или доступ только своим

config security

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

Как понятно из названия, будем делать доступ к сайту по сертификату. На самом деле, вещь очень приятная - при наличии сертификата не нужно вводить ни паролей, ни логинов. Просто заходишь на нужную страницу и получаешь то, что нужно. Удобно, когда, к примеру, нужно убрать админку сайта или похожие вещи.

Я исхожу из предположения, что читатели представляют себе, что такое TLS и сертификаты, как и с чем это едят и зачем это нужно. Если нет - вам в википедию.

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

Генерация CA

Первым делом генерируем ключ для CA и сразу же сертификат:

$ openssl genrsa -des3 -out ca.key 4096

$ openssl req -new -x509 -days 90 -key ca.key -out ca.crt 

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

Важное замечание

При обновлении сертификата имеет смысл не менять данные, которые вы вводите при его генерации.

Генерация клиентского сертификата

Клиентский сертификат генерируется аналогично CA, с той лишь разницей, что есть промежуточная ступень - создание CSR (Certificate Signing Request).

$ openssl genrsa -des3 -out user.key 4096

$ openssl req -new -key user.key -out user.csr 

$ openssl x509 -req -days 90 -in user.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out user.crt

Опять срок действия 90 дней, будьте внимательны!

Следующим шагом необходимо привести сертификат в состояние, в котором его можно импортировать в браузер:

$ openssl pkcs12 -export -out user.pfx -inkey user.key -in user.crt -certfile ca.crt

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

Настройка Nginx

Теперь нужно настроить Nginx, чтобы он проверял сертификат и, в зависимости от результатов проверки, отдавал разные данные.

Простейший конфиг выглядит примерно следующим образом:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
  worker_connections 768;
}

http {
  # some HTTP boilerplate
  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  keepalive_timeout 65;
  types_hash_max_size 2048;
  server_tokens off;

  include /etc/nginx/mime.types;
  default_type application/octet-stream;

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;

  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;

  gzip on;
  gzip_disable "msie6";

  map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
  }

  # server on port 80 for HTTP -> HTTPS redirect
  server {
    listen 80;
    server_name example.com;
    return 301 https://example.com$request_uri;
  }

  # The letsencrypt-secured HTTPS server, which proxies our requests
  server {
    listen 443 ssl;
    server_name example.com;

    ssl_protocols TLSv1.1 TLSv1.2;
    # letsencrypt certificate
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # client certificate
    ssl_client_certificate /etc/nginx/client_certs/ca.crt;
    # make verification optional, so we can display a 403 message to those
    # who fail authentication
    ssl_verify_client optional;

    access_log /var/log/nginx/example.com;

    location / {
      # if the client-side certificate failed to authenticate, show a 403
      # message to the client
      if ($ssl_client_verify != SUCCESS) {
        return 403;
      }

      proxy_set_header        Host $host;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        X-Forwarded-Proto $scheme;

      # Fix the "It appears that your reverse proxy set up is broken" error.
      proxy_pass          http://localhost:8080;
      proxy_read_timeout  90;

      # web sockets
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection $connection_upgrade;

      proxy_redirect      http://localhost:8080 https://example.com;
    }
  }
}

В данном примере все HTTP-соединения заворачиваются на HTTPS, для которого выпущен сертификат Let's Encrypt. После чего происходит проверка сертификата, с которым пришел пользователь. Если сертификат проверку не прошел, отдается 403 ошибка. В случае, если проверка все-таки пройдена, то происходит перенаправление на приложение, которое запущено на порту 8080. Важный момент - приложение повешенно на локалхост, то есть его порт не торчит наружу. Все что видно снаружи - nginx на 443 порту.

Previous Post Next Post