From 98146c00e08c14edb39096d417b6176d32b037fd Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Mon, 28 May 2012 07:55:21 +0000 Subject: [PATCH] Initial import of a lot of firewall code. This creates a basic firewall for IPv4. --- Makefile | 1 + firewall | 69 ++++++++++++++ functions.constants | 6 ++ functions.events | 17 +++- functions.firewall | 118 ++++++++++++++++++++++++ functions.iptables | 190 +++++++++++++++++++++++++++++++++++++++ functions.policy | 118 ++++++++++++++++++++++++ functions.ppp | 2 +- functions.util | 12 +-- systemd/firewall.service | 12 +++ 10 files changed, 532 insertions(+), 13 deletions(-) create mode 100755 firewall create mode 100644 functions.firewall create mode 100644 functions.iptables create mode 100644 functions.policy create mode 100644 systemd/firewall.service diff --git a/Makefile b/Makefile index a7cbeae9..3d2300c0 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ install: -mkdir -pv $(DESTDIR)$(systemdunitdir) -mkdir -pv $(DESTDIR)$(tmpfilesdir) + install -m 755 -v firewall $(DESTDIR)$(sbindir) install -m 755 -v network $(DESTDIR)$(sbindir) cp -rfv {hooks,header*,functions*} $(DESTDIR)$(libdir)/network/ diff --git a/firewall b/firewall new file mode 100755 index 00000000..ca4d16cc --- /dev/null +++ b/firewall @@ -0,0 +1,69 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2012 IPFire Network Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# 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. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +. /usr/lib/network/functions + +function cli_start() { + firewall_start +} + +function cli_stop() { + firewall_stop +} + +# Parse the command line +while [ $# -gt 0 ]; do + case "${1}" in + -d|--debug) + DEBUG=1 + log DEBUG "Enabled debugging mode" + ;; + *) + action=${1} + ;; + esac + shift + [ -n "${action}" ] && break +done + +# Process the given action +case "${action}" in + start|restart|reload) + cli_start $@ + ;; + + stop) + cli_stop $@ + ;; + + ""|help|--help|-h) + cli_usage root + exit ${EXIT_OK} + ;; + + *) + error "Invalid command given: ${action}" + cli_usage usage + exit ${EXIT_CONF_ERROR} + ;; +esac + +exit ${EXIT_OK} diff --git a/functions.constants b/functions.constants index 56baa6e3..2c550b9f 100644 --- a/functions.constants +++ b/functions.constants @@ -85,3 +85,9 @@ DEVICE_PRINT_LINE1=" %-24s %s\n" PORT_PATTERN="pN" PORT_PATTERN_ACCESSPOINT="apN" PORT_PATTERN_WIRELESS="wN" + +# This variable is used to point to a directory +# in which the iptables ruleset will be generated. +IPTABLES_TMPDIR= + +FIREWALL_LOG_FACILITY="syslog" diff --git a/functions.events b/functions.events index 5416e1fe..5b5ba800 100644 --- a/functions.events +++ b/functions.events @@ -29,10 +29,23 @@ function event_emit() { # At a later point of time, we need to find another solution how to # react on those events. #initctl emit ${event} $@ + + case "${event}" in + firewall-reload) + # Try to reload the firewall service. + __event_firewall_reload + ;; + esac } -function event_firewall_reload() { - event_emit firewall-reload +function __event_firewall_reload() { + local service="firewall" + + # Reload the firewall service when it has + # been started earlier. + if service_is_running ${service}; then + service_restart ${service} + fi } function event_interface_up() { diff --git a/functions.firewall b/functions.firewall new file mode 100644 index 00000000..8032a338 --- /dev/null +++ b/functions.firewall @@ -0,0 +1,118 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2012 IPFire Network Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# 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. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +# High-level function which will create a ruleset for the current firewall +# configuration and load it into the kernel. +function firewall_start() { + firewall_lock_acquire + + # Initialize an empty iptables ruleset. + iptables_init DROP + + # Add default chains. + firewall_tcp_state_flags + firewall_connection_tracking + + # Add policies for every zone. + policy_add_localhost + + local zone + for zone in $(zones_get_all); do + policy_add_zone ${zone} + done + + # Commit the new ruleset. + iptables_commit + + firewall_lock_release +} + +function firewall_stop() { + firewall_lock_acquire + + # Initialize an empty firewall ruleset + # with default policy ACCEPT. + iptables_init ACCEPT + + # Commit it. + iptables_commit + + firewall_lock_release +} + +function firewall_lock_acquire() { + lock_acquire ${RUN_DIR}/.firewall_lock + + # Make sure the lock is released after the firewall + # script has crashed or exited early. + trap firewall_lock_release EXIT TERM KILL + + # Create a directory where we can put our + # temporary data in the most secure way as possible. + IPTABLES_TMPDIR=$(mktemp -d) +} + +function firewall_lock_release() { + if isset IPTABLES_TMPDIR; then + # Remove all temporary data. + rm -rf ${IPTABLES_TMPDIR} + + # Reset the tempdir variable. + IPTABLES_TMPDIR= + fi + + # Reset the trap. + trap true EXIT TERM KILL + + lock_release ${RUN_DIR}/.firewall_lock +} + +function firewall_tcp_state_flags() { + log INFO "Creating TCP State Flags chain..." + iptables_chain_create BADTCP_LOG + iptables -A BADTCP_LOG -p tcp -j $(iptables_LOG "Illegal TCP state: ") + iptables -A BADTCP_LOG -j DROP + + iptables_chain_create BADTCP + iptables -A BADTCP -p tcp --tcp-flags ALL NONE -j BADTCP_LOG + iptables -A BADTCP -p tcp --tcp-flags SYN,FIN SYN,FIN -j BADTCP_LOG + iptables -A BADTCP -p tcp --tcp-flags SYN,RST SYN,RST -j BADTCP_LOG + iptables -A BADTCP -p tcp --tcp-flags FIN,RST FIN,RST -j BADTCP_LOG + iptables -A BADTCP -p tcp --tcp-flags ACK,FIN FIN -j BADTCP_LOG + iptables -A BADTCP -p tcp --tcp-flags ACK,PSH PSH -j BADTCP_LOG + iptables -A BADTCP -p tcp --tcp-flags ACK,URG URG -j BADTCP_LOG + + iptables -A INPUT -p tcp -j BADTCP + iptables -A OUTPUT -p tcp -j BADTCP + iptables -A FORWARD -p tcp -j BADTCP +} + +function firewall_connection_tracking() { + log INFO "Creating Connection Tracking chain..." + iptables_chain_create CONNTRACK + iptables -A CONNTRACK -m state --state ESTABLISHED,RELATED -j ACCEPT + iptables -A CONNTRACK -m state --state INVALID -j $(iptables_LOG "INVALID packet: ") + iptables -A CONNTRACK -m state --state INVALID -j DROP + + iptables -A INPUT -j CONNTRACK + iptables -A OUTPUT -j CONNTRACK + iptables -A FORWARD -j CONNTRACK +} diff --git a/functions.iptables b/functions.iptables new file mode 100644 index 00000000..cf1e4769 --- /dev/null +++ b/functions.iptables @@ -0,0 +1,190 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2012 IPFire Network Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# 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. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +function iptables() { + local arg + local args + local table + + # Check if the directory where we put our rules in is set and + # exists. + assert isset IPTABLES_TMPDIR + assert [ -d "${IPTABLES_TMPDIR}" ] + + table=filter + + # Parsing arguments + while [ $# -gt 0 ]; do + case "${1}" in + -t) + table=${2} + shift 2 + ;; + -A) + args="${args} -A ${2^^}" + shift 2 + ;; + *) + args="${args} ${1}" + shift + ;; + esac + done + + echo "${args:1:${#args}}" >> ${IPTABLES_TMPDIR}/${table} +} + +function iptables_init() { + local policy=${1} + assert isoneof policy ACCEPT DROP + + iptables "* filter" + iptables_chain_create -t filter INPUT ${policy} + iptables_chain_create -t filter OUTPUT ${policy} + iptables_chain_create -t filter FORWARD ${policy} + + iptables -t mangle "* mangle" + iptables_chain_create -t mangle PREROUTING ACCEPT + iptables_chain_create -t mangle INPUT ACCEPT + iptables_chain_create -t mangle OUTPUT ACCEPT + iptables_chain_create -t mangle FORWARD ACCEPT + iptables_chain_create -t mangle POSTROUTING ACCEPT + + iptables -t nat "* nat" + iptables_chain_create -t nat PREROUTING ACCEPT + iptables_chain_create -t nat OUTPUT ACCEPT + iptables_chain_create -t nat POSTROUTING ACCEPT +} + +function iptables_commit() { + local chain + + # Check if the directory where we put our rules in is set and + # exists. + assert isset IPTABLES_TMPDIR + assert [ -d "${IPTABLES_TMPDIR}" ] + + log INFO "Committing firewall configuration..." + iptables -t filter "COMMIT" + iptables -t mangle "COMMIT" + iptables -t nat "COMMIT" + + local iptables_ruleset="${IPTABLES_TMPDIR}/commit" + : > ${iptables_ruleset} + + # Concat the rules for every chain into one file. + local table + for table in filter mangle nat; do + cat ${IPTABLES_TMPDIR}/${table} \ + >> ${iptables_ruleset} 2>/dev/null + done + + log DEBUG "Dumping iptables ruleset" + local counter=1 + local line + while read line; do + line=$(printf "%4d | %s\n" "${counter}" "${line}") + log DEBUG "${line}" + + counter=$(( $counter + 1 )) + done < ${iptables_ruleset} + + iptables-restore < ${iptables_ruleset} +} + +function iptables_chain_create() { + local args + if [ "${1}" = "-t" ]; then + args="${1} ${2}" + shift 2 + fi + + iptables ${args} ":$1 ${2--} [0:0]" +} + +function iptables_LOG() { + local prefix=${1} + + if [ "${FIREWALL_LOG_FACILITY}" = "syslog" ]; then + echo -n "LOG" + [ -n "$prefix" ] && echo -n " --log-prefix \"$prefix\"" + else + echo -n "NFLOG" + [ -n "$prefix" ] && echo -n " --nflog-prefix \"$prefix\"" + echo -n " --nflog-threshold 30" + fi + echo +} + +function iptables_protocol() { + local PROTO + PROTO=$1 + for proto in tcp udp esp ah; do + if [ "$PROTO" = "$proto" ]; then + echo "-p $PROTO" + break + fi + done +} + +IPTABLES_PORT=0 +IPTABLES_MULTIPORT=1 +IPTABLES_PORTRANGE=2 + +function _iptables_port_range() { + grep -q ":" <<< $@ +} + +function _iptables_port_multiport() { + grep -q "," <<< $@ +} + +function _iptables_port() { + if _iptables_port_range "$@"; then + echo $IPTABLES_PORTRANGE + elif _iptables_port_multiport "$@"; then + echo $IPTABLES_MULTIPORT + else + echo $IPTABLES_PORT + fi +} + +function iptables_source_port() { + [ -z "$@" ] && return + local type + type=$(_iptables_port $@) + if [ "$type" = "$IPTABLES_MULTIPORT" ]; then + echo "-m multiport --source-ports $@" + else + echo "--sport $@" + fi +} + +function iptables_destination_port() { + [ -z "$@" ] && return + local type + type=$(_iptables_port $@) + if [ "$type" = "$IPTABLES_MULTIPORT" ]; then + echo "-m multiport --destination-ports $@" + else + echo "--dport $@" + fi +} diff --git a/functions.policy b/functions.policy new file mode 100644 index 00000000..3871720b --- /dev/null +++ b/functions.policy @@ -0,0 +1,118 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2012 IPFire Network Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# 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. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +function policy_add_zone() { + local zone=${1} + assert isset zone + + log DEBUG "Creating firewall policy for zone '${zone}'." + + local chain="ZONE_${zone}" + chain=${chain^^} + + # Create filter chain. + iptables_chain_create ${chain} + iptables -A INPUT -i ${zone} -j ${chain} + iptables -A FORWARD -i ${zone} -j ${chain} + iptables -A FORWARD -o ${zone} -j ${chain} + iptables -A OUTPUT -o ${zone} -j ${chain} + + # Leave some space for own rules right at the beginning + # to make it possible to overwrite _everything_. + iptables_chain_create ${chain}_CUSTOM + iptables -A ${chain} -j ${chain}_CUSTOM + + # Intrusion Prevention System + iptables_chain_create ${chain}_IPS + iptables -A ${chain} -i ${zone} -j ${chain}_IPS + + # Port forwarding chain. + iptables_chain_create ${chain}_PORTFW + iptables -A ${chain} -i ${zone} -j ${chain}_PORTFW + + # Outgoing firewall + iptables_chain_create ${chain}_OUTFW + iptables -A ${chain} -o ${zone} -j ${chain}_OUTFW + + # Policy rules + iptables_chain_create ${chain}_POLICY + iptables -A ${chain} -j ${chain}_POLICY + + # Create mangle chain. + iptables_chain_create -t mangle ${chain} + iptables -t mangle -A PREROUTING -i ${zone} -j ${chain} + iptables -t mangle -A POSTROUTING -o ${zone} -j ${chain} + + # Quality of Service + iptables_chain_create -t mangle ${chain}_QOS_INC + iptables -t mangle -A ${chain} -i ${zone} -j ${chain}_QOS_INC + iptables_chain_create -t mangle ${chain}_QOS_OUT + iptables -t mangle -A ${chain} -o ${zone} -j ${chain}_QOS_OUT + + # Create NAT chain. + iptables_chain_create -t nat ${chain} + iptables -t nat -A PREROUTING -i ${zone} -j ${chain} + iptables -t nat -A POSTROUTING -o ${zone} -j ${chain} + + # Network Address Translation + iptables_chain_create -t nat ${chain}_NAT + iptables -t nat -A ${chain} -i ${zone} -j ${chain}_NAT + + # Portforwarding + iptables_chain_create -t nat ${chain}_PORTFW + iptables -t nat -A ${chain} -i ${zone} -j ${chain}_PORTFW + + # UPNP + iptables_chain_create -t nat ${chain}_UPNP + iptables -t nat -A ${chain} -j ${chain}_UPNP + + # After the chains that are always available have been + # created, we will add a custom policy to every single + # zone. + + # Local zones are currently allowed to access everything. + if zone_is_local ${zone}; then + policy_allow_all ${zone} ${chain} + + # Uplink connections are not. + else + : # XXX TODO + fi +} + +function policy_add_localhost() { + log DEBUG "Creating firewall policy for localhost..." + + # Accept everything on lo + iptables -A INPUT -i lo -j ACCEPT + iptables -A OUTPUT -o lo -j ACCEPT +} + +function policy_allow_all() { + local zone=${1} + assert isset zone + + local chain=${2} + assert isset chain + + # Just accept everything. + iptables -A ${chain}_POLICY -j ACCEPT +} diff --git a/functions.ppp b/functions.ppp index 78b967e4..9c793b43 100644 --- a/functions.ppp +++ b/functions.ppp @@ -31,7 +31,7 @@ function ppp_common_ip_pre_up() { routing_db_from_ppp ${zone} ipv4 # Request firewall reload - event_firewall_reload + event_emit firewall-reload return ${EXIT_OK} } diff --git a/functions.util b/functions.util index bb3f6484..6aee47d1 100644 --- a/functions.util +++ b/functions.util @@ -324,16 +324,8 @@ function cmd() { return ${ret} } -function uppercase() { - local input - read input - echo "${input^^}" -} - -function lowercase() { - local input - read input - echo "${input,,}" +function cmd_quiet() { + cmd $@ &>/dev/null } function seq() { diff --git a/systemd/firewall.service b/systemd/firewall.service new file mode 100644 index 00000000..7d6b64e0 --- /dev/null +++ b/systemd/firewall.service @@ -0,0 +1,12 @@ +[Unit] +Description=Firewall Ruleset Manager +Before=network.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/sbin/firewall start +ExecStop=/usr/sbin/firewall stop + +[Install] +WantedBy=multi-user.target -- 2.47.3