Will UFW limit Discourse too?

How can Discourse bypass UFW? I had enabled only port 22 so all other ports should be closed then. But a forum worked anyway. How’s that possible?

DigitalOcean droplet, but that should not mean anything. And no one-click install, but official way.

This is not a pure support question, but we don’t have here a catogory named Stupid basic questions by beginners :wink:

5 Likes

So if you ufw status verbose you only see the sshd?

1 Like

That’s right. And UFW was enabled.

1 Like

What was listed for default when you ran ufw status verbose?

For what I think you want, I would expect to see this:
Default: deny (incoming), allow (outgoing), disabled (routed)

With the behaviour you’re describing, I would expect to see this:
Default: allow (incoming), allow (outgoing), disabled (routed)

1 Like

Did you try opening the forum in an incognito window or a different browser? It’s easy too be fooled by the cached version coming up. I’ve been fooled myself and had another developer tell me that the staging site looked good when it wasn’t running at all.

1 Like

Just 22/tcp (OpenSSH) ALLOW IN Anywhere as it should be. That is the very two things I always do: allow OpenSSH and enable UFW.

I open ports only if needed, like when I’ll install Nginx. But because of that VPS was only for Discourse I didn’t do anything else — I forgot UFW totally.

That can’t be the case. That forum was alive weeks.

I woke to this situation when I connected Nginx/Varnish to that VPS and I needed another port open — and I realized only that was open was port 22.

Edit:

How could I send emails? Port 587 wasn’t open either :flushed:

This is really strange.

1 Like

I’m unclear if you’re saying the default is set to deny or not. The reason I ask about the “Default” line specifically is because it is possible to have both a default of allow and set a specific port to allow, though the latter doesn’t change anything in that arrangement.

If someone else set up Discourse, is it possible that person changed the default to allow instead of allowing the HTTP/HTTPS ports?

I’m assuming this is SMTP somewhere else, your Discourse server connecting to that SMTP server using port 587. Outbound connections are allowed on all ports by default, so UFW won’t get in the way of sending emails unless you explicitly change the outgoing policy.

4 Likes

My bad :man_facepalming:

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

No. But does allow (outgoing) allow anything outbound? If yes, then we are back to ” we don’t have here a catogory named Stupid basic questions by beginners” and I’ve learned new things.

Edit:

I’m still lost — but how’s deny (incoming)?

2 Likes

I found this:

2 Likes

The short answer is that Discourse cannot bypass your firewall rules and the place to ask stupid questions about os thingsis somewhere like stack exchange. (Please don’t think me rude. That’s where those answers lie. After running Linux since the very earliest releases, I still go places like that to all my stupid questions. I do still have them!)

3 Likes

I know the place :wink: There is way too high signal/noise ratio. Well, that was unfaif characterization, but I was meaning it is really hard to find from there relevant things. It is so massive.

2 Likes

This is actually a really good question and one I’m surprised nobody else has yet asked. The answer is complicated, but so far the responses on this topic have unfortunately been dismissive in tone without answering the question.

It isn’t per se that Discourse is bypassing ufw, but docker bypasses ufw by adding rules that cause any exposed ports of docker containers to work despite the presence of ufw.

What’s going on?

Incoming packets destined for a container hit the FORWARD table, not the INPUT table as one might expect.

Pre docker install

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

Post docker install

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

The reason the packets hit the forward table is due to rules that docker adds to the nat table:

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 is processed prior to the decision being taken on whether to send packets through INPUT or FORWARD.

Ultimately the problem is there are two services on the system modifying the firewall rules. ufw knows about none of this, so it can only report what it has configured.

A solution

to this is reconfiguring the firewall to pass traffic destined for docker through the ufw chains as well:

I use the following slight adaptation of their work, put in place before enabling 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
19 Likes

Thanks! That was complete explanation.

4 Likes

Welcome, that’s how I roll :smiley:

5 Likes

This topic is solved, but it revealed one minor issue’ish of my setup. I’ll explain it if someone will search something similar someday.

