FreeBSD jailの構築、プライベートIP割当とpfによるNAT・ポートフォワーディング

# 対象読者の前提
* 当該マシンにFreeBSDをインストール済み(プライベートIP固定済み、x86_64 CPU)である
* NATとポートフォワーディング(記事ではリダイレクトと記述)についての知識がある
* pkg によるパッケージ管理の知識(install, deleteが分かれば良い)がある
* TCP/IP に於ける各プロトコルの動作レイヤについての知識がある
* 各種仮想化方式のメリット・デメリットを理解し、適宜選択できる程度の知識がある
* 基本乃至応用情報技術者試験合格程度の、基礎的なネットワークの知識がある
* 初心者向けと言いながら前提知識が多すぎる不親切な他記事に辟易している状況である

# はじめに
注釈は参考にした記事のリンクです。これらのうち(必読)とついているものは公式マニュアル・ガイドのうち、運用に当たって特に読むべきと判断したものです。私は読者がこれらを読んでいない前提で説明をしますし、あらゆる行動の「意図」をできるだけ説明します。しかし、曖昧で理解に苦しむことがあれば、公式マニュアルを読まれることをお勧め致します。

#### 今回作るもの
例はできるだけ単純なものが良いと考え、ホストと仮想環境両方でsshdを動かし、ポート22でホストへ直結、1022で仮想環境のポート22へリダイレクトするだけの構成にしました。(ホスト、仮想環境両方ともsshdがポート22でLISTEN)

```
                                    +-------+
                                    | jail0 |                    22:ssh
                                    +-------+  192.168.20.1/30     ^
                                        |                          |
  +------+                          +--lo1--+                      |
  | host |                          | host  |  22:ssh              |
  +--re0-+ (192.168.10.2/24)        +--re0--+ (192.168.10.2/24)  リダイレクト: 1022 
      |                                 |
  router(192.168.10.1/24)           router(192.168.10.1/24)
      |                                 |
  インターネット                     インターネット
  <前提の環境>                       <構築する環境>      
```

## jailとは
仮想化技術にはハイパーバイザ型・ホスト型・コンテナ型等様々な形態がありますが、特にjailは[^1]operating system-level virtualizationと公式に呼ばれています。具体的にはホストと同じカーネルを共有しつつ、当該仮想環境をホストのファイルシステム(以下: FS)とプロセスから隔離したものです。VMと違ってオーバーヘッドが小さい上、ネットワーク的にはホストと別ノードと扱われるため、インターネットに接続する際NATを行ったり、ホストと同じネットワークに参加させたり、設定によって様々に取り扱うことが可能です。chrootの便利版くらいに捉えておけば十分運用可能であると思います。以下にchrootをご存知でない方のために図を用意しておきます。特定のディレクトリを仮想環境にとっての(/)と認識させ、これを上位から隔離するようなものです。

```
/--
  |-bin
  |-boot
  |-dev
  |-usr/jail/jail0    # このディレクトリ以下を新たにjail環境"jail0"とする場合
              |-bin   # jail0は/usr/jail/jail0をルート(/)と認識して、
              |-boot  # より上位のディレクトリにアクセスできない → FS隔離
              |-dev   # jail0にとって見れば/usr/jail/jail0以下が自分のシステムであり、これを以てホストと独立して動作する
              ...
```

[^1]: https://www.freebsd.org/doc/handbook/jails.html

## 何故jailを使うのか
### 意図
* ホストのFSから隔離出来る特徴は、複数サービスを動かすアプリケーションサーバのセキュリティを向上させる点で有能。
* ホストと別ノードとしてネットワークの設定が柔軟に行える特徴は、↑と同じ理由で有能。
* オーバーヘッドが小さい点で、VMに比べてパフォーマンス上有利。
* 多少の可搬性があり、再構築が容易である。

### 目的(例)
* メールサーバをjail上で隔離して動かす
* ホストの環境汚染を防ぐために依存関係の大きいソフトウェア開発時にjail上で実験する

## pfとは
Packet Filter[^2](必読)の省略形であり、FWに用いるアプリケーションです。現状、FreeBSDではFWにipfw(FreeBSD公式)・pf(OpenBSD由来)のどちらかを用いることが合理的と思われます(ライセンスの問題)。標準ではipfwが用いられますが、NATやリダイレクトを記述する際の利便性・機能性の問題で今回pfを用います。

[^2]: https://www.openbsd.org/faq/pf/index.html

## /etc/rc.conf
FreeBSDシステムに於いて、各デーモン並びに機能の有効化・無効化などの簡単な設定を行うものです。

# 構築
## jailその1(FS構築)

この項以降で参考にした記事[^3](2020/09/02 17:48 時点で存在を確認):
[^3]: https://qiita.com/furandon_pig/items/b40db38ac2a2d795224d
(参考記事内に於いてjailはコンテナと呼称されていますが当記事ではjailで統一します)  
まずjail環境のFSを作るための要件を、必要になる順番で提示します。
#### jailのFSを構築するための要件

