Ограничит ли UFW и Discourse?

Как Discourse может обходить UFW? Я включил только порт 22, значит все остальные порты должны быть закрыты. Но форум всё равно работал. Как это возможно?

Это Droplet от DigitalOcean, но это не должно иметь значения. И это не установка в один клик, а официальный способ.

Это не чисто вопрос поддержки, но у нас здесь нет категории под названием Глупые базовые вопросы новичков :wink:

Так что если вы выполните ufw status verbose, то увидите только sshd?

Верно. И UFW был включен.

Что было указано по умолчанию при запуске команды ufw status verbose?

Для того, что, по моему мнению, вам нужно, я ожидал бы увидеть следующее:
Default: deny (incoming), allow (outgoing), disabled (routed)

С тем поведением, которое вы описываете, я ожидал бы увидеть следующее:
Default: allow (incoming), allow (outgoing), disabled (routed)

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

Только 22/tcp (OpenSSH) ALLOW IN Anywhere, как и должно быть. Это именно то, что я всегда делаю: разрешаю OpenSSH и включаю UFW.

Я открываю порты только при необходимости, например, когда собираюсь установить Nginx. Но поскольку этот VPS использовался только для Discourse, я ничего больше не делал — совсем забыл про UFW.

Это не может быть так. Этот форум работал уже несколько недель.

Я обнаружил эту ситуацию, когда подключил Nginx/Varnish к этому VPS и мне нужно было открыть ещё один порт — и я понял, что открыт только порт 22.

Редактирование:

Как же я мог отправлять письма? Порт 587 тоже не был открыт :flushed_face:

Это действительно странно.

Я не совсем понимаю, говорите ли вы, что значение по умолчанию установлено на «отказывать» или нет. Причина, по которой я спрашиваю конкретно о строке «Default», заключается в том, что можно иметь значение по умолчанию «разрешать» и при этом установить разрешение для конкретного порта, хотя в такой конфигурации последнее ничего не меняет.

Если Discourse настраивал кто-то другой, возможно, этот человек изменил значение по умолчанию на «разрешать» вместо того, чтобы открыть порты HTTP/HTTPS?

Я предполагаю, что речь идёт о SMTP-сервере где-то ещё, и ваш сервер Discourse подключается к этому SMTP-серверу через порт 587. Исходящие соединения по умолчанию разрешены на всех портах, поэтому UFW не будет мешать отправке писем, если вы явно не измените политику для исходящего трафика.

Моя вина :man_facepalming:

Default: deny (incoming), allow (outgoing), deny (routed)

Нет. Но разрешает ли allow (outgoing) любой исходящий трафик? Если да, то мы возвращаемся к тому, что «у нас здесь нет категории под названием Глупые базовые вопросы новичков», и я узнал что-то новое.

Редактирование:

Я всё ещё в недоумении — но как насчёт deny (incoming)?

Я нашел это:

Короткий ответ: Discourse не может обойти правила вашего фаервола, а задавать глупые вопросы об операционных системах лучше где-нибудь вроде Stack Exchange. (Пожалуйста, не считайте меня грубым. Ответы на такие вопросы именно там. Я использую Linux с самых первых версий, но всё ещё обращаюсь туда со своими глупыми вопросами. Они у меня всё ещё есть!)

Я знаю это место :wink: Соотношение сигнала к шуму там слишком низкое. Ну, это не совсем точная характеристика, но я имел в виду, что оттуда действительно сложно найти что-то полезное. Там просто слишком огромный объём информации.

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

Дело не в том, что Discourse обходит ufw, а в том, что docker обходит ufw, добавляя правила, благодаря которым открытые порты контейнеров Docker работают, несмотря на наличие ufw.

Что происходит?

Входящие пакеты, предназначенные для контейнера, попадают в таблицу FORWARD, а не в таблицу INPUT, как можно было бы ожидать.

До установки Docker

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ufw-before-logging-forward  all  --  any    any     anywhere             anywhere            
    0     0 ufw-before-forward  all  --  any    any     anywhere             anywhere            
    0     0 ufw-after-forward  all  --  any    any     anywhere             anywhere            
    0     0 ufw-after-logging-forward  all  --  any    any     anywhere             anywhere            
    0     0 ufw-reject-forward  all  --  any    any     anywhere             anywhere            
    0     0 ufw-track-forward  all  --  any    any     anywhere             anywhere

После установки Docker

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    8   416 DOCKER-USER  all  --  any    any     anywhere             anywhere            
    8   416 DOCKER-ISOLATION-STAGE-1  all  --  any    any     anywhere             anywhere            
    0     0 ACCEPT     all  --  any    docker0  anywhere             anywhere             ctstate RELATED,ESTABLISHED
    4   256 DOCKER     all  --  any    docker0  anywhere             anywhere            
    4   160 ACCEPT     all  --  docker0 !docker0  anywhere             anywhere            
    0     0 ACCEPT     all  --  docker0 docker0  anywhere             anywhere            
    0     0 ufw-before-logging-forward  all  --  any    any     anywhere             anywhere            
    0     0 ufw-before-forward  all  --  any    any     anywhere             anywhere            
    0     0 ufw-after-forward  all  --  any    any     anywhere             anywhere            
    0     0 ufw-after-logging-forward  all  --  any    any     anywhere             anywhere            
    0     0 ufw-reject-forward  all  --  any    any     anywhere             anywhere            
    0     0 ufw-track-forward  all  --  any    any     anywhere             anywhere

Причина, по которой пакеты попадают в таблицу FORWARD, заключается в правилах, которые Docker добавляет в таблицу nat:

Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
  210 12734 DOCKER     all  --  any    any     anywhere             anywhere             ADDRTYPE match dst-type LOCAL

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 RETURN     all  --  docker0 any     anywhere             anywhere            
    0     0 DNAT       tcp  --  !docker0 any     anywhere             anywhere             tcp dpt:https to:172.17.0.2:443
  107  6848 DNAT       tcp  --  !docker0 any     anywhere             anywhere             tcp dpt:http to:172.17.0.2:80

nat/PREROUTING обрабатывается до принятия решения о том, отправлять ли пакеты через INPUT или FORWARD.

В конечном итоге проблема заключается в том, что в системе есть два сервиса, изменяющие правила фаервола. ufw ничего об этом не знает, поэтому может сообщать только о том, что настроил он.

Решение

Одним из решений является перенастройка фаервола так, чтобы трафик, предназначенный для Docker, также проходил через цепочки ufw:

Я использую следующую небольшую адаптацию их работы, внедрённую до включения ufw:

# украдено из https://github.com/chaifeng/ufw-docker - выглядит разумно
# добавление forward в ufw-user-input позволяет подключениям
# к перенаправленным портам, которые мы явно открыли
cat <<EOUFW >> /etc/ufw/after.rules
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-user-input - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN

-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j ufw-user-input

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16

-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
EOUFW

Спасибо! Это было полное объяснение.

Добро пожаловать, я именно такой :smiley:

Эта тема решена, но она выявила одну небольшую проблему в моей настройке. Я объясню её, если кто-то когда-нибудь будет искать что-то подобное.

У меня был один VPS, где Nginx отвечает за SSL и другие задачи для всех моих сайтов (их много, и английский — один из самых странных языков ;)). Nginx отправляет запросы в Varnish. Для Discourse это бесполезный обратный прокси, но он выполняет некоторую фильтрацию. Varnish пересылает запросы на другой VPS, где работает Discourse, используя порт 83. Оба VPS слушают один и тот же порт, и доступ разрешён только с обоих IP-адресов. Я был полностью доволен — до сих пор.

Я проверил, что происходит при использовании порта 443: curl -I https://forum.example.tld:443. Всё работало отлично, потому что на стороне Discourse всё ещё действовал SSL-сертификат (я внес эти изменения несколько недель назад).

