#!/bin/bash 
#
# iptables firewall ruleset for servers. For detailed FAQs, HOWTOs and other documentation, see the netfilter website at: 
# http://www.netfilter.org/documentation/index.html#documentation-faq. There is also a tutorial at: 
# http://www.frozentux.net/documents/iptables-tutorial/
#
# For the latest copy of this script, see: http://rlogin.net/iptables/iptables.loose.sh. For a discussion of the use of this script, and
# the logic behind the decisions implemented here, see http://baldric.net/2012/09/07/iptables-firewall-for-servers/

# This ruleset implements the "loose" policy. There is a more stringent policy ruleset at http://rlogin.net/iptables/iptables.tight.sh.

# Note that we policy drop on all three chains (INPUT, OUTPUT and FORWARD) in the filter table. This means that we must specify allowed 
# outbound connections as well as allowed inbound. This is safer than allowing a default outbound to anywhere, but inevitably is more 
# complex since we must specify exact rules for each outbound connection (and its return). Note also that I add a (redundant) set of
# "DROP" directives at the end of this script. You do not need to do this, but I find it helps me when reading the output of "iptables -nvL"
# because it then expicitly lists the drops at the end of the rules. (I can't shake old Cisco IOS habits.....)  
#
# Remember that whichever rule matches first will be applied and no other rules will be checked. 
#
# MBM
# http://baldric.net
# 1 September 2012
#

# System admin note - this ruleset for server: XXXXXXXXXX. 
#

# First some defines

IPTABLES=/sbin/iptables			# the iptables program itself

OURIP="x.x.x.x"				# replace x.x.x.x with local IP address (on eth0 for example)
SSHIPS="x.x.x.x y.y.y.y"		# ip address(es) allowed to SSH in to this server. 
DNSIPS="x.x.x.x y.y.y.y"		# address(es) of our DNS servers
NTPIPS="x.x.x.x y.y.y.y z.z.z.z"	# address(es) of our NTP server(s)
SERVICESIN="80 443"			# defines the services we run and allow access to - here only http and https 
SERVICESOUT="80"			# defines the services we wish to connect out to - here only http. DNS and NTP are handled above. 
SSH="122"				# define our (non standard) ssh port

#
# In most cases, you should not have to change anything below this line. 
#
################################################################################################################################################

# First flush all rules from all chains in the filter table 

$IPTABLES -F

# and zero the packet and byte counters

$IPTABLES -Z
 
# Set default policy on all chains. We use DROP rather than REJECT to stop us being used as a DDOS source. This can be a religious decision.
# This policy is implemented where no match is otherwise made.

$IPTABLES -P INPUT DROP
$IPTABLES -P OUTPUT DROP
$IPTABLES -P FORWARD DROP

# Now the rules.

# Anti spoof rules to drop all RFC1918 addresses, martian networks, multicasts, all ones and all zeros etc. This should not really be 
# necessary because the ISP should apply these filters at the border routers.  But just in case ......
#
# Note however, that if you are running a VPN endpoint (such as openvpn) on your server, you will need to modify these rules otherwise
# your VPN access will be blocked.

$IPTABLES -A INPUT -s 10.0.0.0/8       -j DROP
$IPTABLES -A INPUT -s 172.16.0.0/12    -j DROP
$IPTABLES -A INPUT -s 192.168.0.0/16   -j DROP
$IPTABLES -A INPUT -s 169.254.0.0/16   -j DROP
$IPTABLES -A INPUT -s 224.0.0.0/4      -j DROP
$IPTABLES -A INPUT -d 224.0.0.0/4      -j DROP
$IPTABLES -A INPUT -s 240.0.0.0/5      -j DROP
$IPTABLES -A INPUT -d 240.0.0.0/5      -j DROP
$IPTABLES -A INPUT -s 0.0.0.0/8        -j DROP
$IPTABLES -A INPUT -d 0.0.0.0/8        -j DROP
$IPTABLES -A INPUT -s 1.1.1.1          -j DROP
$IPTABLES -A INPUT -d 1.1.1.1          -j DROP
$IPTABLES -A INPUT -d 239.255.255.0/24 -j DROP
$IPTABLES -A INPUT -d 255.255.255.255  -j DROP

# Drop invalid packets. There is some dispute as to whether this is still necessary. Most "odd" tcp flag combinations should be
# handled correctly in modern kernel implementation. However, I leave this in because in my experience, my servers get hit by such
# traffic. Try it yourself. Leave the rules in place for a few days and then take a look at the packet counts against those rules.  

$IPTABLES -A INPUT   -m state --state INVALID -j DROP
$IPTABLES -A FORWARD -m state --state INVALID -j DROP
$IPTABLES -A OUTPUT  -m state --state INVALID -j DROP

# localhost connections are always allowed (failure to allow this will break many programs which rely on localhost)

$IPTABLES -A INPUT -i lo -j ACCEPT
$IPTABLES -A OUTPUT -o lo -j ACCEPT

