ServerAdmin.ru
28.4K subscribers
264 photos
33 videos
12 files
2.59K links
Авторская информация о системном администрировании.

Информация о рекламе: @srv_admin_reklama_bot
Автор: @zeroxzed

Второй канал: @srv_admin_live
Сайт: serveradmin.ru
Download Telegram
​​Сейчас без HTTPS не хотят работать многие сервисы. А даже если и работают, то браузеры не дадут спокойно пользоваться. Поэтому приходится получать и настраивать сертификаты, даже если большой нужды в этом нет. Особенно если ты работаешь с ним один в локальной сети, либо вообще поднимаешь временно. Я обычно получаю сертификаты let's encrypt и копирую на нужный сервер, если к нему не проброшен доступ из интернета.

Решил подготовить пошаговую инструкцию, чтобы быстро выпустить свой сертификат через свой CA, добавив его к себе в доверенные, чтобы браузеры не ругались. Во многих ситуациях это будет удобнее, чем постоянно обновлять доверенные сертификаты через интернет. Свой можно выпустить вечным. Покажу на примере настройки сертификата в Nginx.

Будем выпускать сертификат для доменного имени zabbix.internal и IP адреса 172.30.245.222. Будет работать и так, и эдак.

Выпускаем ключ и сертификат для своего CA:

# mkdir ~/tls && cd ~/tls
# openssl ecparam -out myCA.key -name prime256v1 -genkey
# openssl req -x509 -new -nodes -key myCA.key -sha256 -days 9999 -out myCA.crt

Вам зададут серию вопросов. Отвечать можно всё, что угодно. В данном случае это не важно. Выпускаем ключ и сертификат для сервера:

# openssl genrsa -out zabbix.internal.key 2048
# openssl req -new -key zabbix.internal.key -out zabbix.internal.csr

Тут тоже зададут похожие вопросы. Отвечать можно всё, что угодно. Готовим конфигурационный файл:

# mcedit zabbix.internal.ext

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
IP.1 = 172.30.245.222
DNS.1 = zabbix.internal

Генерируем сертификат на его основе:

# openssl x509 -req -in zabbix.internal.csr -CA myCA.crt -CAkey myCA.key \
-CAcreateserial -out zabbix.internal.crt -days 9999 -sha256 -extfile zabbix.internal.ext

Копируем сертификат и ключ в директорию веб сервера:

# mkdir /etc/nginx/certs
# cp zabbix.internal.crt /etc/nginx/certs/.
# cp zabbix.internal.key /etc/nginx/certs/.

Создаём файл dhparam, который понадобится для конфигурации Nginx:

# openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Добавляем в конфиг Nginx в целевом виртуальном хосте:

listen     443 http2 ssl;
server_name   zabbix.internal 172.30.245.222;
ssl_certificate /etc/nginx/certs/zabbix.internal.crt;
ssl_certificate_key /etc/nginx/certs/zabbix.internal.key;
ssl_dhparam /etc/ssl/certs/dhparam.pem;

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

# nginx -t
# nginx -s reload

Передаём на свой компьютер файл myCA.crt и добавляем его в хранилище корневых доверенных центров сертификации. Настройка будет зависеть от операционной системы. Если нужно тут же, локально на сервере с Debian 12 настроить доверие этому CA, то делаем так:

# cp myCA.crt /usr/local/share/ca-certificates/.
# update-ca-certificates

Теперь можно браузером заходить по доменному имени или IP адресу, будет работать самоподписанный сертификат на 9999 дней без каких-либо предупреждений.

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

#webserver
​​Существует эффективный стандарт сжатия zstd. Не так давно современные браузеры стали его поддерживать, так что можно использовать в веб серверах. Разработчики Angie подсуетились и подготовили модуль для своего веб сервера, так что включить сжатие zstd максимально просто и быстро. Достаточно установить модуль в виде deb пакета и добавить настройки в конфигурацию, которые идентичны настройкам gzip, только название меняется на zstd.

По этому поводу вышел очень информативный ролик на ютубе:

Zstd (Zstandard): новый стандарт сжатия текста. Полный тест

Автор не только показал, как настроить zstd на веб сервере, но и сравнил его эффективность с привычными gzip и brotli. Результаты тестирования в динамическом сжатии получились очень любопытные. Zstd оказался лучше всех. Но если разница с brotli не сильно заметна, то вот gzip на фоне остальных выглядит очень медленным. Буквально в разы в некоторых случаях.

Я решил провести свои тесты, чтобы убедиться в такой большой разнице. Сразу скажу, что если не настроено https, то браузеры не будут использовать ни brotli, ни zstd. Не знаю, с чем это связано, но я потратил некоторое время, пока не разобрался с тем, почему не работает ничего, кроме gzip. И второй момент. Если на веб сервере настроены все 3 типа сжатия, то разные браузеры выбирают разное сжатие: либо brotli, либо zstd. Gzip не выбирает никто.

Тестировал так же, как и автор ролика. Установил Angie и оба модуля сжатия:

# curl -o /etc/apt/trusted.gpg.d/angie-signing.gpg https://angie.software/keys/angie-signing.gpg

# echo "deb https://download.angie.software/angie/debian/ `lsb_release -cs` main" | tee /etc/apt/sources.list.d/angie.list > /dev/null

