Let’s Encrypt: firewall и верификация домена

Автор: | 09/26/2017
 

Во время установки и получения сертификата с помощью Ansible — возник вопрос с доступом к серверу для верификации, т.к. доступ к портам 80 и 443 ограничен на уровне Azure Network Security Group.

Для certbot можно было бы использовать manual верификацию через DNS — но тут требовалось или ручная обработка, или боль с Azure DNS API — и не факт, что она работала бы.

Другое решение — ограничить доступ к порту 80 на уровне NGINX с помощью правил allow/deny и открыть этот порт в Azure.

Далее, сервер на порту 80 будет выполнять редирект на 443, где дополнительно используется обычная HTTP-авторизация.

Для установки и получения сертификата Lets Encrypt — используется Ansible роль jaywink.letsencrypt.

Используется сетап из предыдущеего поста — Ansible: ansible-galaxy – репозиторий ролей и Jenkins VM provision.

Lets Encrypt и Ansible

Роль jaywink.letsencrypt вызывается из файла provision.yml, который сейчас выглядит следующим образом:

- hosts: all
  become:
    true
  roles:
    - role: common
    - role: jnv.unattended-upgrades
      unattended_mail: user@domain.com
      unattended_automatic_reboot: true
      unattended_automatic_reboot_time: 03:00
    - role: mongrelion.docker
    - role: jaywink.letsencrypt
      letsencrypt_domain: "{{ inventory_hostname }}"
      letsencrypt_email: user@domain.com
      letsencrypt_request_www: false
      letsencrypt_pause_services: nginx
    - role: nginx
    - role: SoInteractive.prometheus
      prometheus_external_labels:
        monitoring: dev

letsencrypt роль выполнит установку certbot, потом остановит веб-сервер, указанный в letsencrypt_pause_services (тут —  nginx), выполнит верификацию и получит сертификат.

Полностью его команда будет выглядеть так:

"cmd": "./certbot-auto --renew-by-default certonly --standalone --expand --text -n --no-self-upgrade -m 'user@domain.com' --agree-tos --domains dev.monitor.domain.ms 2>&1"

Для верификации — certbot использует порт 80, который мы и откроем в Azure Network Security Group.

NGINX и Ansible

Роль nginx описана в посте Ansible: пример установки NGINX и в её шаблоне ничего не менялось, кроме адреса бекенда в переменных инвентори-файла.

Теперь — обновим шаблон NGINX, и выполним следующее:

  1. добавим server {} на порту 80 с правилами allow/deny, который будет выполнять редирект на порт 443 ssl;
  2. уже в сервере, слушающем порт 443 — HTTP-авторизация, плюс доступ будет ограничен правилами Network Security Group в Azure

Шаблоны NGINX:

ls -l roles/nginx/templates/
total 8
-rw-r--r-- 1 setevoy setevoy  875 Sep 20 17:31 nginx.conf
-rw-r--r-- 1 setevoy setevoy 1032 Sep 20 17:31 nginx_vhost.conf

Приводим файл настроек виртуалхоста к такому виду:

upstream {{ upstream_name }} {
    server {{ upstream_url }};
}

server {
       
    server_name    www.{{ inventory_hostname }};
    listen         80;
    return         301 https://{{ inventory_hostname }}$request_uri;
}

server {
       
    server_name    {{ inventory_hostname }};
    listen         80;

    allow {{ nginx_allow_kiev_ip }}; 
    allow {{ nginx_allow_berlin_ip }}; 
    deny  all;
        
    return 301 https://{{ inventory_hostname }}$request_uri;
}

server {

    server_name {{ inventory_hostname }};
    listen 443 ssl;

    access_log /var/log/nginx/{{ inventory_hostname }}-access.log proxy;
    error_log /var/log/nginx/{{ inventory_hostname }}-error.log notice;

    ssl on;
    ssl_certificate /etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem;

    root /var/www/{{ inventory_hostname }};

    location / {

        proxy_redirect          off;
        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_pass http://{{ upstream_name }}$request_uri;
    }
}

В файл provision.yml для роли nginx добавляем две переменные с IP, с которых будет разрешён доступ:

...
    - role: nginx
      nginx_allow_kiev_ip: 194.***.***.45
      nginx_allow_berlin_ip: 37.***.***.130
...

В шаблоне провижена группы ресурсов (из поста Azure: provisioning с Resource Manager, Jenkins и Groovy) — обновляем правила Network Security Group и открываем доступ к порту 80 отовсюду:

...
          {
            "name": "HTTP_All_Allow",
            "properties": {
              "description": "HTTP All Allow",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 100,
              "direction": "Inbound"
            }
          },
...

HTTP авторизация

Последним шагом — надо добавить HTTP авторизацию.

В файл шаблона NGINX добавляем:

...
    root /var/www/{{ inventory_hostname }};

    auth_basic_user_file /var/www/{{ inventory_hostname }}/.htaccess;
    auth_basic "Password-protected Area";

    location / {
...

В каталоге роли nginx — создаём каталог files:

mkdir roles/nginx/files

В и нём — файл .htaccess с логином-паролем:

htpasswd -c roles/nginx/files/.htpasswd jmadmin
New password:
Re-type new password:
Adding password for user jmadmin

В файл roles/nginx/tasks/main.yml добавляем копирование файла на хост:

...
- name: Copy .htaccess
  template:
    src=files/.htpasswd
    dest=/var/www/{{ inventory_hostname }}/.htaccess
...

Готово:

git add -A && git commit -m "HTTP Auth" && git push

Из логов Jenkins:

...
TASK [jaywink.letsencrypt : debug] *********************************************
ok: [dev.monitor.domain.ms] => {
    "msg": [
        "IMPORTANT NOTES:", 
        " - Congratulations! Your certificate and chain have been saved at", 
        "   /etc/letsencrypt/live/dev.monitor.domain.ms/fullchain.pem. Y
...

HTTP Auth:

...
TASK [nginx : Copy .htaccess] **************************************************
sudo
-H -S -n
sudo
-H -S -n
...

Готово.