web-dev-qa-db-ja.com

「iptables-S」出力を幅優先リストに変換する方法

iptables -Sの出力を取得し、それを 幅優先 リストに変換するプログラムを探しています。

どうして? VyOS を使用してルーターで作業を行っています。ここでは、テーブルの複数のレイヤーがプリインストールされているため、INPUT、FORWARD、およびOUTPUTに接続するすべてのルールをさかのぼることは困難です。


@JeffSchallerの[リクエスト]に従って、解析する必要のある出力例を次に示します。

$ Sudo iptables -S 
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N LAN1_IN
-N MINIUPNPD
-N UBNT_FW_IN_SUSPEND_HOOK
-N UBNT_PFOR_FW_HOOK
-N UBNT_PFOR_FW_RULES
-N UBNT_VPN_IPSEC_FW_HOOK
-N UBNT_VPN_IPSEC_FW_IN_HOOK
-N VYATTA_FW_IN_HOOK
-N VYATTA_FW_LOCAL_HOOK
-N VYATTA_FW_OUT_HOOK
-N VYATTA_POST_FW_FWD_HOOK
-N VYATTA_POST_FW_IN_HOOK
-N VYATTA_POST_FW_OUT_HOOK
-N WAN_IN
-N WAN_LOCAL
-N WAN_OUT
-A INPUT -j UBNT_VPN_IPSEC_FW_HOOK
-A INPUT -j VYATTA_FW_LOCAL_HOOK
-A INPUT -j VYATTA_POST_FW_IN_HOOK
-A FORWARD -j MINIUPNPD
-A FORWARD -j UBNT_VPN_IPSEC_FW_IN_HOOK
-A FORWARD -j UBNT_PFOR_FW_HOOK
-A FORWARD -j UBNT_FW_IN_SUSPEND_HOOK
-A FORWARD -j VYATTA_FW_IN_HOOK
-A FORWARD -j VYATTA_FW_OUT_HOOK
-A FORWARD -j VYATTA_POST_FW_FWD_HOOK
-A OUTPUT -j VYATTA_POST_FW_OUT_HOOK
-A LAN1_IN -m comment --comment LAN1_IN-10 -m state --state INVALID -j LOG --log-prefix "[LAN1_IN-10-D]"
-A LAN1_IN -m comment --comment LAN1_IN-10 -m state --state INVALID -j DROP
-A LAN1_IN -p udp -m comment --comment LAN1_IN-20 -m udp --dport 53 -m set --match-set dnsaddr dst -j RETURN
-A LAN1_IN -p udp -m comment --comment LAN1_IN-30 -m set --match-set dnsaddr src -m udp --dport 53 -j RETURN
-A LAN1_IN -m comment --comment LAN1_IN-60 -m state --state NEW -j RETURN
-A LAN1_IN -m comment --comment LAN1_IN-70 -m state --state RELATED -j RETURN
-A LAN1_IN -m comment --comment LAN1_IN-80 -m state --state ESTABLISHED -j RETURN
-A LAN1_IN -m comment --comment "LAN1_IN-10000 default-action drop" -j LOG --log-prefix "[LAN1_IN-default-D]"
-A LAN1_IN -m comment --comment "LAN1_IN-10000 default-action drop" -j DROP
-A VYATTA_FW_IN_HOOK -i eth0 -j WAN_IN
-A VYATTA_FW_IN_HOOK -i eth1 -j LAN1_IN
-A VYATTA_FW_LOCAL_HOOK -i eth0 -j WAN_LOCAL
-A VYATTA_FW_OUT_HOOK -o eth0 -j WAN_OUT
-A VYATTA_POST_FW_FWD_HOOK -j ACCEPT
-A VYATTA_POST_FW_IN_HOOK -j ACCEPT
-A VYATTA_POST_FW_OUT_HOOK -j ACCEPT
-A WAN_IN -m comment --comment WAN_IN-10 -m state --state ESTABLISHED -j RETURN
-A WAN_IN -m comment --comment WAN_IN-20 -m state --state RELATED -j RETURN
-A WAN_IN -m comment --comment WAN_IN-30 -m state --state INVALID -j LOG --log-prefix "[WAN_IN-30-D]"
-A WAN_IN -m comment --comment WAN_IN-30 -m state --state INVALID -j DROP
-A WAN_IN -m comment --comment "WAN_IN-10000 default-action drop" -j DROP
-A WAN_LOCAL -m comment --comment WAN_LOCAL-10 -m state --state ESTABLISHED -j RETURN
-A WAN_LOCAL -m comment --comment WAN_LOCAL-20 -m state --state RELATED -j RETURN
-A WAN_LOCAL -m comment --comment WAN_LOCAL-30 -m state --state INVALID -j LOG --log-prefix "[WAN_LOCAL-30-D]"
-A WAN_LOCAL -m comment --comment WAN_LOCAL-30 -m state --state INVALID -j DROP
-A WAN_LOCAL -m comment --comment "WAN_LOCAL-10000 default-action drop" -j LOG --log-prefix "[WAN_LOCAL-default-D]"
-A WAN_LOCAL -m comment --comment "WAN_LOCAL-10000 default-action drop" -j DROP
-A WAN_OUT -m comment --comment WAN_OUT-10 -m state --state NEW -j RETURN
-A WAN_OUT -m comment --comment WAN_OUT-20 -m state --state RELATED -j RETURN
-A WAN_OUT -m comment --comment WAN_OUT-30 -m state --state ESTABLISHED -j RETURN
-A WAN_OUT -m comment --comment WAN_OUT-40 -m state --state INVALID -j LOG --log-prefix "[WAN_OUT-40-D]"
-A WAN_OUT -m comment --comment WAN_OUT-40 -m state --state INVALID -j DROP
-A WAN_OUT -m comment --comment "WAN_OUT-10000 default-action drop" -j LOG --log-prefix "[WAN_OUT-default-D]"
-A WAN_OUT -m comment --comment "WAN_OUT-10000 default-action drop" -j DROP


