【PF】macOSでIPアドレスによるアクセス制限を行う

# はじめに
macOSでは、ネットワークへのアクセスを制御する手法として、BSD系のOSで利用されてきた[PF（packet filter）](https://ja.wikipedia.org/wiki/PF_(%E3%83%95%E3%82%A1%E3%82%A4%E3%82%A2%E3%82%A6%E3%82%A9%E3%83%BC%E3%83%AB))が採用されている。この設定をカスタマイズすることで、アクセス制限が可能になる。

:::note warn
筆者はネットワークに関する知識が乏しく、根本的に間違っている箇所があるかもしれませんので、十分に注意して自己責任で設定してください。macOSのPFに関する日本語の情報があまり多くないので、本稿はその一助とするための記事です。有識者の方はぜひより詳しいことを教えて下さい。
:::

:::note info
設定を行った環境：macOS Sequoia 15.4
:::

# PFの設定ファイルを作成する

:::note info
PFのデフォルトの設定ファイルは、 `/etc/pf.conf` である。しかし、このファイルはOSアップデート時などに上書きされる。そこで、本稿では、 `/usr/local/etc/pf.conf` を設定ファイルとする。
:::

</aside>

まず、PFの設定ファイルなどを置くためのディレクトリを作成する。

```bash
$ sudo mkdir -p /usr/local/etc /usr/local/bin
```

設定ファイルとして、 `/usr/local/etc/pf.conf` を以下のように作成する。

:::note warn
`pf.conf` の最後の行には、空白行を入れる必要がある？
:::

```bash:/usr/local/etc/pf.conf
# IPアドレスによるアクセス制限を行うためのPFの設定ファイル
# !!!! このファイルの最後の行は、空白行にする必要がある？ !!!!

# このコンピュータのIPアドレス（適宜変更する）
my_ip = "xxx.xxx.xxx.xxx"

# アクセス可能なIPアドレスリストの例
allowed_ips = "{ yyy.yyy.yyy.yyy, zzz.zzz.zzz.zzz/24 }"

# ループバックは常に許可する（他の設定よりも優先される）
pass quick on lo0 all

# 許可されたIPアドレスから下で設定するポートに対して入力される接続以外のすべてはブロックする
block in all  # ブロックされた接続のログも取るには、 block in log all とする

# 既存の接続は維持する（外向きの接続はそのまま通過させる）
pass out all keep state

# 許可されたIPアドレスからのSSH/SFTP（22番ポート）を許可する
pass in log on en0 proto tcp from $allowed_ips to $my_ip port 22 keep state

# 許可されたIPアドレスからのXXXX番ポートに対する通信を許可する
# pass in log on en0 proto tcp from $allowed_ips to $my_ip port XXXX keep state

```

このファイルの所有者と権限を以下のように設定する。

```bash
$ sudo chown root:wheel /usr/local/etc/pf.conf
$ sudo chmod 644 /usr/local/etc/pf.conf
```

# PFの設定を読み込むためのスクリプトを作成する
次に、OS起動時にこの設定でPFを起動するためのシェルスクリプト（ `/usr/local/bin/load_pf.sh` ）を以下のように作成する。

```bash:/usr/local/bin/load_pf.sh
#!/bin/bash
# pflog0インタフェースを作成する
/sbin/ifconfig pflog0 create

# pfの設定を読み込む
/sbin/pfctl -f /usr/local/etc/pf.conf
# pfを有効化する
/sbin/pfctl -e

# pfのログを保存するためのディレクトリを作成する（存在しない場合）
mkdir -p /var/log/pflog
# pfのログを書き出す（/var/log/pflogにpflog_YYYYmmddHHMMの命名形式のバイナリファイルが作成される）
tcpdump -n -e -w /var/log/pflog/pflog_$(date +%Y%m%d%H%M).log -ttt -i pflog0
```

このスクリプトに実行権限を付与する。

```bash
$ sudo chmod +x /usr/local/bin/load_pf.sh
```

:::note info
BSD系のOSでは、PFのログは `pflog0` というインターフェイスに出力され、これをリッスンしている `pflogd` というデーモンがログファイルを生成する。しかし、macOSで `pflogd` を使用する方法が不明なので、暫定的に `tcpdump` によってログファイルを生成する。
:::

このスクリプトでは、OSが再起動するごとにログファイルが生成され、 `tcpdump` のバイナリフォーマットで書き込まれる。このログファイルを読むときには、 
```bash
$ tcpdump -n -e -ttt -r /var/log/pflog/pflog_*YYYYmmddHHMM*.log
```
というコマンドを使用する。また、例えば22番ポートに対する通信のみを表示するには、 
```bash
$ tcpdump -n -e -ttt -r /var/log/pflog/pflog_*YYYYmmddHHMM*.log port 22
```
のようにする。

前節のPFの設定では、許可された接続のみをログするようにしている。もし、ブロックされた接続も含めてすべてのログを取るには、 `pf.conf` の `block in all` となっている箇所を、 `block in log all` とする。

# OS起動時に自動的に起動させる
macOSでは、デーモンの管理・実行をLaunchDaemons（ `launchd` ）が行っている。したがって、上のスクリプトの実行をLaunchDaemonsに登録すれば、OS起動時に自動的に起動することができる。

このために、以下のような `/Library/LaunchDaemons/local.pf.plist`  を作成して、上のスクリプトの実行を登録する。

```xml:/Library/LaunchDaemons/local.pf.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>local.pf</string>

    <key>ProgramArguments</key>
    <array>
      <string>/usr/local/bin/load_pf.sh</string>
    </array>

    <key>RunAtLoad</key>
    <true/>

    <key>KeepAlive</key>
    <true/>
  </dict>
</plist>
```

このファイルの所有者と権限を以下のように設定する。

```bash
$ sudo chown root:wheel /Library/LaunchDaemons/local.pf.plist
$ sudo chmod 644 /Library/LaunchDaemons/local.pf.plist
```

`local.pf.plist` を、LaunchDaemonsに読み込む。

```bash
$ sudo launchctl load -w /Library/LaunchDaemons/local.pf.plist
```

`local.pf.plist` では、 `RunAtLoad` を `<true/>` としているので、上のコマンドを実行することで、読み込みと同時にプロセスが起動する。