# apt update && apt install angie angie-module-zstd angie-module-brotli

Подключил оба модуля в angie.conf:

load_module modules/ngx_http_zstd_static_module.so;
load_module modules/ngx_http_zstd_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
load_module modules/ngx_http_brotli_filter_module.so;

И добавил для них настройки:

  gzip on;
  gzip_static on;
  gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/x-icon image/svg+xml application/x-font-ttf;
  gzip_comp_level 4;
  gzip_proxied any;
  gzip_min_length 1000;
  gzip_vary on;

  brotli on;
  brotli_static on;
  brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/x-icon image/svg+xml application/x-font-ttf;
  brotli_comp_level 4;

  zstd on;
  zstd_static on;
  zstd_min_length 256;
  zstd_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/x-icon image/svg+xml application/x-font-ttf;
  zstd_comp_level 4;

Если использовать ванильный Nginx, то придётся самостоятельно собирать его с нужными модулями 🤷‍♂️

Тестировал с помощью ab, передавая ему метод компрессии через заголовок:

# ab -n 1000 -k -c 1 -H "Accept-Encoding: zstd" https://10.20.1.36/scripts.js

Не буду приводить свои результаты, так как они получились примерно такие же, как у автора ролика, только разница между zstd и brotli с компрессией 4 поменьше. Zstd по rps (247) быстрее всех. Brotli чуть лучше жмёт в плане объёма, то есть трафик будет ниже, но и rps (211) немного меньше, чем у zstd.

В Angie очень легко настроить и brotli, и zstd, и gzip, так что имеет смысл это сделать. Клиент пусть сам выбирает, какой тип сжатия он будет использовать.

Один важный момент, который я вынес из этой темы. Не нужно ставить сжатие выше 3 или 4. Дальше идёт очень существенное падение производительности при незначительном уменьшении размера файлов. Я раньше бездумно ставил 9 и думал, что современные процессоры и так нормально вытягивают, если сервер не нагружен в потолок. Это не так. Смысла в высокой компрессии нет.

#webserver #angie
​​Для управления веб сервером есть множество готовых веб панелей. Основная проблема их использования - дополнительный вектор атаки сервера через эту веб панель. Причём, случается это довольно часто. В идеале, доступ к веб панели управления хостингом должен быть ограничен. Второй момент - часто веб панели раскидывают конфиги в нестандартные места, переписывают их после перезапуска службы управления. Это часто делает невозможным изменение конфигов вне веб панели, а иногда хочется, так как их возможности ограничены.

Есть промежуточные варианты автоматизации и упрощения управления веб хостингом. Набор консольных инструментов для управления. Наглядным примером подобного продукта является Bitrixenv. Это инструмент для автоматической настройки и управления веб хостингом для размещения сайтов на движке Битрикс.

Для сайтов на Wordpress есть похожий продукт - WordOps. Он позволяет автоматически разворачивать готовое окружение для сайтов на базе Wordpress, а так же любых других статических или php сайтов.

Установить WordOps можно автоматически прямо на сервер:

# wget -qO wo wops.cc && bash wo

Скрипт установки выполняет несколько простых шагов:
устанавливает необходимые системные пакеты
создаёт директории
устанавливает WordOps через pip
устанавливает acme.sh и WP-CLI

Всё это можно проделать и вручную. После установки становятся доступны консольные команды для управления хостингом. Создадим сайт Wordpress на php81 с сертификатом от Let's Encrypt:

# wo site create 330693.simplecloud.ru --wp --php81 -le

Эта команда выполнит следующие действия:
▪️ добавит репозитории Nginx, Php, MySQL в систему
▪️ установит все необходимые пакеты
▪️ создаст конфигурации Nginx и Php-fpm
▪️ получит tls сертификаты для домена
▪️ установит последнюю версию wordpress
▪️ создаст и настроит подключение к базе данных

В консоли вы получите адрес созданного сайта и учётную запись админа. При этом в директории /etc/nginx/sites-enabled будет создана конфигурация для сайта. Причём довольно навороченная. Там сразу будут лимиты на wp-cron.php и wp-login.php, ограничение доступа белым списком к xmlrpc.php, блокировка доступа к некоторым другим внутренностям Wordpress. В директории /var/www/330693.simplecloud.ru будут лежать исходники сайта, логи и некоторые настройки. В рутовском кроне /var/spool/cron/crontabs будут добавлены задания на обновление сертификатов и некоторые другие действия. Все конфигурации в стандартном формате и на своих местах. Искать ничего не надо.

Помимо Wordpress сайтов, можно создать обычный html или php:

# wo site create site.tld --html
# wo site create site.tld --mysql --php81

Из минусов сразу отмечу, то все сайты одной версии php создаются общим пулом php-fpm. Лучше было бы их разделять между собой.

Помимо управления сайтами, у WordOps есть и другие команды, с помощью которых, к примеру, можно:
● обновить системные пакеты
● настроить некоторые параметры ssh
● установить phpMyAdmin, Adminer, Dashboard, Netdata, MySQLTuner, eXtplorer Filemanager, Fail2ban, proftpd и некоторые другие программы
● посмотреть логи служб