私は@ LL3の答えを正しいものとして選択しています。 @ LL3の回答は、stdinを読み取れるように変更されたため、同じようにパッチを削除します

<patch removed>

Perl-master @JeffSchallerの(少し後で)完全な回答への称賛は、幅優先リストと個別にgraphviz出力の両方を示しています。

2
Craig Hicks

少し前に、幅を平坦化する必要がありました。まず、カスタムファイアウォールのルールのツリーを作成し、それらをすべてファイルに保存します。 VyOSではなく、とにかくiptablesです。私は次のスクリプトを思いついた、それがあなたを助けるかどうか見てください。

注意してくださいこのスクリプトには少なくともBashv4が必要です

#!/bin/bash -

declare -A all_chains=()
declare -A queued_chains=()

builtin_chains_as_regexp='INPUT|OUTPUT|FORWARD|PREROUTING|POSTROUTING'
queue_list=""
prepend_chain=""
show_chain_heading=false
one_go=false
uniquify=true

_print_usage() {
   cat <<- EOF
        Usage: $0 [-npofh] <starting-chain>

        -n    shows chain's creation command as heading, useful for spotting empty chains
        -p    prepends chain's name to each rule
        -o    read everything in one go, 10x quicker when many small chains
        -f    expand all references to a same chain, but beware of chain loops or chains referenced hundreds of times
        -h    shows this help
EOF
}

_expand_chain() {
    local chain_to_expand="${1}"

    local rules=""
    # if one_go selected, work with in-memory cache of chains
    if $one_go ; then
        rules="${all_chains[${chain_to_expand}]}"
    # otherwise read in chain to consider
    else
        rules="$(iptables -S "${chain_to_expand}")"
    fi

    $show_chain_heading && \
        ! [[ "${chain_to_expand}" =~ ${builtin_chains_as_regexp} ]] && \
        echo "-N ${chain_to_expand}"
    while read -r cmd chain rule ; do
        case "${cmd}" in
        -A)
            set -- ${rule}
            # look for target option in rule
            while [ -n "${1}" ] && ! [[ "${1}" =~ -(j|g) ]] ; do shift ; done
            # a few sanity checks
            [ -n "${1}" ] || continue # a rule with no target, skip it
            shift
            [ -n "${1}" ] || { echo "what!? empty target in ${rule}" >&2 ; continue ; }
            if [ -n "${all_chains[${1}]}" ] ; then
                # if target is a chain
                # add to queued chains if uniquify *not* requested or if chain never queued
                if ! $uniquify || [ -z "${queued_chains[${1}]}" ] ; then
                    queue_list+="${1} "
                    queued_chains[${1}]="1"
                fi
            fi
            # show rule
            echo "${prepend_chain:+[${chain_to_expand}] }${cmd} ${chain} ${rule}"
        ;;
        esac
    done <<<"${rules}"
}

###
# ACTUAL EXECUTION STARTS HERE
#

# parse command options if any
while getopts nphfo option ; do
    case $option in
    n) show_chain_heading=true
    ;;
    p) prepend_chain="1"
    ;;
    h) _print_usage ; exit 0
    ;;
    o) one_go=true
    ;;
    f) uniquify=false
    ;;
    '?') exit 1
    ;;
    esac
done

[ -n "${!OPTIND}" ] || { _print_usage ; exit 1 ; }

