この記事は、木更津高専 Advent Calender 2023の9日目の記事です。
前→距離減衰式で長周期地震動階級を推定してみた by @maikuradentetu
次→RustでDNAT設定いじるサイトを作った by @toma09to
動機
私は、Raspberry Piを使って自宅サーバーを公開していましたが、自宅の他のネットワーク機器と切り離されていなかったため、万が一Raspberry Piが乗っ取られた時に、他の機器にも影響しかねない危険な状態でした。
これを解消するために、Raspberry Piを使ってDMZ機能を持つルーターを構築することにしました。
あれ?Webサーバーに使うRaspberry Piは何処に?
使用機器
- Raspberry Pi 4 Model B 4GB
- OS: Ubuntu Server 22.04.3 LTS
- UGREEN 20256 × 2
- 光BBユニット
NICの購入ページ(Amazon)はこちら
ネットワーク構成
今回は、下図のようなネットワークを作成します。元のルーターは残す方針で構築しました。
各ネットワークの概要は以下の通りです。
ネットワーク名 | ネットワークアドレス | 概要 |
---|---|---|
外部ネットワーク | 192.168.0.0/24 | 親ルーターとラズパイ |
DMZ | 192.168.1.0/24 | 公開サーバー等の置き場 |
内部ネットワーク | 192.168.3.0/24 | 自宅のネットワーク機器の所属先 |
親ルーターの設定
ネットワークアドレスを192.168.0.0/24
に変更し、ルーターのIPアドレスを192.168.0.1
とします。
また、ルーターの簡易DMZ機能等の、グローバルIPアドレスへの接続要求を転送する機能を有効にして、転送先アドレスを192.168.0.2
にします。
Raspberry Piの設定
IPアドレスの固定
NICをラズパイに挿すと、enx************
のようなインターフェースが追加されます。
私の環境では、enx207bd26c92bb
を外部ネットワーク、enx207bd27657d3
を内部ネットワーク、eth0
をDMZに割り当てました。以下、各自の環境に合わせて読み替えてください。
今回はnetplanを用いてIPアドレスの設定をします。/etc/netplan/99_config.yaml
を作成して、以下のように編集します。
network:
ethernets:
# external network
enx207bd26c92bb:
dhcp4: false
dhcp6: false
addresses:
- 192.168.0.2/24
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4
routes:
- to: default
via: 192.168.0.1
# DMZ network
eth0:
dhcp4: false
dhcp6: false
addresses:
- 192.168.1.1/24
# internal network
enx207bd27657d3:
dhcp4: false
dhcp6: false
addresses:
- 192.168.3.1/24
version: 2
ファイルを作成出来たら、それを適用します。
$ sudo netplan apply
IPフォワードの有効化
このままでは、NICの間でパケットが転送されないので、IPフォワードを有効にします。
/etc/sysctl.conf
にIPフォワードの設定があるので、その部分をアンコメントします。
# Uncomment the next line to enable packet forwarding for IPv4
- #net.ipv4.ip_forward=1
+ net.ipv4.ip_forward=1
これで、IPフォワードの設定が出来たので、それを適用するために再起動します。
$ sudo reboot
iptablesの設定
iptablesを使って、ファイアウォール等の設定をしていきます。
/etc/iptables.sh
を作成して、そこにルールを記述していきます。
変数の定義とテーブルの初期化
まず、変数と初期化を記述します。GLOBAL_IP
にはあなたのグローバルIPアドレスを入れて、その他の値についても適宜読み替えてください。
#!/bin/bash
# interface names
EXT_INT=enx207bd26c92bb
DMZ_INT=eth0
INT_INT=enx207bd27657d3
# network addresses
EXT_NET=192.168.0.0/24
DMZ_NET=192.168.1.0/24
INT_NET=192.168.3.0/24
# my IP address
PRIVATE_IP=192.168.0.2
GLOBAL_IP=0.0.0.0
# initialize iptables
iptables -F
iptables -t nat -F
iptables -X
ポリシー
ポリシーはどのルールにもマッチしないパケットの処理を決めます。
INPUTとFORWARDはDROP(破棄)、OUTPUTはACCEPT(許可)とするのが安全です。
# policy
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
各種通信の許可
許可された通信のパケットは許可します。
# established packets
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
ループバック(自分自身)からのパケットは許可します。
# loopback
iptables -A INPUT -i lo -j ACCEPT
今回の構成では、インターネットからpingが飛んでくることは(多分)ないので、利便性のためにpingは許可します。
# ping
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT
内部ネットワークからのSSHを許可します。
# SSH
iptables -A INPUT -s ${INT_NET} -i ${INT_INT} -p tcp --dport 22 -j ACCEPT
フォワード
内部→外部、内部→DMZ、DMZ→外部は無条件でパケットを通します。また、後述するDNATの設定によって許可されたポートへの通信を通すために、外部→DMZも許可します。
さらに、確立された通信のパケットも通します。
# forward
iptables -A FORWARD -s ${INT_NET} -i ${INT_INT} -o ${EXT_INT} -j ACCEPT
iptables -A FORWARD -s ${INT_NET} -i ${INT_INT} -o ${DMZ_INT} -j ACCEPT
iptables -A FORWARD -s ${DMZ_NET} -i ${DMZ_INT} -o ${EXT_INT} -j ACCEPT
iptables -A FORWARD -i ${EXT_INT} -o ${DMZ_INT} -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
SNAT
インターネットへの通信が親ルーターに届くようにSNATを設定します。
# SNAT
iptables -t nat -A POSTROUTING -o ${EXT_INT} -j MASQUERADE
DNAT
外部からの特定ポートへの通信がDMZへ届くようにDNATを設定します。これは、宛先アドレスをルーターから他のアドレスに書き換える機能で、今回はDMZ内の機器のアドレスを指定します。
私の環境では、192.168.1.10
にWebサーバーがあるので、ここにHTTPとHTTPSを通します。
また、通常はローカルからサーバーへグローバルIPを使ってアクセスすることが出来ないので、ここで設定します。
ポート番号と送信先アドレスを変更すれば他の通信も許可できます。
# DNAT
iptables -t nat -A PREROUTING -p tcp -d ${PRIVATE_IP} --dport 80 -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING -p tcp -d ${PRIVATE_IP} --dport 443 -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING -i ${INT_INT} -p tcp -d ${GLOBAL_IP} --dport 80 -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING -i ${INT_INT} -p tcp -d ${GLOBAL_IP} --dport 443 -j DNAT --to-destination 192.168.1.10
ログ
破棄されたパケットを記録します。
# logging
iptables -N LOGGING
iptables -A LOGGING -m limit --limit 1/s -j LOG --log-level warning --log-prefix "DROP: "
iptables -A LOGGING -j DROP
iptables -A INPUT -j LOGGING
iptables -A FORWARD -j LOGGING
設定の適用
完成したファイルは以下のようになります。
#!/bin/bash
# interface names
EXT_INT=enx207bd26c92bb
DMZ_INT=eth0
INT_INT=enx207bd27657d3
# network addresses
EXT_NET=192.168.0.0/24
DMZ_NET=192.168.1.0/24
INT_NET=192.168.3.0/24
# my IP address
GLOBAL_IP=0.0.0.0
# initialize iptables
iptables -F
iptables -t nat -F
iptables -X
# policy
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# established packets
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# ping
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT
# SSH
iptables -A INPUT -s ${INT_NET} -i ${INT_INT} -p tcp --dport 22 -j ACCEPT
# forward
iptables -A FORWARD -s ${INT_NET} -i ${INT_INT} -o ${EXT_INT} -j ACCEPT
iptables -A FORWARD -s ${INT_NET} -i ${INT_INT} -o ${DMZ_INT} -j ACCEPT
iptables -A FORWARD -s ${DMZ_NET} -i ${DMZ_INT} -o ${EXT_INT} -j ACCEPT
iptables -A FORWARD -i ${EXT_INT} -o ${DMZ_INT} -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# SNAT
iptables -t nat -A POSTROUTING -o ${EXT_INT} -j MASQUERADE
# DNAT
iptables -t nat -A PREROUTING -p tcp -d ${PRIVATE_IP} --dport 80 -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING -p tcp -d ${PRIVATE_IP} --dport 443 -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING -i ${INT_INT} -p tcp -d ${GLOBAL_IP} --dport 80 -j DNAT --to-destination 192.168.1.10
iptables -t nat -A PREROUTING -i ${INT_INT} -p tcp -d ${GLOBAL_IP} --dport 443 -j DNAT --to-destination 192.168.1.10
# logging
iptables -N LOGGING
iptables -A LOGGING -m limit --limit 1/s -j LOG --log-level warning --log-prefix "DROP: "
iptables -A LOGGING -j DROP
iptables -A INPUT -j LOGGING
iptables -A FORWARD -j LOGGING
このスクリプトを適用するために、実行権限を与えて実行します。
$ sudo chmod +x /etc/iptables.sh
$ sudo /etc/iptables.sh
iptablesのルールの自動適用
iptablesのルールはシャットダウンすると消えてしまいます。そこで、systemdで起動時に先ほど作成したスクリプトを実行させるデーモンを作成します。
/etc/systemd/system
内にauto-iptables.service
というファイルを作成します。
[Unit]
Description=Automatic iptables setup service
After=network.target
[Service]
User=root
ExecStart=/etc/iptables.sh
Type=oneshot
[Install]
WantedBy=multi-user.target
そして、以下のコマンドを実行して、システム起動時にデーモンが起動するようにします。
$ sudo systemctl daemon-reload
$ sudo systemctl enable auto-iptables.service
DHCPの設定
ルーターとしての設定はここまでで終わりなのですが、家族に
内部ネットワークにはDHCPでIPアドレスが自動で降ってくるようにしてほしい
と言われたので、DHCPサーバーの設定もしていきます。
まず、isc-dhcp-server
をインストールします。
$ sudo apt install isc-dhcp-server
次に、設定ファイルである/etc/dhcp/dhcpd.conf
を編集します。
以下の部分をそれぞれコメント、アンコメントします。
# option definitions common to all supported networks...
- option domain-name "example.org";
- option domain-name-servers ns1.example.org, ns2.example.org;
+ #option domain-name "example.org";
+ #option domain-name-servers ns1.example.org, ns2.example.org;
# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
- #authoritative;
+ authoritative;
そして、ファイルの末尾に以下のように追記します。
subnet 192.168.3.0 netmask 255.255.255.0 {
range 192.168.3.2 192.168.3.254;
option routers 192.168.3.1;
option domain-name-servers 8.8.8.8;
option broadcast-address 192.168.3.255;
ignore declines;
}
最後に、サービスを起動し、有効化します。
$ sudo systemctl start isc-dhcp-server.service
$ sudo systemctl enable isc-dhcp-server.service
/var/lib/dhcp/dhcpd.leases
に、取得されたIPアドレス等の情報が記録されます。
あとがき
今回は内容に含みませんでしたが、
- DMZへのアクセスのDoS対策(hashlimit)
- IPv6対応
などのことをこれから実現させていきたいと思っています。
それでは、また明日お会いしましょう!