Русский
Русский
English
Статистика
Реклама

Letsencrypt

Lets Encrypt перейдут на цепочку с использованием сразу двух сертификатов

12.01.2021 20:07:52 | Автор: admin


Совсем недавно веб-сайты с Let's Encrypt (225 миллионов доменов) рисковали остаться без трафика с устройств на старых версиях Android (34% от всех девайсов). По заявлениям Let's Encrypt они нашли выход из ситуации и IdenTrust выпустил для них новый трехлетний перекрестный знак для корня ISRG X1 со своего корневого центра сертификации DST X3. Но действительно ли владельцам сайтов с Let's Encrypt можно расслабиться?


Истекающий срок действия корневого сертификата Let's Encrypt означает, что каждое третье устройство Android (более 34% устройств используют версии 7.1.1 и ниже) будет заблокировано с миллионов сайтов, защищенных системой Let's Encrypt.


Данная проблема результат того, что Let's Encrypt больше не использует перекрестную подпись со сторонним корневым сертификатом. Из-за этого посетители веб-сайтов на старых версиях Android будут заблокированы от доступа к сайтам, защищенным с помощью Let's Encrypt.



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


Что именно привело к такому повороту событий? Кого могут коснутся изменение? Что могут сделать владельцы сайтов, чтобы минимизировать ущерб?


Предыстория проблемы


Корни проблемы исходят из 2015 года, когда компания Lets Encrypt была основана группой Internet Security Research Group (ISRG) и их партнерами. Поскольку могут потребоваться годы на то, чтобы все ОС и браузеры начали доверять их собственному сертификату, они подписали свои сертификаты перекрестно с доверенным корнем существующего ЦС (IdenTrust и их сертификатом DST Root X3), что является типичным поведением для новых центров сертификации.


Это позволило Lets Encrypt немедленно начать выпуск сертификатов, которым можно было бы доверять в Интернете. Но перенесемся в наши дни: IdenTrust DST Root X3 приближается к дате истечения срока действия 30 сентября 2021 года.


Let's Encrypt попыталась подготовиться к истечению срока действия, выпустив собственный корневой сертификат ISRG Root X1. К сожалению, у него пока нет того всеобъемлющего доверия, которое было у их корня IdenTrust, поэтому пользователи на некоторых старых платформах будут заблокированы от доступа к любому веб-сайту, который использует сертификат Let's Encrypt SSL/TLS.


Кто именно пострадает?


Пользователи старых версий Android


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


Стоит уточнить, что основная проблема появилась не по вине Let's Encrypt, а в медленном обновлении ПО для многих платформ.


Почему устройства на Android не обновляются?


Производители и операторы мобильной связи обычно подгоняют ОС под свои нужды перед тем, как устанавливать ее на девайсы и отдавать конечным пользователям. Когда Google выпускает новое обновление Android, производителям и операторам нужно сначала внести изменения в свои собственные версии ПО, а уже потом обновить своих клиентов. Вдобавок производители постоянно выпускают новые устройства, которые нужно продавать, а поэтому на устаревших моделях обновления ОС часто просто не работают. Вот почему в настоящее время есть миллионы устройств Android с устаревшими операционными системами.


Java


Старые версии Java также подвержены изменению корневого каталога. Любые клиенты, использующие версии Java до 1.8.0_141-b15, будут получать предупреждения или ошибки при обнаружении сертификатов Lets Encrypt.


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


Решение проблемы: два сертификата вместо одного


IdenTrust согласился выпустить трехлетний перекрестный знак для ISRG X1. Новый перекрестный знак немного необычный, так как действует после истечения срока действия DST Root CA X3. Это решение работает, потому что Android намеренно не устанавливает даты истечения срока действия сертификатов, используемых в качестве якорей доверия. Таким образом, Lets Encrypt собирается предоставить своим подписчикам цепочку, содержащую как ISRG Root X1, так и DST Root CA X3, обеспечивая бесперебойное обслуживание всех пользователей и избегая потенциальных сбоев.



Ранее запланированное на 11 января переключение цепочки не состоялось, а переход на новую цепочку будет произведен в начале февраля. Тем не менее, во избежание возможных проблем совместимостью можно использовать следующие рекомендации:


Предложить посетителям обновить Android


Как вариант можно предупредить посетителей со старыми версиями Android о том, что нужно обновить их перед использованием вашего сайта, но:


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

Предложить использовать Firefox Mobile


Firefox Mobile полагается на собственный (регулярно обновляемый) список корневых сертификатов, а не на список операционных систем. Но заставить пользователя перейти на другой браузер, пожалуй, сложнее, чем заставить его обновиться.


Прекратить поддержку более старых версий


Можно прекратить поддержку более старых версий, но это может привести к:


  • Сокращению трафика
  • Увеличения запросов в службу поддержки

Владельцы сайтов на ACME


Владельцы сайтов, использующие ACME, могут изменить настройки своих клиентов, чтобы продолжать использовать сертификаты Let's Encrypt с перекрестной подписью. Однако это будет работать только до сентября 2021 года. Тем не менее, это поможет выиграть время, чтобы разработать долгосрочное решение.


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


image

Подробнее..

Перевод Автоматизируем установку WordPress с NGINX Unit и Ubuntu

23.10.2020 08:18:45 | Автор: admin


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


В этой статье мы постараемся собрать лучшее из двух подходов, предоставляя скрипт на bash для автоматической установки WordPress на Ubuntu, а также пройдемся по нему, поясняя, что делает каждый его кусочек, а также на какие компромиссы мы пошли при его разработке. Если вы опытный пользователь можете пропустить текст статьи и просто взять скрипт для модификации и использования в ваших окружениях. На выходе скрипта получается настраиваемая установка Wordpress с поддержкой Lets Encrypt, работающая на NGINX Unit и пригодная для промышленного применения.


Разработанная архитектура для развертывания WordPress с использованием NGINX Unit описана в более старой статье, сейчас мы также дополнительно настроим вещи, которые там не были охвачены (как и во многих других руководствах):


  • WordPress CLI
  • Let's Encrypt и сертификаты TLS\SSL
  • Автоматическое обновление сертификатов
  • Кэширование NGINX
  • Сжатие NGINX
  • Поддержка HTTPS и HTTP/2
  • Автоматизация процесса

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


Требования


  • Сервер-контейнер (LXC или LXD), виртуальная машина, или обычный железный сервер, с не менее чем 512Мб оперативной памяти и установленной Ubuntu 18.04 или более свежей.
  • Доступные из интернета порты 80 и 443
  • Доменное имя, связанное с публичным ip-адресом этого сервера
  • Доступ с правами root (sudo).

Обзор архитектуры


Архитектура такая же, как было описано ранее, трехуровневое web-приложение. Оно состоит из скриптов PHP, исполняемых на обработчике PHP, и статических файлов, обрабатываемых веб-сервером.



Общие принципы


  • Многие команды для настройки в скрипте обернуты в условия (if) для идемпотентности: скрипт можно запускать несколько раз без риска изменения настроек, которые уже готовы.
  • Скрипт старается устанавливать ПО из репозиториев, так что вы можете применять обновления системы в одну команду (apt upgrade для Ubuntu).
  • Команды стараются определить, что они запускаются в контейнере, чтобы изменить соответствующим образом свои настройки.
  • Для того, чтобы задать число запускаемых процессов\потоков в настройках, скрипт пробует угадать автоматические параметры настройки для работы в контейнерах, виртуальных машинах, железных серверах.
  • При описании настроек всегда думаем в первую очередь об автоматизации, которая, как мы надеемся, станет основой для создания вашей собственной инфраструктуры как кода.
  • Все команды запускаются от пользователя root, потому что они изменяют основные системные настройки, но непосредственно WordPress работает от обычного пользователя.

Установка переменных окружения


Установите следующие переменные окружения, прежде чем запускать скрипт:


  • WORDPRESS_DB_PASSWORD пароль к базе данных WordPress
  • WORDPRESS_ADMIN_USER имя администратора WordPress
  • WORDPRESS_ADMIN_PASSWORD пароль администратора WordPress
  • WORDPRESS_ADMIN_EMAIL email администратора WordPress
  • WORDPRESS_URL полный URL сайта WordPress, начиная с https://.
  • LETS_ENCRYPT_STAGING пустая по-умолчанию, но, выставив значение в 1, вы будете использовать staging сервера Lets Encrypt, необходимые для частого запроса сертификатов при тестировании ваших настроек, иначе Lets Encrypt может временно заблокировать ваш ip-адрес из-за большого числа запросов.

Скрипт проверяет, что эти связанные с WordPress переменные выставлены, и завершает работу, если нет.
Строки скрипта 572-576 проверяют значение LETS_ENCRYPT_STAGING.


Установка производных переменных окружения


Скрипт в строках 55-61 выставляет следующие переменные окружения, либо в некоторое жестко заданное значение, либо с применением значения, полученного из переменных, установленных в предыдущем разделе:


  • DEBIAN_FRONTEND="noninteractive" сообщает приложениям, что они запускаются в скрипте и нет возможности взаимодействия с пользователем.
  • WORDPRESS_CLI_VERSION="2.4.0" версия приложения WordPress CLI.
  • WORDPRESS_CLI_MD5= "dedd5a662b80cda66e9e25d44c23b25c" контрольная сумма исполняемого файла WordPress CLI 2.4.0 (версия указывается в переменной WORDPRESS_CLI_VERSION). Скрипт на 162 строке использует это значение для проверки, что был скачан корректный файл WordPress CLI.
  • UPLOAD_MAX_FILESIZE="16M" максимальный размер файла, который может быть закачан в WordPress. Эта настройка используется в нескольких местах, так что проще задавать ее в одном месте.
  • TLS_HOSTNAME= "$(echo ${WORDPRESS_URL} | cut -d'/' -f3)" hostname системы, извлекаемый из переменной WORDPRESS_URL. Используется для получения соответствующих TLS/SSL сертификатов от Lets Encrypt, а также для внутренней проверки WordPress.
  • NGINX_CONF_DIR="/etc/nginx" путь к каталогу с настройками NGINX, включая основной файл nginx.conf.
  • CERT_DIR="/etc/letsencrypt/live/${TLS_HOSTNAME}" путь к сертификатам Lets Encrypt для сайта WordPress, получаемый из переменной TLS_HOSTNAME.

Назначение hostname WordPress серверу


Скрипт устанавливает hostname серверу, чтобы значение соответствовало доменному имени сайта. Это не обязательно, но так удобнее отправлять исходящую почту через SMTP при настройке единственного сервера, как это настраивается скриптом.