# preparation step:
# if one_go selected, Slurp everything in
if $one_go ; then
    # invoke explicit command only when stdin is the terminal
    [ -t 0 ] && exec 0< <(iptables -S)
    while read -r cmd chain rule ; do
        case "${cmd}" in
        -N)
            all_chains[${chain}]=" " # <<-- whitespace to make provision for empty chains
        ;;
        -A)
            # assign rule to its chain in cache
            all_chains[${chain}]+=$'\n'"${cmd} ${chain} ${rule}"
        ;;
        esac
    done
# otherwise read in chain names only
else
    while IFS= read -r chain ; do
        all_chains[${chain}]="1"
    done < <(iptables -S | sed -ne '/^-N /s///p')
fi

# expand starting chain
_expand_chain ${!OPTIND}

# breadth-first expand queued chains
# as long as queue is not empty
while [ "${#queue_list}" -gt 0 ] ; do
    # take next queued chain
    subchain="${queue_list%% *}"
    # expand it
    _expand_chain "${subchain}"
    # remove expanded chain from queue
    queue_list="${queue_list#${subchain} }"
    # queue gets updated by _expand_chain as needed
done

exit 0

確かにあまりコメントされていませんが、Bashに精通している場合は、従うのはそれほど難しいことではありません。

オプションなしで実行すると、ヘルプの概要が表示されます。

特に、複数回参照されるチェーンの場合でも、デフォルトでは各チェーンが1回だけ展開されることに注意してください。 -fオプションを使用して、真にフラット化された出力を要求できます。他の何千ものチェーンによって参照されるチェーンがいくつかあり、何時間もかかるすべてをフラット化するため、このようにしました(もちろん、このスクリプトは並列処理を行いません)。したがって、同様の設定をしている場合は、このことを覚えておいてください。

2
LL3

これがiptables -S出力でのBFSソートの私の解釈です。各ルールを読み込み、ターゲット(または-Pポリシー)を見つけます。すべてのルールが読み込まれると、組み込みのターゲットから開始され、連続するレベルのルールが出力されます。

iptables-bfs.pl

#!/usr/bin/Perl -w
use strict;

# for now, a chain name has to match regex: [[:alnum:]_-]+

my %jumpsto = ();

while (<>) {
  chomp;
  next if /^#/;
  if (/-[AIR]\s+([[:alnum:]_-]+).*-j\s+([[:alnum:]_-]+)/) {
        unless (exists $jumpsto{$1}{$2}) {
                $jumpsto{$1}{$2}=$_;
        }
  } elsif (/-P ([[:alnum:]_-]+)\s+(ACCEPT|DROP)/) {
        unless (exists $jumpsto{$1}{$2}) {
                $jumpsto{$1}{$2}=$_;
        }
  }
}

my @queue = ();
Push @queue, qw(INPUT OUTPUT FORWARD PREROUTING POSTROUTING);
my @nextqueue = ();
while (@queue) {
  my $item = shift @queue;
  foreach my $target (keys %{ $jumpsto{$item} }) {
    print $jumpsto{$item}{$target} . "\n";
    Push @nextqueue, $target;
  }
  if (! @queue && @nextqueue) {
    @queue = @nextqueue;
    @nextqueue = ();
    print "---------------\n";
  }
}

質問のサンプル入力では、出力は次のとおりです。

-A INPUT -j UBNT_VPN_IPSEC_FW_HOOK
-P INPUT ACCEPT
-A INPUT -j VYATTA_POST_FW_IN_HOOK
-A INPUT -j VYATTA_FW_LOCAL_HOOK
-P OUTPUT ACCEPT
-A OUTPUT -j VYATTA_POST_FW_OUT_HOOK
-A FORWARD -j VYATTA_FW_IN_HOOK
-A FORWARD -j MINIUPNPD
-P FORWARD ACCEPT
-A FORWARD -j VYATTA_POST_FW_FWD_HOOK
-A FORWARD -j UBNT_VPN_IPSEC_FW_IN_HOOK
-A FORWARD -j UBNT_FW_IN_SUSPEND_HOOK
-A FORWARD -j UBNT_PFOR_FW_HOOK
-A FORWARD -j VYATTA_FW_OUT_HOOK
---------------
-A VYATTA_POST_FW_IN_HOOK -j ACCEPT
-A VYATTA_FW_LOCAL_HOOK -i eth0 -j WAN_LOCAL
-A VYATTA_POST_FW_OUT_HOOK -j ACCEPT
-A VYATTA_FW_IN_HOOK -i eth1 -j LAN1_IN
-A VYATTA_FW_IN_HOOK -i eth0 -j WAN_IN
-A VYATTA_POST_FW_FWD_HOOK -j ACCEPT
-A VYATTA_FW_OUT_HOOK -o eth0 -j WAN_OUT
---------------
-A WAN_LOCAL -m comment --comment WAN_LOCAL-30 -m state --state INVALID -j DROP
-A WAN_LOCAL -m comment --comment WAN_LOCAL-10 -m state --state ESTABLISHED -j RETURN
-A WAN_LOCAL -m comment --comment WAN_LOCAL-30 -m state --state INVALID -j LOG --log-prefix "[WAN_LOCAL-30-D]"
-A LAN1_IN -p udp -m comment --comment LAN1_IN-20 -m udp --dport 53 -m set --match-set dnsaddr dst -j RETURN
-A LAN1_IN -m comment --comment LAN1_IN-10 -m state --state INVALID -j DROP
-A LAN1_IN -m comment --comment LAN1_IN-10 -m state --state INVALID -j LOG --log-prefix "[LAN1_IN-10-D]"
-A WAN_IN -m comment --comment WAN_IN-30 -m state --state INVALID -j DROP
-A WAN_IN -m comment --comment WAN_IN-10 -m state --state ESTABLISHED -j RETURN
-A WAN_IN -m comment --comment WAN_IN-30 -m state --state INVALID -j LOG --log-prefix "[WAN_IN-30-D]"
-A WAN_OUT -m comment --comment WAN_OUT-40 -m state --state INVALID -j DROP
-A WAN_OUT -m comment --comment WAN_OUT-10 -m state --state NEW -j RETURN
-A WAN_OUT -m comment --comment WAN_OUT-40 -m state --state INVALID -j LOG --log-prefix "[WAN_OUT-40-D]"
---------------