# allow inbound SSH access for remote management. I like to limit my SSH access to one known secure source. You may have more than
# that. I recommend you avoid the lazy approach of allowing all inbound SSH, even if you are using fail2ban.

for IP in $SSHIPS ;
do
$IPTABLES -A INPUT -s $IP -p tcp -m tcp -d $OURIP --dport $SSH -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A OUTPUT -d $IP -p tcp -m tcp -s $OURIP --sport $SSH -m state --state ESTABLISHED -j ACCEPT
done

# allow DNS queries to our trusted servers. Both TCP and UDP are required. And, yes, I know UDP is connectionless. I know that
# the concept of an ESTABLISHED connection is therefore questionable, but the syntax allows this and I find it useful. If the
# formulation offends your sense of decency, then by all means change it.

for SERVERS in $DNSIPS ;
do
$IPTABLES -A OUTPUT -s $OURIP -p udp -m udp -d $SERVERS --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -d $OURIP -p udp -m udp -s $SERVERS --sport 53 -m state --state ESTABLISHED -j ACCEPT
$IPTABLES -A OUTPUT -s $OURIP -p tcp -m tcp -d $SERVERS --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -d $OURIP -p tcp -m tcp -s $SERVERS --sport 53 -m state --state ESTABLISHED -j ACCEPT
done

# allow us access to our NTP servers. If you are not sure where these are, check the ntp configuration file. 

for SERVERS in $NTPIPS ; 
do
$IPTABLES -A OUTPUT -s $OURIP -p udp -m udp -d $SERVERS --dport 123 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -d $OURIP -p udp -m udp -s $SERVERS --sport 123 -m state --state ESTABLISHED -j ACCEPT
done

# Allow inbound connections to our permitted services. 

for SERVICE in $SERVICESIN ; 
do
$IPTABLES -A INPUT -p tcp -m tcp -d $OURIP --dport $SERVICE -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A OUTPUT -p tcp -m tcp -s $OURIP --sport $SERVICE -m state --state ESTABLISHED -j ACCEPT
done

# allow outbound connections initiated by us, but only to specified services (let's not be too lax eh?)
# Unlike the "tight" policy (q.v.), this ruleset runs the risk of us allowing connections out to servers
# we should not trust. If our $SERVICESOUT define permits access to port 80 (or 443) a hostile program
# internally could call out to a command and control server or a staging server and could then download
# further malware such as a rootkit. However, this rule is easier to manage. You pays your money....

for SERVICE in $SERVICESOUT ;
do 
$IPTABLES -A OUTPUT -s $OURIP -p tcp -m tcp --dport $SERVICE -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -d $OURIP -p tcp -m tcp --sport $SERVICE -m state --state ESTABLISHED -j ACCEPT
done

# allow selected ICMP messages - outbound source must always be our ip to prevent outbound spoof and we only really want to allow
# types 0, 3 and 8 (ping reply, destination unreachable and ping). Blocking all icmp is NOT a good idea. In particular, fragmentation
# error reporting is is vital to the PMTU discovery process - see RFC 2923 for example and https://en.wikipedia.org/wiki/Path_MTU_Discovery

# ping from outside inwards - but rate limit to 1/sec. This helps prevent us being used in a spoofed address ping flood
# But note that rate limiting here may not be necessary on most modern distros. Check the contents of the files:
# /proc/sys/net/ipv4/icmp_ratelimit and /proc/sys/net/ipv4/icmp_ratemask. See commentary at baldric.net for details. 

$IPTABLES -A INPUT -p icmp -m icmp -d $OURIP --icmp-type echo-request -m limit --limit 1/sec -j ACCEPT
$IPTABLES -A OUTPUT -s $OURIP -p icmp -m icmp --icmp-type echo-reply -m limit --limit 1/sec -j ACCEPT				
    
# and from inside outwards

$IPTABLES -A OUTPUT -s $OURIP -p icmp -m icmp --icmp-type echo-request -m limit --limit 1/sec -j ACCEPT
$IPTABLES -A INPUT -p icmp -m icmp -d $OURIP --icmp-type echo-reply -m limit --limit 1/sec -j ACCEPT				

# destination unreachables (type 3). Be a good net citizen and respond appropriately. ICMP type 3, code 4 in particular is necessary.

$IPTABLES -A INPUT -p icmp -m icmp -d $OURIP --icmp-type destination-unreachable -j ACCEPT
$IPTABLES -A OUTPUT -s $OURIP -p icmp -m icmp --icmp-type destination-unreachable -j ACCEPT				

# Now the belt to the braces above. These lines are not necessary, but they give a reassuring positive affirmation in the output of
# iptables -nvL that we /do/ have a default drop in place at the end of our ruleset. 
 

$IPTABLES -A INPUT -j DROP
$IPTABLES -A OUTPUT -j DROP
$IPTABLES -A FORWARD -j DROP

# end
