Dedicated servers and firewalls (iptables)
Posted: Sat Mar 25, 2006 2:22 pm
I'm seeing a growing trend (your mileage may vary) with dedicated servers running with iptables and emtpy rulesets. So I figured I'd throw this little script out there which may be of help/use/interest to those running dedicated servers. It's nothing more than a simple ruleset builder for iptables.
Code: Select all
#!/bin/sh
#------------------------------------------------------------------------------
# filename /etc/iptables/active.conf
#
# description iptables configuration script for single interface webserver
# machines not workstations
#
# author redmonkey
#
# status alpha
#
# dependancy none
#
# license this program is free software, you can redistribute it and/or
# modify it (keeping the original copyright intact would be nice)
#
# copyright 2005 redmonkey, all rights reserved
#
# history
# 03/09/05 v0.1 - initial version
# 24/03/06 v0.2 - added SSH connection throttling for configs that allow
# SSH connections from any IP source
#
# tested
# 03/09/05 Debian GNU/Linux v3.1 (Sarge), kernel v2.6.8, iptables v1.2.11
# 12/03/06 Fedora Core 3, kernel v2.6.12, iptables v1.2.11
# 24/03/06 Debian GNU/Linux v3.1 (Sarge), kernel v2.6.8, iptables v1.2.11
#
# todo UDP and ICMP services
#
# note this is not the most efficient script in the world, it wasn't
# designed to be. readability and maintainability were of higher
# importance
#
# note the ruleset defined here does not automatically get saved
# rebooting the machine will clear any rules that have been put in
# place. saving the ruleset is 'distro dependant' for Fedora...
#
# # service iptables save
# or
# # /etc/init.d/iptables save
#
# seem to work, for Debian, if you are running or upgraded from
# 'woody' then....
#
# # /etc/init.d/iptables save active
#
# for 'sarge' the new method is to use '/etc/network/if-up.d',
# although the older method can still be used if you look hard
# enough
#
# notice this program is distributed in the hope that it will be useful
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#------------------------------------------------------------------------------
#----------------------------START OF USER CONFIG-----------------------------#
#------------------------------------------------------------------------------
#
# define the server's external interface and IP address, if IP address is left
# blank the script will try to find it. ideally you should define the IP
# address here unless your server is getting it's IP from a DHCP server (which
# is a very bad idea)
#
#------------------------------------------------------------------------------
EXTIF="eth0"
EXTIP=
#------------------------------------------------------------------------------
#
# define full path to the 'iptables' executable, if left blank the script will
# attempt to find it
#
#------------------------------------------------------------------------------
IPTABLES=/sbin/iptables
#------------------------------------------------------------------------------
#
# define full path to the 'modprobe' executable, if left blank the script will
# attempt to find it
#
#------------------------------------------------------------------------------
MODPROBE=/sbin/modprobe
#------------------------------------------------------------------------------
#
# define what tcp services to allow, space seperated list, currently supported
# services are.... (PGSQL is PostgreSQL database server)
#
# HTTP,HTTPS,SSH,FTP,SMTP,SSMTP,POP3,POP3S,IMAP,IMAPS,MYSQL,PGSQL,SVN
#
# .... although it's a trivial task to add/define new services
#
# SVN is the SVNSERVE port, not required if you access your Subversion
# repository via Apache's WebDAV module
#
# if this is a remote server, you will always want at least SSH
#------------------------------------------------------------------------------
TCP_ALLOW="HTTP HTTPS SSH SMTP SSMTP FTP POP3 POP3S"
#------------------------------------------------------------------------------
#
# define what icmp services to allow, space seperated list, currently
# supported services are
#
# PING
#
#------------------------------------------------------------------------------
ICMP_ALLOW="PING"
#------------------------------------------------------------------------------
#
# define the acceptable source IP addresses for any services which are
# restricted to only descrete IP addresses or netmasks
#
# the format is SERVICE_IPS and can be a space seperated list for example to
# allow SSH connections from IP addresses 123.123.123.123 and 231.231.231.231
# the entry would be
#
# SSH_IPS="123.123.123.123 231.231.231.231"
#
# any services which are omitted or left blank are assumed to accept
# connections from any IP address, assuming of course they are defined in the
# ALLOW lists
#
# NOTE: you will most likely want to omit or leave blank for most services
#
#------------------------------------------------------------------------------
SSH_IPS=
#------------------------------------------------------------------------------
#
# define which services log accepted connections, space seperated list
#
# by default iptables logs to /var/log/messages
#
#------------------------------------------------------------------------------
LOG_ACCEPT="SSH"
#------------------------------------------------------------------------------
#
# define which services log dropped connections, space seperated list
#
# two special cases here you can also specify 'INVALID' and 'UNKNOWN', UNKNOWN
# may get a bit verbose
#
#------------------------------------------------------------------------------
LOG_DROP="SSH INVALID"
#------------------------------------------------------------------------------
#
# define ports for each service, you should not need to change these unless you
# are running the service under a non-default port
#
# this is also where you can define/add new tcp services, if your service
# requires a port range the syntax is standard iptables syntax
# i.e. <start port>:<end port> example for FTP may be FTP=20:21
#
#------------------------------------------------------------------------------
FTP=21
HTTP=80
HTTPS=443
IMAP=143
IMAPS=993
MYSQL=3306
PGSQL=5432
POP3=110
POP3S=995
SMTP=25
SSH=22
SSMTP=465
SVN=3690
#-----------------------------END OF USER CONFIG------------------------------#
#------------------------------------------------------------------------------
#
# we won't get very far if we can't find the iptables executable
#
#------------------------------------------------------------------------------
if [ -z ${IPTABLES} ]; then
IPTABLES=`which iptables`
fi
if [ -z ${IPTABLES} ]; then
echo "Failed to find the iptables executable, firewall config aborted!!"
exit 1
fi
#------------------------------------------------------------------------------
#
# similarly for the modprobe executable
#
#------------------------------------------------------------------------------
if [ -z ${MODPROBE} ]; then
MODPROBE=`which modprobe`
fi
if [ -z ${MODPROBE} ]; then
echo "Failed to find the modprobe executable, firewall config aborted!!"
exit 1
fi
#------------------------------------------------------------------------------
#
# internal definitions
#
#------------------------------------------------------------------------------
ANYIP="0/0"
#------------------------------------------------------------------------------
#
# define the external IP address if it hasn't been defined previously, if it
# can't be found, bork
#
#------------------------------------------------------------------------------
if [ -z ${EXTIP} ]; then
EXTIP=$(ifconfig $EXTIF | grep 'inet addr' | \
awk '{print $2}' | sed -e 's/.*://')
fi
if [ -z ${EXTIP} ]; then
echo "Failed to set external IP address, firewall config aborted!!"
exit 1
fi
#------------------------------------------------------------------------------
# loop through the TCP_ALLOW list and set the individual allow flags
#------------------------------------------------------------------------------
for i in $TCP_ALLOW
do
SERVICE=`echo $i | tr 'a-z' 'A-Z'`
if [ -z $(eval echo $\{`echo $SERVICE`\}) ]
then
echo "No port definition found for $SERVICE, firewall config aborted!!"
exit 1
fi
eval ALLOW_${SERVICE}=1
done
#------------------------------------------------------------------------------
# loop through the ICMP_ALLOW list and set the individual allow flags
#------------------------------------------------------------------------------
for i in $ICMP_ALLOW
do
SERVICE=`echo $i | tr 'a-z' 'A-Z'`
eval ALLOW_${SERVICE}=1
done
#------------------------------------------------------------------------------
# loop through the LOG_ACCEPT list and set the individual log flags
#------------------------------------------------------------------------------
for i in $LOG_ACCEPT
do
SERVICE=`echo $i | tr 'a-z' 'A-Z'`
eval LOG_ACCEPT_${SERVICE}=1
done
#------------------------------------------------------------------------------
# loop through the LOG_DROP list and set the individual log flags
#------------------------------------------------------------------------------
for i in $LOG_DROP
do
SERVICE=`echo $i | tr 'a-z' 'A-Z'`
eval LOG_DROP_${SERVICE}=1
done
#------------------------------------------------------------------------------
#
# ensure the required modules are loaded
#
#------------------------------------------------------------------------------
REQ_MODULES="ip_tables ip_conntrack ipt_state ipt_limit ipt_LOG ipt_REJECT"
for MODULE in $REQ_MODULES
do
$MODPROBE ${MODULE} 2> /dev/null
if [ $? != 0 ]; then
echo -n "Load module failure, ${MODULE} could not be found, "
echo "firewall config aborted!!"
exit 1;
fi
done
#------------------------------------------------------------------------------
# special case for FTP, only load the FTP connection tracking module if the
# FTP service is defined as allowed
#------------------------------------------------------------------------------
if [ ! -z ${ALLOW_FTP} ]; then
$MODPROBE ip_conntrack_ftp 2> /dev/null
if [ $? != 0 ]; then
echo -n "Load module failure, ip_conntrack_ftp could not be found, "
echo "firewall config aborted!!"
exit 1;
fi
fi
#------------------------------------------------------------------------------
# special case for SSH, if allowing SSH connections from any IP we still want
# to place some restrictions on connections which require the 'recent' module
#------------------------------------------------------------------------------
if [ -z "${SSH_IPS}" ] || [ "${SSH_IPS}" = "${ANYIP}" ]; then
$MODPROBE ipt_recent 2> /dev/null
if [ $? != 0 ]; then
echo -n "Load module failure, ipt_recent could not be found, "
echo "firewall config aborted!!"
exit 1;
fi
fi
#------------------------------------------------------------------------------
#
# attempt to set some kernel parameters, should make these user configurable???
#
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# use SYNcookies
#------------------------------------------------------------------------------
if [ -e /proc/sys/net/ipv4/tcp_syncookies ]; then
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
fi
#------------------------------------------------------------------------------
# enable spoofing protection
#------------------------------------------------------------------------------
for FILE in /proc/sys/net/ipv4/conf/*/rp_filter
do
echo 1 > $FILE
done
#------------------------------------------------------------------------------
# log spoofed packets and all that other crazy stuff
#------------------------------------------------------------------------------
for FILE in /proc/sys/net/ipv4/conf/*/log_martians
do
echo 0 > $FILE
done
#------------------------------------------------------------------------------
# ignore echo broadcasts
#------------------------------------------------------------------------------
if [ -e /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts ]; then
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
fi
#------------------------------------------------------------------------------
# ignore bad error messages
#------------------------------------------------------------------------------
if [ -e /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses ]; then
echo 1 > /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses
fi
#------------------------------------------------------------------------------
# disable ICMP redirects
#------------------------------------------------------------------------------
for FILE in /proc/sys/net/ipv4/conf/*/accept_redirects
do
echo 0 > $FILE
done
for FILE in /proc/sys/net/ipv4/conf/*/send_redirects
do
echo 0 > $FILE
done
#------------------------------------------------------------------------------
#
# flush existing ruleset and delete any custom chain definitions
#
#------------------------------------------------------------------------------
$IPTABLES -F
$IPTABLES -X
#------------------------------------------------------------------------------
# set default policies
#------------------------------------------------------------------------------
$IPTABLES -P INPUT DROP
$IPTABLES -P OUTPUT ACCEPT
$IPTABLES -P FORWARD DROP
#------------------------------------------------------------------------------
# define the custom chains, only create those that are needed
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# define the logging chain for invalid packets, only if required
#------------------------------------------------------------------------------
if [ ! -z ${LOG_DROP_INVALID} ]; then
$IPTABLES -N LOG-INVALID
$IPTABLES -A LOG-INVALID -j LOG --log-prefix "Invalid Packet: "
$IPTABLES -A LOG-INVALID -j DROP
DROPINVALID="LOG-INVALID"
else
DROPINVALID="DROP"
fi
#------------------------------------------------------------------------------
# define the custom packet validating chain
#------------------------------------------------------------------------------
$IPTABLES -N VALID-PACKET
$IPTABLES -A VALID-PACKET -p tcp --tcp-flags ALL NONE -j ${DROPINVALID}
$IPTABLES -A VALID-PACKET -p tcp --tcp-flags SYN,FIN SYN,FIN -j ${DROPINVALID}
$IPTABLES -A VALID-PACKET -p tcp --tcp-flags SYN,RST SYN,RST -j ${DROPINVALID}
$IPTABLES -A VALID-PACKET -p tcp --tcp-flags FIN,RST FIN,RST -j ${DROPINVALID}
$IPTABLES -A VALID-PACKET -p tcp --tcp-flags ACK,FIN FIN -j ${DROPINVALID}
$IPTABLES -A VALID-PACKET -p tcp --tcp-flags ACK,PSH PSH -j ${DROPINVALID}
$IPTABLES -A VALID-PACKET -p tcp --tcp-flags ACK,URG URG -j ${DROPINVALID}
$IPTABLES -A VALID-PACKET -j RETURN
#------------------------------------------------------------------------------
# define the custom LOG chains, only create if required
#------------------------------------------------------------------------------
for i in $TCP_ALLOW
do
SERVICE=`echo $i | tr 'a-z' 'A-Z'`
IPLIST=$(eval echo $\{`echo ${SERVICE}_IPS`\})
if [ ! -z "${IPLIST}" ] && [ "${IPLIST}" != "${ANYIP}" ] && \
[ ! -z $(eval echo $\{`echo LOG_ACCEPT_$SERVICE`\}) ]; then
$IPTABLES -N LOG-${SERVICE}
$IPTABLES -A LOG-${SERVICE} -j LOG --log-prefix "${SERVICE} Accept: "
$IPTABLES -A LOG-${SERVICE} -j ACCEPT
fi
done
#------------------------------------------------------------------------------
# define the custom FILTER chains, only create if required
#------------------------------------------------------------------------------
for i in $TCP_ALLOW
do
SERVICE=`echo $i | tr 'a-z' 'A-Z'`
IPLIST=$(eval echo $\{`echo ${SERVICE}_IPS`\})
if [ ! -z "${IPLIST}" ] && [ "${IPLIST}" != "${ANYIP}" ]; then
if [ ! -z $(eval echo $\{`echo LOG_ACCEPT_$SERVICE`\}) ]; then
ACCEPT="LOG-${SERVICE}"
else
ACCEPT="ACCEPT"
fi
eval HAVE_${SERVICE}_FILTER=1
$IPTABLES -N ${SERVICE}-FILTER
for IP in ${IPLIST}
do
$IPTABLES -A ${SERVICE}-FILTER -s ${IP} -j ${ACCEPT}
done
if [ ! -z $(eval echo $\{`echo LOG_DROP_$SERVICE`\}) ]; then
$IPTABLES -A ${SERVICE}-FILTER -j LOG --log-prefix "${SERVICE} Drop: "
fi
$IPTABLES -A ${SERVICE}-FILTER -j DROP
fi
done
#------------------------------------------------------------------------------
# special case for SSH. if there is no 'whitelist' ACL defined for SSH
# connections, limit SSH connections to one every 5 minutes for an individual
# IP. this slows down dictionary attacks when SSH connections are being
# accepted from any source IP. you can obviously adjust both the time interval
# and hitcount to suit you own needs but note: 'hitcount' is triggered when the
# hitcount is equal to or greater than the hitcount you specify hence a
# hitcount of 2 equates to allowing 1 connection
#------------------------------------------------------------------------------
if [ -z "${SSH_IPS}" ] || [ "${SSH_IPS}" = "${ANYIP}" ]; then
HAVE_SSH_FILTER=1
$IPTABLES -N SSH-FILTER
$IPTABLES -A SSH-FILTER -m state --state NEW -m recent --set
if [ ! -z ${LOG_DROP_SSH} ]; then
$IPTABLES -A SSH-FILTER -m state --state NEW -m recent --update \
--seconds 300 --hitcount 2 -j LOG --log-prefix "SSH Drop: "
fi
$IPTABLES -A SSH-FILTER -m state --state NEW -m recent --update \
--seconds 300 --hitcount 2 -j DROP
if [ ! -z ${LOG_ACCEPT_SSH} ]; then
$IPTABLES -A SSH-FILTER -j LOG --log-prefix "SSH Accept: "
fi
$IPTABLES -A SSH-FILTER -j ACCEPT
fi
#------------------------------------------------------------------------------
# TCP-SERVICE chain definition
#------------------------------------------------------------------------------
$IPTABLES -N TCP-SERVICE
$IPTABLES -A TCP-SERVICE -p tcp -j VALID-PACKET
for i in $TCP_ALLOW
do
SERVICE=`echo $i | tr 'a-z' 'A-Z'`
DPORT=$(eval echo $\{`echo $SERVICE`\})
if [ ! -z $(eval echo $\{`echo HAVE_${SERVICE}_FILTER`\}) ]; then
$IPTABLES -A TCP-SERVICE -p tcp --dport ${DPORT} -j ${SERVICE}-FILTER
else
if [ ! -z $(eval echo $\{`echo LOG_ACCEPT_$SERVICE`\}) ]; then
$IPTABLES -A TCP-SERVICE -p tcp -s ${ANYIP} --dport ${DPORT} -j LOG \
--log-prefix "${SERVICE} Accept: "
fi
$IPTABLES -A TCP-SERVICE -p tcp --dport ${DPORT} -j ACCEPT
fi
done
#------------------------------------------------------------------------------
# ICMP-SERVICE chain definition
#------------------------------------------------------------------------------
$IPTABLES -N ICMP-SERVICE
#----------------------------------------------------------------------------
# PING (incomming requests)
#----------------------------------------------------------------------------
if [ ! -z ${ALLOW_PING} ]; then
$IPTABLES -A ICMP-SERVICE -p icmp --icmp-type echo-request \
-m limit --limit 1/s -j ACCEPT
fi
#----------------------------------------------------------------------------
# PING (incomming replies)
#----------------------------------------------------------------------------
$IPTABLES -A ICMP-SERVICE -p icmp --icmp-type echo-reply -j ACCEPT
#------------------------------------------------------------------------------
#
# log all trafic (for debugging purposes)
#
#------------------------------------------------------------------------------
# $IPTABLES -A INPUT -j LOG
# $IPTABLES -A FORWARD -j LOG
# $IPTABLES -A OUTPUT -j LOG
#------------------------------------------------------------------------------
# unconditionally accept anything in and out of the loopback device
#------------------------------------------------------------------------------
$IPTABLES -A INPUT -i lo -j ACCEPT
$IPTABLES -A OUTPUT -o lo -j ACCEPT
#------------------------------------------------------------------------------
# all traffic generated by the server is allowed to leave
#------------------------------------------------------------------------------
$IPTABLES -A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
#------------------------------------------------------------------------------
# accept everything that has a previously established connection assuming it's
# a valid packet of course
#------------------------------------------------------------------------------
$IPTABLES -A INPUT -p tcp -i $EXTIF -d $EXTIP \
-m state --state RELATED,ESTABLISHED -j VALID-PACKET
$IPTABLES -A INPUT -i $EXTIF -d $EXTIP \
-m state --state RELATED,ESTABLISHED -j ACCEPT
#------------------------------------------------------------------------------
# drop all and any spoofing attempts
#------------------------------------------------------------------------------
$IPTABLES -A INPUT -s $EXTIP -j DROP
#------------------------------------------------------------------------------
# route incomming requests to either TCP or ICMP custom chains
#------------------------------------------------------------------------------
$IPTABLES -A INPUT -p tcp -i $EXTIF -d $EXTIP -j TCP-SERVICE
$IPTABLES -A INPUT -p icmp -i $EXTIF -d $EXTIP -j ICMP-SERVICE
#------------------------------------------------------------------------------
# still here? it's an unkown packet, this just reinforces the DROP policy
#------------------------------------------------------------------------------
if [ ! -z ${LOG_DROP_UNKOWN} ]; then
$IPTABLES -A INPUT -j LOG --log-prefix "Unknown Packet: "
fi
$IPTABLES -A INPUT -i $EXTIF -d $EXTIP -j DROP