How to make iptables configuration persistent using custom service file

Make iptables configuration persistent using custom service file with additional features like configurable wait time, so you can safely interrupt execution and test mode that will disable firewall after defined period of time.

LSB init script

Create /etc/init.d/iptables-firewall service file. Edit firewall_start function to apply custom iptables configuration.

#!/bin/sh

### BEGIN INIT INFO
# Provides:          iptables-firewall
# Required-Start:    mountkernfs $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Required-Stop:     
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: iptables firewall
# Description:       local iptables firewall
### END INIT INFO

. /lib/lsb/init-functions

# Limit PATH
PATH="/sbin:/usr/sbin:/bin:/usr/bin"

# Time (in seconds) to change your mind and stop operation
WAIT_FOR="15"

# Time (in minutes) to wait before stop action (test action)
TEST_FOR="5"

# Source configuration
if [ -f "/etc/default/iptables-firewall" ]; then
    . /etc/default/iptables-firewall
fi

# iptables configuration
firewall_start() {
  # Flush rules and delete custom chains
  iptables -F
  iptables -X

  # Define chain to allow particular source addresses
  iptables -N chain-incoming-ssh 
  iptables -A chain-incoming-ssh -s 192.168.1.149 -j ACCEPT -m comment --comment "local access"
  iptables -A chain-incoming-ssh -p tcp --dport 22 -j LOG  --log-prefix "[fw-inc-ssh] " -m limit --limit 6/min --limit-burst 4
  iptables -A chain-incoming-ssh -j DROP

  # Define chain to log and drop incoming packets
  iptables -N chain-incoming-log-and-drop
  iptables -A chain-incoming-log-and-drop -j LOG --log-prefix "[fw-inc-drop] " -m limit --limit 6/min --limit-burst 4
  iptables -A chain-incoming-log-and-drop -j DROP

  # Define chain to log and drop outgoing packets
  iptables -N chain-outgoing-log-and-drop
  iptables -A chain-outgoing-log-and-drop -j LOG --log-prefix "[fw-out-drop] " -m limit --limit 6/min --limit-burst 4 
  iptables -A chain-outgoing-log-and-drop -j DROP

  # Drop invalid packets
  iptables -A INPUT -m conntrack --ctstate INVALID -j chain-incoming-log-and-drop

  # Accept everthing on loopback
  iptables -A INPUT  -i lo -j ACCEPT
  iptables -A OUTPUT -o lo -j ACCEPT

  # ACCEPT incoming packets for established connections
  iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

  # Accept incoming ICMP
  iptables -A INPUT -p icmp -j ACCEPT

  # Accept incoming SSH
  iptables -A INPUT -p tcp --dport 22 -j chain-incoming-ssh

  # Accept outgoing packets for established connections
  iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

  # Accept outgoing DNS
  iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
  iptables -A OUTPUT -p udp --dport 53 -j ACCEPT

  # Accept outgoing NTP
  iptables -A OUTPUT -p tcp --dport 123 -j ACCEPT
  iptables -A OUTPUT -p udp --dport 123 -j ACCEPT

  # Accept outgoing HTTP/S
  iptables -A OUTPUT -p tcp --dport 80  -j ACCEPT
  iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT

  # Accept outgoing SSH
  iptables -A OUTPUT -p tcp --dport 22  -j ACCEPT

  # Accept outgoing ICMP
  iptables -A OUTPUT -p icmp -j ACCEPT

  # Log not accounted outgoing traffic
  iptables -A OUTPUT -j chain-outgoing-log-and-drop

  # Log not accounted forwarding traffic
  iptables -A FORWARD -j chain-incoming-log-and-drop

  # Drop everything else
  iptables -P INPUT   DROP
  iptables -P FORWARD DROP
  iptables -P OUTPUT  DROP
}

# clear iptables configuration
firewall_stop() {
  iptables -F
  iptables -X
  iptables -P INPUT   ACCEPT
  iptables -P FORWARD ACCEPT
  iptables -P OUTPUT  ACCEPT
}

# internal
# check if command is available
check_for_command(){
  command -v "$1" 1>/dev/null 2>&-
}

# check if commands are available
check_commands(){
  commands="iptables at"
  status=0

  for command in $commands; do
    if ! check_for_command "$command"; then
      log_action_msg "Command $command is not available" 
      status=1
    fi
  done
 
  if [ "$status" -ne "0" ]; then
    log_action_begin_msg "Starting iptables firewall" 
    log_action_end_msg 1
    exit 1
  fi
}

