Simple thin jail management tool.
Provides:
- a straightforward script to create/destroy jails
- recipe scripts to customize jails, heavily inspired by Bastille
- Easy to understand.
- Limited rigid feature set:
- Leverage jail(8) as much as possible;
- ZFS thin jails only;
- Networking via shared interface or cloned loopback interface and
pf
;
# make install
For loopback networking (optional):
# sysrc cloned_interfaces+="lo1"
# service netif cloneup
Create and review /usr/local/etc/sjail.conf
:
zfs_dataset |
pool to store all sjail data (will be created by sjail init ) |
zfs_mount |
mountpoint for sjail data |
interface |
interface to attach jails to. Dictates the network setup: loX for cloned loopback, anything else is shared interface |
pf_ext_if |
the external interface on which traffic for jails is expected (relevant for loopback networking) |
Following commands are provided:
Init | sjail init |
Create release | sjail rel-create 14.2-RELEASE |
Update release | sjail rel-update 14.2-RELEASE |
Destroy release | sjail rel-destroy 14.2-RELEASE |
Create jail | sjail create alcatraz 14.2-RELEASE ip4=10.1.1.11 ip6=fd10:0:0:100::11 nat=1 rdr=0 |
Destroy jail | sjail destroy alcatraz |
List | jls or sjail list for all |
Start | jail -c alcatraz |
Stop | jail -r alcatraz |
Recipe | sjail apply alcatraz some/recipe |
Compose your own:
forAll | jls name | xargs … |
Pkg upgrade forAll | jls name | xargs -I% jexec -l % pkg upgrade -y |
Recipes live by convention in ${zfs_mount}/recipes
.
Recipes are directly inspired by Bastille templates1. A recipe is a directory comprised of:
- A mandatory
apply.sh
file which contains commands. - Optional files to be deployed via the
CP
command.
apply.sh
is a shell script2. Commands are shell functions.
Command | Comments |
---|---|
CMD |
arguments executed inside sh -c . I.e. quote commands with redirects, logical operations, etc. |
CONF |
breaking compat: name change + no set argument. |
CP |
copies recursively |
INCLUDE |
|
MOUNT |
|
PKG |
|
EXPOSE |
breaking compat: name change |
RESTART |
convenient in conjunction with CONF |
SERVICE |
|
SYSRC |
When migrating from Bastille templates, here's a little checklist:
- Command names; see breaking compat in the previous table
ARG
is replaced with thesh
equivalent:-ARG php_prefix=php82 +php_prefix=${php_prefix:-php82}
INCLUDE
arguments are also replaced with thesh
equivalent:-INCLUDE my/service-php --arg php_prefix=php82 +INCLUDE my/service-php php_prefix=php82
- Make sure to escape spaces in strings:
INCLUDE foudfou/service-nodejs npm_global="pm2\ pnpm" # otherwise interpreted as npm_global=pm2
- Make sure to escape spaces in strings:
Optional arguments must still be defined:
maxmemory=${maxmemory:-}
maxmemory_policy=${maxmemory_policy:-}
Sjail only supports these networking setups: shared interface or cloned loopback interface.
The network setup is determined by the interface
parameter in sjail.conf
.
When the interface
parameter doesn't start with lo
, it's interpreted as
external interface.
Jails get IPs attached to the host's external interface and are in the same subnet:
# Provided host ip = 192.168.1.23/24 and interface="em0" in sjail.conf
sjail create j01 14.2-RELEASE ip4=192.168.1.201/24
Jails are thus accessible to the local network. No specific pf
adaptation is
required.
When the interface
parameter starts with lo
, it's interpreted as a cloned
loopback interface.
Jails' IPs are attached to a clone loopback interface. Traffic between host and
jails is enabled with pf
: outgoing via NAT, incoming via RDR.
ext_if=vtnet0
icmp_types = "{ echoreq, unreach }"
icmp6_types = "{ echoreq, unreach, routeradv, neighbrsol, neighbradv }"
# sjail-managed
table <jails> persist
set skip on lo
# sjail-managed
rdr-anchor "rdr/*"
nat on $ext_if from <jails> to any -> ($ext_if:0)
block in all
pass out all keep state
antispoof for $ext_if
pass inet proto icmp icmp-type $icmp_types
pass inet6 proto icmp6 icmp6-type $icmp6_types
# Allow ssh
pass in inet proto tcp from any to any port ssh flags S/SA keep state
Port forwarding is persisted to ${jail_path}/rdr.conf
as lines of the format
<proto> <host_port> <client_port>
lines. They are applied for ip4 and ip6
via pf
rdr rules on jail start and cleared on jail stop.
rdr.conf
can be created manually of via the recipe EXPOSE
command.
Networking can be fine-tuned and NAT or RDR can be enabled individually,
respectively with the nat=[0|1|]
and rdr=[0|1|]
parameters.
These parameters are persisted to ${jail_path}/meta.conf
.
For example, we can enable NAT for outgoing traffic but disable RDR for incoming traffic:
sjail create j01 14.2-RELEASE ip4=192.168.1.201/24 nat=1 rdr=0
One use case for this is a host with a VPN that forwards default traffic to the VPN server, and jails that:
- are exposed to local network
- can reach out to the internet (ex: pkg install)
- but don't expose ports to the VPN network (
pf_ext_if=vpn_cli_if
)
A solution to these requirements is a shared interface setup and only enabling NAT for jails.
Provided the jails don't hold important state, upgrading to a new minor or major release is best done by re-creating them.
For minor release changes, pointing the jails' fstab
s to the new release
after stopping them is also a viable option.
See internals.
Feedback and PRs welcome. When hacking on the code, make sure to run tests in isolated environments (See t/README.md and t/integration/README.md).