I had one VPS where Nginx takes care of SSL and some other stuff of all of my sites (plenty of ofs, english is one very strange language :wink: ). Nginx sends requests to Varnish. It is useless reverse proxy for Discouse, but it does some filtering. Varnish sends requests to another VPS where Discourse lives using port 83. Both VPS’ listen same port and It is allowed to both IPs only. I was totally happy — until now.

I tried what happends when I used port 443 curl -I https://forum.example.tld:443. It worked nicely, because I have still valid SSL certificate on side of Discourse (I did this change some weeks ago).

@supermathie’s answer explains why this happpends and how to fix it. Sure, there is no security issues because of that what so ever AFAIK, but it is really annoying :stuck_out_tongue_closed_eyes:

2 Likes

Wow. That is some crazy stuff! Thanks

I guess it doesn’t get asked because it’s not often that someone installs discourse and wants it not to work, so when docker makes it work when you’ve configured the firewall such that it shouldn’t people mostly don’t complain. :wink:

It is a very interesting issue, but it does seem a bit esoteric, and it’s not a Discourse issue, but a docker quirk. The question boils down to “how can I configure my firewall to keep discourse from working?” I’m going to try to remember that prerouting stuff.

If I knew the answer I wouldn’t have been dismissive! :wink: thanks for saving the day on this one!

5 Likes

I certainly wasn’t trying to be dismissive, rather troubleshoot…ive, though evidently I was coming from a position of ignorance of what Docker was doing. That’s all very useful information, thanks for answering!

If the OP or someone else can change it, it might be worthwhile changing the marked solution.

While it is more a Docker thing, I can see some Discourse hypotheticals that stem from this. For example, I might have an office server which is reachable from the world but I want to configure UFW to restrict Discourse to only being accessible from within the office. Docker’s additions would prevent that setup.

Though in that particular scenario I would set it up on a hardware/hypervisor firewall rather than UFW on the host anyway.

5 Likes

I just came to the same git repo you found, looking into a ufw/docker issue, it made me think of this topic.

What exactly did you change (I mean I can see the added lines, but what does it mean?), and are these changes specifically for Discourse?

I used the default code in the repo and my UFW problem seemed to be fixed after that.

EDIT: When I use your edited code, the first moment I exec ufw reload I get the following error:

ERROR: Could not load logging rules

1 Like

Actually, I figured it out after a while and wrote this bash script to do the task for Discourse installations.

It resets your firewall, installs ufw-docker-util (that edits the after.rules), then adds ports 443 and 80 to your allowlist. Voila.

It also allows port 22 from any IP to make sure you don’t get locked out. After all works, secure port 22 again.

EDIT: script does work but rebuilding discourse after using it will fail: fatal: unable to access 'https://github.com/discourse/discourse.git/': Could not resolve host: github.com - so do NOT use the script unless you know how to solve this.

EDIT 2: Works on Ubuntu, but not on CentOS!

3 Likes

Last time I did bash script I changed owner of all directories to www-data:www-data — so for me piece of work like this makes life bit easier :wink:

But in generally — I would like to see docker following UFW/iptables totally. Just because I’m using GeoIP-blocking via ipables (yeah, I know — UFW is just minimalistic UI for iptables).

Sure, we aren’t in Discourse anymore, but here we can see why coders and end-users can’t understand each others always so good: coders see the world as logical blocks and if/then/else constructions, but end users see it as full context. Meaning — because I use Discourse, even it works inside docker, from my point of view it is Discourse that doesn’t follow my rules :rofl:

This should go to praise but I changed url of forum some weeks ago. And I got all of worlds script kiddies and useless seo-bots to visit. I got on 2 GB/1 vcpu VPS by DigitalOcean 3000 different user agents per hour and Discourse just didn’t care. Any WordPress installation would be slow/down after that kind ddos’ish rush.

So, I don’t need-need to force discourse (well, docker) to follow bans by Fail2ban and my rules — but I hate those bots so deeply.

3 Likes