# check if user is root
check_user_permissions() {
  if [ "$(id -u)" -ne "0" ]; then
    log_action_begin_msg "Insuffcient permissions"
    log_action_end_msg 1
    exit 1
  fi
}

# wait for $WAIT_FOR seconds
wait_before_execution() {
  log_action_msg "Waiting for $WAIT_FOR seconds"
  sleep $WAIT_FOR
}

# check environment
check_commands
check_user_permissions

# execute action
case "$1" in
  start|restart)
    wait_before_execution
    log_action_begin_msg "Starting firewall"
    firewall_stop
    firewall_start
    log_action_end_msg 0
    ;;
  stop)
    wait_before_execution
    log_action_begin_msg "Stopping firewall"
    firewall_stop
    log_action_end_msg 0
    ;;
  test)
    wait_before_execution
    log_action_msg "Scheduling iptables firewall to stop in $TEST_FOR minutes"
    sudo at now +2 minutes <<EOF
      $(readlink -f $0) stop
EOF
    if [ "$?" -gt "0" ]; then
      log_action_begin_msg "Error: firewall will not stop in $TEST_FOR minutes"
      log_action_end_msg 1
      exit 1
    fi
    log_action_msg "Loading firewall rules"
    firewall_stop
    firewall_start
    log_action_begin_msg "Testing iptables firewall"
    log_action_end_msg 0
    ;;
  show)
    iptables -L -v -n
    ;;
  show-nat)
    iptables -t nat -L -v -n
    ;;
esac

Ensure that init script owned by root.

$ sudo chown root:root /etc/init.d/iptables-firewall

Ensure that script is executable.

$ sudo chmod 755 /etc/init.d/iptables-firewall

Enable service at boot.

$ sudo update-rc.d iptables-firewall defaults
update-rc.d: using dependency based boot sequencing

Usage

Try to start firewall service as regular user.

$ /etc/init.d/iptables-firewall start
[FAIL] Insuffcient permissions...failed.

Try to start firewall service without at command.

$ /etc/init.d/iptables-firewall start
[info] Command at is not available.
[FAIL] Starting iptables firewall...failed.

Stop firewall service.

$ sudo /etc/init.d/iptables-firewall stop
[info] Waiting for 15 seconds.
[ ok ] Stopping firewall...done.

Start firewall service.

$ sudo /etc/init.d/iptables-firewall start
[info] Waiting for 15 seconds.
[ ok ] Starting firewall...done.

Restart firewall service.

 sudo /etc/init.d/iptables-firewall restart
[info] Waiting for 15 seconds.
[ ok ] Starting firewall...done.

Show current iptables configuration.

$ sudo /etc/init.d/iptables-firewall show
Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 chain-incoming-log-and-drop  all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate INVALID
    0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
   37  2524 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 chain-incoming-ssh  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 chain-incoming-log-and-drop  all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      lo      0.0.0.0/0            0.0.0.0/0           
   23  2196 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:53
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:53
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:123
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:123
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 chain-outgoing-log-and-drop  all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-incoming-log-and-drop (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            limit: avg 6/min burst 4 LOG flags 0 level 4 prefix "[fw-inc-drop] "
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-incoming-ssh (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       192.168.1.149        0.0.0.0/0            /* local access */
    0     0 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 limit: avg 6/min burst 4 LOG flags 0 level 4 prefix "[fw-inc-ssh] "
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-outgoing-log-and-drop (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0            limit: avg 6/min burst 4 LOG flags 0 level 4 prefix "[fw-out-drop] "
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Show current iptables configuration for nat table.

$ sudo /etc/init.d/iptables-firewall show-nat
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Test firewall service for 5 minutes.

$ sudo /etc/init.d/iptables-firewall test
[info] Waiting for 15 seconds.
[info] Scheduling iptables firewall to stop in 5 minutes.
warning: commands will be executed using /bin/sh
job 7 at Tue Jan  2 00:53:00 2018
[info] Loading firewall rules.
[ ok ] Testing iptables firewall...done.

About Milosz Galazka

Milosz is a Linux Foundation Certified Engineer working for a successful Polish company as a system administrator and a long time supporter of Free Software Foundation and Debian operating system.