1. jailとは で書いた通り、隔離する適当なディレクトリ
1. jailでのFreeBSDシステム動作に必要な最小限の配布物(以下: ベース): base.txz, kernel.txz
1. 2の展開
\+ その他、jail・ネットワークの設定(後でやります)


これを満たすように構築していきます。
まず1.の適当なディレクトリ、今回は**/usr/jail**とします。
(以下 # はrootのプロンプトです。)

```sh
# mkdir /usr/jail
```

2.の配布物ダウンロードです。格納するディレクトリを用意し、curlをインストールし、現時点(2020/09/02)での最新版12.1をダウンロードします。

(最新版のURLを確認する方法: ブラウザで http://ftp.jaist.ac.jp/pub/FreeBSD/releases/amd64/ にアクセスすると幾らかのRELEASEが並んでいると思います。最新のものを選択して、適宜URLを変更すれば良いです。)

```sh
# mkdir /usr/jail/BASE12_1
# cd /usr/jail/BASE12_1
# pkg install curl
# curl -O http://ftp.jaist.ac.jp/pub/FreeBSD/releases/amd64/12.1-RELEASE/base.txz
# curl -O http://ftp.jaist.ac.jp/pub/FreeBSD/releases/amd64/12.1-RELEASE/kernel.txz
```

3.適当なディレクトリを作り、以下にjail用のベースを展開します

```sh
# cd /usr/jail
# mkdir jail0
# tar Jxfp BASE12_1/base.txz -C jail0
# tar Jxfp BASE12_1/kernel.txz -C jail0
# cp /etc/resolve.conf jail/etc
# echo 'sshd_enable="YES"' >> jail0/etc/rc.conf

```

ここで、**jailのFSを構築するための要件**を全て(3を除く)満たしました。

jailその2へと進む前に、現在の状況を確認し続く処理の**方針**と**意図**を説明します。
今jail0のFSを構築しましたが、これを想定通りに動作させるためには以下の要件を満たさなくてはなりません。

1. jail0とホストを接続するためのネットワークインタフェースを作る
1. jail0とホスト間のプライベートネットワークを作る
1. ホストにjail0を認識させ、また2.で作ったアドレスから1つ割り当てる
1. ホストの外部インタフェースを通じてjail0をNATでインターネットに繋ぐ
 
今FSを作っただけであって、ホストはこれを認識しておらず、またネットワークに接続されてもいません。追加の設定によってホストに認識させ、ネットワークに接続することでjailが動作します。
作業の順番的な依存性の問題でjailその2は保留、まずネットワークの設定を行います。


## ネットワークの設定

この項で参考にした記事[^4](2020/09/02 17:48 時点で存在を確認)

[^4]: https://www.davd.io/posts-freebsd-jails-with-a-single-public-ip-address/
  
まず先程の要件1, 2の通り、ネットワークインタフェース(lo1)・プライベートネットワーク(192.168.20.1/30)を作ります。
`ifconfig`を実行し、現在あるネットワークインタフェースを確認してください。環境にもよりますが、標準ではlo0と、外部インタフェースの**そうではないやつ**がもう一つあるはずです(どうやらホストマシンの環境によって異なる名称がつけられているようです。)今回はre0でした。ここに新たにlo1を追加し、ネットワーク範囲(192.168.20.1/30)を割り当てることになります。
**/etc/rc.conf**に以下を追記してください。

```:/etc/rc.conf
cloned_interfaces="lo1"           #要件1
ipv4_addrs_lo1="192.168.20.1/30"  #要件2
ifconfig_lo1="inet 127.0.0.1"
ifconfig_lo1_alias0="inet 192.168.20.1" #このアドレスを使いますよ的な宣言です。今後これをjail0に割り当てます。
```

設定が終わったら反映させます。

```sh
# /etc/netstart
```

勝手に新たな設定を出力してくれるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/702242/43a4b94d-fd60-e69c-d8d8-08956af65f41.png)
私のディスプレイが4Kなので、クソデカスクリーンショットになってしまいました。この通りに出力されれば作業完了です。
図を以下に貼っておきます。現在の段階でここまで出来ています。(ルータとjail0の部分は省略している)

```
           *(まだ接続していない)
           |
       +--lo1--+ (192.168.20.1/30) << これを作成しました。
       | host  |
       +--re0--+ (192.168.10.2/24)
           |
```

## jailその2(有効化・ネットワーク設定)
先程の手順でjail0に割り当てるためのネットワーク環境を構築できましたから、早速**/etc/jail.conf**にjail0の設定を記述し、ホストに認識させます。これが要件3を満たします。

```:/etc/jail.conf
jail0 {
        jid             = 10; # 適当で良い。今後jailを起動・終了する際に指定する番号として使う。
        path            = /usr/jail/jail0;
        ip4.addr        = 192.168.20.1;
        host.hostname   = jail.host; # 適当で良い。今回ホストのホスト名がhostなので、jailのホスト名は適当にjail.hostとした。
        allow.raw_sockets;
        exec.start      = "/bin/sh /etc/rc";
        # exec.stop     = "/bin/sh /etc/rc.shutdown";
        interface       = lo1;
        mount.devfs;
        persist;
        allow.chflags; # 重要。これが無いとfreebsd-updateで権限エラーを吐かれる。(今回は説明しない)
}
```

### 試しに起動

jlsは起動中のjailの情報を出力してくれるコマンドです。

```sh
# service jail onestart jail0 
# jls 
```

```:出力
   JID  IP Address      Hostname                      Path
    10  192.168.20.1    jail.host                     /usr/jail/jail0
```

起動していますね。
jail0に入り、設定を行います。
jexec で指定している10は先程/etc/jail.confで指定したJIDです。

```sh
# jexec 10 /bin/sh
```
プロンプトが出たら続けて設定を行います。
ユーザ(ここではprisoner)を追加してsudo権限を与えましょう。

```sh
# pkg update
# pkg install sudo
# adduser prisoner
...飛ばして...
Login group is freebsd. Invite freebsd into other groups? []: wheel
...以降はお好みで...
# visudo
```
visudoでユーザにsudoを許可するように設定を行います。(対象読者の前提で記述した通り、ご存知かと思いますので説明しません。)

## pfによるNAT・リダイレクト
pfについて再度公式ガイドのリンクを貼っておきました[^2]。私は Traffic Redirection (Port Forwarding) の節まで読んでいますが、pfををシステム全体のFWとして運用する方は最低でも Packet Filtering まで一読することをお勧めします。非常に詳細で、また読みやすいです。
さて、それはそれとして、まずは**/etc/rc.conf**に追記してpfの有効化を行います。

```:/etc/rc.conf
pf_enable="YES"
pf_rules="/etc/pf.conf"
pf_flags=""

# ログを取りたい方は以下も記述してください。
pflog_enable="YES"
pflog_logfile="/var/log/pflog"
pflog_flags=""
```

続いてNAT・リダイレクトの設定を行います。設定ファイルは**/etc/pf.conf**です。

```:/etc/pf.conf
ext_if="re0" # ホストが外部(インターネット)と通信するためのネットワークインタフェース
int_if="lo1" # ホストがjailと接続しているネットワークインタフェース

table <private> const { 192.168.20.0/30 } # jail が参加しているプライベートネットワーク
  # 上では<private>としてTableを宣言していて、Tableというのは複数のIPアドレスを保存しておくものです。公式には
  # A table is used to hold a group of IPv4 and/or IPv6 addresses (https://www.openbsd.org/faq/pf/tables.html)
  # と記述されています。(参考文献2つめの内部のページです)

nat on $ext_if inet from ($int_if) to ! <private> -> ($ext_if)
  # <意味>: $ext_if に於いて、$int_if から jail内部ネットワーク外へ出るパケットをホストのものにNATせよ
  # ()付きはDHCPその他によるIP変更を考慮して動作しろという意味だそうです。
  # (以下2行はhttps://www.openbsd.org/faq/pf/nat.html より引用)
  # The name or group of the external network interface in parentheses ( ).
  # This tells PF to update the rule if the IP address(es) on the named interface changes.
  # (参考文献2つめの内部のページです)

rdr pass on $ext_if proto tcp from any to ($ext_if) port 1022 -> 192.168.20.1 port 22
  # <意味>: 任意のホストから外部インタフェースにポート1022行きで来たtcpパケットは、
  # 192.168.20.1へポート22行きとしてリダイレクトし、通過させよ
```

```sh
# service pf start
```

これで正常に動作すれば、環境構築が完了です。ホストと同じネットワークにいるマシンから、ホストとjail0に、それぞれsshログインしてみます。
重要でないので特に言及しませんでしたが、ホストにはhogeユーザがいるものとし、sshdが既に動いているものとします。

```:ホスト
client $ ssh hoge@192.168.10.1 
```

```:jail0
client $ ssh prisoner@192.168.10.1 -p 1022
```

両方でログインが出来たら環境構築完了です。


# おわりに
　私の自宅サーバ(物理マシン)、またWindowsのHyper-V上で今回の環境構築を検証し、成功しています。同様に仮想マシン上で構築される場合は、NATではなく仮想スイッチを用いてVMを外部ネットワークに接続して行ってください(仮想スイッチ経由でホスト・VMをルータ直下に接続し、これらを同じネットワーク内に置く)。またその際、NATで動作していたものを途中で外部ネットワークに変更すると/etc/resolv.confの内容に不整合が生じますから、気をつけてください。VM作成の段階でネットワークの設定を完了させた方が良いです。
　読み手にとってはどうでもよいことですが、私がqiitaで記事を公開するのはこれが初めてのことです。至らぬ点がありましたらビシバシ・ボコボコとご指摘をいただければ幸甚であります。



## 参考文献