KUSONEKOの見る世界

CentOS 8 で nftables を設定してみた

CentOS 8インストール後、ずっと後回しにしていたnftablesに挑戦しました。
以前設定していた国内(日本のIP)からのみ許可するsetの作成、VPN通信のTCP SynのMSSサイズを変更する設定も行っています。

nftablesの設定ファイル指定

以下で設定ファイルを指定します。
vi /etc/sysconfig/nftables.conf

/etc配下のinet-filter.nftだけを使用するので、以下のコメントアウトを削除します。
# include "/etc/nftables/inet-filter.nft"
↓
include "/etc/nftables/inet-filter.nft"

nftablesの起動

systemctl start nftables

日本のIPリスト作成

日本のIPレンジの取得から設定までをスクリプトで実施します。
このスクリプトは、IPレンジを更新する際にも使用することを想定しています。
vi nft_AcceptCountry.sh
#!/bin/sh

# IPアドレスリスト作成
IP_LIST=/tmp/cidr.txt
if [ ! -f $IP_LIST ]; then
    wget -q http://nami.jp/ipv4bycc/cidr.txt.gz
    gunzip -c cidr.txt.gz > $IP_LIST
    rm -f cidr.txt.gz
fi

# 既存Accept_Countryをflush
nft flush set inet filter Accept_Country

# 新規Accept_Countryを作成
nft add set inet filter Accept_Country { type ipv4_addr \; flags interval \; }

# /tmp/cidr.txtのJPのアドレスをAccept_Countryに追加
for addr in `cat /tmp/cidr.txt|grep ^JP | awk '{print $2}'`
do
    nft add element inet filter Accept_Country { $addr }
done

# 設定を保存
nft list ruleset > /etc/nftables/inet-filter.nft
sh nft_AcceptCountry.sh
初回は、Accept_Countryのsetが無いため、エラーとなります。
また、全IPレンジを一行ずつ入力するため、少々時間が掛かります。
もっと良いやり方があるかもしれません。

スクリプトは以下のサイトを参考にさせて頂きました。
https://centossrv.com/iptables.shtml

各ルールの設定

input policy

inputにルールを入れていきます。
1行目はサーバ自身のループバック通信の許可。
2行目はローカルネットワークの許可。我が家ではローカルのネットワークとVPNのネットワークをまとめて指定しました。
3行目、4行目は外部に公開しているサービスです。(ポート番号指定でも可)
前項で作成した日本のIPリスト(Accept_Country)からのみ許可します。
5行目は、セッションを張ったTCP通信の許可、
6行目は、DHCPの許可(DHCP DiscoverのソースIPが0.0.0.0であるため)、
7行目は、IPv6で使用するプロトコルを許可します。これが無いとログが沢山でます。
8行目は、拒否した通信のカウンターとログ吐き出しをしています。
nft add rule inet filter input iifname lo accept
nft add rule inet filter input ip saddr 192.168.0.0/16 accept
nft add rule inet filter input ip saddr @Accept_Country udp dport { isakmp, ipsec-nat-t } accept
nft add rule inet filter input ip saddr @Accept_Country tcp dport https accept
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input udp sport { bootps, bootpc } udp dport { bootps, bootpc } accept
nft add rule inet filter input icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
nft add rule inet filter input counter log prefix \"[nftables INPUT DROP] : \"

最後に、input policyのデフォルトをDropに変更します。
ローカルネットワークを指定していないと、SSHが切断され、入れなくなるため注意しましょう。
nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; }

VPN用 policy

strongSwan等のVPNをしている人専用です。
VPN内を通るTCP synのmss値を適切な値に書き換えます。

以前の記事ではIPTABLESでしたので、IPsecを通ってきた or 通っていく通信を判断できたようですが、nftablesでは出来なさそうなので、次のようにしています。

1行目と3行目は、チェインの作成。
2行目は、VPNのIP範囲から来る通信のMSS値の書き換え、
4行目は、VPNのIP範囲への通信のMSS値の書き換えです。
nft add chain inet filter prerouting { type filter hook prerouting priority 0 \; }
nft add rule inet filter prerouting ip saddr 192.168.255.0/24 tcp flags syn tcp option maxseg size set 1334
nft add chain inet filter postrouting { type filter hook postrouting priority 0 \; }
nft add rule inet filter postrouting ip daddr 192.168.255.0/24 tcp flags syn tcp option maxseg size set 1334
また、IPTABLESでは元々のMSS値のマッチングもできましたが、nftablesでは出来なさそうです。
ですが、元々のMSSが、設定するMSSより小さい場合は書き換わらなかったため、動作としては問題ないと思います。

最適MTU/MSSの計算はこちらのページで。

IPsec 備忘録

IPsecパケットのフォーマットと最適MTUの計算方法をまとめ、計算機を作成しました。最適MTUを設定することでVPNが快適になるかもしれません。

設定の確認

以下のコマンドで設定を表示します。
nft list ruleset
前項を設定すると以下のようになるはずです。
table inet filter {
        set Accept_Country {
                type ipv4_addr
                flags interval
                elements = { 1.0.16.0/20, 1.0.64.0/18,
(略)
                             223.252.64.0/19, 223.252.112.0/20 }
        }

        chain input {
                type filter hook input priority 0; policy drop;
                iifname "lo" accept
                ip saddr 192.168.0.0/16 accept
                ip saddr @Accept_Country udp dport { isakmp, ipsec-nat-t } accept
                ip saddr @Accept_Country tcp dport https accept
                ct state established,related accept
                udp sport { bootps, bootpc } udp dport { bootps, bootpc } accept
                icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
                counter packets 5 bytes 248 log prefix "[nftables INPUT DROP] : "
        }

        chain forward {
                type filter hook forward priority 0; policy accept;
        }

        chain output {
                type filter hook output priority 0; policy accept;
        }

        chain prerouting {
                type filter hook prerouting priority 0; policy accept;
                ip saddr 192.168.255.0/24 tcp flags syn tcp option maxseg size set 1334
        }

        chain postrouting {
                type filter hook postrouting priority 0; policy accept;
                ip daddr 192.168.255.0/24 tcp flags syn tcp option maxseg size set 1334
        }
}