Ответ от @supermathie объясняет, почему это происходит и как исправить ситуацию. Конечно, насколько мне известно, это не создаёт никаких проблем с безопасностью, но это действительно раздражает :squinting_face_with_tongue:

Вау! Это просто безумие! Спасибо.

Думаю, этот вопрос редко задают, потому что нечасто кто-то устанавливает Discourse и хочет, чтобы он не работал. Поэтому, когда Docker заставляет его работать, даже если вы настроили фаервол так, чтобы он не работал, люди в основном не жалуются. :wink:

Это очень интересная проблема, но она кажется немного эзотерической, и дело не в Discourse, а в особенности работы Docker. Вопрос сводится к следующему: «Как мне настроить фаервол, чтобы Discourse не работал?» Я постараюсь запомнить эти правила перенаправления.

Если бы я знал ответ, я бы не был пренебрежительным! :wink: Спасибо, что спас ситуацию в этот раз!

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

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

Хотя это больше связано с Docker, я вижу здесь возможные гипотетические сценарии для Discourse. Например, у меня может быть офисный сервер, доступный из внешнего мира, но я хочу настроить UFW так, чтобы Discourse был доступен только изнутри офиса. Особенности работы Docker помешали бы такой настройке.

Хотя в данном конкретном случае я бы всё равно настроил это на уровне аппаратного брандмауэра или гипервизора, а не через UFW на хост-машине.

Я тоже только что зашел в тот же репозиторий Git, который вы нашли, разбираясь с проблемой ufw/docker, и это натолкнуло меня на эту тему.

Что именно вы изменили (я вижу добавленные строки, но что они означают?), и эти изменения специально для Discourse?

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

РЕДАКТИРОВАНИЕ: Когда я использую ваш отредактированный код, в первый же момент выполнения ufw reload я получаю следующую ошибку:

ERROR: Could not load logging rules

На самом деле, я через некоторое время разобрался и написал этот bash-скрипт для выполнения задачи при установке Discourse.

Он сбрасывает ваш брандмауэр, устанавливает ufw-docker-util (который редактирует after.rules), а затем добавляет порты 443 и 80 в ваш список разрешённых. Вот и всё.

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

РЕДАКТИРОВАНИЕ: скрипт работает, но пересборка Discourse после его использования завершится ошибкой: fatal: unable to access 'https://github.com/discourse/discourse.git/': Could not resolve host: github.com. Поэтому НЕ используйте скрипт, если не знаете, как решить эту проблему.

РЕДАКТИРОВАНИЕ 2: Работает на Ubuntu, но не на CentOS!

В прошлый раз, когда я писал bash-скрипт, я изменил владельца всех директорий на www-data:www-data — для меня такая работа немного упрощает жизнь :wink:

Но в целом — я бы хотел, чтобы Docker полностью следовал правилам UFW/iptables. Просто потому что я использую блокировку по GeoIP через iptables (да, я знаю — UFW это лишь минималистичный интерфейс для iptables).

Конечно, мы уже не в Discourse, но здесь видно, почему разработчики и конечные пользователи не всегда хорошо понимают друг друга: разработчики видят мир как логические блоки и конструкции if/then/else, а конечные пользователи видят его в полном контексте. То есть — поскольку я использую Discourse, даже если он работает внутри Docker, с моей точки зрения именно Discourse не следует моим правилам :rofl:

Это должно было попасть в #praise, но несколько недель назад я изменил URL форума. И ко мне пришли все скрипт-кидди и бесполезные SEO-боты со всего мира. На моём VPS от DigitalOcean с 2 ГБ ОЗУ и 1 vCPU приходило 3000 разных User-Agent в час, и Discourse просто не обращал на это внимания. Любая установка WordPress после такого DDOS-подобного наплыва стала бы медленной или упала бы.

Так что мне не так уж и нужно заставлять Discourse (ну, Docker) соблюдать блокировки Fail2ban и мои правила — но я глубоко ненавижу этих ботов.