UFW가 Discourse도 제한할까요?

Discourse가 UFW를 우회하는 방법은 무엇인가요? 포트 22만 활성화했는데, 그렇다면 다른 모든 포트는 닫혀 있어야 합니다. 그런데도 포럼이 정상적으로 작동했습니다. 어떻게 이런 일이 가능한가요?

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:

정말 이상합니다.

기본값이 deny로 설정되어 있는지 아닌지 명확하지 않습니다. “Default” 줄에 대해 특별히 질문하는 이유는, 기본값이 allow로 설정되어 있으면서 특정 포트도 allow로 설정할 수 있기 때문입니다. 물론 후자의 경우 해당 구성에서는 아무것도 변경되지 않습니다.

다른 사람이 Discourse를 설정했다면, HTTP/HTTPS 포트를 허용하는 대신 기본값을 allow로 변경했을 가능성이 있을까요?

이것은 다른 곳에 있는 SMTP 서버로, Discourse 서버가 포트 587을 사용하여 해당 SMTP 서버에 연결하는 것이라고 가정합니다. 기본적으로 모든 포트에 대한傳出(아웃바운드) 연결이 허용되므로, 명시적으로傳出 정책을 변경하지 않는 한 UFW가 이메일 전송을 방해하지 않습니다.

실수했습니다 :man_facepalming:

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

아니요. 하지만 allow (outgoing)가 모든 아웃바운드 트래픽을 허용하나요? 그렇다면 다시 “초보자가 하는 멍청한 기본 질문”이라는 카테고리가 여기에는 없음을 확인하게 되었고, 저는 새로운 것을 배웠습니다.

수정:

아직도 헷갈립니다 — 그런데 deny (incoming는 어떻게 되나요?

이것을 찾았습니다:

https://askubuntu.com/questions/1305308/ufw-ports-open-although-closed-by-default-deny

간단한 답은 Discourse가 방화벽 규칙을 우회할 수 없다는 것입니다. 그리고 운영체제에 관한 멍청한 질문을 할 곳은 Stack Exchange 같은 곳입니다. (무례하게 느껴지지 않았으면 합니다. 그쪽이 바로 그런 답변이 있는 곳이니까요. 아주 초기 릴리스부터 리눅스를 사용해 왔지만, 저는 여전히 그런 멍청한 질문들을 가지고 같은 곳을 찾곤 합니다. 여전히 그런 질문들이 있으니까요!)

그곳을 잘 알고 있죠 :wink: 신호 대 잡음비가 너무 높습니다. 음, 그건 좀 부당한 표현이었지만, 거기서 관련 있는 내용을 찾기가 정말 어렵다는 뜻이었어요. 규모가 너무 크거든요.

실제로 이것은 매우 좋은 질문이며, 아직 아무도 묻지 않았다는 점이 놀랍습니다. 답변은 복잡하지만, 지금까지 이 주제에 대한 반응들은 불행히도 질문을 제대로 답변하지 않고 무시하는 어조였습니다.

Discourse가 ufw를 우회한다는 것이 본질적인 문제는 아닙니다. *docker*가 ufw를 우회하는 것입니다. Docker는 컨테이너의 노출된 포트가 ufw가 존재하더라도 작동하도록 하는 규칙을 추가하여 ufw를 우회합니다.

무슨 일이 일어나고 있는가?

컨테이너로 향하는 수신 패킷은 예상과 달리 INPUT 테이블이 아닌 FORWARD 테이블에 도달합니다.

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

패킷이 포워딩 테이블에 도달하는 이유는 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를 활성화하기 전에 아래 내용을 적용합니다:

# stolen from https://github.com/chaifeng/ufw-docker - looks sensible
# adding the forward to ufw-user-input allows connections to
# forwarded ports that we explicitly opened
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:

이 주제는 해결되었지만, 제 설정에서 발견된 사소한 문제 하나가 드러났습니다. 언젠가 비슷한 문제를 검색하는 분이 계시면 설명해 드리겠습니다.

Nginx가 제 모든 사이트의 SSL과 기타 여러 항목을 처리하는 VPS를 하나 가지고 있었습니다(사이트가 꽤 많아요. 영어는 정말 이상한 언어죠 ;)). Nginx는 요청을 Varnish로 보냅니다. Varnish는 Discourse에는 쓸모없는 리버스 프록시이지만, 일부 필터링을 수행합니다. Varnish는 83 포트를 사용하여 Discourse가 실행되는 다른 VPS로 요청을 보냅니다. 두 VPS 모두 동일한 포트를 경청하며, 이 포트는 두 IP 주소에서만 허용됩니다. 저는 이 설정에 완전히 만족하고 있었습니다 — 지금까지까지는요.