Пример того, как может выглядеть настроенный Dashboard вашего сервера.

Про WordOps узнал случайно. Увидел упоминание в комментариях. Сам никогда не пользовался, но мне понравилось, как всё сделано - просто и удобно. Меняются системные конфигурации, при этом всё остаётся на своих местах. Можно руками что-то подправить и это ничего не сломает. Панель просто помогает выполнить настройку.

Подобный инструмент существенно упрощает и ускоряет настройку веб сервера. Желательно им пользоваться, если вы уже умеете настраивать всё это вручную сами. Либо вы далеки от темы настройки веб серверов, а вам его нужно поднять. Для веб разработчиков это отличный инструмент, чтобы поставить на какой-нибудь свой тестовый сервер для разработки.

В документации с примерами показано, как всем этим управлять. Например, добавляем FTP пользователя или используем удалённый mysql сервер.

Сайт / Исходники

#webserver
Часто доступ к веб ресурсам осуществляется не напрямую, а через обратные прокси. Причём чаще доступ именно такой, а не прямой. При возможности, я всегда ставлю обратные прокси перед сайтами даже в небольших проектах. Это и удобнее, и безопаснее, но немного более хлопотно в управлении.

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

Сразу покажу на практике, в чём заключается удобство Traefik и в каких случаях имеет смысл им воспользоваться. Для примера запущу через Traefik 2 проекта test1 и test2, состоящих из nginx и apache и тестовой страницы, где будет указано имя проекта. В этом примере будет наглядно виден принцип работы Traefik.

Запускаем Traefik через docker-compose.yaml:

# mkdir traefik && cd traefik
# mcedit docker-compose.yaml


services:
reverse-proxy:
image: traefik:v3.0
command: --api.insecure=true --providers.docker
ports:
- "80:80"
- "8080:8080"
networks:
- traefik_default
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
traefik_default:
external: true


# docker compose up


Можно сходить в веб интерфейс по ip адресу сервера на порт 8080. Пока там пусто, так как нет проектов. Создаём первый тестовый проект. Готовим для него файлы:

# mkdir test1 && cd test1
# mcedit docker-compose.yaml


services:
nginx:
image: nginx:latest
volumes:
- ./app/index.html:/app/index.html
- ./default.conf:/etc/nginx/conf.d/default.conf
labels:
- "traefik.enable=true"
- "traefik.http.routers.nginx-test1.rule=Host(`test1.server.local`)"
- "traefik.http.services.nginx-test1.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik_default"
networks:
- traefik_default
- test1

httpd:
image: httpd:latest
volumes:
- ./app/index.html:/usr/local/apache2/htdocs/index.html
networks:
- test1

networks:
traefik_default:
external: true
test1:
internal: true


# mcedit default.conf 