код скрипта
# Change the hostname to be the same as the WordPress hostnameif [ ! "$(hostname)" == "${TLS_HOSTNAME}" ]; then  echo " Changing hostname to ${TLS_HOSTNAME}"  hostnamectl set-hostname "${TLS_HOSTNAME}"fi

Добавление hostname в /etc/hosts


Дополнение WPCron используется для запуска периодических задач, требует, чтобы WordPress мог получить доступ к самому себе через HTTP. Чтобы убедиться, что WP-Cron работает корректно на всех окружениях, скрипт добавляет строчку в файл /etc/hosts, так что WordPress может получить доступ к самому себе через интерфейс loopback:


код скрипта
# Add the hostname to /etc/hostsif [ "$(grep -m1 "${TLS_HOSTNAME}" /etc/hosts)" = "" ]; then  echo " Adding hostname ${TLS_HOSTNAME} to /etc/hosts so that WordPress can ping itself"  printf "::1 %s\n127.0.0.1 %s\n" "${TLS_HOSTNAME}" "${TLS_HOSTNAME}" >> /etc/hostsfi

Установка инструментов, требуемых для последующих шагов


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


код скрипта
# Make sure tools needed for install are presentecho " Installing prerequisite tools"apt-get -qq updateapt-get -qq install -y \  bc \  ca-certificates \  coreutils \  curl \  gnupg2 \  lsb-release

Добавление репозиториев NGINX Unit и NGINX


Скрипт устанавливает NGINX Unit и NGINX с открытым исходным кодом из официальных репозиториев NGINX, чтобы удостовериться, что используются версии с последними обновлениями безопасности и исправлениями ошибок.


Скрипт добавляет репозиторий NGINX Unit, а затем репозиторий NGINX, добавляя ключ репозиториев и файлы настроек apt, задающих доступ к репозиториям через интернет.


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


код скрипта
# Install the NGINX Unit repositoryif [ ! -f /etc/apt/sources.list.d/unit.list ]; then  echo " Installing NGINX Unit repository"  curl -fsSL https://nginx.org/keys/nginx_signing.key | apt-key add -  echo "deb https://packages.nginx.org/unit/ubuntu/ $(lsb_release -cs) unit" > /etc/apt/sources.list.d/unit.listfi# Install the NGINX repositoryif [ ! -f /etc/apt/sources.list.d/nginx.list ]; then  echo " Installing NGINX repository"  curl -fsSL https://nginx.org/keys/nginx_signing.key | apt-key add -  echo "deb https://nginx.org/packages/mainline/ubuntu $(lsb_release -cs) nginx" > /etc/apt/sources.list.d/nginx.listfi

Установка NGINX, NGINX Unit, PHP MariaDB, Certbot (Lets Encrypt) и их зависимостей


Как только все репозитории добавлены, обновляем метаданные и устанавливаем приложения. Пакеты, устанавливаемые скриптом, также включают расширения PHP, рекомендуемые при запуске WordPress.org


код скрипта
echo " Updating repository metadata"apt-get -qq update# Install PHP with dependencies and NGINX Unitecho " Installing PHP, NGINX Unit, NGINX, Certbot, and MariaDB"apt-get -qq install -y --no-install-recommends \  certbot \  python3-certbot-nginx \  php-cli \  php-common \  php-bcmath \  php-curl \  php-gd \  php-imagick \  php-mbstring \  php-mysql \  php-opcache \  php-xml \  php-zip \  ghostscript \  nginx \  unit \  unit-php \  mariadb-server

Настройка PHP для использования с NGINX Unit и WordPress


Скрипт создает файл настроек в каталоге conf.d. Тут задается максимальный размер загружаемых файлов для PHP, включается вывод ошибок PHP в STDERR, так что они будут записаны в журнал NGINX Unit, а также перезапускается NGINX Unit.


код скрипта
# Find the major and minor PHP version so that we can write to its conf.d directoryPHP_MAJOR_MINOR_VERSION="$(php -v | head -n1 | cut -d' ' -f2 | cut -d'.' -f1,2)"if [ ! -f "/etc/php/${PHP_MAJOR_MINOR_VERSION}/embed/conf.d/30-wordpress-overrides.ini" ]; then  echo " Configuring PHP for use with NGINX Unit and WordPress"  # Add PHP configuration overrides  cat > "/etc/php/${PHP_MAJOR_MINOR_VERSION}/embed/conf.d/30-wordpress-overrides.ini" << EOM; Set a larger maximum upload size so that WordPress can handle; bigger media files.upload_max_filesize=${UPLOAD_MAX_FILESIZE}post_max_size=${UPLOAD_MAX_FILESIZE}; Write error log to STDERR so that error messages show up in the NGINX Unit logerror_log=/dev/stderrEOMfi# Restart NGINX Unit because we have reconfigured PHPecho " Restarting NGINX Unit"service unit restart

Задание настроек базы данных MariaDB для WordPress


Мы выбрали MariaDB вместо MySQL, поскольку у нее больше активность сообщества, кроме того, она, возможно, предоставляет более высокую производительность по умолчанию (вероятно, тут все проще: чтобы поставить MySQL, надо добавить еще один репозиторий, прим. переводчика).


Скрипт создает новую базу данных и создает учетные данные для доступа WordPress через интерфейс loopback:


код скрипта
# Set up the WordPress databaseecho " Configuring MariaDB for WordPress"mysqladmin create wordpress || echo "Ignoring above error because database may already exist"mysql -e "GRANT ALL PRIVILEGES ON wordpress.* TO \"wordpress\"@\"localhost\" IDENTIFIED BY \"$WORDPRESS_DB_PASSWORD\"; FLUSH PRIVILEGES;"

Установка программы WordPress CLI


На этом шаге скрипт устанавливает программу WP-CLI. С его помощью можно установить и управлять настройками WordPress без необходимости ручной правки файлов, обновления базы или входа в панель управления. Также с его помощью можно установить темы и дополнения и выполнить обновление WordPress.


код скрипта
if [ ! -f /usr/local/bin/wp ]; then  # Install the WordPress CLI  echo " Installing the WordPress CLI tool"  curl --retry 6 -Ls "https://github.com/wp-cli/wp-cli/releases/download/v${WORDPRESS_CLI_VERSION}/wp-cli-${WORDPRESS_CLI_VERSION}.phar" > /usr/local/bin/wp  echo "$WORDPRESS_CLI_MD5 /usr/local/bin/wp" | md5sum -c -  chmod +x /usr/local/bin/wpfi

Установка и настройка WordPress


Скрипт устанавливает последнюю версию WordPress в каталог /var/www/wordpress, а также изменяет настройки:


  • Соединение с базой данных работает через unix domain socket вместо TCP на loopback, чтобы сократить трафик TCP.
  • WordPress добавляет префикс https:// к URL, если клиенты соединяются с NGINX по протоколу HTTPS, а также отправляет удаленный hostname (как это предоставляет NGINX) в PHP. Мы применяем кусочек кода, чтобы это настроить.
  • WordPress нужно HTTPS для входа
  • Структура URL по молчанию основывается на ресурсах
  • Выставляются правильные права на файловой системе для каталога WordPress.

код скрипта
if [ ! -d /var/www/wordpress ]; then  # Create WordPress directories  mkdir -p /var/www/wordpress  chown -R www-data:www-data /var/www  # Download WordPress using the WordPress CLI  echo " Installing WordPress"  su -s /bin/sh -c 'wp --path=/var/www/wordpress core download' www-data  WP_CONFIG_CREATE_CMD="wp --path=/var/www/wordpress config create --extra-php --dbname=wordpress --dbuser=wordpress --dbhost=\"localhost:/var/run/mysqld/mysqld.sock\" --dbpass=\"${WORDPRESS_DB_PASSWORD}\""  # This snippet is injected into the wp-config.php file when it is created;  # it informs WordPress that we are behind a reverse proxy and as such  # allows it to generate links using HTTPS  cat > /tmp/wp_forwarded_for.php << 'EOM'/* Turn HTTPS 'on' if HTTP_X_FORWARDED_PROTO matches 'https' */if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) {    $_SERVER['HTTPS'] = 'on';}if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {    $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];}EOM  # Create WordPress configuration  su -s /bin/sh -p -c "cat /tmp/wp_forwarded_for.php | ${WP_CONFIG_CREATE_CMD}" www-data  rm /tmp/wp_forwarded_for.php  su -s /bin/sh -p -c "wp --path=/var/www/wordpress config set 'FORCE_SSL_ADMIN' 'true'" www-data  # Install WordPress  WP_SITE_INSTALL_CMD="wp --path=/var/www/wordpress core install --url=\"${WORDPRESS_URL}\" --title=\"${WORDPRESS_SITE_TITLE}\" --admin_user=\"${WORDPRESS_ADMIN_USER}\" --admin_password=\"${WORDPRESS_ADMIN_PASSWORD}\" --admin_email=\"${WORDPRESS_ADMIN_EMAIL}\" --skip-email"  su -s /bin/sh -p -c "${WP_SITE_INSTALL_CMD}" www-data  # Set permalink structure to a sensible default that isn't in the UI  su -s /bin/sh -p -c "wp --path=/var/www/wordpress option update permalink_structure '/%year%/%monthnum%/%postname%/'" www-data  # Remove sample file because it is cruft and could be a security problem  rm /var/www/wordpress/wp-config-sample.php  # Ensure that WordPress permissions are correct  find /var/www/wordpress -type d -exec chmod g+s {} \;  chmod g+w /var/www/wordpress/wp-content  chmod -R g+w /var/www/wordpress/wp-content/themes  chmod -R g+w /var/www/wordpress/wp-content/pluginsfi

Настройка NGINX Unit


Скрипт настраивает NGINX Unit для запуска PHP и обработки путей WordPress, изолируя пространство имен процессов PHP и оптимизируя настройки производительности. Тут есть три функции, на которые стоит обратить внимание:


  • Поддержка пространств имен определяется по условию, основана на проверке запуска скрипта в контейнере. Это нужно, поскольку большинство настроек контейнеров не поддерживают вложенный запуск контейнеров.
  • Если есть поддержка пространств имен, отключается пространство имен network. Это нужно, чтобы позволить WordPress одновременно подключаться и к endpoints и быть доступным в интернете.
  • Максимальное число процессов определяется следующим образом: (Доступная память для запущенных MariaDB и NGINX Uniy)/(предел по оперативной памяти в PHP + 5)
    Это значение устанавливается в настройках NGINX Unit.

