Junkie Work

access: 202835

FreeBSD 14.3-RELEASEにてファイアウォールとNAT(pf)


インストール

最近のFreeBSDには標準で組み込まれていますのでパッケージ等は不要です。

/etc/rc.conf で有効にします。(コマンドで自動的に編集してくれます)

# service pf enable
# service pflog enable

設定

今回は、以下の内容を設定します。

フィルタの設定を /etc/pf.conf に記載します。

# -------------------------------------------------------------------
# Macros: Interface assignments
# -------------------------------------------------------------------
int_if = "{em0,wg0}"        # Internal interfaces (LAN and WireGuard)
nat_if = "ng0"              # Interface for NAT operations
ext_if = "ng0"              # External-facing interface (WAN)
lo_if  = "lo0"              # Loopback interface

# -------------------------------------------------------------------
# Service port definitions
# -------------------------------------------------------------------
tcp_services = "{ ssh, http, https, dhcpv6-client }"
udp_services = "{ https, 51820 }"
dhcpv6_ports  = "{ dhcpv6-client, dhcpv6-server }"
netbios_ports = "{ 135, 137, 138, 139, 445, 1433 }"
icmp_in_types = "{ echoreq, unreach, squench, timex }"
icmp_out_types = "{ echorep, unreach, squench, timex }"
icmp6_types = "{
                unreach, toobig, timex, paramprob, echoreq, echorep,
                neighbradv, neighbrsol, routeradv, routersol
}"

# -------------------------------------------------------------------
# Reserved and private address blocks (RFC1918, etc.)
# -------------------------------------------------------------------
table <no_inet_addr> const { \
  127.0.0.0/8, 169.254.0.0/16, \
  192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 \
}

table <no_inet6_addr> const { fc00::/7, fe80::/10 }

# -------------------------------------------------------------------
# Internal network segments
# -------------------------------------------------------------------
table <in_net> { 10.0.0.0/16, 192.168.0.0/24 }    # Local address

# -------------------------------------------------------------------
# PF options
# -------------------------------------------------------------------
set block-policy return     # Return TCP RST or ICMP unreachable

# -------------------------------------------------------------------
# Scrubbing: normalize packets
# -------------------------------------------------------------------
scrub in all
scrub out on $ext_if all random-id max-mss 1414

# -------------------------------------------------------------------
# NAT: outbound translations
# -------------------------------------------------------------------
nat on $nat_if from <in_net> to ! <private> -> ($nat_if)

# -------------------------------------------------------------------
# Filtering Rules
# -------------------------------------------------------------------

# Load blacklistd anchor rules
anchor "blacklistd/*" in on $ext_if

# Default deny policy
block log all
pass quick on $lo_if all
pass quick on $int_if from <in_net> to any

# IPv6 internal traffic
pass in quick on $int_if inet6 all
pass out quick on $int_if inet6 all
pass out quick on $ext_if inet6 all

pass quick on $ext_if inet6 proto udp \
     from fe80::/10 to fe80::/10 port $dhcpv6_ports

# Block spoofed source addresses (reserved/private ranges)
block drop in log quick on $ext_if from <no_inet_addr> to any
block drop out log quick on $ext_if from any to <no_inet_addr>
block drop in log quick on $ext_if inet6 from <no_inet6_addr> to any
block drop out log quick on $ext_if inet6 from any to <no_inet6_addr>

# Load modular block rules (e.g., countries, AP filters) 
anchor "block/*" in on $ext_if 

# Block IDENT queries
block quick inet proto tcp from any to any port = ident

# NetBIOS: block legacy SMB services traffic
block log quick on $ext_if proto {tcp, udp} from any to any port $netbios_ports
block log quick on $ext_if proto {tcp, udp} to any port $netbios_ports

# -------------------------------------------------------------------
# Inbound service access (stateful)
# -------------------------------------------------------------------
pass in quick on $ext_if inet proto tcp from any to any \
     port $tcp_services flags S/SA modulate state

pass in quick on $ext_if inet proto udp from any to any \
     port $udp_services keep state

pass in quick on $ext_if inet proto tcp from any to any \
     port 49152 >< 65535 flags S/SA modulate state

# -------------------------------------------------------------------
# Multicast handling (for protocols like OSPF)
# -------------------------------------------------------------------
pass in quick from any to 224.0.0.0/8 allow-opts
pass out quick from any to 224.0.0.0/8 allow-opts

# -------------------------------------------------------------------
# ICMPv4 traffic
# -------------------------------------------------------------------
pass in quick inet proto icmp all icmp-type $icmp_in_types keep state
pass out quick inet proto icmp all icmp-type $icmp_out_types

# -------------------------------------------------------------------
# Outbound IPv4 traffic
# -------------------------------------------------------------------
pass out quick on $ext_if proto tcp all modulate state flags S/SA
pass out quick on $ext_if proto {udp, icmp} all keep state

# -------------------------------------------------------------------
# IPv6 traffic handling
# -------------------------------------------------------------------
pass inet6 proto ipv6-frag keep state
pass in quick inet6 proto icmp6 all \
     icmp6-type $icmp6_types allow-opts keep state

# Allow incoming IPv6 TCP services
pass in quick on $ext_if inet6 proto tcp from any to any \
     port $tcp_services flags S/SA modulate state

pass in quick on $ext_if inet6 proto udp from any to any \
     port $udp_services keep state

# Allow high ephemeral ports for IPv6
pass in quick on $ext_if inet6 proto tcp from any to any \
     port 49152 >< 65535 flags S/SA modulate state

pass out quick on $ext_if inet6 proto {tcp,udp,icmp6} all keep state

blacklistdの設定

blacklistdは標準でFreeBSDに組み込まれています。

/etc/rc.confに以下の行を追加します。

sshd_enable="YES"
sshd_flags="-o UseBlacklist=yes"
blacklistd_enable="YES"
blacklistd_flags="-f"

必要なら、/etc/blacklistd.conf をポリシーに合うように編集します。

blacklistdを起動します。

# service blacklistd start

国別フィルタの設定

国別フィルタのディレクトリを生成します。

# mkdir -p /etc/pf.block
# cd /etc/pf.block

/etc/pf.block/update_blocktable.sh を以下の内容で生成します。

https://www.ipdeny.com/ipblocks/data/countriesから国別のIPアドレスを取得して、リストを更新するスクリプトです。

#!/bin/sh

PFCTL="/sbin/pfctl"
ANCHOR_FILE="/etc/pf.block/countries.conf"
ZONE_TMPDIR="/var/tmp/pf.zone.$$"
COUNTRY_FILE="/etc/pf.block/countries"
BASE_URL="https://www.ipdeny.com/ipblocks/data/countries"
EXT_IF="ng0"
TABLE_NAME="country_block"

: > "$ANCHOR_FILE"
echo "# Auto-generated PF anchor for $TABLE_NAME" >> "$ANCHOR_FILE"
echo "" >> "$ANCHOR_FILE"
echo "table <${TABLE_NAME}> const {" >> "$ANCHOR_FILE"

[ -f "$COUNTRY_FILE" ] || {
  echo "Missing country file: $COUNTRY_FILE" >&2
  exit 1
}

mkdir -p "$ZONE_TMPDIR"

while read CODE; do
  case "$CODE" in ""|\#*) continue ;; esac
  ISO_LOWER=$(echo "$CODE" | tr 'A-Z' 'a-z')
  ZONE_URL="${BASE_URL}/${ISO_LOWER}.zone"
  ZONE_FILE="${ZONE_TMPDIR}/${ISO_LOWER}.zone"

  if fetch -q -o "$ZONE_FILE" "$ZONE_URL"; then
    cat "$ZONE_FILE" >> "$ANCHOR_FILE"
  else
    logger "Zone missing or fetch failed: $CODE"
  fi
done < "$COUNTRY_FILE"

rm -rf "$ZONE_TMPDIR"

echo "}" >> "$ANCHOR_FILE"
echo "block in log quick on ${EXT_IF} from <${TABLE_NAME}> to any" >> "$ANCHOR_FILE"

if ${PFCTL} -n -a block/countries -f "$ANCHOR_FILE"; then
  ${PFCTL} -a block/countries -f "$ANCHOR_FILE"
  logger "PF anchor reloaded: block/countries"
else
  echo "Syntax error in anchor file. Reload skipped." >&2
  exit 1
fi

実行権限を付与します。

# chmod +x /etc/pf.block/update_countory.sh

/etc/pf.block/countriesを以下の内容で生成します。
このファイルは、国コードを1行ずつ記載します。(cnは中国、kpは北朝鮮)

cn
kp

/etc/pf.block/update_countory.sh を実行して、国別フィルタのファイルを生成します。

# /etc/pf.block/update_countory.sh
うまく動くようなら、/etc/pf.block/update_blocktable.sh を定期的に実行するように cron に設定します。

cronの設定例は以下の通りです。

# crontab -e

以下の行を追加します。(毎週日曜日の午後8時に更新します)

0 20 * * 0 /etc/pf.block/update_blocktable.sh >/dev/null 2>&1

設定の読み込みまたは起動

pfが有効になっていない場合は、以下のコマンドで起動します。
# service pf start

pfがすでに有効になっている場合は、以下のコマンドで設定を読み込みます。

# pfctl -f /etc/pf.conf