OpenBSD: установка NGINX и настройки безопасности

Автор: | 14/12/2016
 

Задача – запустить EC2 с OpenBSD, установить NGINX, добавить настройки для его безопасной работы.

Позже – сюда же можно добавить Fail2ban, PSAD и AIDE. Кроме того – у AWS имеется сервис AWS WAF (Amazon Web App Firewall), пример его использования можно найти тут>>>.

Описание создания EC2 и сети можно найти тут>>>.

Базовые пакеты для установки:

# pkg_add vim bash curl git wget python-2.7.12 py-pip py-virtualenv

OpenBSD basic security

Версия OpenBSD:

# uname -mrs
OpenBSD 6.0 amd64
# sysctl kern.version
kern.version=OpenBSD 6.0 (GENERIC.MP) #2319: Tue Jul 26 13:00:43 MDT 2016
    [email protected]:/usr/src/sys/arch/amd64/compile/GENERIC.MP

Обновляем установленные пакеты:

# pkg_add -Uu
quirks-2.271 signed on 2016-12-09T15:55:27Z
quirks-2.271: ok

Хотя OpenBSD является системой “Secure by Default” – некоторые изменения можно внести.

Неплохой обзор по работе с OpenBSD можно почитать тут>>>.

Аккаунты, sudo и root логин

Проверяем список пользователей, которые могут логиниться в систему:

# cat /etc/passwd  | grep -v nologin
root:*:0:0:Charlie &:/root:/bin/ksh

В OpenBSD имеется пакет doas, как более простая альтернатива sudo, но если sudo привычнее – то устанавливаем его:

# pkg_add sudo
quirks-2.271 signed on 2016-12-09T15:55:27Z
Ambiguous: choose package for sudo
a       0: <None>
        1: sudo-1.8.18.1
        2: sudo-1.8.18.1-gettext
        3: sudo-1.8.18.1-gettext-ldap
Your choice: 1
sudo-1.8.18.1: ok

doas установлен по умолчанию.

Добавляем нового пользователя setevoy, добавив его в группу wheel:

# adduser        
...
Enter username []: setevoy
Enter full name []: 
Enter shell bash csh ksh nologin sh [bash]: 
Uid [1000]: 
Login group setevoy [setevoy]: 
Login group is ``setevoy''. Invite setevoy into other groups: guest no 
[no]: wheel
...
Name:        setevoy
Password:    ****
Fullname:    setevoy
Uid:         1000
Gid:         1000 (setevoy)
Groups:      setevoy wheel
Login Class: default
HOME:        /home/setevoy
Shell:       /usr/local/bin/bash
OK? (y/n) [y]: 
Added user ``setevoy''
Copy files from /etc/skel to /home/setevoy
Add another user? (y/n) [y]: n
Goodbye!

Подробнее о работе с пользователями в OpenBSDтут>>>.

Проверяем логин:

bash-4.4# su -l setevoy
-bash-4.4$ su  
Password:
Sorry

Добавляем правило для doas:

# echo "permit keepenv :wheel" > /etc/doas.conf

Больше о правилах doasтут>>> и тут>>>.

Проверяем ещё раз:

$ cat /var/log/daily.out 
cat: /var/log/daily.out: Permission denied

И через doas:

$ doas cat /var/log/daily.out 
doas ([email protected]) password: 

Checking subsystem status:

disks:
Filesystem  1K-blocks      Used     Avail Capacity iused   ifree  %iused  Mounted on
/dev/wd0a     8254030    640686   7200644     8%   25482 1039860     2%   /
...

Меняем пароль root:

$ doas passwd root
doas ([email protected]) password: 
Changing password for root.
New password:
Retype new password:

Запрещаем вход root по SSH и меняем порт – редактируем /etc/ssh/sshd_config и устанавливаем:

...
Port 2222
...
AllowUsers setevoy
...
PermitRootLogin no
...

Перезапускаем sshd:

# /etc/rc.d/sshd check
sshd(ok)
# /etc/rc.d/sshd restart
sshd(ok)
sshd(ok)

Проверяем с рабочей машины под рутом:

$ ssh [email protected] -p 2222
[email protected]'s password: 
Permission denied, please try again.

Под новым пользователем:

$ ssh [email protected] -p 2222
[email protected]'s password: 
OpenBSD 6.0-current (GENERIC.MP) #2553: Sun Oct  9 20:50:35 MDT 2016
...
-bash-4.4$

OpenBSD firewall – PF

PF (Packer Filter) – фаервол в OpenBSD по умолчанию.

Основные команды для работы с ним.

Отключить:

# rcctl disable pf

Либо через pfctl:

# pfctl -d

Включить:

# pfctl -e
  • pfctl -f /etc/pf.conf – загрузить pf.conf
  • pfctl -nf /etc/pf.conf – прочитать, но не применять правила
  • pfctl -sr – отобразить текущие наборы правил
  • pfctl -ss – отобразить текущие таблицы состояния
  • pfctl -si – вывести статистику пакетов и счётчиков
  • pfctl -sa – вывести всё

Основные правила доступа к хосту будут заданы через настройки самого AWS, поэтому PF можно использовать только для ручного бана (и Fail2ban).

Редактируем файл /etc/pf.conf, добавляем описание таблицы и файла, в котором будут хранится правила:

...
table <manual_banned> persist file "/etc/pf.banned"
block on xnf0 from <manual_banned> to any
...

Перезапускаем PF:

# pfctl -d                
pf disabled
# pfctl -e -f /etc/pf.conf
pf enabled

Проверяем. Баним IP:

# pfctl -t manual_banned -T add 77.120.103.20
1/1 addresses added.

Проверяем:

# cat /etc/pf.banned 
77.120.103.20

И пробуем подключиться с 77.120.103.20:

$ telnet 52.213.7.108 2222
Trying 52.213.7.108...
telnet: connect to address 52.213.7.108: Connection timed out

Просмотреть таблицу:

# pfctl -t manual_banned -T show             
   77.120.103.20

Удалить правило:

# pfctl -t manual_banned -T del 77.120.103.20

Ссылки по PF:

OpenBSD PF – Getting Started

How to block IP address with pf on FreeBSD, NetBSD and OpenBSD

Ссылки по OpenBSD:

OpenBSD Virtual Server on Vultr Runbook

How to install OpenBSD and make yourself comfortable

#ToDo

  1. пересылку почтовых уведомлений root
  2. автоапдейт пакетов

NGINX на OpenBSD

Устанавливаем NGINX:

# pkg_add -v nginx
Update candidates: quirks-2.241 -> quirks-2.241
quirks-2.241 signed on 2016-07-26T16:56:10Z
Ambiguous: choose package for nginx
a       0: <None>
        1: nginx-1.10.1
        2: nginx-1.10.1-lua
        3: nginx-1.10.1-naxsi
        4: nginx-1.10.1-passenger
...

Описание всех пакетов можно найти тут>>> (naxsi – интересное решение, почитать тут>>>).

Выбираем 1:

...
Your choice: 1
nginx-1.10.1:pcre-8.38p0: ok
nginx-1.10.1: ok
The following new rcscripts were installed: /etc/rc.d/nginx
See rcctl(8) for details.
Look in /usr/local/share/doc/pkg-readmes for extra documentation.
Extracted 4897592 from 4899471

В файл /etc/rc.conf.local добавляем строку:

nginx_flags=""

Добавляем автозагрузку в файл /etc/rc.conf:

pkg_scripts=nginx

Запускаем, проверяем:

# /etc/rc.d/nginx start
nginx(ok)
# curl localhost
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>

SSL для NGINX – Let’s encrypt

Настраиваем NGINX.

Создаём каталоги для файлов настроек и логов:

# mkdir /etc/nginx/conf.d
# mkdir /var/log/nginx/

В файл /etc/nginx/nginx.conf добавляем:

...
include /etc/nginx/conf.d/*.conf;
...

В файл /etc/nginx/nginx.conf добавляем:

    server {
        listen       80;
        listen       [::]:80;
        server_name  localhost;
        root         /var/www/htdocs;

        # Allow access to the letsencrypt ACME Challenge
        location ~ /\.well-known\/acme-challenge {
            allow all;
        }
...

Перезапускаем NGINX:

# /etc/rc.d/nginx restart
nginx(ok)
nginx(ok)

Устанавливаем клиент ACME (Automated Certificate Management Environment protocol).

Полный список клиентов можно найти тут>>>, в данном случае используем certbot.

Устанавливаем certbot:

# pkg_add certbot

Запускаем:

# /usr/local/bin/certbot certonly --agree-tos --webroot -w /var/www/openbsdtest.setevoy.org.ua/ -d openbsdtest.setevoy.org.ua
Enter email address (used for urgent notices and lost key recovery) (Enter 'c'
to cancel):user@setevoy.kiev.ua

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/openbsdtest.setevoy.org.ua/fullchain.pem.
   Your cert will expire on 2017-03-13.
   ...

В случае ошибки вида:

# /usr/local/bin/certbot certonly --agree-tos --webroot -w /var/www/openbsdtest.setevoy.org.ua/ -d openbsdtest.setevoy.org.ua
Enter email address (used for urgent notices and lost key recovery) (Enter 'c'
to cancel):[email protected]
Abort trap (core dumped)

Добавьте в /etc/fstab параметр wxallowed:

# cat /etc/fstab 
/dev/wd0a / ffs rw,wxallowed 1 1

Проверяем сертификаты:

# ls -l /etc/letsencrypt/live/openbsdtest.setevoy.org.ua/              
total 0
lrwxr-xr-x  1 root  wheel  50 Dec 13 15:17 cert.pem -> ../../archive/openbsdtest.setevoy.org.ua/cert1.pem
lrwxr-xr-x  1 root  wheel  51 Dec 13 15:17 chain.pem -> ../../archive/openbsdtest.setevoy.org.ua/chain1.pem
lrwxr-xr-x  1 root  wheel  55 Dec 13 15:17 fullchain.pem -> ../../archive/openbsdtest.setevoy.org.ua/fullchain1.pem
lrwxr-xr-x  1 root  wheel  53 Dec 13 15:17 privkey.pem -> ../../archive/openbsdtest.setevoy.org.ua/privkey1.pem

Создаём файл настроек виртуалхоста /etc/nginx/conf.d/openbsdtest.setevoy.org.ua.conf:

server {

    listen 443 ssl;
    server_name openbsdtest.setevoy.org.ua;

    access_log /var/log/nginx/openbsdtest.setevoy.org.ua-access.log;
    error_log /var/log/nginx/openbsdtest.setevoy.org.ua-error.log;

    root /var/www/openbsdtest.setevoy.org.ua;

    ssl_certificate /etc/letsencrypt/live/openbsdtest.setevoy.org.ua/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/openbsdtest.setevoy.org.ua/privkey.pem;

    location / {
        index index.htm;
    }

    location ~ /.well-known {
        allow all;
    }
}

Создаём индексный файл:

# echo "Hello" > /var/www/openbsdtest.setevoy.org.ua/index.html

Проверяем и перезапускаем NGINX:

# nginx -t && /etc/rc.d/nginx reload
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
nginx(ok)

Проверяем:

# netstat -an | grep 443
tcp          0      0  *.443                  *.*                    LISTEN
# curl https://openbsdtest.setevoy.org.ua
Hello

С помощью openssl:

# openssl s_client -showcerts -connect openbsdtest.setevoy.org.ua:443
...
    Start Time: 1481643931
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---
closed

Ссылки по теме Let’s Encrypt и OpenBSD:

Let’s Encrypt for Nginx

Easy Secure Web Serving with OpenBSD’s acme-client and Let’s Encrypt

Let’s Encrypt on OpenBSD

Автообновление сертификата

В cron добавляем задачу:

@weekly /usr/local/bin/certbot renew --quiet --post-hook "/etc/rc.d/nginx reload"

NGINX security

По умолчанию – NGINX сообщает свою версию:

# curl localhost -I
...
Server: nginx/1.11.6

Отключаем, добавив в server {} файла /etc/nginx/nginx.conf строку:

...
server_tokens off;
...

Проверяем:

# curl -s -D- https://openbsdtest.setevoy.org.ua/ | grep Strict
Strict-Transport-Security: max-age=31536000; includeSubdomains

Включаем HSTS (HTTP Strict Transport Security):

server {

    listen 443 ssl;

    server_name openbsdtest.setevoy.org.ua;

    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
...

Добавляем заголовок X-Frame-Options для запрета отображения страниц в frame/iframe:

server {

    listen 443 ssl;

    server_name openbsdtest.setevoy.org.ua;

    add_header Strict-Transport-Security "max-age=31536000;
    add_header X-Frame-Options "DENY";
...

Отключаем автоматическую проверку типа контента для IE, Chrome, Safari:

...
add_header X-Content-Type-Options nosniff;
...

Полезным может быть заголовок Content-Security-Policy (устаревшие – X-Content-Security-Policy и X-WebKit-CSP).

Настройка SSL

Добавляем переадресацию с HTTP на HTTPS:

server {

    listen 80;
    server_name openbsdtest.setevoy.org.ua;
    server_tokens off;
    return 301 https://openbsdtest.setevoy.org.ua$request_uri;
}

server {

    listen 443 ssl;

    server_name openbsdtest.setevoy.org.ua;
...

Проверяем тут: https://www.ssllabs.com/ssltest/analyze.html

Рейтинг B.

Генерируем ключ параметров для установления сессии по протоколу Diffie-Hellman:

# mkdir /etc/ssl/certs
# cd /etc/ssl/certs
# openssl dhparam -out dhparam.pem 4096

Посмотреть его можно так:

# openssl dhparam -inform PEM -in /etc/ssl/certs/dhparam.pem -check -text
    PKCS#3 DH Parameters: (4096 bit)
        prime:
            00:ec:d0:cb:10:12:82:63:33:2f:42:2f:8e:40:02:
            9f:84:bd:e5:60:9d:76:e3:92:3d:c5:f8:9b:34:06:
            ...
            c5:87:dc:de:b8:37:85:94:33:6c:4b:a6:31:c0:4e:
            da:d7:0b:f7:94:65:14:95:70:12:e2:b2:ac:2a:79:
            66:75:7b
        generator: 2 (0x2)
DH parameters appear to be ok

Добавляем в файл настроек:

...
ssl_dhparam /etc/ssl/certs/dhparam.pem;
...

Добавляем список поддерживаемых протоколов:

...
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
...

Задаём поддерживаемые алгоритмы шифрования:

...
ssl_ciphers  "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH !RC4";
ssl_prefer_server_ciphers   on;
...

Включаем кеширование сессий SSL (1MB под кеш, хранить 1 час):

...
ssl_session_cache   shared:SSL:10m;
ssl_session_timeout 1h;
...

Доабвляем OCSP Stapling:

...
    ssl_stapling on;
    ssl_stapling_verify on;
...

Проверяем:

# nginx -t && /etc/rc.d/nginx reload
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
nginx(ok)

Рейтинг А+:

Другие параметры

Параметры ниже больше касаются проивзодительности, но имеет смысл добавить их сейчас.

Что бы не выполнять SSL handshake каждый раз для одного клиента – можно добавить keepalive_timeout:

...
keepalive_timeout 70;
...

Запрещаем доступ, если запрос к серверу не включает в себя имя виртуалхоста:

...
    server_name openbsdtest.setevoy.org.ua;

    if ($host !~ ^(openbsdtest.setevoy.org.ua|www.openbsdtest.setevoy.org.ua)$ ) {
       return 444;
    }
...

HTTP код 444 – закрыть соединение без ответа.

Разрешаем только определённые типы запросов к серверу:

...
    if ($request_method !~ ^(GET|HEAD|POST)$ ) {
        return 444;
    }
...

DELETE, SEARCH и прочие – будут отбрасываться.

Заблокировать hotlink-и изображений (за трафик картинок с нашего сервера платим мы, поэтому разрешаем доступ только со своего домена):

...
    # block hotlinks
    location ~ .(gif|png|jpe?g)$ {
        valid_referers none blocked .openbsdtest.setevoy.org.ua;
        if ($invalid_referer) {
            return   403;
        }
    }
...

Не проверял, подробнее – тут>>>.

Конфигурация, получившаяся в результате:

server {

    listen 80;
    server_name openbsdtest.setevoy.org.ua;
    server_tokens off;
    return 301 https://openbsdtest.setevoy.org.ua$request_uri;
}

server {

    listen 443 ssl;

    server_name openbsdtest.setevoy.org.ua;

    if ($host !~ ^(openbsdtest.setevoy.org.ua|www.openbsdtest.setevoy.org.ua)$ ) {
       return 401;
    }

    # Only allow these request methods
    # Do not accept DELETE, SEARCH and other methods
    if ($request_method !~ ^(GET|HEAD|POST)$ ) {
        return 444;
    }

    # block hotlinks
    location ~ .(gif|png|jpe?g)$ {
        valid_referers none blocked .openbsdtest.setevoy.org.ua;
        if ($invalid_referer) {
            return   403;
        }
    }

    keepalive_timeout 70;

    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options "DENY";

    access_log /var/log/nginx/openbsdtest.setevoy.org.ua-access.log;
    error_log /var/log/nginx/openbsdtest.setevoy.org.ua-error.log;

    root /var/www/openbsdtest.setevoy.org.ua;

    ssl_certificate /etc/letsencrypt/live/openbsdtest.setevoy.org.ua/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/openbsdtest.setevoy.org.ua/privkey.pem;

    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH !RC4";
    ssl_prefer_server_ciphers   on;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1h;
    ssl_stapling on;
    ssl_stapling_verify on;

    location / {
        index index.html;
   }

    location ~ /.well-known {
        allow all;
   }
}

Ссылки по NGINX/TLS:

Top 20 Nginx WebServer Best Security Practices

Оптимизация TLS в NGINX

Nginx SSL and TLS Deployment Best Practices

Strong SSL Security on nginx

How To Secure Nginx on Ubuntu 14.04

Прямая секретность или как достичь максимальной безопасности сайта (про Diffie-Hellman)

Как HTTPS обеспечивает безопасность соединения: что должен знать каждый Web-разработчик (2013 год, но интересные комментарии)

Optimizing HTTPS on Nginx

nginx Tuning (производительность)