ちょっとした実験・作業環境をJailで構築する

今日から[FreeBSD Advent Calendar 2020](https://qiita.com/advent-calendar/2020/freebsd)が始まります。
第一日目の今日は、FreeBSD Jailを活用する話を書こうと思います。

## 背景

UNIX系OSでちょっとした実験・作業用にまっさらな環境を用意したいことは多々あります。
FreeBSDであればJail、LinuxであればDockerでコンテナ環境を作成するかと思いますが、シェルやエディタ、自分がよく使うツールはあらかじめコンテナに含めた状態にしておきたいものです。加えて、コンテナを作成したら `ssh` でログインできるような形にもしておきたいものです。

過去に以下の記事でFreeBSD Jailでコンテナを構築する手順を紹介していますが、この記事では手動でコマンドを実行してコンテナを作成するという方法でした。

 * [さくらVPSでFreeBSD＋Jailを動かして仮想的にVPSが複数使えているような環境を作ってみる](https://qiita.com/furandon_pig/items/b40db38ac2a2d795224d)

このコンテナ環境をさらに改良し、FreeBSD Jailでのコンテナ構築とssh設定、シェルの設定ファイルなどの投入等をスクリプトで行う形の運用が実現できたので、今回はその環境の紹介をさせてもらおうと思います。

## コンテナで実現したいもの

 * `ssh` でコンテナにログインできるようにしたい。
 * VNCでGUIを利用できるようにしたい。
 * シェルやエディタの設定等をコンテナ構築時に配置しておきたい。

## コンテナ環境の構築

### ホストOS側の設定

`/etc/rc.conf` と `/etc/pf.conf` の設定は以前の紹介記事の内容のままでOKです。

 * [/etc/rc.conf](https://qiita.com/furandon_pig/items/b40db38ac2a2d795224d#etcrcconf)の設定
 * [/etc/pf.conf](https://qiita.com/furandon_pig/items/b40db38ac2a2d795224d#etcpfconf)の設定

Jailコンテナ用に以下の手順で配布物を用意します。

[コンテナ用のFreeBSD配布物を用意する。](https://qiita.com/furandon_pig/items/b40db38ac2a2d795224d#%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E7%94%A8%E3%81%AEfreebsd%E9%85%8D%E5%B8%83%E7%89%A9%E3%82%92%E7%94%A8%E6%84%8F%E3%81%99%E3%82%8B)

ホストOS側でのファイルは一は以下のようになります。ファイル中の `create_container.sh` `remove_container.sh` が今回紹介するコンテナ作成・削除用の簡易スクリプトになります。また、コンテナに配置したいユーザ用の設定ファイルを `JAIL_FAILS` ディレクトリに入れておきます。

```
.
|-- FBSD12_DIST
|   |-- base.txz
|   `-- kernel.txz
|-- JAIL_FAILS
|   |-- authorized_keys
|   |-- create_user.sh
|   |-- dot.bashrc
|   |-- dot.emacs
|   |-- dot.gitconfig
|   |-- dot.profile
|   |-- dot.screenrc
|   |-- sshd_config
|   |-- sudoers
|   `-- user__ssh_config
|-- create_container.sh
`-- remove_container.sh
```

### コンテナを作成するスクリプト

```sh
#!/bin/sh

# 引数には作成したいコンテナ名を指定します。
if [ $# -eq 0 ]; then
	echo 'Err: too few argument.'
	echo '  ./easy_setup.sh <jail_name>'
	exit 1
fi
jail_name=$1

if [ -d ${jail_name} ]; then
	echo "Err: Jail already exists. ${jail_name}"
	exit 1
fi

# コンテナ名でディレクトリを作成し、必要なファイルを配置します。
mkdir -p ${jail_name}

# FreeBSD配布物を展開します。
echo '-=> Extract FreeBSD files.'
tar Jxfp FBSD12_DIST/base.txz -C ./${jail_name}
tar Jxfp FBSD12_DIST/kernel.txz -C ./${jail_name}

# 必要なファイルをコンテナ用ディレクトリに配置します。
cp /etc/resolv.conf ./${jail_name}/etc/resolv.conf
echo 'sshd_enable="YES"' | sudo tee -a ./${jail_name}/etc/rc.conf
cat JAIL_FAILS/sshd_config | sudo tee -a ./${jail_name}/etc/ssh/sshd_config
cp JAIL_FAILS/sudoers ./${jail_name}/tmp
cp JAIL_FAILS/authorized_keys ./${jail_name}/tmp
cp JAIL_FAILS/create_user.sh ./${jail_name}/tmp

cp JAIL_FAILS/dot.profile ./${jail_name}/tmp
cp JAIL_FAILS/dot.bashrc ./${jail_name}/tmp
cp JAIL_FAILS/dot.emacs ./${jail_name}/tmp
cp JAIL_FAILS/dot.screenrc ./${jail_name}/tmp

# どのコンテナか識別しやすいように、modファイルを設定します。
# (sshログイン時にmodファイルが表示されます)
cat <<_EOF > ./${jail_name}/etc/motd
FreeBSD `freebsd-version`

  *** ${jail_name} ***

_EOF

# Jail IDとして使用する値をjail.confから抽出します。
cur_max_ip=`grep 'ip4.addr' /etc/jail.conf | sed -e "s/;.*$//" -e "s/^.*\.//" | sort | tail -n1`
if [ -z "$cur_max_ip" ]; then
	new_ip=10
else
	new_ip=$((cur_max_ip + 1))
fi

# 作成するコンテナのjail.confエントリを追記します。
cat <<_EOF | tee -a /etc/jail.conf
${jail_name} {
	path		= /enc/jail/${jail_name};
	ip4.addr	= 172.16.0.${new_ip};
	ip6.addr	= fe80::11;
	host.hostname	= ${jail_name}.furandon.net;
	allow.raw_sockets;
	exec.start	= "/bin/sh /etc/rc";
#	exec.stop	= "/bin/sh /etc/rc.shutdown";
	interface	= lo0;
	mount.devfs;
	persist;
}
_EOF

# jailコマンドの起動例を表示します。
cat <<_EOF
Jail command.

  $ sudo service jail onestart ${jail_name}
  $ sudo service jail onestop  ${jail_name}

_EOF

# にコンテナを起動します。
echo -n "start jail '${jail_name}'..."
sudo service jail onestart ${jail_name}
if [ $? -eq 0 ]; then
	# コンテナが起動できたら、初期化用のスクリプトを実行します。
	jexec ${jail_name} sh /tmp/create_user.sh
	echo 'OK (^_^)'
	jls
else
	echo 'FAIL (T_T)'
fi
```

上記のスクリプトでコンテナを起動すると、そのまま初期化用のスクリプトを実行します。
このスクリプトでは、 `pkg update` の実行と `JAIL_FAILS` ディレクトリからコピーしてきた各種設定ファイルの配置を行います。

```sh
#!/bin/sh

pkg update
pkg install sudo bash

cp /tmp/sudoers /usr/local/etc/sudoers

_USER_NAME=fbsd
_FULL_NAME="freebsd_jail_user"
_UID=1001

# ユーザの追加。
pw groupadd -n users -g 1001
pw useradd -n $_USER_NAME -c $_FULL_NAME -g users -G wheel -G wheel -s /usr/local/bin/bash -u $_UID -m

# ssh鍵の配置。
cd /home/$_USER_NAME
sudo -i -u $_USER_NAME mkdir tmp
sudo -i -u $_USER_NAME mkdir -m 700 .ssh

sudo -i -u $_USER_NAME cp /tmp/authorized_keys .ssh/authorized_keys
chown $_USER_NAME:users .ssh/authorized_keys
sudo -i -u $_USER_NAME chmod 600 .ssh/authorized_keys

sudo -i -u $_USER_NAME cp /tmp/user__ssh_config .ssh/config
chown $_USER_NAME:users .ssh/config
sudo -i -u $_USER_NAME chmod 600 .ssh/config

sudo -i -u $_USER_NAME cp /tmp/dot.gitconfig .gitconfig
chown $_USER_NAME:users .gitconfig
sudo -i -u $_USER_NAME chmod 600 .gitconfig


# タイムゾーンの設定。
cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
chmod 444 /etc/localtime
echo "Asia/Tokyo" > /var/db/zoneinfo
chmod 644 /var/db/zoneinfo

# ユーザ用の設定ファイルのコピー。
[ -f /tmp/dot.bashrc ] && sudo -i -u $_USER_NAME cp /tmp/dot.bashrc /home/$_USER_NAME/.bashrc
[ -f /tmp/dot.profile ] && sudo -i -u $_USER_NAME cp /tmp/dot.profile /home/$_USER_NAME/.profile
[ -f /tmp/dot.emacs ] && sudo -i -u $_USER_NAME cp /tmp/dot.emacs /home/$_USER_NAME/.emacs
[ -f /tmp/dot.screenrc ] && sudo -i -u $_USER_NAME cp /tmp/dot.screenrc /home/$_USER_NAME/.screenrc
```

### コンテナを削除するスクリプト

コンテナが不要になった場合もスクリプト経由で削除できるようにしておきます。

```sh
#!/bin/sh

if [ $# -eq 0 ]; then
        echo 'Err: too few argument.'
        echo '  remove_container.sh <jail_name>'
        exit 1
fi
jail_name=$1

service jail onestop $jail_name \
        && chflags -R noschg $jail_name \
        && rm -rf $jail_name \
        && echo "removed '$jail_name'"
```

## 実際にコンテナを作成・削除してみる

以下のような感じで簡単にコンテナの作成・削除が行えます。

```sh
$ # コンテナの作成。
$ sudo ./create_container.sh tmp02
...
$ jls
   JID  IP Address      Hostname                      Path
     1  172.16.0.10     tmp01.furandon.net            /enc/jail/tmp01
     2  172.16.0.11     tmp02.furandon.net            /enc/jail/tmp02
$
$ # コンテナの削除。
$ sudo ./remove_container.sh tmp02
```

## まとめ

私の環境で使用しているJailコンテナ管理用スクリプトを紹介してみました。sshで接続できる＋自分用の設定が投入された状態のコンテナが簡単に作成できるので、ちょっとした環境を作成したいんだけど...という方はお試しいただければと思います。

また、この環境はJailコンテナを配置するストレージをGEOM/GELIで暗号化して運用しています。明日はこのあたりの話を紹介できればと思います。