포트 443을 사용할 때 어떤 일이 일어나는지 테스트해 보았습니다(curl -I https://forum.example.tld:443). Discourse 쪽에 여전히 유효한 SSL 인증서가 있으므로(몇 주 전에 이 변경을 수행했습니다) 정상적으로 작동했습니다.

@supermathie님의 답변은 이것이 왜 발생하는지 그리고 어떻게 수정해야 하는지를 설명합니다. 물론 AFAIK(제 지식 범위 내에서) 이로 인해 보안 문제가 발생하지는 않지만, 정말 짜증나는 부분입니다 :squinting_face_with_tongue:

와. 정말 놀라운 일이네요! 감사합니다.

사람들이 discourse를 설치하면서 동작하지 않게 원하는 경우는 드물기 때문에, 방화벽을 그렇게 설정해 두었는데도 docker가 작동하게 만들 때 사람들이 불평하지 않는 것 같습니다. 그래서 이런 질문이 자주 올라오지 않는 것 같아요. :wink:

매우 흥미로운 문제이지만, 다소 생소한 주제인 것 같고, 이것은 Discourse의 문제가 아니라 docker의 특이한 점입니다. 질문의 핵심은 "방화벽을 어떻게 설정해야 discourse가 작동하지 않게 할 수 있는가?"입니다. prerouting 관련 내용을 기억해 두어야겠습니다.

답을 알았다면 무시하는 태도를 보이지 않았을 겁니다! :wink: 이번 문제 해결에 도움을 주셔서 감사합니다!

저는 무시하려는 의도는 전혀 없었고, 문제 해결을 시도하려 했을 뿐입니다… 물론, Docker가 실제로 무엇을 하고 있는지 모르는 상태에서 접근한 것이 분명해 보입니다. 정말 유용한 정보 감사합니다, 답변해 주셔서!

OP나 다른 누군가가 변경할 수 있다면, 표시된 해결책(정답)을 바꾸는 것이 가치가 있을 것 같습니다.

Docker 관련 사항이기는 하지만, 이로부터 파생되는 Discourse의 가설적인 상황을 몇 가지 떠올릴 수 있습니다. 예를 들어, 전 세계 어디서나 접근 가능한 사무실 서버를 가지고 있지만, UFW를 구성하여 Discourse가 사무실 내부에서만 접근 가능하도록 제한하고 싶은 경우입니다. Docker의 추가 설정은 이러한 구성을 방해할 수 있습니다.

다만, 그 특정 시나리오에서는 호스트의 UFW가 아니라 하드웨어/가상화 프로그램(Hypervisor) 레벨의 파이어월에 설정을 적용할 것입니다.

방금 여러분이 찾은 그 git 저장소에 UFW/Docker 문제를 조사하러 왔는데, 이 주제에 대해 생각하게 되었습니다.

정확히 무엇을 변경했나요? (추가된 줄은 볼 수 있지만, 그것이 무엇을 의미하는지 알고 싶습니다.) 그리고 이 변경 사항들은 Discourse를 위한 것인가요?

저는 저장소의 기본 코드를 사용했고, 그 후 UFW 문제가 해결된 것 같았습니다.

수정: 여러분이 편집한 코드를 사용하면 ufw reload를 실행하는 순간 다음 오류가 발생합니다:

오류: 로깅 규칙을 로드할 수 없습니다

사실, 한참을 고민하다가 해결 방법을 찾았고, Discourse 설치 환경에서 이 작업을 수행하기 위해 이 bash 스크립트를 작성했습니다.

이 스크립트는 방화벽을 초기화하고, ufw-docker-util (after.rules 파일을 수정하는 도구)을 설치한 후, 포트 443과 80을 허용 목록에 추가합니다. 그러면 끝입니다.

또한 잠금으로 인해 접속할 수 없게 되는 상황을 방지하기 위해 모든 IP에서 포트 22에 대한 접근을 허용합니다. 모든 작업이 완료된 후에는 포트 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:

하지만 일반적으로는 도커가 UFW/iptables를 완전히 따랐으면 합니다. 왜냐하면 저는 iptables를 통해 GeoIP 차단 기능을 사용하고 있기 때문이죠. (네, 알고 있습니다 — UFW는 iptables의 단순한 UI일 뿐이니까요.)

물론 더 이상 Discourse 커뮤니티에 있는 것은 아니지만, 여기서 코더와 최종 사용자가 왜 항상 서로를 잘 이해하지 못하는지 볼 수 있습니다. 코더들은 세상을 논리적 블록과 if/then/else 구성으로 보는 반면, 최종 사용자는 전체적인 맥락으로 세상을 봅니다. 즉, 제가 Discourse를 사용하더라도, 그것이 docker 내부에서 작동하더라도 제 관점에서는 제 규칙을 따르지 않는 것이 Discourse입니다. :rofl:

이 이야기는 Community Building > Praise 게시판에 올리는 것이 맞았을 텐데, 몇 주 전에 포럼의 URL을 변경했습니다. 그러자 전 세계의 스크립트 키디와 쓸모없는 SEO 봇들이 방문하기 시작했습니다. DigitalOcean의 2 GB/1 vCPU VPS에서 시간당 3000개의 다른 사용자 에이전트가 접속했고, Discourse는 그걸 전혀 신경 쓰지 않았습니다. 그런 종류의 DDoS 수준의 폭주였다면 어떤 WordPress 설치 환경이든 느려지거나 다운되었을 것입니다.

그래서, 저는 discourse(아니면 docker)가 Fail2ban의 밴 목록과 제 규칙을 따르도록 강요할 필요은 없는데 — 하지만 저는 그 봇들을 정말로 싫어합니다.