Также это значение подразумевает, что всегда есть как минимум два запущенных процесса PHP, что важно, поскольку WordPress делает много асинхронных запросов к самому себе, а без дополнительных процессов запуск, к примеру, WP-Cron, сломается. Вы, возможно, захотите увеличить или уменьшить эти ограничения, основываясь на ваших локальных настройках, потому что созданные настройки здесь консервативные. На большинстве производственных систем настройки находятся между 10 и 100.


код скрипта
if [ "${container:-unknown}" != "lxc" ] && [ "$(grep -m1 -a container=lxc /proc/1/environ | tr -d '\0')" == "" ]; then  NAMESPACES='"namespaces": {        "cgroup": true,        "credential": true,        "mount": true,        "network": false,        "pid": true,        "uname": true    }'else  NAMESPACES='"namespaces": {}'fiPHP_MEM_LIMIT="$(grep 'memory_limit' /etc/php/7.4/embed/php.ini | tr -d ' ' | cut -f2 -d= | numfmt --from=iec)"AVAIL_MEM="$(grep MemAvailable /proc/meminfo | tr -d ' kB' | cut -f2 -d: | numfmt --from-unit=K)"MAX_PHP_PROCESSES="$(echo "${AVAIL_MEM}/${PHP_MEM_LIMIT}+5" | bc)"echo " Calculated the maximum number of PHP processes as ${MAX_PHP_PROCESSES}. You may want to tune this value due to variations in your configuration. It is not unusual to see values between 10-100 in production configurations."echo " Configuring NGINX Unit to use PHP and WordPress"cat > /tmp/wordpress.json << EOM{  "settings": {    "http": {      "header_read_timeout": 30,      "body_read_timeout": 30,      "send_timeout": 30,      "idle_timeout": 180,      "max_body_size": $(numfmt --from=iec ${UPLOAD_MAX_FILESIZE})    }  },  "listeners": {    "127.0.0.1:8080": {      "pass": "routes/wordpress"    }  },  "routes": {    "wordpress": [      {        "match": {          "uri": [            "*.php",            "*.php/*",            "/wp-admin/"          ]        },        "action": {          "pass": "applications/wordpress/direct"        }      },      {        "action": {          "share": "/var/www/wordpress",          "fallback": {            "pass": "applications/wordpress/index"          }        }      }    ]  },  "applications": {    "wordpress": {      "type": "php",      "user": "www-data",      "group": "www-data",      "processes": {        "max": ${MAX_PHP_PROCESSES},        "spare": 1      },      "isolation": {        ${NAMESPACES}      },      "targets": {        "direct": {          "root": "/var/www/wordpress/"        },        "index": {          "root": "/var/www/wordpress/",          "script": "index.php"        }      }    }  }}EOMcurl -X PUT --data-binary @/tmp/wordpress.json --unix-socket /run/control.unit.sock http://localhost/config

Настройка NGINX


Настройка основных параметров NGINX


Скрипт создает каталог для кэша NGINX, а затем создает основной файл настройки nginx.conf. Обратите внимание на число процессов-обработчиков и задание максимального размера файла для загрузки. Также есть строка, на которой подключается файл настройки сжатия, определяемый в следующем разделе, далее идут настройки кэширования.


код скрипта
# Make directory for NGINX cachemkdir -p /var/cache/nginx/proxyecho " Configuring NGINX"cat > ${NGINX_CONF_DIR}/nginx.conf << EOMuser nginx;worker_processes auto;error_log  /var/log/nginx/error.log warn;pid        /var/run/nginx.pid;events {    worker_connections  1024;}http {    include       ${NGINX_CONF_DIR}/mime.types;    default_type  application/octet-stream;    log_format  main  '\$remote_addr - \$remote_user [\$time_local] "\$request" '                      '\$status \$body_bytes_sent "\$http_referer" '                      '"\$http_user_agent" "\$http_x_forwarded_for"';    access_log  /var/log/nginx/access.log  main;    sendfile        on;    client_max_body_size ${UPLOAD_MAX_FILESIZE};    keepalive_timeout  65;    # gzip settings    include ${NGINX_CONF_DIR}/gzip_compression.conf;    # Cache settings    proxy_cache_path /var/cache/nginx/proxy        levels=1:2        keys_zone=wp_cache:10m        max_size=10g        inactive=60m        use_temp_path=off;    include ${NGINX_CONF_DIR}/conf.d/*.conf;}EOM

Настройка сжатия NGINX


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


код скрипта
cat > ${NGINX_CONF_DIR}/gzip_compression.conf << 'EOM'# Credit: https://github.com/h5bp/server-configs-nginx/# ----------------------------------------------------------------------# | Compression                                                        |# ----------------------------------------------------------------------# https://nginx.org/en/docs/http/ngx_http_gzip_module.html# Enable gzip compression.# Default: offgzip on;# Compression level (1-9).# 5 is a perfect compromise between size and CPU usage, offering about 75%# reduction for most ASCII files (almost identical to level 9).# Default: 1gzip_comp_level 6;# Don't compress anything that's already small and unlikely to shrink much if at# all (the default is 20 bytes, which is bad as that usually leads to larger# files after gzipping).# Default: 20gzip_min_length 256;# Compress data even for clients that are connecting to us via proxies,# identified by the "Via" header (required for CloudFront).# Default: offgzip_proxied any;# Tell proxies to cache both the gzipped and regular version of a resource# whenever the client's Accept-Encoding capabilities header varies;# Avoids the issue where a non-gzip capable client (which is extremely rare# today) would display gibberish if their proxy gave them the gzipped version.# Default: offgzip_vary on;# Compress all output labeled with one of the following MIME-types.# `text/html` is always compressed by gzip module.# Default: text/htmlgzip_types  application/atom+xml  application/geo+json  application/javascript  application/x-javascript  application/json  application/ld+json  application/manifest+json  application/rdf+xml  application/rss+xml  application/vnd.ms-fontobject  application/wasm  application/x-web-app-manifest+json  application/xhtml+xml  application/xml  font/eot  font/otf  font/ttf  image/bmp  image/svg+xml  text/cache-manifest  text/calendar  text/css  text/javascript  text/markdown  text/plain  text/xml  text/vcard  text/vnd.rim.location.xloc  text/vtt  text/x-component  text/x-cross-domain-policy;EOM

Настройка NGINX для WordPress


Далее скрипт создает файл настройки для WordPress default.conf в каталоге conf.d. Здесь настраивается:


  • Активация сертификатов TLS, полученных от Let's Encrypt через Certbot (его настройка будет в следующем разделе)
  • Настройка параметров безопасности TLS, основанная на рекомендациях от Let's Encrypt
  • Подключение кэширования пропускаемых запросов на 1 час по умолчанию
  • Отключение журналирования доступа, а также журналирования ошибок, если файл не найден, для двух общих запрашиваемых файлов: favicon.ico и robots.txt
  • Запрет доступа к скрытым файлам и некоторым файлам .php, чтобы предотвратить нелегальный доступ или непреднамеренный запуск
  • Отключение журналирования доступа для статики и файлов шрифтов
  • Задание заголовка Access-Control-Allow-Origin для файлов шрифтов
  • Добавление маршрутизации для index.php и прочей статики.