server {
listen 8080;
server_name _;

root /app;
index index.php index.html;

location / {
proxy_pass http://httpd:80;
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;
}


# mcedit app/index.html


It's Container for project test1


Запускаем этот проект:

# docker compose up


В веб интерфейсе Traefik, в разделе HTTP появится запись:

Host(`test1.server.local`) http nginx-test1@docker  nginx-test1

Он по меткам в docker-compose проекта test1 автоматом подхватил настройки и включил проксирование всех запросов к домену test1.server.local в контейнер nginx-test1. При этом сам проект test1 внутри себя взаимодействует по своей внутренней сети test1, а с Traefik по сети traefik_default, которая является внешней для приёма запросов извне.

Теперь можно скопировать проект test1, изменить в нём имя домена и имя внутренней сети на test2 и запустить. Traefik автоматом подцепит этот проект и будет проксировать запросы к test2.server.local в nginx-test2. Работу этой схемы легко проверить, зайдя откуда-то извне браузером на test1.server.local и test2.server.local. Вы получите соответствующую страницу index.html от запрошенного проекта.

К Traefik легко добавить автоматическое получение TLS сертификатов от Let's Encrypt. Примеров в сети и документации много, настроить не составляет проблемы. Не стал показывать этот пример, так как не уместил бы его в формат заметки. Мне важно было показать суть - запустив один раз Traefik, можно его больше не трогать. По меткам в контейнерах он будет автоматом настраивать проксирование. В некоторых ситуациях это очень удобно.

#webserver #traefik #devops
В работе веб сервера иногда есть необходимость разработчикам получить доступ к исходникам сайта напрямую. Проще всего и безопаснее это сделать по протоколу sftp. Эта на первый взгляд простая задача на деле может оказаться не такой уж и простой с множеством нюансов. В зависимости от настроек веб сервера, может использоваться разный подход. Я сейчас рассмотрю один из них, а два других упомяну в самом конце. Подробно рассказывать обо всех методах очень длинно получится.

Для примера возьму классический стек на базе nginx (angie) + php-fpm. Допустим, у нас nginx и php-fpm работают от пользователя www-data, исходники сайта лежат в /var/www/html. Владельцем файлов и каталогов будет пользователь и группа www-data. Типичная ситуация. Нам надо дать доступ разработчику напрямую к исходникам, чтобы он мог вносить изменения в код сайта.

Сразу предвижу комментарии на тему того, что так делать неправильно и в код лезть напрямую не надо. Я был бы только рад, если бы туда никто не лазил. Мне так проще управлять и настраивать. Но иногда надо и выбирать не приходится. Разработчиков выбираю не я, как и их методы работы.

Первым делом добавляю в систему нового пользователя, для которого не будет установлен shell. Подключаться он сможет только по sftp:

# useradd -s /sbin/nologin webdev
# passwd webdev

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

Находим в файле /etc/ssh/sshd_config строку и комментируем её:

#Subsystem   sftp  /usr/lib/openssh/sftp-server

Вместо неё добавляем:

Subsystem sftp internal-sftp -u 002
Match User webdev          
ChrootDirectory /var/www      
ForceCommand internal-sftp -u 002

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

# systemctl restart sshd

Если сейчас попробовать подключиться пользователем webdev, то ничего не получится. Особенность настройки ChrootDirectory в sshd в том, что у указанной директории владелец должен быть root, а у всех остальных не должно быть туда прав на запись. В данном случае нам это не помешает, но в некоторых ситуациях возникают неудобства. На остальных директориях внутри ChrootDirectory права доступа и владельцы могут быть любыми.

# chown root:root /var/www
# chmod 0755 /var/www

Теперь пользователь webdev может подключиться по sftp. Он сразу попадёт в директорию /var/www и не сможет из неё выйти дальше в систему. Осталось решить вопрос с правами. Я сделал так. Добавил пользователя webdev в группу www-data, а пользователя www-data в группу webdev:

# usermod -aG www-data webdev
# usermod -aG webdev www-data

Благодаря указанной umask 002 в настройках sshd все созданные пользователем webdev файлы будут иметь права 664 и каталоги 775. То есть будет полный доступ и у пользователя, и у группы. Так что веб сервер сможет нормально работать с этими файлами, и наоборот.

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

Если же вы заранее планируете прямой доступ к исходникам сайта или сайтов разными людьми или сервисами, то сделать лучше по-другому. Сразу создавайте под каждый сайт отдельного системного пользователя. Запускайте под ним php-fpm пул. Для каждого сайта он будет свой. Владельцем исходников сайта делайте этого пользователя и его группу. А пользователя, под которым работает веб сервер, добавьте в группу этого пользователя. Это более удобный, практичный и безопасный способ. Я его описывал в своей статье.

Третий вариант - создавайте новых пользователей для доступа к исходникам с таким же uid, как и у веб сервера. Чрутьте их в свои домашние каталоги, а сайты им добавляйте туда же через mount bind. Я когда то давно этот способ тоже описывал в статье.

#webserver
​​Для быстрого доступа к СУБД Mysql очень удобно использовать небольшой скрипт Adminer, который представляет из себя ровно один php файл и запускается на любом хостинге. Я бы не писал о нём в очередной раз, если бы не столкнулся на днях с некоторыми нюансами при его использовании, так что решил оформить в заметку, чтобы самому потом не забыть.

С помощью Adminer можно создать пользователя и базу данных, назначить или изменить права, посмотреть содержимое таблиц, выгрузить или загрузить дамп. Лично мне для административных целей больше ничего и не надо. Разработчики, работая над сайтом, обычно просят phpmyadmin. Я не очень его люблю, потому что он монструозный, для него надо ставить дополнительные расширения php, поддерживать это дело.

Если я пользуюсь сам, то обычно просто закидываю adminer на сайт, делаю, что мне надо и потом сразу удаляю. В этот раз решил оставить на некоторое время и закрыть через basic_auth в Nginx (на самом деле в Angie). Вроде бы нет ничего проще:

# wget https://github.com/vrana/adminer/releases/download/v4.8.1/adminer-4.8.1-mysql-en.php
# mv adminer-4.8.1-mysql-en.php /var/www/html/adminer.php

Закрываем паролем. Создаём файл с учёткой:

# htpasswd -c /etc/nginx/.htpasswd admineruser21

Добавляем в Nginx:

location /adminer.php {
auth_basic "Administrator’s Area";
  auth_basic_user_file /etc/nginx/.htpasswd;
}

Перезапускаю Nginx, проверяю. Пароль не спрашивает, доступ прямой. Всё внимательно проверяю, ошибок нет. Я 100 раз это проделывал. Тут нет никаких нюансов. Но не работает.

Быстро догадался, в чём тут дело. В Nginx есть определённые правила обработки locations. Я с этой темой когда-то разбирался подробно и кое-что в памяти осело. Ниже для php файлов уже есть location:

location ~ \.php$ {
....
}

Он с регулярным выражением, поэтому обрабатывается раньше и при первом же совпадении поиск прекращается. Так что до моего location с паролем очередь просто не доходит. Сделать надо так:

location ~ adminer.php {
  auth_basic "Administrator’s Area";
  auth_basic_user_file /etc/nginx/.htpasswd;
.......................  
}

Тут надо указать все те же настройки, что у вас стоят для всех остальных php файлов в location ~ \.php$. И расположить location с adminer выше location для остальных php файлов. Это важно, так как судя по документации:

Затем nginx проверяет location’ы, заданные регулярными выражениями, в порядке их следования в конфигурационном файле. При первом же совпадении поиск прекращается и nginx использует совпавший location.

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

#webserver #nginx #mysql
​​Ранее рассказывал о том, как можно выпустить самоподписанный сертификат, установить его в веб сервер и настроить доверие. В комментариях были замечания, что это неудобно делать вручную для многих серверов. То решение было описано для одного сервера. Ниже рассмотрю вариант, как решить эту же задачу, только для всей внутренней инфраструктуры.

Речь пойдёт про open source центр сертификации от компании SmallStep - step-ca. Его можно развернуть у себя и использовать для различных задач. Я расскажу на примере получения HTTPS-сертификатов X.509 для локальных веб ресурсов с помощью протокола ACME и одноимённой утилиты. Помимо этого на базе step-ca можно организовать:
выдачу SSH-сертификатов для пользователей
выпуск токенов единого входа OAuth OIDC
выпуск клиентских сертификатов X.509 для TLS аутентификации
Всё это описано в документации и в рамках заметки не раскрыть.

Установка step-ca описана в документации. Выглядит она относительно просто, так как есть готовые пакеты. А относительно, потому что юнит systemd и рабочие каталоги придётся делать вручную.

Вот инструкция для установки на Debian/Ubuntu:

# wget https://dl.smallstep.com/cli/docs-ca-install/latest/step-cli_amd64.deb
# dpkg -i step-cli_amd64.deb

# wget https://dl.smallstep.com/certificates/docs-ca-install/latest/step-ca_amd64.deb
# dpkg -i step-ca_amd64.deb

После установки делаем начальную настройку, но перед этим выполним ручную инициализацию. Покажу свои ответы на вопросы в тестовой среде, чтобы вам не гадать, что там отвечать. Хотя по смыслу понятно.

# step-cli ca init
Deployment Type: Standalone
(e.g. Smallstep): PrivateCA (можно любое имя использовать)
(e.g. :443 or 127.0.0.1:443): :443
(e.g. you@smallstep.com): zeroxzed@gmail.com
[leave empty and we'll generate one]:
Password: E}b;-9DU9UyАВ8TU7$FINl-T8OM

Теперь переносим настройки в /etc/step-ca и запускаем как службу.

# useradd --user-group --system --home /etc/step-ca --shell /bin/false step
# setcap CAP_NET_BIND_SERVICE=+eip $(which step-ca)
# mkdir /etc/step-ca
# mv $(step path)/* /etc/step-ca

# touch /etc/step-ca/password.txt
# chmod 600 /etc/step-ca/password.txt
# mcedit /etc/step-ca/password.txt
# chown -R step:step /etc/step-ca

В файл password.txt записываем пароль от CA, который был создан во время инициализации. В файлах /etc/step-ca/config/ca.json и defaults.json меняем все пути с /root/.step на /etc/step-ca. И создаём юнит для systemd:

# mcedit /etc/systemd/system/step-ca.service

Содержимое не привожу, оно длинное, в заметку не уместится. Взял 1 в 1 из документации, он же в репозитории.

# systemctl daemon-reload
# systemctl enable --now step-ca

Служба должна запуститься на порту 443. Можно браузером зайти. Там будет страница с ошибкой, потому что никакого веб интерфейса нет, но тем не менее будет видно, что сервис работает.

Сразу отмечу важный нюанс. По умолчанию step-ca выпускает сертификаты на 24 часа. Для каких-то задач это, наверное, нормально, но для веб серверов не вижу смысла перевыпускать так часто, даже с учётом того, что это автоматизировано. Можете оставить так, а можете увеличить время. Тут об этом рассказано в доках.

Активируем ACME провизионер в step-ca:

# step ca provisioner add acme --type ACME

Эта команда добавит некоторые настройки в ca.json. Перезапускаем службу:

# systemctl restart step-ca

Теперь можно брать acme.sh или любой другой клиент (certbot и т.д.) и настраивать выпуск сертификатов через свой CA. Для этого надо либо на клиентских системах добавить root_ca.crt в системные, либо при запуске клиента указывать явно:

acme.sh --issue --standalone -d debian12-vm \
    --server https://10.20.1.36/acme/acme/directory \
    --ca-bundle ~/certs/root_ca.crt \
    --fullchain-file debian12-vm.crt \
    --key-file debian12-vm.key

Работаем с этим CA так же, как и с другими: добавляем серты в веб сервера, настраиваем автообновление. И не забываем в клиентские машины добавить CA в доверенные, чтобы в браузерах не было предупреждения.

#security #webserver
Небезызвестный Василий Озеров открыл свой youtube канал (вовремя, как раз к блокировке ютуба) и выложил первое видео. Для тех, кто не знает, поясню. Василий - сооснователь школы Rebrain, рекламу которой вы тут периодически видите, и аутсорс компании Fevlake.

Ролик получился очень информативным, наглядным, нескучным, хоть и длинным - 50 минут. Я посмотрел его весь без перемотки и не в режиме только прослушивания. Большую часть того, что услышал, знал, но не всё. Почерпнул для себя новую информацию.

▶️ Разбор TLS параметров в Nginx: Подробная инструкция по настройке TLS

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

📌 Замена TLS 1.2 на TLS 1.3 даёт уменьшение времени отклика веб сервера. Тест Василия показал уменьшение со 170 мс до 130 мс только из-за смены версии протокола. Это связано с более быстрой инициализацией подключения клиента к серверу.

📌 Сертификат сервера, основанный на протоколе ECDSA, уменьшает при прочих равных условиях нагрузку на CPU сервера в сравнении с RSA где-то на 20-25%. Это связано с гораздо меньшей длиной сертификатов ECDSA.

📌 В конфигурацию Nginx можно добавить оба сертификата: ECDSA и RSA. Это обеспечит поддержку всех клиентов, и новых, и сильно старых, которые ECDSA не поддерживают.

📌 Если у вас только один веб сервер, то нет необходимости включать ssl_session_tickets, достаточно только ssl_session. Тикеты нужны, когда клиенту могут отвечать разные веб сервера с одинаковыми приватными ключами.

Очень подробно эти и некоторые другие моменты разобраны в видео. Рекомендую к просмотру. Реально полезная база, актуальная для любых сервисов, где используется TLS. Ну и не забывайте про лайк и подписку на канал, чтобы у Василия была мотивация записывать больше роликов. Видно, что для этого проделана большая работа.

#nginx #webserver
Если хотите быстро потестировать настройки веб сервера на предмет ограничения числа одновременных соединений в Nginx совместно с баном Fail2Ban, могу предложить готовый публичный сервис:

https://loadest.nodes-studio.com

Он вообще платный, причём принимает оплату в Bitcoin, что сразу вызывает подозрение. Так выглядят платные панели управления для ддоса. Подобных сервисов много и они не особо шифруются, а представляют себя как услуги по тестированию сайтов.

Этот сервис даёт небольшой тестовый баланс, который позволяет побомбить запросами какой-то сайт. Причём любой. Достаточно указать его адрес с главной страницы и свою почту (не проверяется), на которую будет зарегистрирован личный кабинет. В нём сразу же начнётся тест по нагрузке сайта.

Я толком не понял, с какой интенсивностью происходит тестирование, так как у меня все IP адреса сервиса улетели в бан за превышение лимита в 30 запросов в секунду с одного IP. Отработала настройка Nginx:

limit_req_zone $binary_remote_addr zone=lim_20r:10m rate=20r/s;

И фильтр Fail2Ban на записи о превышении этого лимита в лог файле nginx error.log с соответствующей строкой:

limiting requests, excess: 30.080 by zone "lim_20r", client: 194.87.110.21

Забанил IP адреса в соответствии вот с этим правилом:

[Definition]
ngx_limit_req_zones = [^"]+
failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>,
ignoreregex =.
datepattern = {^LN-BEG}

Не рассказываю подробно о настройке Fail2Ban, так как заметка не про него, а про сервис. Мне понравилось, что без лишних телодвижений запустил тестирование с посторонних IP. Мой указанный почтовый ящик, кстати, был указан в referrer запросов от сервиса:

referer=http://loadest.io user_agent="<username@gmail.com> via http://loadest.io"

Сервис, судя по всему, российский. Посмотрел заголовки писем, которые он шлёт. Там почтовый сервер из зоны .ru используется. И бомбил он меня российскими IP адресами.

#нагрузочное_тестирование #webserver

🦖 Selectel — дешёвые и не очень дедики с аукционом!
Please open Telegram to view this post
VIEW IN TELEGRAM
Заметил по статистике, что один сайт стали донимать какие-то роботы сканированием несуществующих страниц. В статистике 404 ошибок стало существенно больше, чем 200-х ответов. Я обычно не практикую блокировку по этому признаку, так как можно словить неявные проблемы от каких-нибудь поисковиков или других легитимных пользователей. В этот раз решил сделать исключение.

С помощью fail2ban нетрудно выполнить блокировку. Не буду подробно рассказывать про настройку fail2ban, так как это не тема заметки. Покажу, как я забанил тех, от кого сыпется много 404 кодов ответа веб сервера.

Для начала создаём правило фильтрации в директории /etc/fail2ban/filter.d. Назвал его nginx-4xx.conf:

[Definition]
#failregex = ^<HOST>.*"(GET|POST).*" (404|429) .*$
failregex = ^<HOST>.*"(GET|POST).*" *.* (status=404|status=429) .*$
ignoreregex =

Закомментированная строка подойдёт для стандартного формата логов Nginx или Apache. Вторая для моего изменённого. Я обычно использую свой формат, если собираю логи в ELK и анализирую. У меня есть набор готовых grok фильтров для этого. Вот мой модернизированный формат логов:

log_format full '$remote_addr - $host [$time_local] "$request" '
  'request_length=$request_length '
  'status=$status bytes_sent=$bytes_sent '
  'body_bytes_sent=$body_bytes_sent '
  'referer=$http_referer '
  'user_agent="$http_user_agent" '
  'upstream_status=$upstream_status '
  'request_time=$request_time '
  'upstream_response_time=$upstream_response_time '
  'upstream_connect_time=$upstream_connect_time '
  'upstream_header_time=$upstream_header_time';

Соответственно, получаем вот такую запись в логе:

178.218.43.55 - site01.ru [01/Oct/2024:13:46:24 +0300] "GET /about HTTP/2.0" request_length=123 status=200 bytes_sent=489 body_bytes_sent=8 referer=https://site01.ru/ user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101" upstream_status=200 request_time=0.075 upstream_response_time=0.069 upstream_connect_time=0.000 upstream_header_time=0.069

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

%{IPORHOST:remote_ip} - %{DATA:virt_host} \[%{HTTPDATE:access_time}\] \"%{WORD:http_method} %{DATA:url} HTTP/%{NUMBER:http_version}\" request_length=%{INT:request_length} status=%{INT:status} bytes_sent=%{INT:bytes_sent} body_bytes_sent=%{NUMBER:body_bytes_sent} referer=%{DATA:referer} user_agent=\"%{DATA:user_agent}\" upstream_status=%{DATA:upstream_status} request_time=%{NUMBER:request_time} upstream_response_time=%{DATA:upstream_response_time} upstream_connect_time=%{DATA:upstream_connect_time} upstream_header_time=%{DATA:upstream_header_time}

Приведённая в nginx-4xx.conf регулярка находит 404 и 429 ответы в таком логе. Я не силён в написании регулярок, поэтому хз, правильно составил или нет, но на практике работает. В составлении регулярок помогают вот эти сервисы:

https://regex101.com/
https://regexper.com/

Постоянно ими пользуюсь. Вот тут была хорошая подборка по этой теме. Дальше создаём файл настроек блокировки в /etc/fail2ban/jail.d, назвал его так же nginx-4xx.conf:

[nginx-4xx]
enabled = true
filter = nginx-4xx
port = http,https
action = iptables-multiport[name=Nginx404, port="http,https", protocol=tcp]
logpath = /web/sites/*/logs/access.log
maxretry = 30
findtime = 10m
bantime = 30m

Баню на 30 минут тех, кто за последние 10 минут получил 30 раз 404 ответ. На мой взгляд это достаточно мягкое условие, которое отсечёт явных ботов, но не тронет легитимных пользователей.

Если кто-то тоже банит по признаку 404 ответа, поделитесь опытом, как это лучше делать и к каким проблемам может привести. Я немного понаблюдал, вроде никто лишний не попадается на это правило.

#fail2ban #webserver
Одной из обязательных настроек, которую делаю на новом сервере, касается ротации текстовых логов. Несмотря на то, что их активно заменяют бинарные логи systemd, я предпочитаю возвращать текстовые логи syslog. Для этого на чистый сервер устанавливаю rsyslog:

# apt install rsyslog

Далее выполняю некоторые настройки, которые относятся к ротации логов с помощью logrotate. В первую очередь в фале /etc/logrotate.conf включаю параметр:

dateext

Он нужен, чтобы в имени файла была указана дата, когда лог-файл был ротирован. С этой настройкой связан один очень важный момент, если вы хотите ротировать логи чаще, чем раз в сутки. По умолчанию logrotate во время ротации лога с этой настройкой переименовывает текущий лог файл, назначая ему имя вида access.log.20241001.gz. То есть в имени файла будет указан текущий день. Второй раз в этот день ротация закончится ошибкой, так как logrotate скажет, что не могу переименовать лог, так как файл с таким именем уже существует. Для тех логов, что точно будут ротироваться чаще, чем раз в сутки, нужно обязательно добавить параметр:

dateformat -%Y-%m-%d_%H-%s

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

Далее в обязательном порядке нужно настроить запуск logrotate чаще, чем раз в сутки. По умолчанию в Debian запуск logrotate настроен в /etc/cron.daily и запускается не чаще раза в сутки. Я обычно просто добавляю запуск в /etc/crontab каждые 5 минут:

*/5 * * * * root logrotate /etc/logrotate.conf

Эти настройки особенно актуальны для веб серверов, где очень быстро могут разрастись лог файлы.

Все возможные настройки файлов logrotate можно почитать в man. Там довольно подробно всё описано. Если не хочется читать man, можно воспользоваться простым конфигуратором:

🌐 https://scoin.github.io/logrotate-tool

Там наглядно перечислены настройки с описанием. Можно мышкой потыкать и посмотреть, как меняется конфигурационный файл. Вот пример моего файла ротации логов веб сервера:

/web/sites/*/logs/*.log {
  create 644 angie webuser
  size=50M
  dateext
  dateformat -%Y-%m-%d_%H-%s
  missingok
  rotate 30
  compress
  notifempty
  sharedscripts
  postrotate
if [ -f /run/angie.pid ]; then
kill -USR1 $(cat /run/angie.pid)
fi
  endscript
}

Маска /web/sites/*/logs/*.log захватывает все виртуальные хосты веб сервера, где логи располагаются в директориях:

◽️/web/sites/site01.ru/logs/
◽️/web/sites/site02.ru/logs/
и т.д.

Когда будете настраивать ротацию логов, проверяйте, чтобы у службы, для которой управляем логами, в итоге оказался доступ к этим логам. Я иногда ошибаюсь и потом приходится разбираться, почему после ротации логи нулевого размера. Это актуально, когда логи куда-то выносятся из /var/log и отдельно настраиваются права, например, для того, чтобы какие-то пользователи имели к ним доступ, к примеру, только на чтение.

#linux #logs #logrotate #webserver
Please open Telegram to view this post
VIEW IN TELEGRAM
Немного информации для тех, кто имеет дело с обычными веб серверами под php сайты. Вчера провозился несколько часов с одним сайтом, который передали разработчики для публикации. Причём провозился из-за ерунды. Это был немного навороченный мультиязычный сайт на базе Wordpress, но с полностью самописной темой.

Я этих вордпрессов десятки устанавливал и поддерживал. Никак не ожидал проблем с этим движком. Но разработчики сумели меня удивить и нагрузить работой. Для начала пришлось повозиться с тем, что режим работы сайта http или https был жёстко зашит в коде сайта. И не где-то в настройках Wordpress или базе, а в коде темы. Это создавало некоторые проблемы при публикации за Cloudflare и Nginx в режиме proxy_pass. Пока всё развернул, разобрался, нашёл и вычистил в коде, немного подустал. Плюс, сайт под Apache разрабатывали, запустил под Nginx.

В итоге на сайте кое-где всплывали ошибки php, что-то не работало. В логе тоже ошибки и предупреждения со стороны php. Причём ошибки какие-то странные, типа скобки не закрыты, переменная не объявлена и т.д. Сначала подумал, что сайт написали под старую версию php. На веб сервере стояла относительно свежая 8.2. Уточнил у разработчиков, у них на 8.1 нормально работает. Разница в этих версиях не такая большая, должно нормально работать. Потом подумал, что по ошибке скинули какой-то черновик, а не итоговую работу.

Немного поразбирался в ошибках и выяснил, в чём проблема. В php по умолчанию параметр short_open_tag выключен. Это нормальная история, так как стандартному коду Wordpress он не нужен. Мне и в голову не пришло его включить. В версиях php до 6-й он был включен, потом стали отключать из соображений совместимости с другим кодом. Например, с тэгами <?xml.

В коде этого сайта были конструкции типа <? вместо <?php, а для их корректной работы как раз и нужен параметр short_open_tag. Есть проекты, где явно пишут, что надо включить этот параметр. А тут мне никто ни слова насчёт него не сказал. Включил параметр и всё заработало.

Если где-то придётся писать код php, то пишите сразу полные тэги <?php, а не короткие. Сейчас это более правильный подход, который улучшает переносимость проекта и не вызывает проблем с совместимостью с другим кодом, который использует тэги. Вот наглядный пример, где будут проблемы с короткими тэгами:

<?xml version="1.0"?>

Это элемент xml разметки, а с включённым short_open_tag это будет интерпретироваться как php код.

#webserver #php
Если обратиться к веб серверу на базе Nginx по IP адресу, то увидите либо первый виртуальных хост в конфигурации, либо тот, где указана директива default в параметре listen. Показывать виртуальный хост не очень хочется, поэтому обычно на IP адрес ставят какую-то заглушку. Например, так:

server {
    listen 80 default_server;
    server_name _;
    return 404;
}

С 80-м портом и протоколом HTTP проблем обычно нет. А вот с HTTPS есть нюансы, так как нам нужно использовать сертификат. Доверенный сертификат на IP адрес получить нельзя. По крайней мере я не знаю, как это сделать. Если сертификат не указать, то будет использован сертификат первого в конфигурации виртуального хоста, где он указан. То есть если сделать вот так:

server {
    listen 443 ssl default_server;
    server_name _;
    return 404;
}

То пользователь, который зайдёт по IP адресу на сервер, увидит сначала предупреждение о том, что имя домена в сертификате не соответствует домену в адресной строке браузера. А когда согласится перейти по адресу, в свойствах сертификата можно будет увидеть адрес виртуального домена, сертификат которого использован.

Можно выйти из этой ситуации, выпустив самоподписанный сертификат-пустышку, не заполняя там вообще никаких полей:

# openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/nginx/certs/nginx.key -out /etc/nginx/certs/nginx.crt

Используем его:

server {
    listen 443 ssl default_server;
    server_name _;
    ssl_certificate /etc/nginx/certs/nginx.crt;
    ssl_certificate_key /etc/nginx/certs/nginx.key;
    return 404;
}

Больше мы домены не светим, но пользователь по прежнему видит предупреждение о сертификате, что тоже не очень красиво. Хотя в целом можно оставить и так. Я всегда так и делал. А недавно увидел новое для себя решение этой же проблемы, чтобы пользователю показать сразу ошибку, без всяких предупреждений. Выглядит это вот так. Показываю сразу итоговый вариант:

server {
    listen 80 default_server;
    listen 443 ssl default_server;
    server_name _;
    ssl_reject_handshake on;
    return 404;
}

Объединил для удобства оба протокола. Ключевой момент тут в настройке ssl_reject_handshake on. Когда она включена, веб сервер отклоняет все попытки соединения, где адрес сервера не совпадает с тем, что указано в директиве server_name. Тут у нас в этой директиве _, так что все соединения будут сразу отклоняться. Пользователь сразу увидит ошибку без необходимости выполнять какие-либо действия.

Не увидел тут никаких подводных камней. Взял на вооружение, обновил свой стандартный конфиг default.conf для подобных настроек.

❗️Если заметка вам полезна, не забудьте 👍 и забрать в закладки.

#nginx #webserver #angie