pkrix89
Goto Top

Stateful Inspection mit FWbuilder und iptables

Hallo zusammen face-smile

ich verzweifle grad ein bisschen an meiner iptables-Firewall..

Szenario:
Ein Kunde bekommt von uns einen mobilen Router mit Stromversorgung über einen USB-Port, der "stealth" zwischen den Rechner und den Netzwerkport geschaltet wird. Der Rechner bekommt dann ganz normal eine IP via DHCP aus dem Netzwerk, in dem er lokal angeschlossen wurde. Der Router hingegen hat eine feste IP, mit der er eine VPN-Verbindung zu unserem Kundensupport-System aufbaut. Nun kann der Kunde, wenn er die richtige IP im Browser mit HTTPs eingibt, auf eine Webseite zugreifen, über die der Kunde einige Funktionen zur Verfügung gestellt bekommt.

Die Funktion, für die ich gerade eine (oder mehrere) Firewallregel(n) konfigurieren muss, bevor das produktiv geschaltet wird, ist die, dass der Kunde über das Webportal eine virtuelle Maschine starten kann. Auf diese kann er dann per VNC zugreifen, um an einer seiner Produktionsmaschinen (die wir bauen und verkaufen) Wartungsprogramme laufen zu lassen. Der Vorteil ist einfach, dass nicht wegen jeder "Kleinigkeit" ein Monteur zum Kunden geschickt werden muss. Zusätzlich kann der Kunde und auch gleichzeitig ein Mitarbeiter aus unserem technischen Support auf die VM schauen, um zusammen einem eventuellen Problem auf die Schliche zu kommen.

Da wir jedoch wesentlich viele Maschinen haben (es laufen immer nur einige wenige, jedoch halten wir viele auf Bedarf vor) werden die IP's der Maschinen via DHCP vergeben, was für mich die Firewallkonfiguration nicht unbedingt leichter macht ;)

Zielsetzung:
Ein fester Parameter, den ich für die von der Webseite dynamisch angepassten iptables-Firewall nutzen kann, ist natürlich die MAC-Adresse. Diese ist für jede VM in der Datenbank hinterlegt, um beispielsweise für Firewallregeln auf diese zurückgreifen zu können. Um nun den VNC-Zugriff auf diese Maschinen soweit einschränken zu können, dass der Kunde nur auf die ihm zugewiesenen VM's kommt, hatte ich mir überlegt, VNC-Anfragen (Syn) generell zu erlauben, und nur die Antwortpakete (ack) von der Firewall dynamisch durchzulassen oder eben zu verwerfen. Auf diese Idee bin ich gekommen, weil ich durch eine Recherche im Internet festgestellt habe, dass man bei iptables eine MAC-Adresse nur als Source angeben kann, leider nicht als Destination. Sollte für dieses Szenario jemand eine bessere Idee haben, bin ich gerne bereit darüber zu sprechen ;)

