#!/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.tight..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 "tight" policy. There is a more relaxed policy ruleset at http://rlogin.net/iptables/iptables.loose.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"	# allowed NTP server(s)
MAILOUT="x.x.x.x"			# address of our mail server if we want to send mail and we are not a mail server
MAILIN="x.x.x.x"			# address of downstream server(s) allowed to connect to us if we are a "smart" mail relay
OTHERIP="x.x.x.x"			# address of another server we wish to connect to (e.g. via ssh)
REPOIPS="x.x.x.x y.y.y.y"		# address(es) of our distro update repository (or mirror) for software updates
WEBALLOWED="x.x.x.x y.y.y.y" 	        # address(es) of webservers we must connect to (e.g. akismet)
SERVICES="80 443"			# defines the services we allow - here only http and https 
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 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 us to connect out to our distro repository webserver or ISP mirror (for apt-get, or yum updates for example)
# If you are running unattended security updates, you may wish to test that this works with an "apt-get update" before
# moving on.

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

# Allow connection to other web servers we need to access (e.g. akismet if running wordpress)

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

# allow us 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

# Note - I  do not recommend this practice, but if we want to allow SSH OUT of this server (to another server for example) we must 
# reverse the logic of the above ruleset. Thus:
#  
# $IPTABLES -A OUTPUT -s $OURIP -p tcp -m tcp -d $OTHERIP --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
# $IPTABLES -A INPUT -s $OTHERIP -p tcp -m tcp --sport 22 -d $OURIP -m state --state ESTABLISHED -j ACCEPT

# allow us to mail out (e.g. for alerts or reports to the admin) or we are a mail server. Note that simply adding port 25 (or 587) to
# the list of ports at $SERVICES will NOT allow the server to send out mail to other servers.  
  
$IPTABLES -A OUTPUT -s $OURIP -p tcp -m tcp --dport 25 -m state --state NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A INPUT -d $OURIP -p tcp -m tcp --sport 25 -m state --state ESTABLISHED -j ACCEPT

# If we are a mail relay and only want to accept incoming mail from a specific server (or servers) we could use:

# $IPTABLES -A INPUT -d $OURIP -p tcp -m tcp --dport 25 -s $MAILIN -j ACCEPT

# or if we are only allowed to send mail out via a smart host elsewhere, we could use:

# $IPTABLES -A OUTPUT -s $OURIP -p tcp -m tcp -d $MAILOUT --dport 25 -m state --state NEW,ESTABLISHED -j ACCEPT
# $IPTABLES -A INPUT -d $OURIP -p tcp -m tcp -s $MAILOUT --sport 25 -m state --state ESTABLISHED -j ACCEPT
 
# now allowed services to/from anywhere. Typically these will be web services. But note again that we only allow out the 
# responses to inbound connections. We do not permissively allow out all connections. 

for SERVICE in $SERVICES ; 
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 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 you /do/ have a default drop in place at the end of your ruleset. 
 

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

# end