私の元のmisinterpretationは、iptables -Sをgraphviz互換ファイルに変換する次のPerlスクリプトでした。ソースチェーンとターゲットチェーンをリンクするグラフを作成します。

iptables-dot.pl

#!/usr/bin/Perl -w
use strict;

# for now, a chain name has to match regex: [[:alnum:]_-]+

print "digraph rules {\n";
print "\toverlap=scalexy;\n";

my %jumpsto = ();

while (<>) {
  chomp;
  next if /^#/;
  if (/-[AIR]\s+([[:alnum:]_-]+).*-j\s+([[:alnum:]_-]+)/) {
        unless (exists $jumpsto{$1}{$2}) {
                print "\"$1\" -> \"$2\";\n";
                $jumpsto{$1}{$2}=1;
        }
  } elsif (/-P ([[:alnum:]_-]+)\s+(ACCEPT|DROP)/) {
        unless (exists $jumpsto{$1}{$2}) {
                print "\"$1\" -> \"$2\";\n";
                $jumpsto{$1}{$2}=1;
        }
  }
}

print "}\n";

質問のサンプル入力が与えられると、結果の出力は次のようになります。

digraph rules {
        overlap=scalexy;
"INPUT" -> "ACCEPT";
"FORWARD" -> "ACCEPT";
"OUTPUT" -> "ACCEPT";
"INPUT" -> "UBNT_VPN_IPSEC_FW_HOOK";
"INPUT" -> "VYATTA_FW_LOCAL_HOOK";
"INPUT" -> "VYATTA_POST_FW_IN_HOOK";
"FORWARD" -> "MINIUPNPD";
"FORWARD" -> "UBNT_VPN_IPSEC_FW_IN_HOOK";
"FORWARD" -> "UBNT_PFOR_FW_HOOK";
"FORWARD" -> "UBNT_FW_IN_SUSPEND_HOOK";
"FORWARD" -> "VYATTA_FW_IN_HOOK";
"FORWARD" -> "VYATTA_FW_OUT_HOOK";
"FORWARD" -> "VYATTA_POST_FW_FWD_HOOK";
"OUTPUT" -> "VYATTA_POST_FW_OUT_HOOK";
"LAN1_IN" -> "LOG";
"LAN1_IN" -> "DROP";
"LAN1_IN" -> "RETURN";
"VYATTA_FW_IN_HOOK" -> "WAN_IN";
"VYATTA_FW_IN_HOOK" -> "LAN1_IN";
"VYATTA_FW_LOCAL_HOOK" -> "WAN_LOCAL";
"VYATTA_FW_OUT_HOOK" -> "WAN_OUT";
"VYATTA_POST_FW_FWD_HOOK" -> "ACCEPT";
"VYATTA_POST_FW_IN_HOOK" -> "ACCEPT";
"VYATTA_POST_FW_OUT_HOOK" -> "ACCEPT";
"WAN_IN" -> "RETURN";
"WAN_IN" -> "LOG";
"WAN_IN" -> "DROP";
"WAN_LOCAL" -> "RETURN";
"WAN_LOCAL" -> "LOG";
"WAN_LOCAL" -> "DROP";
"WAN_OUT" -> "RETURN";
"WAN_OUT" -> "LOG";
"WAN_OUT" -> "DROP";
}

...次の図になります。大きいバージョンを表示するには、1回クリックしてください。ブラウザが自動縮小する場合は、もう一度クリックします。

iptables chain targets

1
Jeff Schaller