問題なければ、設定を保存します。
nft list ruleset > /etc/nftables/inet-filter.nft

一部のテーブルのみを指定する場合は以下の通りです。
nft list table inet filter > /etc/nftables/inet-filter.nft

個人的な感想

なんとかやりたいことが設定できました。
他にもセキュリティ関連の設定ができますが、日本からのトラフィックのみを許可ができたので、まずは良いかと思っています。

nftablesは、IPTABLESよりネットワークエンジニアが理解しやすい構文だと感じました。
nft -iで設定することもできますが、tab補完や構文の候補が見られると使いやすくなるのですが。

追記

ルールの追加 ESP

我が家のVPNは、UDPではなくESPが飛んでくることもあるため、ルールを追加しました。
ルールを途中に挿入するには、挿入したい行の直前のhandle番号を調べ、その番号をコマンドに入れます。

handle番号は以下で確認ができます。
nft list ruleset --handle
または
nft --handle list ruleset
table inet filter {
(略)
        chain input {
                type filter hook input priority 0; policy drop;
                iifname "lo" accept # handle 11
                ip saddr 192.168.0.0/16 accept # handle 12
                ip saddr @Accept_Country udp dport { isakmp, ipsec-nat-t } accept # handle 13
                ip saddr @Accept_Country tcp dport https accept # handle 14
                ct state established,related accept # handle 15
                udp sport { bootps, bootpc } udp dport { bootps, bootpc } accept # handle 16
                icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept # handle 17
                counter packets 907 bytes 42202 log prefix "[nftables INPUT DROP] : " # handle 18
        }
(略)
}

送信元IPが日本で、ESPを許可するルールを挿入します。
nft add rule inet filter input handle 15 ip saddr @Accept_Country ip protocol esp accept

nft list ruleset
table inet filter {
(略)
        chain input {
                type filter hook input priority 0; policy drop;
                iifname "lo" accept
                ip saddr 192.168.0.0/16 accept
                ip saddr @Accept_Country udp dport { isakmp, ipsec-nat-t } accept
                ip saddr @Accept_Country tcp dport https accept
                ct state established,related accept
                ip saddr @Accept_Country ip protocol esp accept
                udp sport { bootps, bootpc } udp dport { bootps, bootpc } accept
                icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
                counter packets 908 bytes 42242 log prefix "[nftables INPUT DROP] : "
        }
(略)
}
指定した番号の直後に挿入されました。
handle 15でなく、position 15 と指定すると直前に挿入となります。

ルールの追加 LLMNR

IPv6 UDP 5535 が沢山ログに出ていたため、追加しました。
これはLLMNRというもので、Windowsで名前解決をする物のようです。
以下のコマンドで追加しました。
nft add rule inet filter input handle 18 udp dport 5355 accept

入れるとhostmonに変わります。
        chain input {
                type filter hook input priority 0; policy drop;
                iifname "lo" accept
                ip saddr 192.168.0.0/16 accept
                ip saddr @Accept_Country udp dport { isakmp, ipsec-nat-t } accept
                ip saddr @Accept_Country tcp dport https accept
                ct state established,related accept
                ip saddr @Accept_Country ip protocol esp accept
                udp sport { bootps, bootpc } udp dport { bootps, bootpc } accept
                icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
                udp dport hostmon accept
                counter packets 4307 bytes 198793 log prefix "[nftables INPUT DROP] : "
        }

ルールの変更 IPv6周り

IPv6 LLMNR を許可したら今度は、IPv6 TCP 445 が出てくるようになりました。
これはWindowsファイル共有の通信で、最初にリンクローカルアドレスで開始し、失敗するとグローバルアドレスでアクセスしようとします。
そのため、リンクローカルアドレスをすべて許可することにました。
LLMNR のルールは、このルールで賄えるため、不要になりました。

順番なども整理し、以下となりました。
chain input {
        type filter hook input priority 0; policy drop;
        iifname "lo" accept
        ct state established,related accept
        ip saddr 192.168.0.0/16 accept
        ip6 saddr fe80::/10 accept
        ip saddr @Accept_Country udp dport { isakmp, ipsec-nat-t } accept
        ip saddr @Accept_Country tcp dport https accept
        ip saddr @Accept_Country ip protocol esp accept
        udp sport { bootps, bootpc } udp dport { bootps, bootpc } accept
        icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
        counter packets 6039 bytes 278960 log prefix "[nftables INPUT DROP] : "
}
SAMBAにIPv6を許可する場合は、smb.conf内にリンクローカルの追加が必要です。
hosts allow = 127.0.0.1 192.168. fe80::/10

ルールの追加 DSCP値書き換え

サーバから送信される特定のポート番号のDSCP値を書き換えます。
この例では、送信元のTCPポート50000をDSCP 48(cs6)にしています。
nft add rule inet filter postrouting tcp sport 50000 ip dscp set cs6
        chain postrouting {
                type filter hook postrouting priority filter; policy accept;
                ip daddr 192.168.255.0/24 tcp flags syn tcp option maxseg size set 1334
                tcp sport 58118 ip dscp set cs6
        }