Everybody, hello!
I'm trying to setup my server firewall using iptables (I have to admit that last time I used iptables was a year ago), but iptables acts contrary to what I ask.
Here is my test script :
#!/bin/sh
IPT="/sbin/iptables"
echo -n "Loading iptables rules..."
# Flush old rules
$IPT --flush
$IPT --delete-chain
# Allow incoming and outgoing for loopback interfaces
$IPT -A INPUT -i lo -j ACCEPT
$IPT -A OUTPUT -o lo -j ACCEPT
# Allow incoming traffic for HTTP(S), SSH and SMTP
$IPT -A INPUT -p tcp --dport 80 -i eth0 -j ACCEPT
$IPT -A INPUT -p tcp --dport 443 -i eth0 -j ACCEPT
$IPT -A INPUT -p tcp --dport 22 -j ACCEPT
$IPT -A INPUT -p tcp --dport 25 -i eth0 -j ACCEPT
# Allow ICMP requests
$IPT -A INPUT -p icmp -i eth0 -j ACCEPT
$IPT -A OUTPUT -p icmp -o eth0 -j ACCEPT
# Allow outgoing traffic for SMTP, DNS, NTP, PgSQL, SolR, and SSH
$IPT -A OUTPUT -p tcp --dport 25 -o eth0 -j ACCEPT
$IPT -A OUTPUT -p tcp --dport 53 -o eth0 -j ACCEPT
$IPT -A OUTPUT -p udp --dport 53 -o eth0 -j ACCEPT
$IPT -A OUTPUT -p udp --dport 123 -o eth0 -j ACCEPT
$IPT -A OUTPUT -p tcp --dport 5433 -o eth0.2654 -j ACCEPT
$IPT -A OUTPUT -p udp --dport 5433 -o eth0.2654 -j ACCEPT
$IPT -A OUTPUT -p tcp --dport 8983 -o eth0.2654 -j ACCEPT
$IPT -A OUTPUT -p udp --dport 8983 -o eth0.2654 -j ACCEPT
$IPT -A OUTPUT -p tcp --dport 22 -o eth0 -j ACCEPT
$IPT -A OUTPUT -p tcp --dport 22 -o eth0.2654 -j ACCEPT
# Deny web server user outgoing connections
$IPT -A OUTPUT -o eth0 -m owner --uid-owner www-data -j DROP
# Drop everything else
$IPT -A INPUT -j DROP
$IPT -A OUTPUT -j DROP
$IPT -A FORWARD -j DROP
echo "rules loaded."
# Print rules as understood, then flush to avoid lockout
sleep 10
$IPT -L
# Flush old rules
$IPT --flush
$IPT --delete-chain
With this script, the server no more aswers to any request but ping (ICMP), then, after 10 seconds, prints following text and exits :
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
100 5920 ACCEPT all -- lo any anywhere anywhere
0 0 ACCEPT tcp -- eth0 any anywhere anywhere tcp dpt:www
0 0 ACCEPT tcp -- eth0 any anywhere anywhere tcp dpt:https
1 52 ACCEPT tcp -- any any anywhere anywhere tcp dpt:ssh
0 0 ACCEPT tcp -- eth0 any anywhere anywhere tcp dpt:smtp
0 0 ACCEPT icmp -- eth0 any anywhere anywhere
0 0 DROP all -- any any anywhere anywhere
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DROP all -- any any anywhere anywhere
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
100 5920 ACCEPT all -- any lo anywhere anywhere
0 0 ACCEPT icmp -- any eth0 anywhere anywhere
0 0 ACCEPT tcp -- any eth0 anywhere anywhere tcp dpt:smtp
0 0 ACCEPT tcp -- any eth0 anywhere anywhere tcp dpt:domain
0 0 ACCEPT udp -- any eth0 anywhere anywhere udp dpt:domain
0 0 ACCEPT udp -- any eth0 anywhere anywhere udp dpt:ntp
0 0 ACCEPT tcp -- any eth0.2654 anywhere anywhere tcp dpt:5433
0 0 ACCEPT udp -- any eth0.2654 anywhere anywhere udp dpt:5433
0 0 ACCEPT tcp -- any eth0.2654 anywhere anywhere tcp dpt:8983
0 0 ACCEPT udp -- any eth0.2654 anywhere anywhere udp dpt:8983
0 0 ACCEPT tcp -- any eth0 anywhere anywhere tcp dpt:ssh
0 0 ACCEPT tcp -- any eth0.2654 anywhere anywhere tcp dpt:ssh
0 0 DROP all -- any eth0 anywhere anywhere owner UID match www-data
14 2061 DROP all -- any any anywhere anywhere
First disturbing element, I noticed that INPUT and OUTPUT first rules are ACCEPT all packets whereas I did not asked to do so. In addition, I tried to set policy of INPUT and OUTPUT to DROP (using $IPT -P INPUT DROP
and $IPT -P OUTPUT DROP
), but doing this definitively locked me out, even after the ten seconds timeout, and the server then only answers to ICMP, which force me to hard reboot the server. Same effect if I set policy settings at the beginning of the script.
I suspect my error to be obvious for regular iptables users, but I searched the solution for hours now, and, as always in this kind of situations, the answer will only be obvious to me when somebody will point it out. Please, any do-gooder to help me?
eth0.2654
is a VLAN used for communications with our PgSQL server. Concerning HTTP answers, I was under the impression that they use the connection the client opened, which is allowed by my rule. Was I wrong?
Yes, you were wrong. It is most likely that you want to accept connection that are established or related to incoming connections. Thus, anything that is accepted on the input rule can be answered. With a stateless firewall, one would have to open the source port up explicitly. Since IPtables is stateful, you don't need to do that. It will track the state of connections for you and automatically allow outbound connections the way you're thinking, but only if you tell it to do so. The rule for this is
iptables -A INPUT -p ALL -m state --state ESTABLISHED,RELATED -j ACCEPT
. Put that at the top of your output list. If you want to be particularly aggressive, you could bind that rule to specific ports.If you were to look at this in a stateless way, you'd have to remember where packets are coming from and going to. You have your server currently setup to allow you to SSH to another machine. Since you don't allow any traffic that has port 22 as the source, your server cannot answer incoming connections. Again, the "ESTABLISHED,RELATED" rule solves this for permitting traffic to reply to inbound connections as would allowing traffic sourced from port 22, but unlike opening 22 on a stateless firewall, this setup won't allow new connections to start from there.
Your approach is bad. You're allowing all input connections and then stopping them at output. This will not prevent DDoS attacks. The right way is to stop the input connections and allow the output.
Reading through your code I've tried to remake. Here is how I think it should look:
I haven't tested so keep that in mind. I'm not sure if "-i eth0.2654" would work on virtual interfaces.
If you want - try it and let me know if it works or doesn't.