код скрипта
cat > ${NGINX_CONF_DIR}/conf.d/default.conf << EOMupstream unit_php_upstream {    server 127.0.0.1:8080;    keepalive 32;}server {    listen 80;    listen [::]:80;    # ACME-challenge used by Certbot for Let's Encrypt    location ^~ /.well-known/acme-challenge/ {      root /var/www/certbot;    }    location / {      return 301 https://${TLS_HOSTNAME}\$request_uri;    }}server {    listen      443 ssl http2;    listen [::]:443 ssl http2;    server_name ${TLS_HOSTNAME};    root        /var/www/wordpress/;    # Let's Encrypt configuration    ssl_certificate         ${CERT_DIR}/fullchain.pem;    ssl_certificate_key     ${CERT_DIR}/privkey.pem;    ssl_trusted_certificate ${CERT_DIR}/chain.pem;    include ${NGINX_CONF_DIR}/options-ssl-nginx.conf;    ssl_dhparam ${NGINX_CONF_DIR}/ssl-dhparams.pem;    # OCSP stapling    ssl_stapling on;    ssl_stapling_verify on;    # Proxy caching    proxy_cache wp_cache;    proxy_cache_valid 200 302 1h;    proxy_cache_valid 404 1m;    proxy_cache_revalidate on;    proxy_cache_background_update on;    proxy_cache_lock on;    proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;    location = /favicon.ico {        log_not_found off;        access_log off;    }    location = /robots.txt {        allow all;        log_not_found off;        access_log off;    }    # Deny all attempts to access hidden files such as .htaccess, .htpasswd,    # .DS_Store (Mac)    # Keep logging the requests to parse later (or to pass to firewall utilities    # such as fail2ban)    location ~ /\. {        deny all;    }    # Deny access to any files with a .php extension in the uploads directory;    # works in subdirectory installs and also in multi-site network.    # Keep logging the requests to parse later (or to pass to firewall utilities    # such as fail2ban).    location ~* /(?:uploads|files)/.*\.php\$ {        deny all;    }    # WordPress: deny access to wp-content, wp-includes PHP files    location ~* ^/(?:wp-content|wp-includes)/.*\.php\$ {        deny all;    }    # Deny public access to wp-config.php    location ~* wp-config.php {        deny all;    }    # Do not log access for static assets, media    location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ {        access_log off;    }    location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ {        add_header Access-Control-Allow-Origin "*";        access_log off;    }    location / {        try_files \$uri @index_php;    }    location @index_php {        proxy_socket_keepalive on;        proxy_http_version 1.1;        proxy_set_header Connection "";        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;        proxy_set_header Host \$host;        proxy_pass       http://unit_php_upstream;    }    location ~* \.php\$ {        proxy_socket_keepalive on;        proxy_http_version 1.1;        proxy_set_header Connection "";        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;        proxy_set_header Host \$host;        try_files        \$uri =404;        proxy_pass       http://unit_php_upstream;    }}EOM

Настройка Certbot для сертификатов от Let's Encrypt и их автоматическое продление


Certbot бесплатный инструмент от Electronic Frontier Foundation (EFF), с помощью которого можно получать и автоматически обновлять сертификаты TLS от Let's Encrypt. Скрипт выполняет следующие действия, приводящие к настройке Certbot для обработки сертификатов от Let's Encrypt в NGINX:


  • Останавливает NGINX
  • Скачивает рекомендуемые параметры TLS
  • Запускает Certbot, чтобы получить сертификаты для сайта
  • Перезапускает NGINX для использования сертификатов
  • Настраивает ежедневный запуск Certbot в 3:24 ночи для проверки необходимости обновления сертификатов, а также, при необходимости, скачивания новых сертификатов и перезагрузки NGINX.

код скрипта
echo " Stopping NGINX in order to set up Let's Encrypt"service nginx stopmkdir -p /var/www/certbotchown www-data:www-data /var/www/certbotchmod g+s /var/www/certbotif [ ! -f ${NGINX_CONF_DIR}/options-ssl-nginx.conf ]; then  echo " Downloading recommended TLS parameters"  curl --retry 6 -Ls -z "Tue, 14 Apr 2020 16:36:07 GMT" \    -o "${NGINX_CONF_DIR}/options-ssl-nginx.conf" \    "https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf" \    || echo "Couldn't download latest options-ssl-nginx.conf"fiif [ ! -f ${NGINX_CONF_DIR}/ssl-dhparams.pem ]; then  echo " Downloading recommended TLS DH parameters"  curl --retry 6 -Ls -z "Tue, 14 Apr 2020 16:49:18 GMT" \    -o "${NGINX_CONF_DIR}/ssl-dhparams.pem" \    "https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem" \    || echo "Couldn't download latest ssl-dhparams.pem"fi# If tls_certs_init.sh hasn't been run before, remove the self-signed certsif [ ! -d "/etc/letsencrypt/accounts" ]; then  echo " Removing self-signed certificates"  rm -rf "${CERT_DIR}"fiif [ "" = "${LETS_ENCRYPT_STAGING:-}" ] || [ "0" = "${LETS_ENCRYPT_STAGING}" ]; then  CERTBOT_STAGING_FLAG=""else  CERTBOT_STAGING_FLAG="--staging"fiif [ ! -f "${CERT_DIR}/fullchain.pem" ]; then  echo " Generating certificates with Let's Encrypt"  certbot certonly --standalone \         -m "${WORDPRESS_ADMIN_EMAIL}" \         ${CERTBOT_STAGING_FLAG} \         --agree-tos --force-renewal --non-interactive \         -d "${TLS_HOSTNAME}"fiecho " Starting NGINX in order to use new configuration"service nginx start# Write crontab for periodic Let's Encrypt cert renewalif [ "$(crontab -l | grep -m1 'certbot renew')" == "" ]; then  echo " Adding certbot to crontab for automatic Let's Encrypt renewal"  (crontab -l 2>/dev/null; echo "24 3 * * * certbot renew --nginx --post-hook 'service nginx reload'") | crontab -fi

Дополнительная настройка вашего сайта


Мы выше рассказали о том, как наш скрипт настраивает NGINX и NGINX Unit для обслуживания готового к промышленной работе сайта с включенным TLS\SSL. Вы можете также, в зависимости от ваших нужд, добавить в будущем:


  • Поддержку Brotli, улучшенное сжатие на лету по HTTPS
  • ModSecurity с правилами для WordPress, чтобы предотвратить автоматические атаки на ваш сайт
  • Резервное копирование для WordPress, подходящее вам
  • Защиту с помощью AppArmor (на Ubuntu)
  • Postfix или msmtp, чтобы WordPress мог отправлять почту
  • Проверки вашего сайта, чтобы вы понимали, сколько трафика он может выдержать

Для еще более лучшей производительности сайта мы рекомендуем обновиться до NGINX Plus, наш коммерческий продукт корпоративного уровня, основанный на NGINX c открытым исходным кодом. Его подписчики получат динамически загружаемый модуль Brotli, а также (за дополнительную оплату) NGINX ModSecurity WAF. Мы также предлагаем NGINX App Protect, модуль WAF для NGINX Plus, основанный на технологии, ведущей в отрасли безопасности, от F5.


N.B. За поддержкой высоконагруженного сайта вы можете обратиться к специалистам Southbridge. Обеспечим быструю и надежную работу вашего сайта или сервиса под любой нагрузкой.
Подробнее..

Как установить SSL сертификат на Onlyoffice docker сборки

14.01.2021 14:20:31 | Автор: admin

Поставив и настроив комьюнити версию этого пакета я столкнулся с тем что нет официальных рекомендаций как сгенерировать и запустить работу этого комплекса по защищенному протоколу используя сертификат от Letsencrypt. Этот короткий туториал поможет вам получить сертификат в течении 5-10 минут без сложных телодвижений.

Итак, поехали по шагам.

Я все делал на системе Ubuntu 18.04 запущенной в режиме ВМ на хост машине Proxmox. Данная ВМ была не единой в пуле этого хоста потому, если кому то понадобится, могу дать свой конфиг работающего решения для реверс прокси HAPROXY

Остановим все контейнеры одной командой docker stop $(docker ps -a -q)

Удалим из системы certbot (если он у вас есть, хотя если вы ставите Onlyoffice на чистую систему его там быть не должно в принципе)

Зайдем на официальный сайт https://certbot.eff.org/ и в выпадающем меню выставим нужные параметры как на скриншоте:

3.1 Кому лень или он не разобрался переходим сразу на готовый линк https://certbot.eff.org/lets-encrypt/ubuntubionic-other Выполняем инструкции с шага 1 по шаг 7 на этой странице. Напомню, в процессе по ссылке - вы должны будете установить пакет snapd, без него не заработает. Просто сделайте и все будет работать без лишних слов.

Запустим в консоли от рута certbot certonly standalone sitename.ru (вместо sitename.ru укажите тот домен на котором будет виден из интернета ваш офисный сервер)

4.1 Если все пройдет Ок - вы получите сообщение что сертификаты сгенерированы и лежат на вашем сервере по адресу: /etc/letsencrypt/live/sitename.ru/fullchain.pem /etc/letsencrypt/live/sitename.ru/privkey.pem

4.2 Далее, переименуем и перенесем в нужный каталог полученные сертификаты

cp /etc/letsencrypt/live/sitename.ru/fullchain.pem app/onlyoffice/CommunityServer/data/certs/onlyoffice.crt

cp /etc/letsencrypt/live/sitename.ru/privkey.pem /app/onlyoffice/CommunityServer/data/certs/onlyoffice.key

Перезагрузим сервер с офисом командой shutdown -r now Так будет надежней и наверняка все сервисы запустятся сами, т.к. сам пакет весьма большой и завязан на связанные между собой сервисы лучше его перезапустить целиком а не стартовать докер контейнеры по одному, так меньше шансов что какой то не запустился.

В принципе всё. Теперь зайдя по адресу sitename.ru вы должны попасть на корректно работающий портал документооборота.

На последок крайне желательно зайти в админку офиса и проверить:

Система подцепила ваш домен:

В настройках вы прописали доменное имя sitename.ru - в противном случае в письмах этого пакета будет некорректный адрес типа https://iuyhgi8798ygiknqwq - такой адрес или вымышленный или присваивается по имени докер контейнера - таким образом ни зайти на сайт, ни скачать документов - ничего. Вот такой вот капризный пакет.

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

На этом всё, позвольте откланяться.

Подробнее..

Acme.sh Ansible Alias mode Автоматизируем получение и распространение TLS сертификатов

09.06.2021 20:16:20 | Автор: admin

Acme.sh - скрипт, позволяющий без особых проблем получать let's encrypt сертификаты очень разными способами. В данной статье я разберу как получать сертификаты через DNS api, но этим уже никого не удивишь, поэтому расскажу про метод DNS alias, он свежий (всего 3 года) и интересный. А так же про автоматизацию на Ansible и немного про мониторинг сертификатов.

Видеоверсия

Режимы acme.sh получения сертификатов прямо на целевом сервере

  • Webroot

  • Nginx\Apache

  • Stanalone

Режимы хорошие и удобные, когда у вас один - два сервера и можно просто на каждый установить acme.sh. Когда количество серверов, которым нужно TLS, переваливает за десяток, удобнее начать использовать wilcard сертификаты и выделить отдельный сервер для получения и распространения сертификата\ов. Получить wildcard сертификат можно только через подтверждение владения DNS зоной. DNS режимов несколько:

  • DNS manual

  • DNS API

  • DNS alias

Все примеры буду показывать на моём личном домене и установленном локально acme.sh.

DNS manual mode

Manual режим работает супер просто. Запускаем acme.sh с флагом --dns

acme.sh --issue --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please

koala@x220:~$ acme.sh --issue --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please[Ср мая  5 14:52:29 MSK 2021] Using CA: https://acme-v02.api.letsencrypt.org/directory[Ср мая  5 14:52:29 MSK 2021] Creating domain key[Ср мая  5 14:52:29 MSK 2021] The domain key is here: /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key[Ср мая  5 14:52:29 MSK 2021] Single domain='*.itdog.info'[Ср мая  5 14:52:29 MSK 2021] Getting domain auth token for each domain[Ср мая  5 14:52:32 MSK 2021] Getting webroot for domain='*.itdog.info'[Ср мая  5 14:52:32 MSK 2021] Add the following TXT record:[Ср мая  5 14:52:32 MSK 2021] Domain: '_acme-challenge.itdog.info'[Ср мая  5 14:52:32 MSK 2021] TXT value: 'QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8'[Ср мая  5 14:52:32 MSK 2021] Please be aware that you prepend _acme-challenge. before your domain[Ср мая  5 14:52:32 MSK 2021] so the resulting subdomain will be: _acme-challenge.itdog.info[Ср мая  5 14:52:32 MSK 2021] Please add the TXT records to the domains, and re-run with --renew.[Ср мая  5 14:52:32 MSK 2021] Please add '--debug' or '--log' to check more details.[Ср мая  5 14:52:32 MSK 2021] See: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh

--issue - запрос на получение

--dns без аргумента - режим ручного DNS

--yes-I-know-dns-manual-mode-enough-go-ahead-please - интересное решение проблемы, когда люди не понимают что такое ручной режим

Он выдаёт TXT запись, которую нам необходимо добавить в наш dns хостинг, в данном случае это _acme-challenge.itdog.info TXT QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8

Ручной он потому что, мы вручную добавляем эту запись.

Анимация manual modeАнимация manual mode

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

После добавления записи проверяем начала ли она резолвится на гугловом dns

koala@x220:~$ dig -t txt _acme-challenge.itdog.info @8.8.8.8; <<>> DiG 9.11.3-1ubuntu1.15-Ubuntu <<>> -t txt _acme-challenge.itdog.info @8.8.8.8;; ANSWER SECTION:_acme-challenge.itdog.info. 1798 IN    TXT    "QXRgFOfVOZGOBC1qxAToMNOf7Xsv9gjM8hYG6akRoJ8"

Она резолвится, а значит можно получать сертификат

koala@x220:~$ acme.sh --renew --dns -d *.itdog.info --yes-I-know-dns-manual-mode-enough-go-ahead-please[Ср мая  5 14:58:08 MSK 2021] Renew: '*.itdog.info'[Ср мая  5 14:58:09 MSK 2021] Using CA: https://acme-v02.api.letsencrypt.org/directory[Ср мая  5 14:58:09 MSK 2021] Single domain='*.itdog.info'[Ср мая  5 14:58:09 MSK 2021] Getting domain auth token for each domain[Ср мая  5 14:58:09 MSK 2021] Verifying: *.itdog.info[Ср мая  5 14:58:13 MSK 2021] Success[Ср мая  5 14:58:13 MSK 2021] Verify finished, start to sign.[Ср мая  5 14:58:13 MSK 2021] Lets finalize the order.[Ср мая  5 14:58:13 MSK 2021] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/121...'[Ср мая  5 14:58:15 MSK 2021] Downloading cert.[Ср мая  5 14:58:15 MSK 2021] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/042...'[Ср мая  5 14:58:16 MSK 2021] Cert success.-----BEGIN CERTIFICATE-----certificate-----END CERTIFICATE-----[Ср мая  5 14:58:16 MSK 2021] Your cert is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.cer [Ср мая  5 14:58:16 MSK 2021] Your cert key is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key [Ср мая  5 14:58:16 MSK 2021] The intermediate CA cert is in  /home/koala/.acme.sh/*.itdog.info/ca.cer [Ср мая  5 14:58:16 MSK 2021] And the full chain certs is there:  /home/koala/.acme.sh/*.itdog.info/fullchain.cer 

После этого TXT запись можно удалить.

Теперь есть ключ и сертификат, который будет действителен 3 месяца. Обновить сертификат можно будет через 2 месяца, let's enctypt даёт запас времени и если у вас вдруг что-то сломается, будет целый месяц чтобы починить и обновить сертификат.

И да, обновляется только сертификат, ключ остаётся таким, какой был выдан в первый раз. Обратите внимание на даты создания файлов, особенно *.example.com.key

# ls -l --time-style=+%Y-%m-%d \*.example.com/total 28-rw-r--r-- 1 root root 1587 2021-04-15 ca.cer-rw-r--r-- 1 root root 3433 2021-04-15 fullchain.cer-rw-r--r-- 1 root root 1846 2021-04-15 *.example.com.cer-rw-r--r-- 1 root root  719 2021-04-15 *.example.com.conf-rw-r--r-- 1 root root  980 2021-04-15 *.example.com.csr-rw-r--r-- 1 root root  211 2021-04-15 *.example.com.csr.conf-rw-r--r-- 1 root root 1675 2019-04-10 *.example.com.key

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

DNS API mode

Как это работает? Принцип действия тот же самый что у manual, только acme.sh сам вносит и удаляет TXT запись с помощью API вашего dns провайдера.

Анимация API modeАнимация API mode

Под DNS хостингом и DNS провайдером я буду иметь в виду сервис, в который вносятся DNS записи. Это может быть и DNS хостинг, который есть почти у каждой компании, торгующей доменами (namecheap, beget итд) или как отдельный сервис за деньги (Amazon Route 53, ClouDNS итд), или же ваш собственный сервис развернутый с помощью BIND, PowerDNS итд.

У каждого DNS провайдера свой не стандартизированный API и их поддержка в acme.sh реализована отдельными скриптами. Список всех поддерживаемых провайдеров находится тут https://github.com/acmesh-official/acme.sh/wiki/dnsapi

Для каждого написано как пользоваться и пример. Если вашего провайдера или сервиса нет в списке, можно написать свой скрипт, но не спешите открывать vim, дождитесь третьего способа.

Работу этого режима покажу на примере хостинга DNS у namecheap.

Для каждого DNS провайдера свои настройки, где-то нужно просто включить API и сгенерировать токен, где-то бывает посложнее, например для namecheap нужно ещё внести IP в allow list. Включаем API и сразу генерируется token, добавляем IP в список.

Теперь на локальной машине нужно настроить доступ к API

export NAMECHEAP_USERNAME="USERNAME"export NAMECHEAP_API_KEY="TOKEN"export NAMECHEAP_SOURCEIP="MY-IP"

Отступление про дополнительные флаги force и test. Будем использовать флаг -f (--force), что бы наши сертификаты генерировались заново, т.к. acme.sh видит уже сгенерированные сертификаты при их наличии не будет заново получать. Можно конечно просто сделать rm -rf ~/.acme.sh/domain/ вместо этого. Так же будем использовать флаг --test, что бы лишний раз не нагружать продакшн сервера let's encrypt. Вот такое сообщение мы получим, если после подтверждения в manual режиме попробуем другой режим.

[Ср мая 5 16:39:31 MSK 2021] *.itdog.info is already verified, skip dns-01.

Команда получения через API выглядит таким образом

acme.sh --issue --dns dns_namecheap -d *.itdog.info --test

Здесь после --dns мы добавляем имя провайдера.

Запускаем acme.sh

Раскрыть
koala@x220:~$ acme.sh --issue --dns dns_namecheap -d *.itdog.info --test[Ср мая  5 16:48:05 MSK 2021] Using ACME_DIRECTORY: https://acme-staging-v02.api.letsencrypt.org/directory[Ср мая  5 16:48:06 MSK 2021] Using CA: https://acme-staging-v02.api.letsencrypt.org/directory[Ср мая  5 16:48:06 MSK 2021] Creating domain key[Ср мая  5 16:48:07 MSK 2021] The domain key is here: /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key[Ср мая  5 16:48:07 MSK 2021] Single domain='*.itdog.info'[Ср мая  5 16:48:07 MSK 2021] Getting domain auth token for each domain[Ср мая  5 16:48:09 MSK 2021] Getting webroot for domain='*.itdog.info'[Ср мая  5 16:48:10 MSK 2021] Adding txt value: nCH4tBWCkSVn76301f2SdJqCAzmtXvzQAB_Ag8hURLo for domain:  _acme-challenge.itdog.info[Ср мая  5 16:48:15 MSK 2021] The txt record is added: Success.[Ср мая  5 16:48:15 MSK 2021] Let's check each DNS record now. Sleep 20 seconds first.[Ср мая  5 16:48:36 MSK 2021] You can use '--dnssleep' to disable public dns checks.[Ср мая  5 16:48:36 MSK 2021] See: https://github.com/acmesh-official/acme.sh/wiki/dnscheck[Ср мая  5 16:48:36 MSK 2021] Checking itdog.info for _acme-challenge.itdog.info[Ср мая  5 16:48:37 MSK 2021] Domain itdog.info '_acme-challenge.itdog.info' success.[Ср мая  5 16:48:37 MSK 2021] All success, let's return[Ср мая  5 16:48:37 MSK 2021] Verifying: *.itdog.info[Ср мая  5 16:48:41 MSK 2021] Success[Ср мая  5 16:48:41 MSK 2021] Removing DNS records.[Ср мая  5 16:48:41 MSK 2021] Removing txt: nCH4tBWCkSVn76301f2SdJqCAzmtXvzQAB_Ag8hURLo for domain: _acme-challenge.itdog.info[Ср мая  5 16:48:46 MSK 2021] Removed: Success[Ср мая  5 16:48:46 MSK 2021] Verify finished, start to sign.[Ср мая  5 16:48:46 MSK 2021] Lets finalize the order.[Ср мая  5 16:48:46 MSK 2021] Le_OrderFinalize='https://acme-staging-v02.api.letsencrypt.org/acme/finalize/193...'[Ср мая  5 16:48:48 MSK 2021] Downloading cert.[Ср мая  5 16:48:48 MSK 2021] Le_LinkCert='https://acme-staging-v02.api.letsencrypt.org/acme/cert/fa62...'[Ср мая  5 16:48:49 MSK 2021] Cert success.-----BEGIN CERTIFICATE-----certificate-----END CERTIFICATE-----[Ср мая  5 16:48:49 MSK 2021] Your cert is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.cer [Ср мая  5 16:48:49 MSK 2021] Your cert key is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key [Ср мая  5 16:48:49 MSK 2021] The intermediate CA cert is in  /home/koala/.acme.sh/*.itdog.info/ca.cer [Ср мая  5 16:48:49 MSK 2021] And the full chain certs is there:  /home/koala/.acme.sh/*.itdog.info/fullchain.cer

В логе прям видно, что acme.sh добавляет TXT запись, ждёт немного, проверяет запись через доверенные DNS сервера, удаляет запись и скачивает сертификаты с ключом.

После первого запуска через API, acme.sh заносит env переменные c доступами к API себе в файл ~/.acme.sh/account.conf и вам не нужно каждый раз их экспортировать.

Отлично, получение автоматизировали, всё вроде классно. Но у этого метода есть свои недостатки:

  • Если никто не написал скрипта для вашего провайдера\сервиса, то нужно либо писать, либо переезжать на другой провайдер

  • А есть ли у вашего провайдера API?

  • Поразмышляем немного об безопасности. Вот у меня в "открытую" лежит полный доступ к редактированию моего домена, если он каким-то образом попадёт в чужие руки, эти руки могут сделать что угодно. Эту проблему можно решить ограничим доступа в API, например по токену можно только добавлять\удалять txt записи _acme-challenge. Есть ли такая возможность у вашего провайдера? Я не встречал такого, наверное есть у какого-нибудь AWS конечно. Обычно уже хорошо если есть API, а токен один и даёт полный доступ

  • У вас несколько доменов на разных провайдерах (сочувствую). Тут конечно можно настроить каждое API и сделать для каждого провайдера отдельный запуск acme.sh со своими переменными, но мне кажется это не очень удобным. Тем более если у одного из них отсутствует API или скрипт

  • Кто-то просто не любит, что бы в DNS постоянно лазил какой-то скрипт и что-то добавлял\удалял

DNS aliase mode

Это модернизированный режим DNS API.

Идея такая: Есть технический домен, через добавления TXT записей на котором мы подтверждаем владение основным доменом. т.е. acme.sh смотрит CNAME запись у основного домена, видит "перенаправление" на технический домен и идёт к нему проверять TXT запись. А дальше всё как в режиме DNS API.

Анимация alias modeАнимация alias mode

Разберём последовательно. Для демонстрации я купил домен tech-domain.club, он выступает в качестве технического домена. В моём примере основной домен itdog.info располагается на namecheap, а техничский tech-domain.club я делегирую на Hetzner DNS, таким образом операции с записями будут производиться через API Hetzner'a.

В записях основного домена мы добавляем CNAME запись, которая указывает на технический домен

_acme-challenge CNAME _acme-challenge.tech-domain.club

Для провайдера с техническим доменом мы настраиваем доступ к API.

Экспортируем токен Hetzner export HETZNER_Token="TOKEN"

Команда выглядит так (-f и --test опять же для примера)

acme.sh --issue -d *.itdog.info --challenge-alias tech-domain.club --dns dns_hetzner -f --test
Раскрыть
koala@x220:~$ acme.sh --issue -d *.itdog.info -d itdog.info --challenge-alias tech-domain.club --dns dns_hetzner -f --test[Пт мая  7 13:40:11 MSK 2021] Domains have changed.[Пт мая  7 13:40:11 MSK 2021] Using CA: https://acme-v02.api.letsencrypt.org/directory[Пт мая  7 13:40:11 MSK 2021] Multi domain='DNS:*.itdog.info,DNS:itdog.info'[Пт мая  7 13:40:11 MSK 2021] Getting domain auth token for each domain[Пт мая  7 13:40:15 MSK 2021] Getting webroot for domain='*.itdog.info'[Пт мая  7 13:40:15 MSK 2021] Getting webroot for domain='itdog.info'[Пт мая  7 13:40:15 MSK 2021] Adding txt value: Zlrij9n4y5QXfH6yx_PBn45bgmIcT70-JuW2rIUa6lc for domain:  _acme-challenge.tech-domain.club[Пт мая  7 13:40:16 MSK 2021] Adding record[Пт мая  7 13:40:17 MSK 2021] Record added, OK[Пт мая  7 13:40:20 MSK 2021] The txt record is added: Success.[Пт мая  7 13:40:20 MSK 2021] Let's check each DNS record now. Sleep 20 seconds first.[Пт мая  7 13:40:41 MSK 2021] You can use '--dnssleep' to disable public dns checks.[Пт мая  7 13:40:41 MSK 2021] See: https://github.com/acmesh-official/acme.sh/wiki/dnscheck[Пт мая  7 13:40:41 MSK 2021] Checking itdog.info for _acme-challenge.tech-domain.club[Пт мая  7 13:40:42 MSK 2021] Domain itdog.info '_acme-challenge.tech-domain.club' success.[Пт мая  7 13:40:42 MSK 2021] All success, let's return[Пт мая  7 13:40:42 MSK 2021] *.itdog.info is already verified, skip dns-01.[Пт мая  7 13:40:42 MSK 2021] Verifying: itdog.info[Пт мая  7 13:40:46 MSK 2021] Success[Пт мая  7 13:40:46 MSK 2021] Removing DNS records.[Пт мая  7 13:40:46 MSK 2021] Removing txt: Zlrij9n4y5QXfH6yx_PBn45bgmIcT70-JuW2rIUa6lc for domain: _acme-challenge.tech-domain.club[Пт мая  7 13:40:50 MSK 2021] Record deleted[Пт мая  7 13:40:50 MSK 2021] Removed: Success[Пт мая  7 13:40:50 MSK 2021] Verify finished, start to sign.[Пт мая  7 13:40:50 MSK 2021] Lets finalize the order.[Пт мая  7 13:40:50 MSK 2021] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/121...'[Пт мая  7 13:40:52 MSK 2021] Downloading cert.[Пт мая  7 13:40:52 MSK 2021] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/04e...'[Пт мая  7 13:40:53 MSK 2021] Cert success.-----BEGIN CERTIFICATE-----certificate-----END CERTIFICATE-----[Пт мая  7 13:40:53 MSK 2021] Your cert is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.cer [Пт мая  7 13:40:53 MSK 2021] Your cert key is in  /home/koala/.acme.sh/*.itdog.info/*.itdog.info.key [Пт мая  7 13:40:53 MSK 2021] The intermediate CA cert is in  /home/koala/.acme.sh/*.itdog.info/ca.cer [Пт мая  7 13:40:53 MSK 2021] And the full chain certs is there:  /home/koala/.acme.sh/*.itdog.info/fullchain.cer 

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

Кстати, если вам нужно в одном файле иметь несколько сертификатов, например и itdog.info и wildcard *.itdog.info, то просто перечислите их с -d, например

acme.sh --issue --challenge-alias tech-domain.club --dns hetzner -d *.itdog.info -d itdog.info

Это правило действует и для других методов.

И так, что даёт нам этот режим:

  • Если у вашего провайдера нет API или лень писать скрипт, возьмите технический домен, делегируйте его на сервис, который поддерживает acme.sh

  • Если у вашего провайдера нет настройки прав для доступа через API, то технический домен тоже выручает. В случае, если наш token утечёт, у злоумышленника будет доступ только к вашему техническому домену, и если вы его используете только для acme.sh, то максимум что сможет сделать злоумышленник - получить ключ и сертификат для вашего домена. Это тоже неприятно и можно использовать, но это совершенно другой уровень угрозы, по сравнению с полным доступом к доменной зоне

  • В ситуации с кучей доменов на одном или нескольких провайдерах, жизнь так же становится проще, когда все они просто имеют CNAME запись

Есть так же режим domain-alias, он даёт возможность использовать не _acme-challenge запись, а кастомную, подробности можно прочитать в документации

Автоматизация получения и распространения сертификатов

Мы получили сертификаты, лежат они у нас красиво в ~/.acme.sh и никак не используются. Надо каким-то образом их распространять на сервера. Далее расскажу, как я это делаю с помощью ansible. Ansible используется и для получения\обновления и для распространения. Сразу предупреждаю, мои плейбуки простые как три копейки и заточены под определенную инфраструктуру. Playbooks, hosts на github.

Мой сервер с ansible, уже имеет доступ ко всем необходимым серверам, на нём установлен acme.sh и реализовано два плейбука, на получение и распространение. Кстати, не забудьте закомментировать acme.sh в crontab, что бы не было лишних запросов и путаницы.

Playbook для получения сертификатов

В vars указывается только технический домен, эта переменная используется несколько раз. Токен от API вынесен в отдельный vars файл, что бы хранить его в зашифрованном виде в git. Task "Date and time" нужен для логирования, что бы понимать когда именно что-то пошло не так. Следующие два плейбука это простой shell, отличаются друг от друга количеством доменов в одном файле сертификата. Всем доменам, которым не нужно сочетать в себе обычный и wildcard домен, идут списком в loop.

Домены, которые должны подходить как для обычного, так и для wildcard идут по втором taks, тоже с помощью loop. Если вам нужно например wilcard вида *.*.itdog.info, то просто добавьте ещё один -d и ещё один subkey в item. Опция ignore_errors необходима, потому что exit code 0 будет только 6 раз за год при обновлении сертификата, в остальное время будут сообщения о том, что сертификат не нужно обновлять, для ansible это ошибка на которой он будет останавливаться.

Для чего плейбук на получение? Ведь в acme.sh и так уже всё настроено!

В одном плейбуке мы собираем всю нашу конфигурацию, доступы и все домены, которым необходим TLS, как минимум, это удобно - не надо копаться конфигах acme.sh. В случае изменения, например, токена, мы просто редактируем его в vars_files, а если нужно добавить ещё один домен\подомен, мы просто добавляем его в loop. Ну и в случае переноса сервера, не нужно переносить ~/.acme.sh, только плейбуки с vars_files взять из git.

Playbook для распространения сертификатов

Здесь нужно писать конечно под вашу инфраструктуру, поэтому повторюсь, показываю это для примера.

Три типа серверов из моей инфраструктуры:

  • tls-hosts - Обычный nginx установленный как пакет из стандартного репозитория

  • tls-hosts-docker - Веб проект с тем же nginx, но уже в docker

  • tls-hosts-docker-rename - Сторонний продукт, в который надо подкладывать сертификат и ключ с определённым именем в определённую директорию (например Harbor, Zabbix)

Первый кейс самый простой, мы сами пишем конфигурацию nginx и можем куда угодно положить сертификаты и как угодно назвать их. При обновлении сертификата, требуется сделать nginx -s reload

Во втором случае всё плюс-минус так же, но уже нужно сделать docker exec project-nginx -s reolad, т.е. уже другой handler.

В третьем случае у нас помимо handler в контейнере, ещё нужно дать сертификату с ключом определённое имя, потому что оно прописано в их конфигурации, которую лучше не трогать, чтоб не было проблем при обновлении.

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

nginx.itdog.info tls_path=/etc/letsencrypt/*.itdog.info/ DOMAIN=*.itdog.info

Для случаев, когда доменов на сервере несколько, делается два хоста с разными именами и одинаковым ansible_host (Совет, как сделать лучше, приветствуется).

nginx.example.com-1 ansible_host=nginx.example.com tls_path=/etc/letsencrypt/*.example.com/ DOMAIN=example.comnginx.example.com-2 ansible_host=nginx.example.com tls_path=/etc/letsencrypt/*.example.org/ DOMAIN=example.org

Для каждого типа серверов создана своя группа в hosts. Для каждой группы свои немного отличающиеся друг от друга tasks. Для tls-hosts-docker так же добавлена переменная с именем контейнера nginx. А для tls-hosts-docker-rename добавлена переменная, в которой задаётся конечное имя сертификата и ключа.

docker-zabbix.itdog.info tls_path=/root/docker-zabbix/zbx_env/etc/ssl/nginx/ DOMAIN=*.itdog.info CONTAINER=docker-zabbix_zabbix-web-nginx-pgsql_1 cert_name=ssl.crt key_name=ssl.key

Для nginx нужен fullchain и domain.key - копируются только они. Если файлы различаются, происходит копирование и срабатывает handler nginx -s reload. Так же есть проверка, перед тем как зарелоудить nginx, что это файл. У меня один раз был случай, в самом начале пользования acme.sh, скрипт вместо файла с сертификатом создал директорию. Прямо как traefik 1.7 создаёт acme.json директорию, вместо файла. Поэтому я сделал простую проверку. В идеале нужно делать проверку, что сертификат валидный и не просроченный, но для этого требуется иметь на каждом хосте python-pyOpenSSL.

Crontab

23 3 * * * /usr/bin/ansible-playbook /etc/ansible/playbook-get-tls.yml -v >> /var/log/get-tls.log23 4 * * * /usr/bin/ansible-playbook /etc/ansible/playbook-copy-tls.yml -v >> /var/log/copy-tls.log

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

Мониторинг сертификатов

Надо следить за тем, что бы сертификаты на доменах обновлялись. Можно мониторить логи получения и распространения, и когда что-то идёт не так - кидать warning в системе мониторинга. Но мы пойдём с одной стороны на минималках, с другой более основательно и ещё помимо этого захватим другие исталяции, например с traefik, который живёт там сам по себе.

Я использую zabbix и скрипт от @selivanov_pavel

Проверим с его помощью мой домен локально

koala@x220 ~/t/acme.sh-test> ./ssl_cert_check.sh expire itdog.info 44341

41 день сертификат на itdog.info будет актуален. Сертификат в let's encrypt обновляется за 30 дней до протухания. А значит, например, если ему осталось жить 10 дней, значит что-то пошло не так и надо идти смотреть.

Темплейт состоит из одного item и одного trigger. Теплейт есть так же на github

ssl_cert_check.sh["expire","{HOST.NAME}","{$TLS_PORT}"]

В item две переменных, первая HOST.NAME берёт имя хоста, предполагается что у нас хост назван по доменному имени. Переменная $TLS_PORT по дефолту 443, но если нужно проверять на нестандартном порту, то записываем значение порта в macros.

Триггер тоже супер простой

{Check tls expire:ssl_cert_check.sh["expire","{HOST.NAME}","{$TLS_PORT}"].last()}<=10

Если полученное значение меньше 10ти - аллерт. Таким образом, мы узнаем если у нас начнут протухать сертификаты и будет 10 дней на починку.

Нормально работает?

Да, acme.sh + DNS API + ansible у меня крутится два года. acme.sh + DNS Alias + ansible крутится пол года. Проблемы возникали только, когда при тестировании доменов забыл отключить crontab и он принёс staging сертификат на прод. Такая проблема решается проверкой на валидность.

Да, в идеале, ansible должен проверять перед копированием, что сертификат валидный и не просроченный. А система мониторинга проверять, помимо expire, валидность сертификатов.

Подробнее..

Traefik, docker и docker registry

11.04.2021 16:04:19 | Автор: admin

Под катом вы увидите:

  1. Использования Traefik в качестве обратного прокси для маршрутизации трафика внутрь docker контейнеров.

  2. Использование Traefik для автоматического получения Lets Encrypt сертификатов

  3. Использование Traefik для разграничения доступа к docker registry при помощи basic auth

  4. Все перечисленное выше будет настраиваться исключительно внутри docker-compose.yml и не потребует передачи отдельных конфигурационных файлов внутрь контейнеров.

Актуальность вопроса

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

Помимо этого в интернете мало информации на тему использования traefik для контроля доступа к docker registry. Описанную ниже технику можно использовать для контроля доступа к любому приложению, реализующему Rest API.

Поиск решения

Вот ссылка на официальную статью по развертыванию docker registry. Крутим страницу вниз и видим пример развертывания через docker-compose. Я перепечатаю пример ниже:

registry:  restart: always  image: registry:2  ports:    - 5000:5000  environment:    REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt    REGISTRY_HTTP_TLS_KEY: /certs/domain.key    REGISTRY_AUTH: htpasswd    REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd    REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm  volumes:    - /path/data:/var/lib/registry    - /path/certs:/certs    - /path/auth:/auth

Нам предлагают терминировать https трафик прямо внутри сервиса registry, чего мы делать не будем. Мы не станем усложнять себе жизнь и копировать сертификаты внутрь сервиса. Кроме того у нас есть другие https сервисы, которым также нужны сертификаты, так что у нас уже есть единая точка входа, где происходит автоматическая генерация сертификатов для новых сервисов с помощью let's encrypt - это контейнер с traefik.

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

В интернете мы находим идеальный пример использования nginx для авторизации и разделения прав доступа. Мы сделаем то же самое, но через traefik.

Итак, приступаем.

Базовая конфигурация Registry

Сперва запустим сервис registry через отдельный compose файл. Создадим новую папку registry", в которой создадим compose файл:

mkdir registrycd registrynano docker-compose.yml

Вставим в файл следующее содержимое и сохраним:

version: '2.4'services:  registry:    restart: always    image: registry:2    ports:      - 5000:5000

Запустим сервис

docker-compose up -d

Откроем в браузере страницу http://<IP>:5000/v2/_catalog , где <IP> - это ip адрес докер машины.
В ответ увидим страницу с текстом:

{"repositories":[]}

Значит, все работает. Если это не так - проверьте firewall.

Базовая конфигурация Traefik

Знакомство с traefik начнем с базовой конфигурации.
Позже мы добавим SSL, сжатие трафика и авторизацию с аутентификацией.

Сперва запустим сервис registry через отдельный compose файл. Создадим новую папку registry", в которой создадим compose файл:

mkdir traefikcd traefiknano docker-compose.yml

Вставим в файл следующее содержимое и сохраним:

version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"    ports:      - "8080:8080"
Разберем каждую новую строку (нажать)
command:- "--api.insecure=true"

Через command передаются параметры запуска нашего приложения.
Включаем доступ к dashboard в insecure режиме. Это означает, что dashboard будет доступен напрямую в точке входа с названием traefik. Если указанная точка входа traefik не настроена, она будет автоматически создана на порту 8080.

- "8080:8080"

Перенаправление с порта 8080 на docker машине в аналогичный порт контейнера traefik. Эта настройка также как и предыдущая необходима, чтобы попасть в dashboard traefik

Запускаем наш сервис:

docker-compose up -d

Открываем страницу с IP адресом докер машины и портом 8080:

Подключение Registry к Traefik (настройка домена)

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

Новый compose для Traefik:

Version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"    ports:      - "80:80"      - "8080:8080"    networks:      - registry_default    volumes:      - "/var/run/docker.sock:/var/run/docker.sock:ro"networks:  registry_default:    external: true
Разберем каждую новую строку (нажать)
- "--providers.docker=true"

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

- "--providers.docker.exposedbydefault=false"

Запрещаем автоматическое добавление HTTP сервисов и HTTP маршрутов в traefik. Если этого не сделать, то traefik опубликует все docker контейнеры, в которых есть expose порта наружу автоматически. В качестве доменного имени он будет использовать имя контейнера.

Таким образом, без этой строки мы неявно открываем публичный доступ ко всем своим контейнерам! Все, что нужно для атаки - угадать имя контейнера! Я проверил эту теорию на практике: после добавления в файл hosts строки IP_докер_машины имя_контейнера, страница http://имя_контейнера" открылась в браузере.

- "80:80"

Перенаправление стандартного веб порта 80 (http) на docker машине в аналогичный порт контейнера traefik. Это нужно для обработки и маршрутизации полезного трафика.

    networks:      - registry_defaultnetworks:  registry_default:    external: true

Подключение к коммутатору, обслуживающему контейнеры из другого compose файла. Дело в том, что для каждого compose файла создается виртуальный коммутатор с именем имя_родительской_папки_default, таким образом сервисы внутри одного compose файла могут легко друг с другом взаимодействовать.

volumes:  - "/var/run/docker.sock:/var/run/docker.sock:ro"

Подключаем docker.sock докер машины внутрь контейнера traefik. Это необходимо, чтобы traefik мог отслеживать изменения в чужих контейнерах. Он будет перенастраивать свою конфигурацию на лету, исходя из меток, принадлежащих другим контейнерам.

Новый compose для Registry:

version: '2.4'services:  registry:    restart: always    image: registry:2    ports:      - 5000:5000    labels:      - "traefik.enable=true"      - "traefik.http.routers.registry.rule=Host(`<REGISTRY.FQDN>`)"
Разберем каждую новую строку (нажать)
- "traefik.enable=true"

Эта метка оповещает traefik, что данный сервис нужно опубликовать

- "traefik.http.routers.registry.rule=Host(`<REGISTRY.FQDN>`)"

Эта метка указывает traefik, что доменное имя <REGISTRY.FQDN> необходимо связать с данным сервисом. Запросы, в заголовках которых будет указано данное имя, будут перенаправлены в текущий контейнер.

- "traefik.http.services.registry.loadbalancer.server.port=5000"

Мы также можем указать порт, на который будет перенаправлен трафик, но в данном случае это делать не обязательно. Если docker файл нашего сервиса открывает наружу всего лишь 1 порт, этот порт будет выбран автоматически.

Перезапустим оба наших сервиса:

docker-compose up -d

Откроем в браузере страницу http://<REGISTRY.FQDN>:5000/v2/_catalog , где <REGISTRY.FQDN> - это полное доменное имя нашего сервиса, описанное в метке compose файла.

В ответ увидим страницу в текстом:

{"repositories":[]}

Значит все работает.

Добавление SSL (настройка https)

Мы будем автоматически получать и продлять SSL сертификаты через Let's Encrypt.

Новый compose для Traefik:

version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"      - "--entrypoints.web.address=:80"      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"      - "--entrypoints.websecure.address=:443"      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"      - "--certificatesresolvers.myresolver.acme.email=<EMAIL>"      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"    ports:      - "80:80"      - "443:443"      - "8080:8080"    networks:      - registry_default    volumes:      - "letsencrypt:/letsencrypt"      - "/var/run/docker.sock:/var/run/docker.sock:ro"volumes:  letsencrypt:networks:  registry_default:    external: true
Разберем каждую новую строку (нажать)
- "--entrypoints.web.address=:80"

Меняем имя стандартного entrypoint с http на web для удобства.

- "--entrypoints.web.http.redirections.entryPoint.to=websecure"

Добавляем автоматическое перенаправление трафика с entrypoint web на websecure. Другими словами перенаправление с HTTP на HTTPS

- "--entrypoints.websecure.address=:443"

Создаем новый entrypoint на 443 порту с именем websecure

- "--certificatesresolvers.myresolver.acme.httpchallenge=true"

Настраиваем режим выдачи сертификатов Lets Encrypt через http challenge

- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"

Настраиваем entrypoint для http challenge

- "--certificatesresolvers.myresolver.acme.email=<EMAIL>"

Настраиваем <email> адрес для регистрации в центре сертификации

- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"

Меняем стандартное расположение файла acme.json. В этот файл будут записываться выданные сертификаты. Дело в том, что стандартное расположение файла /acme.json" в корне не позволяет хранить этот файл на подключенном томе.

- "443:443"

Перенаправление стандартного веб порта 443 (https) на docker машине в аналогичный порт контейнера traefik. Это нужно для обработки и маршрутизации полезного трафика.

volumes:- "letsencrypt:/letsencrypt"volumes:letsencrypt:

Подключаем именованный том для постоянного хранения SSL сертификатов. Теперь даже после пересоздания контейнера нам не придется заново получать все сертификаты.
Именованный том будет храниться здесь: /var/lib/docker/volumes/<имя тома>

Новый compose для Registry:

version: '2.4'services:  registry:    restart: always    image: registry:2    ports:      - 5000:5000    labels:      - "traefik.enable=true"      - "traefik.http.routers.registry.rule=Host(`<REGISTRY.FQDN>`)"      - "traefik.http.routers.registry.entrypoints=websecure"      - "traefik.http.routers.registry.tls.certresolver=myresolver"
Разберем каждую новую строку (нажать)
- "traefik.http.routers.registry.entrypoints=websecure"

Меняем entrypoint со стандартного http (web) на websecure

- "traefik.http.routers.registry.tls.certresolver=myresolver"

Задаем имя резолвера для работы SSL сертификатов

Перезапустим оба наших сервиса:

docker-compose up -d

Откроем в браузере страницу http://<REGISTRY.FQDN>:5000/v2/_catalog , где <REGISTRY.FQDN> - это полное доменное имя нашего сервиса, описанное в метке compose файла.

Мы увидим, что:

  • схема сменилась с http на https автоматически

  • соединение защищено сертификатом выданным Let's Encrypt

В случае проблем с получением сертификата, traefik будет использовать само-подписанный сертификат. Если это произошло, следует использовать команду docker logs traefik для просмотра логов.

Настройка домена и SSL для dashboard

Как ни странно, маршрутизация трафика во встроенный dashboard отличается от маршрутизации трафика во внешние сервисы, запущенные в докер контейнерах. Я потратил не мало времени, пытаясь использовать те же правила, что и раньше.

Новый compose для Traefik:

version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"      - "--entrypoints.web.address=:80"      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"      - "--entrypoints.websecure.address=:443"      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"      - "--certificatesresolvers.myresolver.acme.email=<EMAIL>"      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"    ports:      - "80:80"      - "443:443"      - "8080:8080"    networks:      - registry_default    volumes:      - "letsencrypt:/letsencrypt"      - "/var/run/docker.sock:/var/run/docker.sock:ro"    labels:      - "traefik.enable=true"      - "traefik.http.routers.traefik.rule=Host(`<TRAEFIK.FQDN>`)"      - "traefik.http.routers.traefik.entrypoints=websecure"      - "traefik.http.routers.traefik.tls.certresolver=myresolver"      - "traefik.http.routers.traefik.service=api@internal"volumes:  letsencrypt:networks:  registry_default:    external: true
Разберем каждую новую строку (нажать)
labels:  - "traefik.enable=true"  - "traefik.http.routers.traefik.rule=Host(`<TRAEFIK.FQDN>`)"  - "traefik.http.routers.traefik.entrypoints=websecure"  - "traefik.http.routers.traefik.tls.certresolver=myresolver"

Регистрируем роутер для направления трафика домена <traefik.fqdn> во внутренний dashboard.

- "traefik.http.routers.traefik.service=api@internal"

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

api@internal - это зарезервированное имя сервиса. Перенаправление в dashboard не будет работать без этой строки.

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

Перезапустим traefik:

docker-compose up -d

Откроем в браузере страницу http://<TRAEFIK.FQDN>, где <TRAEFIK.FQDN> - это полное доменное имя нашего для доступа к traefik dashboard, описанное в метке compose файла.

Добавление сжатия трафика

Сжатие сильно ускоряет загрузку сайтов на клиенте. Обязательно нужно включать.

Новый compose для Traefik:

version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"      - "--entrypoints.web.address=:80"      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"      - "--entrypoints.websecure.address=:443"      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"      - "--certificatesresolvers.myresolver.acme.email=<email>"      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"    ports:      - "80:80"      - "443:443"      - "8080:8080"    networks:      - registry_default    volumes:      - "letsencrypt:/letsencrypt"      - "/var/run/docker.sock:/var/run/docker.sock:ro"    labels:      - "traefik.http.middlewares.traefik-compress.compress=true"      - "traefik.enable=true"      - "traefik.http.routers.traefik.rule=Host(`<TRAEFIK.FQDN>`)"      - "traefik.http.routers.traefik.entrypoints=websecure"      - "traefik.http.routers.traefik.tls.certresolver=myresolver"      - "traefik.http.routers.traefik.service=api@internal"      - "traefik.http.routers.traefik.middlewares=traefik-compress"volumes:  letsencrypt:networks:  registry_default:    external: true
Разберем каждую новую строку (нажать)
- "traefik.http.middlewares.traefik-compress.compress=true"

Регистрируем новый middleware с именем traefik-compress и функцией сжатия трафика. Этот middleware мы затем сможем использовать в любом стороннем докер контейнере.

- "traefik.http.routers.traefik.middlewares=traefik-compress"

Добавляем middleware с именем traefik-compress в цепочку обработки трафика для сервиса traefik

Новый compose для Registry:

version: '2.4'services:  registry:    restart: always    image: registry:2    ports:      - 5000:5000    labels:      - "traefik.enable=true"      - "traefik.http.routers.registry.rule=Host(`<REGISTRY.FQDN>`)"      - "traefik.http.routers.registry.entrypoints=websecure"      - "traefik.http.routers.registry.tls.certresolver=myresolver"      - "traefik.http.routers.registry.middlewares=traefik-compress"
Разберем каждую новую строку (нажать)
- "traefik.http.routers.registry.middlewares=traefik-compress"

Добавляем middleware с именем traefik-compress в цепочку обработки трафика для сервиса registry

Добавление basic авторизации для доступа к Dashboard

Сначала мы собираемся сгенерировать комбинацию пользователя и пароля для базовой аутентификации с использованием htpasswd. Если он у вас не установлен, вам нужно сначала сделать это (например, для сервера Ubuntu):

apt-get install apache2-utils

Мы должны экранировать каждый символ $ в нашем зашифрованном пароле (заменить $ на $$), если мы используем пароль напрямую в docker-compose.yml

echo $(htpasswd -nbB USER "PASS") | sed -e s/\\$/\\$\\$/g

Пример вывода команды (результат будет разный при каждом запуске команды):

USER:$$2y$$05$$iPGcI0PwxkDoOZUlGPkIFe31e47F5vewcjlhzhgf0EHo45H.dFyKW

Вывод команды нужно поместить в наш docker-compose.yml внутрь traefik метки, заменив <USER-PASSWORD-OUTPUT> в примере ниже.

Новый compose для Registry:

version: "2.4" services:   traefik:    image: "traefik:v2.4"    container_name: "traefik"    command:      - "--api.insecure=true"      - "--providers.docker=true"      - "--providers.docker.exposedbydefault=false"      - "--entrypoints.web.address=:80"      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"      - "--entrypoints.websecure.address=:443"      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"      - "--certificatesresolvers.myresolver.acme.email=<EMAIL>"      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"    ports:      - "80:80"      - "443:443"      - "8080:8080"    networks:      - registry_default    volumes:      - "letsencrypt:/letsencrypt"      - "/var/run/docker.sock:/var/run/docker.sock:ro"    labels:      - "traefik.http.middlewares.traefik-compress.compress=true"      - "traefik.http.middlewares.auth.basicauth.users=<USER-PASSWORD-OUTPUT>"      - "traefik.enable=true"      - "traefik.http.routers.traefik.rule=Host(`<TRAEFIK.FQDN>`)"      - "traefik.http.routers.traefik.entrypoints=websecure"      - "traefik.http.routers.traefik.tls.certresolver=myresolver"      - "traefik.http.routers.traefik.service=api@internal"      - "traefik.http.routers.traefik.middlewares=traefik-compress,auth"volumes:  letsencrypt:networks:  registry_default:    external: true
Разберем каждую новую строку (нажать)
- "traefik.http.middlewares.auth.basicauth.users=<USER-PASSWORD-OUTPUT>"

Регистрируем новый middleware с именем auth и функцией авторизации. Этот middleware мы затем сможем использовать в любом стороннем докер контейнере.

- "traefik.http.routers.traefik.middlewares=traefik-compress,auth"

Добавляем middleware с именем auth в цепочку обработки трафика для сервиса traefik

Внимание: Если вы используете переменные среды (например .env файл) в вашем docker-compose.yml вместо прямого указания <USER-PASSWORD-OUTPUT>, то вы не должны экранировать $. Генерация пароля в этом случае будет выглядеть так:

echo $(htpasswd -nbB <USER> "<PASS>")

После перезапуска docker контейнера (docker-compose up -d) мы увидим окно базовой авторизации, когда откроем dashboard traefik в браузере.

Разделение прав доступа пользователей Registry

Новый compose для Registry:

version: '2.4'services:  registry:    restart: always    image: registry:2    ports:      - 5000:5000    labels:      - "traefik.enable=true"      - "traefik.http.routers.registry.rule=Host(`REGISTRY.FQDN`) && Method(`POST`, `PUT`, `DELETE`, `PATCH`)"      - "traefik.http.routers.registry.entrypoints=websecure"      - "traefik.http.routers.registry.tls.certresolver=myresolver"      - "traefik.http.routers.registry.service=registry"      - "traefik.http.services.registry.loadbalancer.server.port=5000"      - "traefik.http.routers.registry.middlewares=auth-registry,traefik-compress"      - "traefik.http.middlewares.auth-registry.basicauth.users=<ADMIN-PASSWORD-OUTPUT>"      - "traefik.http.routers.guest-registry.rule=Host(`REGISTRY.FQDN`) && Method(`GET`, `HEAD`)"      - "traefik.http.routers.guest-registry.entrypoints=websecure"      - "traefik.http.routers.guest-registry.tls.certresolver=myresolver"      - "traefik.http.routers.guest-registry.service=guest-registry"      - "traefik.http.services.guest-registry.loadbalancer.server.port=5000"      - "traefik.http.routers.guest-registry.middlewares=aguest-registry,traefik-compress"      - "traefik.http.middlewares.aguest-registry.basicauth.users=<USER-PASSWORD-OUTPUT>"

Зарегистрируем на этом контейнере 2 набора роутеров и сервисов:

  • registry - роутер и сервис с таким именем будут предоставлять полный доступ (чтение\запись)

  • guest-registry - роутер и сервис с таким именем будут предоставлять гостевой доступ (чтение)

Так же мы создаем одноименные middleware для basic авторизации и добавляем их в роутеры.

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

Перезапустим registry:

docker-compose up -d
Проведем простую проверку с помощью Postman

Авторизовываемся пользователем с ограниченными правами.

Делаем Get запрос - работает.

Делаем Post запрос - 401.

Авторизовываемся пользователем с полными правами.

Делаем Get запрос - работает.

Делаем Post запрос - работает. Авторизация пройдена, но сам запрос отклоняется registry, так как не является допустимым. Мы не стали подбирать правильный запрос для экономии времени.

Заключение

На мой взгляд traefik гораздо удобнее классического nginx, если мы живем внутри docker контейнеров.

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru