ラズパイで簡単なNATコンバータを使ってiptablesコマンドからIoTファイアウォールの理解を深める  
※ 当ページには【広告/PR】を含む場合があります。
2022/12/11

遡ること3年も前になりますが、
NASAのような国家機密級の技術情報を外部に漏洩させないためのセキュリティー部門は当然最高レベルに堅牢でなくてはいけませんが、組織の内部から見ると、ITセキュリティー技術に疎い研究者やスタッフが何気なく接続してしまったラズパイを外部のハッカーから踏み台にされてしまったのが問題のようです。
近年では、企業や研究機関の試作や製品の一部にもラズパイが採用されるケースも増えてきているので、裏を返すと、開発エンジニアにも必要最低限の「IoTセキュリティー」の理解が必要になっています。
今回は、以前説明した
おさらい〜ラズパイで簡単なNATコンバータを構成する
Dockerネットワークなどもそうですが、2つ以上のローカルネットワークを超えるにしてもファイアウォールのルールの設置は結構面倒です。
ただ、ブロードバンドルーターを一つ越えてインターネットに接続する単純な設定ならさほど小難しいことを考える必要はありません。
            1. DHCPサーバー(ここではisc-dhcp-server)をセットアップする
2. ラズパイ側のIPアドレスを固定する(etc/dhcpcd.conf)
3. DNSレゾルバー(ここではdnsmasq)をセットアップする
4. IPv4のパケット転送を有効にする(/etc/sysctl.conf)
5. ファイアウォールにルールを追加する(iptables)
        という主に5つの手順を抑えることでラズパイを簡単にNATコンバーターにすることができます。
isc-dhcp-server(DHCPサーバー)のセットアップ
まずはラズパイをWiFi無線をインターネット側に接続した状態でのネットワークデバイスの構成から確認してみます。
            $ ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether 11:11:11:22:22:22 brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 11:11:11:33:33:33 brd ff:ff:ff:ff:ff:ff
    inet 192.168.x.y/24 brd 192.168.x.z scope global noprefixroute wlan0
       valid_lft forever preferred_lft forever
    inet6 2001:1111:1111:1111:2222:2222:2222:2222/64 scope global dynamic mngtmpaddr noprefixroute 
       valid_lft 296sec preferred_lft 296sec
    inet6 fe80:1111:1111:1111:2222:2222/64 scope link 
       valid_lft forever preferred_lft forever
        ラズパイ3/4には
lo(ローカルホスト=127.0.0.1)wlan0(WiFiネットワークデバイス)eth0(有線LANデバイス)ネットワークルーティングの状態も確認すると
            $ ip route show
default via 192.168.x.1 dev wlan0 src 192.168.x.y metric 303 
192.168.x.0/24 dev wlan0 proto dhcp scope link src 192.168.x.y metric 303 
        となっています。
このラズパイの
eth0つまりこの構成だと適切に
DHCPサーバー初心者からベテランまで幅広く使えるDebian系OSの定番DHCPサーバーとしては
先に
isc-dhcp-server            $ sudo apt update
$ sudo apt upgrade -y
$ sudo apt install -y isc-dhcp-server
        ではisc-dhcp-serverをインストールしたらDHCPサーバーから下位のネットワークに接続したデバイスに配布するローカルアドレスを
/etc/dhcp/dhcpd.confとりあえず今回はさほど接続するデバイスも多くないので192.168.x.11から192.168.x.20までの範囲でアドレスの割当をしてみます。
なお伏せ字の
x = ...            #...中略
#👇DNSレゾルバーはdnsmasqにお任せするため、以下はコメントアウト
#option domain-name "example.org";
#option domain-name-servers ns1.example.org, ns2.example.org;
#...中略
#👇デフォルトのDHCPサーバー以外を利用するためコメントアウト
authoritative;
#👇追記
subnet 192.168.x.0 netmask 255.255.255.0 {
    range 192.168.x.11 192.168.x.20;
    option routers 192.168.x.1;
    option domain-name-servers 192.168.x.1;
    option broadcast-address 192.168.x.255;
    ignore declines;
}
        DHCPサーバーは
eth0            #...
#👇eth0を追加
INTERFACESv4="eth0"
#👇IPv6は今回利用しない
#INTERFACESv6=""
#...
        では、
isc-dhcp-server            $ sudo systemctl enable isc-dhcp-server
isc-dhcp-server.service is not a native service, redirecting to systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable isc-dhcp-server
        ラズパイを再起動して、
isc-dhcp-server            $ systemctl status isc-dhcp-server
● isc-dhcp-server.service - LSB: DHCP server
   Loaded: loaded (/etc/init.d/isc-dhcp-server; generated)
   Active: failed (Result: exit-code) since Thu 2022-12-08 12:00:48 JST; 35s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 536 ExecStart=/etc/init.d/isc-dhcp-server start (code=exited, status=1/FAILURE)
#...
        この時点では、おそらくまだ設定が足りていないのでisc-dhcp-serverは起動でコテてしまうはずです。
ルーター(ラズパイ)側のIPアドレスを固定する
そもそもラズパイにルーターのデフォルゲートウェイとなる起点のIPアドレス(
192.168.***.1/etc/dhcpcd.conf            #...
#👇以下を追加
interface eth0
static ip_address=192.168.x.1/24
static routers=0.0.0.0
static domain_name_servers=0.0.0.0
#...
        ここで
routersdomain_name_servers0.0.0.0wlan0dnsmasq(DNSレゾルバー)のセットアップ
まだDNSレゾルバが正しく設定されていないので、おそらく下位デバイスを接続しても正しくIPアドレスが付与されない状態だと思います。
ということで、
dnsmasq            $ sudo apt install -y dnsmasq
        こちらはラズパイを再起動してもすぐに正常に立ち上がります。
            $ systemctl status dnsmasq
● dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
   Loaded: loaded (/lib/systemd/system/dnsmasq.service; enabled; vendor preset: 
#...
        IPv4のパケット転送を有効にする
忘れてはいけないのが、
/etc/sysctl.conf            net.ipv4.ip_forward=1
        これでラズパイを介して、IPv4が異なるネットワーク間で転送可能になります。
ここまで、NATコンバーターの設定はおおよそ完成ですが、
ここで、一旦ラズパイの電源をOFFにして、適当なPCをeth0にイーサネット接続してから、再度ラズパイの電源をONにしてから次の
ちなみに、有線LANできるマシーンが1台しかない状況ならば、イーサネット接続で繋いだクライアントからSSH接続により、ラズパイ側を設定するほうが便利かも知れません。
iptablesからNATコンバーターのルールを追加する
ラズパイの起動後、問題なければ下のような感じでDHCPサーバーが立ち上がっていると思います。
            $ systemctl status isc-dhcp-server
● isc-dhcp-server.service - LSB: DHCP server
   Loaded: loaded (/etc/init.d/isc-dhcp-server; generated)
   Active: active (running) since Thu 2022-12-08 14:04:28 JST; 8min ago
     Docs: man:systemd-sysv-generator(8)
  Process: 585 ExecStart=/etc/init.d/isc-dhcp-server start (code=exited, status=
    Tasks: 1 (limit: 2178)
   CGroup: /system.slice/isc-dhcp-server.service
           └─644 /usr/sbin/dhcpd -4 -q -cf /etc/dhcp/dhcpd.conf eth0
#...
        ラズパイ側のネットワークインターフェースの状態も確認すると、
            $ ip address show
#...
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    #...
    ☆ inet 192.168.xxx.1/24 brd 192.168.xxx.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    #...
        という感じで(☆)のアドレスがeth0に固定されていることが分かります。
ルーティング経路も確認してみますと、
            $ ip route show
default via 192.168.zzz.1 dev wlan0 src 192.168.zzz.250 metric 303 
#...
192.168.xxx.0/24 dev eth0 proto dhcp scope link src 192.168.xxx.1 metric 202 
        というように、
eth0192.168.xxx.0/24ちなみにラズパイに接続したマシーンからもネットワークインターフェースの状態を確認すると、
            $ ip address show
#...
2: enp0s25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    #...
    ☆inet 192.168.xxx.11/24 brd 192.168.xxx.255 scope global dynamic noprefixroute enp0s25
       valid_lft 497sec preferred_lft 497sec
#...
        ☆の箇所にようにラズパイルーターのDHCP機能で割り当てられた
192.168.xxx.11よし、これでラズパイを介してインターネット使える!...かと思いきや、ファイアウォールを何も設定していないため、ラズパイとしては通信をどう扱っていいものかルールがないので何もしてはくれません。
            $ sudo iptables -vnL --line-numbers
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination     
        つまり、まだインターネット側には繋がっていない状態です。
ちなみに、eth0越し直接接続されている下位のクライアントデバイスからの
ping            $ ping -c 4 192.168.xxx.1
PING 192.168.xxx.1 (192.168.xxx.1) 56(84) bytes of data.
64 bytes from 192.168.xxx.1: icmp_seq=1 ttl=64 time=0.569 ms
64 bytes from 192.168.xxx.1: icmp_seq=2 ttl=64 time=0.414 ms
64 bytes from 192.168.xxx.1: icmp_seq=3 ttl=64 time=0.343 ms
64 bytes from 192.168.xxx.1: icmp_seq=4 ttl=64 time=0.328 ms
--- 192.168.xxx.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3061ms
rtt min/avg/max/mdev = 0.328/0.413/0.569/0.095 ms
        pingだけでなく、SSHやFTPなども同一ネットワークではファイアウォールの影響は受けません。
基本的にファイアウォールは異なるネットワーク間でのパケットの通信経路を制御するための仕組みですので、同一ネットワーク間での通信は制限されることは無いようです。
ではインターネットが繋がらない状態から、iptablesでファイアウォールに以下のルールを一つ追加しましょう。
            $ sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
        POSTROUTINGのチェーンにルールがちゃんと追加されているか確認するには以下のコマンドです。
            $ sudo iptables -t nat -vnL POSTROUTING
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
   24  2675 MASQUERADE  all  --  *      wlan0   0.0.0.0/0            0.0.0.0/0 
        では、クライアント側のコンソールで、外部のネットワークにアクセスできるか確かめてみましょう。
            $ ping -c 4 www.google.com
PING www.google.com (xxx.xxx.xxx.xxx) 56(84) bytes of data.
64 bytes from n********.*****0.net (xxx.xxx.xxx.xxx): icmp_seq=1 ttl=110 time=50.8 ms
64 bytes from n********.*****0.net (xxx.xxx.xxx.xxx): icmp_seq=2 ttl=110 time=62.3 ms
64 bytes from n********.*****0.net (xxx.xxx.xxx.xxx): icmp_seq=3 ttl=110 time=60.1 ms
64 bytes from n********.*****0.net (xxx.xxx.xxx.xxx): icmp_seq=4 ttl=110 time=59.0 ms
--- www.google.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 50.821/58.041/62.262/4.333 ms
        今度はちゃんとインターネットに接続されています。
ただしこのルールだけでは、
なお、iptablesのルールはラズパイの電源を落とすと消えてします。 永続化したい場合には、
rc.localちょっと脇道〜ラズパイ・ルーターでIPv6は取り扱えるの?
今回のお話は
なのでラズパイに構築したローカルネットワークをIPv6にも対応させたければ、
DHCPサーバーへのIPv6対応はIPv4ほど単純ではありません。
最近のブロードバンドルーターはほとんどがIPv6にも対応しているものがほとんどかと思いますので、そこにラズパイを接続しているならば、ラズパイ自体には
IPv6なのでラズパイ自体は、問題なくIPv6が利用出来ているはずです。
            $ ping6 -c 4 www.google.com
PING www.google.com(n*******.*******0.net (2404:xxxx:xxxx:xxxx:xxxx:xxxx)) 56 data bytes
64 bytes from n*******.*******0.net (2404:xxxx:xxxx:xxxx:xxxx:xxxx): icmp_seq=1 ttl=113 time=48.3 ms
64 bytes from n*******.*******0.net (2404:xxxx:xxxx:xxxx:xxxx:xxxx): icmp_seq=2 ttl=113 time=57.7 ms
64 bytes from n*******.*******0.net (2404:xxxx:xxxx:xxxx:xxxx:xxxx): icmp_seq=3 ttl=113 time=55.8 ms
64 bytes from n*******.*******0.net (2404:xxxx:xxxx:xxxx:xxxx:xxxx): icmp_seq=4 ttl=113 time=53.8 ms
--- www.google.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 9ms
rtt min/avg/max/mdev = 48.274/53.878/57.669/3.517 ms
        しかし、ラズパイの下位側に接続したデバイスからping6してみると、
            $ ping6 -c 4 www.google.com
ping6: connect: ネットワークに届きません
        ということが確認できると思います。
それもそのはずで、
            $ ip address show
...
2: enp0s25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    #👇無効なipv6
    inet6 fe80::xxxx:xxxx:xxxx:xxxx/64 scope link noprefixroute
        つまり、下位デバイスのインターフェイスをIPv6対応させるには、ルーター側のDHCPサーバーからIPv6を接続したデバイスに割り当てられるようにする必要があります。
IPv6はIPv4ほど単純なものではなく、DHCPサーバーを設定するにはIPv6の通信仕様自体にもよく理解する必要があります。
面倒なことをする気力が起きない方は、ラズパイ自体も高価ですので、それなら「
以降、余裕があればIPv6のDHCPサーバーの立ち上げ方法もこのブログで解説してみたいと思います。
ラズパイNATコンバータでiptablesコマンドを利用したファイアウォールを理解する
ここからはラズパイの単なるNATコンバーター化の内容をもっと発展させて、外部のネットワークからラズパイ側のネットワークに送られてくる通信に「もう人手間加えた」制限をかけることを念頭に、IoT機器のファイアウォールの設計例などをいくつか試してみましょう。
iptables-persistentでファイアウォールの設定を管理する
いきなり脇道に逸れますが、もっとiptablesのルールの管理をしやすくするために、
iptablesコマンドから打ち込んだ設定は次のサーバー起動時には消えてしまうため、ファイアウォールの設定を永続化したいなら、
rc.localただこれだと後々になってファイアウォールのルールが複雑化するほど、自分でrc.localのファイルの中身を細かく管理するのが大変です。
そこでiptablesに熟れてきたら、
            $ sudo apt install iptables-persistent -y
        インストールの際に、IPv4とIPv6の設定を保存するかを個別に聞いてくるので、利用状況に併せて設定を選択します。


iptables-persistentは自動で現在のルールを保存してくれないので、iptablesコマンドを使って構築したルールに変更を加えた場合には、忘れずに以下のコマンドを実行しましょう。
            $ sudo netfilter-persistent save
#👇IPv4用Netfilterルール
run-parts: executing /usr/share/netfilter-persistent/plugins.d/15-ip4tables save
#👇IPv6用Netfilterルール
run-parts: executing /usr/share/netfilter-persistent/plugins.d/25-ip6tables save
        なお、元々は
iptables-persistentnetfilter-persistentiptables-persistent既存のルールを一旦すべてクリアしたい場合には、
            $ sudo netfilter-persistent flush
        を利用します。
もしも誤って
netfilter-persistent flush            $ sudo netfilter-persistent reload
        としてあげると、前回の
netfilter-persistent saveDebianOS系では、iptables-persistentをインストールするとsystemdに登録されて、OS起動時に自動で
netfilter-persistent start起動時のサービスデーモンに頼らず、手動でnetfilterルールをリストアする場合には、
            $ sudo netfilter-persistent start
        とすると、netfilter-persistentサービスが起動します。
またサービスの停止したい時には、
            $ sudo netfilter-persistent stop
        を使います。
異なるネットワークが見えるように静的ルーティングを設定する
自分で独自に作成したネットワークは、そのままだと他のネットワークからは認識されません。 そこで、異なるネットワーク間を互いに見えるようにするために
例えば
192.168.1.0192.168.1.123192.168.2.0静的ルーティングラズパイルーター・NATコンバーターのwlan0インターフェースが
192.168.1.124192.168.2.0/24192.168.1.123            $ sudo ip route add 192.168.2.0/24 via 192.168.1.124
        静的ルーティングがきちんと追加されたか確認するには、
ip route shownetstat -nr            $ netstat -nr
カーネルIP経路テーブル
受信先サイト    ゲートウェイ    ネットマスク   フラグ   MSS Window  irtt インタフェース
0.0.0.0         192.168.1.1   0.0.0.0         UG        0 0          0 wlan0
192.168.1.0   0.0.0.0         255.255.255.0   U         0 0          0 wlan0
☆192.168.2.0   192.168.1.124 255.255.255.0   UG        0 0          0 wlan0
        ☆のルーティングが追加されたので、クライアント・
192.168.1.123192.168.2.0            $ ping -c 4 192.168.2.1
PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
64 bytes from 192.168.2.1: icmp_seq=1 ttl=64 time=5.04 ms
64 bytes from 192.168.2.1: icmp_seq=2 ttl=64 time=3.13 ms
64 bytes from 192.168.2.1: icmp_seq=3 ttl=64 time=3.45 ms
64 bytes from 192.168.2.1: icmp_seq=4 ttl=64 time=2.97 ms
--- 192.168.2.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 2.966/3.647/5.040/0.824 ms
        ただし、
ip-route/etc/network/if-up.dstatic-routes            #!/bin/sh
ip route add 192.168.2.0/24 via 192.168.1.124
        として実行権限を与えます。
            $ sudo chmod +x /etc/network/if-up.d/static-routes
        これでOS起動時に自動実行され、静的ルーティングが永続化できます。
拡張ターゲット・「NETMAP」でアクセス可能なIPアドレス値で制限する
アクセス権限のあるユーザーのIPアドレスが静的に判別できる場合、個別にそのIPを許可するような「ホワイトリスト」をファイアウォールとして明示に指定するのが、シンプルかつセキュアな方法です。
個別のIPアドレスをピンポイントでフィルタするなら、一見
SNATSNAT例えば、外部のローカルネットワーク(
192.168.1.0192.168.1.123この
192.168.1.123192.168.2.0--to-source192.168.2.123            $ sudo iptables -t nat -A POSTROUTING \
    -o eth0 \
    -s 192.168.1.123 \
    -j SNAT --to-source 192.168.2.123
        これで
192.168.1.0192.168.2.0一見これはUDPやICMP通信(1Way)だと上手くいくのですが、WebサーバーなどのTCP(つまり3Wayハンドシェイク)では通信の確立シグナルを返せないので接続がおかしいことになります。
この場合、TCP通信でも対応させたいなら、PREROUTINGかOUTPUTチェーンに
DNAT            $ sudo iptables -t nat -A PREROUTING \
   -s 192.168.1.123 \
   -j DNAT --to-destination 192.168.2.123
        ただし、パケットの送り先となるクライアントの数が多くなると、DNATではパケット発信元IPアドレスを指定して一つ一つ変換後のIPアドレスを指定しないといけなくなるのがとても苦痛です。
個別のクライアントではなく、もう少し範囲を広げて特定のネットワークを全て許可したいときに使えるのが、
NETMAPつまり
NETMAP192.168.1.0192.168.1.123192.168.2.0192.168.2.123            $ sudo iptables -t nat -A PREROUTING \
   -s 192.168.1.0/24 \
   -j NETMAP --to 192.168.2.0/24
        これだけで、192.168.2.0ネットワークが根こそぎ172.26.16.0ネットワークへと一対一変換されるようになります。
拡張マッチングモジュール・macでアクセス可能なMACアドレス値を制限する
主題のようにラズパイに代表されるIoT機器はハードウェアですので、対象となるネットワークインターフェース1台につき最低1つの
先ほどのように、固有のIPアドレスを持たない場合、MACアドレスを使ったファイアウォールというのも、場合によっては必要になってくるかもしれません。
iptablesiptables-extensionsman            $ man iptables-extensions
        日本語化されたマニュアルを参照したい場合には、例えば
この拡張マッチングモジュールは、適用先の対象チェーンの種類は個別に異なり、例えばルールを追加したい場合、
            $ iptables -A [適用チェーン] \
   -m [適用モジュール] [モジュールの固有オプション] [オプションのターゲット]
        という感じで使います。
macINPUT各ネットワークインターフェースごとにMACアドレスが存在し、
link/ether            $ ip addr | grep ether
    #...
    link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff
    #...
        ここでは、送信元MACアドレスが00:11:22:33:44:55のパケットのみを通過させる場合、
            $ sudo iptables -A INPUT \
    -m mac ! --mac-source 00:11:22:33:44:55 \
    -j DROP
$ sudo iptables -nvL INPUT --line-numbers
Chain INPUT (policy ACCEPT 291 packets, 34123 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        5   300 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            MAC ! 00:11:22:33:44:55
        すると、MACアドレス・
00:11:22:33:44:55まとめ
以上、ホームネットワークで使えるラズパイをルーター・NATコンバーターにして使う際のiptablesを利用したファイアウォールに関して解説してきました。
iptablesのターゲットやモジュールはかなり種類があり、アイディア次第でここでは紹介しきれないくらい複雑なルールでNAT間をフィルタリングする条件が存在すると思います。
お使いのネットワーク環境の状態によっても、最適なファイアウォールのルールが違ってきますので、ここではあくまでも参考とお考えください。