Problem:
Heute habe ich nach langem googlen (am Freitag hatte ich es aus Frust abgebrochen..) herausgefunden, dass der FWbuilder wohl ein Modul lädt, was automatisch für jede Regel entsprechende Regeln für die Antwortpakete erstellt, was ja auch erstmal ne super Sache ist, und die Konfiguration wesentlich vereinfacht. der Nachteil ist, dass diese Funktionalität für dieses eine Szenario sehr hinderlich ist. Wenn ich nun in Regel 16 die Verbindungsanfrage mit folgender Regel freischalte (gekürzt auf einen Kunden mit einer Maschine, um die Übersichtlichkeit nicht zu verlieren):
$IPT="/sbin/iptables"  
#Dem Kunden VNC-Anfragen gestatten:
$IPT -N Cid4937X3018.0
$IPT -a INPUT -i br3 -p tcp -m tcp -d 192.168.0.0/24 --dport 5900 -m state --state NEW -j Cid4937X3018.0
$IPT -N In_RULE_16
$IPT -A Cid4937X3018.0 -s 192.168.0.x -j In_RULE_16
# Für jeden Kunden eine Zeile, da ich VNC-SYN ja für die Kunden global freischalten lassen möchte
$IPT -N Cid4937X3018.1
$IPT -A FORWARD -i br3 -p tcp -m tcp -d 192.168.0.0/24 --dport 5900 -m state --state NEW -j Cid4937X3018.1
$IPT -A Cid4937X3018.1 -s 192.168.0.x -j In_RULE_16
# Für jeden Kunden eine Zeile, da ich VNC-SYN ja für die Kunden global freischalten lassen möchte
$IPT -A In_RULE_16 -j LOG --log-level info --log-prefix "RULE 16 -- ACCEPT "  
$IPT -A In_RULE_16 -j ACCEPT
Diese Regel schaltet jedoch dummerweise auch automatisch durch dieses geladene Modul die Antwortpakete frei, was allerdings denkbar ungünstig ist, weil der Kunde A so beispielsweise auf die VM einer Maschine von Kunde B gelangen kann. Daher habe ich eine Regel für die Antwortpakete erstellt, die jedoch beim Compilieren im FWBuilder eine Fehlermeldung ausspuckt
#Dem Kunden die VNC-Antworten SEINER VM's gestatten: 
Firewall:Policy:17: error: TCPService object with option "established" is not dupportet by firewall platform "iptables". Use stateful rule instead.  

Frage:
Welche Möglichkeiten habe ich nun, um dieses Szenario zu realisieren? Irgendwelche Ideen? (Am besten, ohne dass ich die ganzen anderern Regeln anfassen muss, weil das sind dann doch schon einige Hundert Zeilen Code -.-*

Von FWbuilder generierter Code (ohne die ganzen Regeln):
Anbei noch der Rest der Firewall-Konfiguration, die vom FWbuilder generiert wird. Ausgenommen habe ich sämtliche von mir erstellten Regeln, jedoch habe ich einen Hinweis an der Stelle eingebaut, wo normal das Regelwerk käme. Ich denke, dass das "Vorgeplänkel" vor dem Regelwerk bei der Problemlösung sinnvoll sein könnte. Also bei Bedarf stehen die folgenden 400 Zeilen zur Verfügung ;)
FWBDEBUG=""  

PATH="/sbin:/usr/sbin:/bin:/usr/bin:${PATH}"  
export PATH



LSMOD="/sbin/lsmod"  
MODPROBE="/sbin/modprobe"  
IPTABLES="/usr/sbin/iptables"  
IP6TABLES="/usr/sbin/ip6tables"  
IPTABLES_RESTORE="/usr/sbin/iptables-restore"  
IP6TABLES_RESTORE="/usr/sbin/ip6tables-restore"  
IP="/sbin/ip"  
IFCONFIG="/sbin/ifconfig"  
VCONFIG="/sbin/vconfig"  
BRCTL="/sbin/brctl"  
IFENSLAVE="/sbin/ifenslave"  
IPSET="/usr/sbin/ipset"  
LOGGER="/usr/bin/logger"  

log() {
    echo "$1"  
    command -v "$LOGGER" >/dev/null 2>&1 && $LOGGER -p info "$1"  
}

getInterfaceVarName() {
    echo $1 | sed 's/\./_/'  
}

getaddr_internal() {
    dev=$1
    name=$2
    af=$3
    L=$($IP $af addr show dev $dev |  sed -n '/inet/{s!.*inet6* !!;s!/.*!!p}' | sed 's/peer.*//')  
    test -z "$L" && {  
        eval "$name=''"  
        return
    }
    eval "${name}_list=\"$L\""  
}

getaddr() {
    getaddr_internal $1 $2 "-4"  
}

getaddr6() {
    getaddr_internal $1 $2 "-6"  
}

# function getinterfaces is used to process wildcard interfaces
getinterfaces() {
    NAME=$1
    $IP link show | grep ": $NAME" | while read L; do  
        OIFS=$IFS
        IFS=" :"  
        set $L
        IFS=$OIFS
        echo $2
    done
}

diff_intf() {
    func=$1
    list1=$2
    list2=$3
    cmd=$4
    for intf in $list1
    do
        echo $list2 | grep -q $intf || {
        # $vlan is absent in list 2
            $func $intf $cmd
        }
    done
}

find_program() {
  PGM=$1
  command -v $PGM >/dev/null 2>&1 || {
    echo "$PGM not found"  
    exit 1
  }
}
check_tools() {
  find_program $IPTABLES
 find_program $MODPROBE
  find_program $IP
}
reset_iptables_v4() {
  $IPTABLES -P OUTPUT  DROP
  $IPTABLES -P INPUT   DROP
  $IPTABLES -P FORWARD DROP

cat /proc/net/ip_tables_names | while read table; do
  $IPTABLES -t $table -L -n | while read c chain rest; do
      if test "X$c" = "XChain" ; then  
        $IPTABLES -t $table -F $chain
      fi
  done
  $IPTABLES -t $table -X
done
}

reset_iptables_v6() {
  $IP6TABLES -P OUTPUT  DROP
  $IP6TABLES -P INPUT   DROP
  $IP6TABLES -P FORWARD DROP

cat /proc/net/ip6_tables_names | while read table; do
  $IP6TABLES -t $table -L -n | while read c chain rest; do
      if test "X$c" = "XChain" ; then  
        $IP6TABLES -t $table -F $chain
      fi
  done
  $IP6TABLES -t $table -X
done
}


P2P_INTERFACE_WARNING=""  

missing_address() {
    address=$1
    cmd=$2

    oldIFS=$IFS
    IFS="@"  
    set $address
    addr=$1
    interface=$2
    IFS=$oldIFS



    $IP addr show dev $interface | grep -q POINTOPOINT && {
        test -z "$P2P_INTERFACE_WARNING" && echo "Warning: Can not update address of interface $interface. fwbuilder can not manage addresses of point-to-point interfaces yet"  
        P2P_INTERFACE_WARNING="yes"  
        return
    }

    test "$cmd" = "add" && {  
      echo "# Adding ip address: $interface $addr"  
      echo $addr | grep -q ':' && {  
          $FWBDEBUG $IP addr $cmd $addr dev $interface
      } || {
          $FWBDEBUG $IP addr $cmd $addr broadcast + dev $interface
      }
    }

    test "$cmd" = "del" && {  
      echo "# Removing ip address: $interface $addr"  
      $FWBDEBUG $IP addr $cmd $addr dev $interface || exit 1
    }

    $FWBDEBUG $IP link set $interface up
}

list_addresses_by_scope() {
    interface=$1
    scope=$2
    ignore_list=$3
    $IP addr ls dev $interface | \
      awk -v IGNORED="$ignore_list" -v SCOPE="$scope" \  
        'BEGIN {  
           split(IGNORED,ignored_arr);
           for (a in ignored_arr) {ignored_dict[ignored_arr[a]]=1;}
         }
         (/inet |inet6 / && $0 ~ SCOPE && !($2 in ignored_dict)) {print $2;}' | \  
        while read addr; do
          echo "${addr}@$interface"  
        done | sort
}


update_addresses_of_interface() {
    ignore_list=$2
    set $1
    interface=$1
    shift

    FWB_ADDRS=$(
      for addr in $*; do
        echo "${addr}@$interface"  
      done | sort
    )

    CURRENT_ADDRS_ALL_SCOPES=""  
    CURRENT_ADDRS_GLOBAL_SCOPE=""  

    $IP link show dev $interface >/dev/null 2>&1 && {
      CURRENT_ADDRS_ALL_SCOPES=$(list_addresses_by_scope $interface 'scope .*' "$ignore_list")  
      CURRENT_ADDRS_GLOBAL_SCOPE=$(list_addresses_by_scope $interface 'scope global' "$ignore_list")  
    } || {
      echo "# Interface $interface does not exist"  
      # Stop the script if we are not in test mode
      test -z "$FWBDEBUG" && exit 1  
    }

    diff_intf missing_address "$FWB_ADDRS" "$CURRENT_ADDRS_ALL_SCOPES" add  
    diff_intf missing_address "$CURRENT_ADDRS_GLOBAL_SCOPE" "$FWB_ADDRS" del  
}

clear_addresses_except_known_interfaces() {
    $IP link show | sed 's/://g' | awk -v IGNORED="$*" \  
        'BEGIN {  
           split(IGNORED,ignored_arr);
           for (a in ignored_arr) {ignored_dict[ignored_arr[a]]=1;}
         }
         (/state/ && !($2 in ignored_dict)) {print $2;}' | \  
         while read intf; do
            echo "# Removing addresses not configured in fwbuilder from interface $intf"  
            $FWBDEBUG $IP addr flush dev $intf scope global
            $FWBDEBUG $IP link set $intf down
         done
}

check_file() {
    test -r "$2" || {  
        echo "Can not find file $2 referenced by address table object $1"  
        exit 1
    }
}

check_run_time_address_table_files() {
    :

}

load_modules() {
    :
    OPTS=$1
    MODULES_DIR="/lib/modules/`uname -r`/kernel/net/"  
    MODULES=$(find $MODULES_DIR -name '*conntrack*' \! -name '*ipv6*'|sed  -e 's/^.*\///' -e 's/\([^\.]\)\..*/\1/')  
    echo $OPTS | grep -q nat && {
        MODULES="$MODULES $(find $MODULES_DIR -name '*nat*'|sed  -e 's/^.*\///' -e 's/\([^\.]\)\..*/\1/')"  
    }
    echo $OPTS | grep -q ipv6 && {
        MODULES="$MODULES $(find $MODULES_DIR -name nf_conntrack_ipv6|sed  -e 's/^.*\///' -e 's/\([^\.]\)\..*/\1/')"  
    }
    for module in $MODULES; do
        if $LSMOD | grep ${module} >/dev/null; then continue; fi
        $MODPROBE ${module} ||  exit 1
    done
}

verify_interfaces() {
    :
    echo "Verifying interfaces: br0 br2 br3 br4 lo"  
    for i in br0 br2 br3 br4 lo ; do
        $IP link show "$i" > /dev/null 2>&1 || {  
            log "Interface $i does not exist"  
            exit 1
        }
    done
}

prolog_commands() {
    echo "Running prolog script"  

}

epilog_commands() {
    echo "Running epilog script"  

}

run_epilog_and_exit() {
    epilog_commands
    exit $1
}

configure_interfaces() {
    :
    # Configure interfaces
    update_addresses_of_interface "br0 172.16.0.x/24" ""  
    update_addresses_of_interface "br2 172.17.0.y/24" ""  
    update_addresses_of_interface "br3 192.168.0.x/24" ""  
    update_addresses_of_interface "lo 127.0.0.1/8" ""  
}

script_body() {
    # ================ IPv4

##############################################
##############################################
#                                            #
#           Regelwerk weggelassen            #
#                                            #
##############################################
##############################################

ip_forward() {
    :
    echo 1 > /proc/sys/net/ipv4/ip_forward
}

reset_all() {
    :
    reset_iptables_v4
}

block_action() {
    reset_all
}

stop_action() {
    reset_all
    $IPTABLES -P OUTPUT  ACCEPT
    $IPTABLES -P INPUT   ACCEPT
    $IPTABLES -P FORWARD ACCEPT
}

check_iptables() {
    IP_TABLES="$1"  
    [ ! -e $IP_TABLES ] && return 151
    NF_TABLES=$(cat $IP_TABLES 2>/dev/null)
    [ -z "$NF_TABLES" ] && return 152  
    return 0
}
status_action() {
    check_iptables "/proc/net/ip_tables_names"  
    ret_ipv4=$?
    check_iptables "/proc/net/ip6_tables_names"  
    ret_ipv6=$?
    [ $ret_ipv4 -eq 0 -o $ret_ipv6 -eq 0 ] && return 0
    [ $ret_ipv4 -eq 151 -o $ret_ipv6 -eq 151 ] && {
        echo "iptables modules are not loaded"  
    }
    [ $ret_ipv4 -eq 152 -o $ret_ipv6 -eq 152 ] && {
        echo "Firewall is not configured"  
    }
    exit 3
}

# See how we were called.
# For backwards compatibility missing argument is equivalent to 'start' 

cmd=$1
test -z "$cmd" && {  
    cmd="start"  
}

case "$cmd" in  
    start)
        log "Activating firewall script by FWbuilder"  
        check_tools
         prolog_commands
        check_run_time_address_table_files

        load_modules " "  
        configure_interfaces
        verify_interfaces

         reset_all

        script_body
        ip_forward
        epilog_commands
        RETVAL=$?
        ;;

    stop)
        stop_action
        RETVAL=$?
        ;;

    status)
        status_action
        RETVAL=$?
        ;;

    block)
        block_action
        RETVAL=$?
        ;;

    reload)
        $0 stop
        $0 start
        RETVAL=$?
        ;;

    interfaces)
        configure_interfaces
        RETVAL=$?
        ;;

    test_interfaces)
        FWBDEBUG="echo"  
        configure_interfaces
        RETVAL=$?
        ;;

    *)
        echo "Usage $0 [start|stop|status|block|reload|interfaces|test_interfaces]"  
        ;;

esac

exit $RETVAL

Bin für jede Hilfe Dankbar!!

Viele Grüße pkrix89

Content-Key: 194082

Url: https://administrator.de/contentid/194082

Printed on: April 23, 2024 at 16:04 o'clock

Member: pkrix89
pkrix89 Nov 16, 2012 at 06:57:52 (UTC)
Goto Top
Hallo zusammen,

ich habe das Problem endlich lösen können, und möchte euch an meiner Lösung teilhaben lassen ;)

Der FWbuilder hat die doofe Angewohnheit, noch vor das eigentliche Regelwerk folgende 3 Regeln zu setzen:
    $IPTABLES -A INPUT   -m state --state ESTABLISHED,RELATED -j ACCEPT
    $IPTABLES -A OUTPUT  -m state --state ESTABLISHED,RELATED -j ACCEPT
    $IPTABLES -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
Diese Regeln bewirken, dass alle Pakete, die auf eine erlaubte Verbindungsanfrage (SYN) gesendet werden, automatisch gestattet werden. Dazu zählt natürlich auch die gesamte Kommunikation nach erfolgreichem Verbindungsaufbau.

Diese 3 Regeln habe ich nun an das Ende des Firewallscripts gepackt (direkt vor die DENY-ALL-Regel), und siehe da, das SYN-Paket wird durchgelassen, das ACK nur von den Clients, von denen ich es möchte. Einziger Nachteil: Es wurden auch noch ein paar andere Antwortpakete der betreffenden Hosts geblockt. Diese waren jedoch über das Firewall-Logfile leicht auszumachen und anschließend in dem Script nachzutragen. Im Großen und Ganzen war es sehr viel Aufwand für eine Kleinigkeit -.-*

Ich hoffe, ich konnte nachvollziehbar machen, was ich meine, und auch vielleicht dem Einen oder Anderen einen Nützlichen Hinweis liefern ;)

Vielen Dank für die vielen Aufrufe, auch wenn keiner etwas geantwortet hat ;)

Grüße, pkrix89