This page looks best with JavaScript enabled

Managing Jails in FreeBSD with Bastille

 ·  πŸŽƒ kr0m

Bastille is a container (jails) manager with 0 dependencies since it is written in Bourne Shell. It makes use of the best functionalities and technologies that FreeBSD offers us, thus facilitating the management, creation, destruction, and updating of jails throughout their life cycle.
Some advantages over other virtualization systems such as IOCage are:

  • Better maintained project, receives updates more often.
  • 0 dependencies for being written in Bourne Shell.
  • More complete template system.
  • Possibility of reusing template recipes through Rocinante .
  • Jails natting with integrated port redirection.

We can find a very illustrative table on the Bastille website.


The article is composed of several parts:


Installation

We install the dependencies:

pkg install vim git-lite bash ca_root_nss

We install Bastille:

pkg install bastille

We configure if we want to use ZFS or not:

vi /usr/local/etc/bastille/bastille.conf

## ZFS options
bastille_zfs_enable="YES"  ## default: ""
bastille_zfs_zpool="zroot"   ## default: ""

We enable the service:

sysrc bastille_enable=YES
service bastille start

We download the base files of the FreeBSD version we want to use and apply the latest security patches:

bastille bootstrap 13.1-RELEASE update

Optionally, we can verify the image:

bastille verify 13.1-RELEASE


Networking

Bastille has some limitations regarding jail names, for example:

  • Jail names cannot contain the “.” character.
  • Names cannot be too long or network configuration commands will fail:
ifconfig: ioctl SIOCSIFNAME (set name): File name too long
jail: alcatrazVnet2: ifconfig epair0a up name e0a_alcatrazVnet2: failed

All networking documentation can be found at this link .

To see the network type of each jail, we must consult its configuration directly:

cat /usr/local/bastille/jails/*/jail.conf

The PF rules we will see later in this same article only affect NAT and Alias network modes, since in VNET and VNET-BRIDGE-PROPIO we explicitly disable traffic filtering on bridges, so they will not be affected.


Networking-NAT

Through NAT, we will have one or more internal networks that will be NATed using PF . To do this, we must perform the following configuration:

sysrc cloned_interfaces+=lo1
sysrc ifconfig_lo1_name="bastille0"

We apply the configuration:

service netif cloneup

We will write a simple PF script where we will NAT the “jails” table, redirect the rdr ports, and allow free access to the 22-ssh port:

vi /etc/pf.conf

ext_if="bge1"

set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo

table <jails> persist
nat on $ext_if from <jails> to any -> ($ext_if:0)
rdr-anchor "rdr/*"

block in all
pass out quick keep state
antispoof for $ext_if inet
pass in inet proto tcp from any to any port ssh flags S/SA keep state

Enable and start the service:

sysrc pf_enable=YES
service pf start

We can check the rules with:

pfctl -sr
scrub in on bge1 all fragment reassemble
block return in all
pass out quick all flags S/SA keep state
block drop in on ! bge1 inet from 192.168.40.0/24 to any
block drop in inet from 192.168.40.113 to any
pass in inet proto tcp from any to any port = ssh flags S/SA keep state

The recommended internal ranges by Bastille are:

IP options include: 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16.

By default, Bastille creates the NATed jails:

bastille create natJail 13.1-RELEASE 10.17.89.50
bastille list
 JID             IP Address      Hostname                      Path
 natJail         10.17.89.50     natJail                       /usr/local/bastille/jails/natJail/root

If we access the jail, we can see the IP:

bastille console natJail
ifconfig
bastille0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
	options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
	inet 10.17.89.50 netmask 0xffffffff
	inet6 fe80::1%bastille0 prefixlen 64 scopeid 0x4
	groups: lo
	nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>

Enable and start the SSH service in the jail:

bastille sysrc natJail sshd_enable=YES
bastille service natJail sshd start

We can see how the socket is listening:

bastille cmd natJail sockstat -4
[natJail]:
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      
root     sshd       6180  3  tcp4   10.17.89.50:22        *:*
[natJail]: 0

We enter the jail to add a user to check SSH connectivity:

bastille console natJail
adduser

Connect:


Networking-Alias

As the name suggests, this mode uses alias IPs on the parent host to assign them to the jails.

We create the jail indicating the interface on which to configure the alias:

bastille create aliasJail 13.1-RELEASE 192.168.40.136/24 bge1
bastille list
 JID             IP Address      Hostname                      Path
 natJail         10.17.89.50     natJail                       /usr/local/bastille/jails/natJail/root
 aliasJail       192.168.40.136  aliasJail                     /usr/local/bastille/jails/aliasJail/root

We can see how the parent interface now has several configured IPs:

ifconfig
bge1: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
	options=8009b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,LINKSTATE>
	ether 00:1a:64:6d:e6:f8
	inet 192.168.40.113 netmask 0xffffff00 broadcast 192.168.40.255
	inet 192.168.40.136 netmask 0xffffff00 broadcast 192.168.40.255
	media: Ethernet autoselect (100baseTX <full-duplex>)
	status: active
	nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

In Alias mode, we must keep in mind that the IPs are configured in the parent, so if we want to bind a service to the jail, we must configure the parent to only use its specific IP and not use wildcards:

vi /etc/ssh/sshd_config
ListenAddress 192.168.40.113
service sshd restart

We enable and start the SSH service in the jail:

bastille sysrc aliasJail sshd_enable=YES
bastille service aliasJail sshd start

We enter the jail to add a user to check SSH connectivity:

bastille console aliasJail
adduser

We connect:


Networking-VNET

Bastille creates a bridge and automatically assigns the jail interface and the WAN interface to this bridge.

For VNET jails to function correctly, devd rules are required that should only be configured once:

vi /etc/devfs.rules
[bastille_vnet=13]
add path 'bpf*' unhide

We restart devd:

service devd restart

We disable traffic filtering on the bridges, so each VNET jail will perform its own filtering:

vi /etc/sysctl.conf
# Bastille:
net.link.bridge.pfil_bridge=0  # Packet filter on the bridge interface
net.link.bridge.pfil_onlyip=0  # Only pass IP packets when pfil is enabled
net.link.bridge.pfil_member=0  # Packet filter on the member interface
sysctl net.link.bridge.pfil_bridge=0
sysctl net.link.bridge.pfil_onlyip=0
sysctl net.link.bridge.pfil_member=0

We create the jail:

bastille create -V jailVnet 13.1-RELEASE 192.168.40.137/24 bge1
bastille list
 JID             IP Address      Hostname                      Path
 aliasJail       192.168.40.136  aliasJail                     /usr/local/bastille/jails/aliasJail/root
 jailVnet                        jailVnet                      /usr/local/bastille/jails/jailVnet/root
 natJail         10.17.89.50     natJail                       /usr/local/bastille/jails/natJail/root

We can see in the parent the bridge with the jail interface (e0a_bastille0) and the WAN interface (bge1):

ifconfig
bge1bridge: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
	ether 58:9c:fc:10:ff:a4
	id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15
	maxage 20 holdcnt 6 proto rstp maxaddr 2000 timeout 1200
	root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0
	member: e0a_bastille0 flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
	        ifmaxaddr 0 port 6 priority 128 path cost 2000
	member: bge1 flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
	        ifmaxaddr 0 port 2 priority 128 path cost 55
	groups: bridge
	nd6 options=9<PERFORMNUD,IFDISABLED>
e0a_bastille0: flags=8963<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
	description: vnet host interface for Bastille jail jailVnet
	options=8<VLAN_MTU>
	ether 02:20:98:6d:e6:f8
	hwaddr 02:b5:d3:fe:09:0a
	groups: epair
	media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
	status: active
	nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

We enable and start the SSH service in the jail:

bastille sysrc jailVnet sshd_enable=YES
bastille service jailVnet sshd start

If we access the jail we can see the IP:

bastille console jailVnet

ifconfig

vnet0: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
	options=8<VLAN_MTU>
	ether 0e:20:98:6d:e6:f8
	hwaddr 02:b5:d3:fe:09:0b
	inet 192.168.40.137 netmask 0xffffff00 broadcast 192.168.40.255
	groups: epair
	media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
	status: active
	nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

We add a user to check SSH connectivity:

adduser

Sometimes the default route is not detected correctly, if this is the case we can adjust it for each jail:

bastille sysrc TARGET defaultrouter=aa.bb.cc.dd
bastille service TARGET routing restart

Or from Bastille for new jails:

vi /usr/local/etc/bastille/bastille.conf

bastille_network_gateway=aa.bb.cc.dd

Networking-VNET-Bridge-Own-Bridge

The same configuration as VNET must be followed.

vi /etc/devfs.rules

[bastille_vnet=13]
add path 'bpf*' unhide

We restart devd:

service devd restart

We disable traffic filtering on bridges, so each VNET jail will perform its own filtering:

vi /etc/sysctl.conf

# Bastille:
net.link.bridge.pfil_bridge=0  # Packet filter on the bridge interface
net.link.bridge.pfil_onlyip=0  # Only pass IP packets when pfil is enabled
net.link.bridge.pfil_member=0  # Packet filter on the member interface
sysctl net.link.bridge.pfil_bridge=0
sysctl net.link.bridge.pfil_onlyip=0
sysctl net.link.bridge.pfil_member=0

In automatic VNET mode we have put the WAN interface in a bridge and interfaces cannot be in two bridges simultaneously, so first we will remove the VNET jail we created in the previous step:

bastille stop jailVnet
bastille destroy jailVnet
ifconfig bge1bridge destroy

We create the bridge in the parent:

sysrc cloned_interfaces+=bridge0
sysrc ifconfig_bridge0=“addm bge1 up”

We apply the configuration:

service netif cloneup

We create the jail:

bastille create -B jailVnet2 13.1-RELEASE 192.168.40.138/24 bridge0

bastille list

 JID             IP Address      Hostname                      Path
 aliasJail       192.168.40.136  aliasJail                     /usr/local/bastille/jails/aliasJail/root
 natJail         10.17.89.50     natJail                       /usr/local/bastille/jails/natJail/root
 jailVnet2                       jailVnet2                     /usr/local/bastille/jails/jailVnet2/root

We can see in the parent the bridge with the jail interface (e0a_jailVnet2) and the WAN interface (bge1):

ifconfig

bridge0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
	ether 58:9c:fc:10:ff:a4
	id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15
	maxage 20 holdcnt 6 proto rstp maxaddr 2000 timeout 1200
	root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0
	member: e0a_jailVnet2 flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
	        ifmaxaddr 0 port 6 priority 128 path cost 2000
	member: bge1 flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
	        ifmaxaddr 0 port 2 priority 128 path cost 200000
	groups: bridge
	nd6 options=9<PERFORMNUD,IFDISABLED>
e0a_jailVnet2: flags=8963<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
	options=8<VLAN_MTU>
	ether 02:f2:2e:f9:94:0a
	groups: epair
	media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
	status: active
	nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

We enable and start the SSH service in the jail:

bastille console jailVnet2
bastille sysrc jailVnet2 sshd_enable=YES
bastille service jailVnet2 sshd start

If we access the jail, we can see the IP:

ifconfig

vnet0: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
	options=8<VLAN_MTU>
	ether 02:f2:2e:f9:94:0b
	inet 192.168.40.138 netmask 0xffffff00 broadcast 192.168.40.255
	groups: epair
	media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
	status: active
	nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

We add a user to check SSH connectivity:

adduser


Networking-VNET-DHCP:

If we want to use DHCP, the only option is the VNET interfaces, the same configuration as VNET must be followed.

vi /etc/devfs.rules

[bastille_vnet=13]
add path 'bpf*' unhide

We restart devd:

service devd restart

We disable traffic filtering on bridges, so each VNET jail will perform its own filtering:

vi /etc/sysctl.conf

# Bastille:
net.link.bridge.pfil_bridge=0  # Packet filter on the bridge interface
net.link.bridge.pfil_onlyip=0  # Only pass IP packets when pfil is enabled
net.link.bridge.pfil_member=0  # Packet filter on the member interface
sysctl net.link.bridge.pfil_bridge=0
sysctl net.link.bridge.pfil_onlyip=0
sysctl net.link.bridge.pfil_member=0

We just need to create the jail with the IP 0.0.0.0:

bastille create -V jailVnet 13.1-RELEASE 0.0.0.0 nfe0

We can see an interface e0b_bastille0:

bastille console jailVnet

ifconfig

lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
        options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
        inet 127.0.0.1 netmask 0xff000000
        groups: lo
        nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
pflog0: flags=0<> metric 0 mtu 33160
        groups: pflog
e0b_bastille0: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=8<VLAN_MTU>
        ether 0e:20:98:eb:c7:e9
        hwaddr 02:f8:eb:0f:50:0b
        groups: epair
        media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

In which we can request an IP manually:

dhclient e0b_bastille0

DHCPREQUEST on e0b_bastille0 to 255.255.255.255 port 67
DHCPACK from 192.168.69.200
bound to 192.168.69.205 -- renewal in 43200 seconds.

Or through this crontab, as I have been unable to configure it through the operating system (RC) configuration:

crontab -e

@reboot /sbin/dhclient e0b_bastille0

In all cases, the interface will be in the following state:

ifconfig e0b_bastille0

e0b_bastille0: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=8<VLAN_MTU>
        ether 0e:20:98:eb:c7:e9
        hwaddr 02:f8:eb:0f:50:0b
        inet 192.168.69.205 netmask 0xffffff00 broadcast 192.168.69.255
        groups: epair
        media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

And the routes:

netstat -nr

Routing tables

Internet:
Destination        Gateway            Flags     Netif Expire
default            192.168.69.200     UGS    e0b_bast

Port forwarding:

Using the rdr command, we can redirect ports to the Nateadas jails:

Usage: bastille rdr TARGET [clear] | [list] | [tcp <host_port> <jail_port>] | [udp <host_port> <jail_port>]

Bastille uses a PF anchor, so the rules can be dynamic in real-time:

bastille rdr NATJail tcp 2001 22

We check the jail’s redirects:

bastille rdr NATJail list

rdr pass on bge1 inet proto tcp from any to any port = 2001 -> 10.17.89.50 port 22
bastille list -a
JID         State  IP Address      Published Ports  Hostname    Release          Path
HAProxy     Up     192.168.40.198  -                HAProxy.alfaexploit.com           13.1-RELEASE-p5  /usr/local/bastille/jails/HAProxy/root
NATJail     Up     10.17.89.50     tcp/2001:22      NATJail     13.1-RELEASE-p5  /usr/local/bastille/jails/NATJail/root
aliasJail   Up     192.168.40.136  -                aliasJail   13.1-RELEASE-p5  /usr/local/bastille/jails/aliasJail/root
aliasJail2  Up     192.168.40.139  -                aliasJail2  13.1-RELEASE-p5  /usr/local/bastille/jails/aliasJail2/root
jailVnet2   Up     192.168.40.138  -                jailVnet2   13.1-RELEASE-p5  /usr/local/bastille/jails/jailVnet2/root
nginx       Up     192.168.40.140  -                nginx       13.1-RELEASE-p5  /usr/local/bastille/jails/nginx/root
thickJail   Up     192.168.40.190  -                thickJail   13.1-RELEASE-p5  /usr/local/bastille/jails/thickJail/root

If we want to remove the redirects of a jail:

bastille rdr NATJail clear


Final firewall script

In my case, the final firewall script would be as follows:

vi /etc/pf.conf

ext_if = "nfe0"

set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo

table <badguys> persist
table <jails> persist

nat on $ext_if from <jails> to any -> ($ext_if:0)
rdr-anchor "rdr/*"

antispoof for $ext_if inet

block log all
pass out quick

pass in proto tcp to 192.168.69.2 port 7777

# SMTP -> HellStorm
pass in proto tcp to 192.168.69.17 port 25

# STATS/HTTP/HTTPS -> Atlas
pass in proto tcp from 192.168.69.4 to 192.168.69.19 port 8404
pass in proto tcp to 192.168.69.19 port 80
pass in proto tcp to 192.168.69.19 port 443

# Continue attending badguys in HA-Proxy, we have a surprise for them:
pass in quick proto tcp from <badguys> to 192.168.69.19 port 80
pass in quick proto tcp from <badguys> to 192.168.69.19 port 443

# Xbox -> Paradox
pass in proto tcp from 192.168.69.196 to 192.168.69.18 port 80
# TARS -> Paradox
pass in proto tcp from 192.168.69.198 to 192.168.69.18 port 80
# Garrus -> Paradox, testing purpose
pass in proto tcp from 192.168.69.4 to 192.168.69.18 port 80

# HACKERS script
pass in proto tcp to 192.168.69.2 port 6666

pass in proto tcp to any port 22

# Garrus -> Flatland testing
pass in proto tcp from 192.168.69.4 to 192.168.69.24 port 80

# Block all traffic from badguys, except 80,443 that was allowed previously:
block in from <badguys>

Types of jails

Bastille allows creating jails of several different types:

    -E | --empty  -- Creates an empty container, intended for custom jail builds (thin/thick/linux or unsupported).
    -L | --linux  -- This option is intended for testing with Linux jails, this is considered experimental.
    -T | --thick  -- Creates a thick container, they consume more space as they are self contained and independent.

But the most commonly used are Thin (default mode) and Thick:

  • Thin:

    bastille create thinJail 13.1-RELEASE 192.168.40.190 bge1

    • All jails share a base in RO mode.
    • When the RELEASE is updated, all jails that share the base are updated.
    • Recommended for jails that are not planned to be updated from one major RELEASE to another, but only within the same RELEASE.
  • Thick:

    bastille create -T thickJail 13.1-RELEASE 192.168.40.190 bge1

    • Each jail has a copy of the RELEASE, they are updated independently and the entire file system is RW.
    • Recommended for jails that will be updated between major RELEASEs over time.

It is possible to convert a Thin jail to Thick, but we must bear in mind that the process is irreversible, that is, it is NOT possible to switch from Thick to Thin:

bastille stop aliasJail2
bastille convert aliasJail2

The only way to know if it is a Thin or Thick jail is to check if the base is mounted:

df -Th|grep nullfs|grep releases

/usr/local/bastille/releases/13.1-RELEASE  nullfs    130G    488M    129G     0%    /usr/local/bastille/jails/aliasJail/root/.bastille
/usr/local/bastille/releases/13.1-RELEASE  nullfs    130G    488M    129G     0%    /usr/local/bastille/jails/jailVnet2/root/.bastille
/usr/local/bastille/releases/13.1-RELEASE  nullfs    130G    488M    129G     0%    /usr/local/bastille/jails/natJail/root/.bastille
/usr/local/bastille/releases/13.1-RELEASE  nullfs    130G    488M    129G     0%    /usr/local/bastille/jails/nginx/root/.bastille

Viewing jails/releases/templates/logs

List of started jails:

bastille list jail

aliasJail
aliasJail2
jailVnet2
natJail

List of started jails with more information:

bastille list

 JID             IP Address      Hostname                      Path
 aliasJail       192.168.40.136  aliasJail                     /usr/local/bastille/jails/aliasJail/root
 natJail         10.17.89.50     natJail                       /usr/local/bastille/jails/natJail/root
 jailVnet2                       jailVnet2                     /usr/local/bastille/jails/jailVnet2/root

To see all jails and their details including the VNET jail IPs:

bastille list -a

 JID        State  IP Address      Published Ports  Hostname   Release          Path
 aliasJail  Up     192.168.40.136  -                aliasJail  13.1-RELEASE-p5  /usr/local/bastille/jails/aliasJail/root
 jailVnet2  Up     192.168.40.138  -                jailVnet2  13.1-RELEASE-p5  /usr/local/bastille/jails/jailVnet2/root
 natJail    Up     10.17.89.50     -                natJail    13.1-RELEASE-p5  /usr/local/bastille/jails/natJail/root

List of releases:

bastille list release

13.1-RELEASE

List of templates:

bastille list template

/usr/local/bastille/templates

List of jail log files:

bastille list log

/var/log/bastille/aliasJail_console.log
/var/log/bastille/aliasJail2_console.log-2022-12-18
/var/log/bastille/jailVnet2_console.log
/var/log/bastille/natJail_console.log
/var/log/bastille/jailVnet_console.log-2022-12-17

Templates

Templates are a way of creating “recipes”, similar to Ansible but for Bastille jails. In these templates, we must indicate the steps to be performed on the jail, for example, install X package, start a certain service, etc.
https://gitlab.com/bastillebsd-templates
https://docs.bastillebsd.org/en/latest/chapters/template.html

As an example, we download the Nginx template:

Cloning into '/usr/local/bastille/templates/bastillebsd-templates/nginx'...
warning: redirecting to https://gitlab.com/bastillebsd-templates/nginx.git/
remote: Enumerating objects: 54, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 54 (delta 2), reused 1 (delta 0), pack-reused 42
Receiving objects: 100% (54/54), 7.53 KiB | 3.76 MiB/s, done.
Resolving deltas: 100% (9/9), done.
Detected Bastillefile hook.
[Bastillefile]:
PKG nginx
CP usr /
SYSRC nginx_enable=YES
CMD nginx -t
SERVICE nginx restart

Template ready to use.

Now we see that we have one more template:

bastille list template

/usr/local/bastille/templates
/usr/local/bastille/templates/bastillebsd-templates
/usr/local/bastille/templates/bastillebsd-templates/nginx

We create a jail to which we will apply the template:

bastille create nginx 13.1-RELEASE 192.168.40.140 bge1

We apply the template:

bastille template nginx bastillebsd-templates/nginx

We can see how Nginx is listening:

bastille cmd nginx sockstat -4

[nginx]:
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      
nobody   nginx      19341 7  tcp4   192.168.40.140:80     *:*
nobody   nginx      18809 7  tcp4   192.168.40.140:80     *:*
root     nginx      17697 7  tcp4   192.168.40.140:80     *:*
[nginx]: 0

And we can connect without problems:

curl -I http://192.168.40.140

HTTP/1.1 200 OK
Server: nginx/1.22.0
Date: Sun, 18 Dec 2022 14:40:55 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Mon, 23 May 2022 23:59:19 GMT
Connection: keep-alive
ETag: "628c1fd7-267"
Accept-Ranges: bytes

Of course, we can create our own templates, it does not use any markup language or any exotic language, just real commands.
The best way is to copy an existing one and modify it:

cat /usr/local/bastille/templates/bastillebsd-templates/nginx/Bastillefile

PKG nginx
CP usr /
SYSRC nginx_enable=YES
CMD nginx -t
SERVICE nginx restart

Currently supported template hooks are: CMD, CP, INCLUDE, LIMITS, MOUNT, PKG, RDR, SERVICE, SYSRC:

HOOK Format Example
CMD /bin/sh command /usr/bin/chsh -s /usr/local/bin/zsh
CP path(s) etc root usr (one per line)
INCLUDE template path/URL http://TEMPLATE_URL or project/path
LIMITS resource value memoryuse 1G
MOUNT fstab syntax /host/path container/path nullfs ro 0 0
PKG port/pkg name(s) vim-console zsh git-lite tree htop
RDR tcp port port tcp 2200 22 (hostport jailport)
SERVICE service command ’nginx start’ OR ‘postfix reload’
SYSRC sysrc command(s) nginx_enable=YES

A very interesting aspect of templates is that they are not limited to jails created by Bastille, but can also be applied to any system, whether physical, virtual machine, or jail, using a software called Rocinante . This way, the recipes we have in template format will be reusable:

pkg install rocinante

We install Nginx using Rocinante:

rocinante template /usr/local/bastille/templates/bastillebsd-templates/nginx


Export/Import Jails

When we export a jail, we are generating a compressed image that can then be imported into another system. This way, we can move jails between hosts and perform backups quickly and easily.
The export command works on both UFS and ZFS:

  • UFS: The jail must be stopped, and only supports txz.
  • ZFS: It will take a snapshot and extract the compressed file from it without the need to stop the jail.
Usage:  bastille export | option(s) | TARGET | PATH
     --gz       -- Export a ZFS jail using GZIP(.gz) compressed image.
-r | --raw      -- Export a ZFS jail to an uncompressed RAW image.
-s | --safe     -- Safely stop and start a ZFS jail before the exporting process.
     --tgz      -- Export a jail using simple .tgz compressed archive instead.
     --txz      -- Export a jail using simple .txz compressed archive instead.
-v | --verbose  -- Be more verbose during the ZFS send operation.
     --xz       -- Export a ZFS jail using XZ(.xz) compressed image.

We export a jail as an example:

bastille export --tgz aliasJail

Exporting 'aliasJail' to a compressed .tgz archive...
 84.5%
Exported '/usr/local/bastille/backups/aliasJail_2022-12-27-121707.tgz' successfully.

We can see the size of the backup:

du -h /usr/local/bastille/backups/aliasJail_2022-12-27-121707.tgz

1.1M	/usr/local/bastille/backups/aliasJail_2022-12-27-121707.tgz

We can also get a list of backups. The subcommands export/import/backup are equivalent:

bastille list export
bastille list import
bastille list backup

aliasJail_2022-12-27-121707.tgz

The restoration is done with the same name, so to restore, we first delete the original jail:

bastille stop aliasJail
bastille destroy aliasJail
bastille import /usr/local/bastille/backups/aliasJail_2022-12-27-121707.tgz


Mounting Directories

Using the mount command, we can mount directories from the parent host into the jail itself:

Usage: bastille mount TARGET host_path container_path [filesystem_type options dump pass_number]

We set up a test directory:

mkdir /mnt/storage
bastille mount aliasJail2 /mnt/storage mnt/storage nullfs rw 0 0

[aliasJail2]:
Added: /mnt/storage /usr/local/bastille/jails/aliasJail2/root/mnt/storage nullfs rw 0 0

We can see how the jail configuration now has an additional entry:

cat /usr/local/bastille/jails/aliasJail2/fstab

/usr/local/bastille/releases/13.1-RELEASE /usr/local/bastille/jails/aliasJail2/root/.bastille nullfs ro 0 0
/mnt/storage /usr/local/bastille/jails/aliasJail2/root/mnt/storage nullfs rw 0 0

We create a file from the parent:

touch /mnt/storage/AA

We access the jail and verify that the file is indeed visible:

bastille console aliasJail2
ls -al /mnt/storage/

total 2
drwxr-xr-x  2 root  wheel  3 Dec 18 16:16 .
drwxr-xr-x  3 root  wheel  3 Dec 18 16:15 ..
-rw-r--r--  1 root  wheel  0 Dec 18 16:16 AA

To unmount it, it is as simple as executing the umount command:

bastille umount aliasJail2 /mnt/storage

[aliasJail2]:
Unmounted: /mnt/storage

NOTE: The umount command will NOT remove the entry from the file /usr/local/bastille/jails/JAIL_NAME/fstab, but it will no longer be available inside the jail.


Useful commands

Login with another user:

bastille console aliasJail kr0m

We can directly install software:

bastille pkg aliasJail install -y htop
bastille pkg ALL install -y htop

Running top/htop is such a common task that Bastille incorporates a command for it:

note: won’t work if you don’t have htop installed in the container.
bastille top aliasJail
bastille htop aliasJail

To delete a jail, first stop it, then destroy it:

bastille stop alcatraz
bastille destroy alcatraz

Run sysrc commands in the jail:

bastille sysrc jailVnet2 sshd_enable=YES
bastille sysrc ALL sshd_enable=YES

Manage services:

bastille service nginx nginx status
bastille service nginx nginx configtest
bastille service ALL nginx status

We can copy files directly to the jail:

bastille cp aliasJail /etc/resolv.conf etc/resolv.conf
bastille cp ALL /etc/resolv.conf etc/resolv.conf

Clone a jail:

bastille clone aliasJail aliasJail2 192.168.40.139
bastille start aliasJail2

Rename a jail:

bastille stop natJail
bastille rename natJail NATJail

Restart a jail:

bastille restart natJail
bastille restart ALL


Update

As in any FreeBSD system, the operating system update is divided into two parts, the base system and the installed packages/ports.


Update BASE

The base system can be upgraded within the same version 13.2-p1 -> 13.2-p2, the same major version 13.2 -> 13.3, or another major version 13.2 -> 14.0.

The first step will be to obtain the new version of FreeBSD. To do this, we must bootstrap it and apply the latest security patches.

bastille bootstrap 14.0-RELEASE update

We check the available releases.

bastille list release

13.1-RELEASE
13.2-RELEASE
14.0-RELEASE

When we finish, we can remove the old releases with:

bastille destroy 13.2-RELEASE

NOTE: If any jail depends on binaries compiled from Poudriere, we must prepare Poudriere beforehand .

Thin jail:
If it’s a Thin jail, they share the base, so updating the RELEASE from Bastille will update the base system of all jails that share that RELEASE.
To determine if a jail is Thick/Thin, you can check the file /usr/local/bastille/jails/JAILNAME/jail.conf. If the osrelease line appears, it’s a thin jail.

for JAIL_PATH in /usr/local/bastille/jails/*; do JAIL=$(basename $JAIL_PATH) && echo -n "-- $JAIL: " && grep osrelease $JAIL_PATH/jail.conf >/dev/null 2>/dev/null ; if [ $? -eq 0 ]; then echo "Thin jail" ; else echo "Thick jail"; fi ; done

-- Atlas: Thick jail
-- DataDyne: Thick jail
-- HellStorm: Thick jail
-- MetaCortex: Thick jail
-- PostgreSQL00-test: Thick jail
-- PostgreSQL01-test: Thick jail
-- PostgreSQLBackups-test: Thick jail
-- PostgreSQLRestore-test: Thick jail
-- RECLog: Thick jail
-- thinJail: Thin jail
-- thinJail2: Thin jail

If it’s an update within the same version, simply execute:

bastille update RELEASENAME

If we are going to update to another version outside of the one we are currently on or to another major version, the process will be longer.

Stop the jail:

bastille stop JAILNAME

We update to the new version as indicated in the official documentation :

bastille edit JAILNAME fstab

In this case, we change the path from 13.2 to 14.0:

/usr/local/bastille/releases/13.2-RELEASE /usr/local/bastille/jails/JAILNAME/root/.bastille nullfs ro 0 0
/usr/local/bastille/releases/14.0-RELEASE /usr/local/bastille/jails/JAILNAME/root/.bastille nullfs ro 0 0

Boot the jail:

bastille start JAILNAME

We reinstall all software depending on whether we use binary packages or ports.

bastille console JAILNAME

If we see the following error message, we should change the shell to the default before accessing the jail:

>ld-elf.so.1: Shared object "libncursesw.so.8" not found, required by "bash"
bastille cmd JAILNAME chsh -s /bin/sh
pkg-static upgrade -f
exit
git -C /usr/ports pull
cd /usr/ports
make fetchindex
for PORT in $(pkg info|awk \'{print$1}\'); do PORT_PATH=$(pkg info $PORT|grep Origin|awk \'{print$3}\') && echo PORT: $PORT - $PORT_PATH && cd /usr/ports/$PORT_PATH && export BATCH=\"yes\" && make clean reinstall clean; done
exit

We revert the shell change.

bastille cmd JAILNAME chsh -s /usr/local/bin/bash

We update in case any patches have been released since the last release.

bastille update JAILNAME

We restart the jail to ensure everything works correctly.

bastille stop JAILNAME
bastille start JAILNAME

Thick jail:
On the contrary, if it’s a Thick jail, we’ll need to update each jail independently, but always from Bastille since the jails start at securelevel 2 and don’t allow it from within.

sysctl kern.securelevel

kern.securelevel: 2

To determine if a jail is Thick/Thin, you can check the file /usr/local/bastille/jails/JAILNAME/jail.conf. If the ‘osrelease’ line appears, it’s a thin jail.

for JAIL_PATH in /usr/local/bastille/jails/*; do JAIL=$(basename $JAIL_PATH) && echo -n "-- $JAIL: " && grep osrelease $JAIL_PATH/jail.conf >/dev/null 2>/dev/null ; if [ $? -eq 0 ]; then echo "Thin jail" ; else echo "Thick jail"; fi ; done

-- Atlas: Thick jail
-- DataDyne: Thick jail
-- HellStorm: Thick jail
-- MetaCortex: Thick jail
-- PostgreSQL00-test: Thick jail
-- PostgreSQL01-test: Thick jail
-- PostgreSQLBackups-test: Thick jail
-- PostgreSQLRestore-test: Thick jail
-- RECLog: Thick jail
-- thinJail: Thin jail
-- thinJail2: Thin jail

To update the thick jails, first, we need to know which release version is the current one.

bastille list -a

 JID                     State  IP Address      Published Ports  Hostname                Release           Path
 Atlas                   Up     192.168.69.19   -                Atlas                   13.2-RELEASE-p10  /usr/local/bastille/jails/Atlas/root
 DataDyne                Up     192.168.69.25   -                DataDyne                13.2-RELEASE-p10  /usr/local/bastille/jails/DataDyne/root
 HellStorm               Up     192.168.69.17   -                HellStorm               13.2-RELEASE-p10  /usr/local/bastille/jails/HellStorm/root
 MetaCortex              Up     192.168.69.20   -                MetaCortex              13.2-RELEASE-p10  /usr/local/bastille/jails/MetaCortex/root
 PostgreSQL00-test       Up     192.168.69.26   -                PostgreSQL00-test       13.2-RELEASE-p10  /usr/local/bastille/jails/PostgreSQL00-test/root
 PostgreSQL01-test       Up     192.168.69.27   -                PostgreSQL01-test       13.2-RELEASE-p10  /usr/local/bastille/jails/PostgreSQL01-test/root
 PostgreSQLBackups-test  Up     192.168.69.29   -                PostgreSQLBackups-test  13.2-RELEASE-p10  /usr/local/bastille/jails/PostgreSQLBackups-test/root
 PostgreSQLRestore-test  Up     192.168.69.28   -                PostgreSQLRestore-test  13.2-RELEASE-p10  /usr/local/bastille/jails/PostgreSQLRestore-test/root
 RECLog                  Up     192.168.69.21   -                RECLog                  13.2-RELEASE-p10  /usr/local/bastille/jails/RECLog/root
 thinJail                Up     192.168.40.190  -                thinJail                14.0-RELEASE-p5   /usr/local/bastille/jails/thinJail/root
 thinJail2               Up     192.168.40.191  -                thinJail2               14.0-RELEASE-p5   /usr/local/bastille/jails/thinJail2/root

If it’s an update within the same version, simply execute:

bastille update JAILNAME

If we are going to update to another version outside of the one we are currently on or to another major version, the process will be longer. The documentation currently only covers how to update thin jails as of today (03/18/2024), the procedure for updating a thick jail has been deduced from the Bastille man pages and the help command.

man bastille
bastille upgrade
Usage: bastille upgrade release newrelease | target newrelease | target install | [force]

Update it:

bastille upgrade JAILNAME 14.0-RELEASE

Apply the update:

bastille upgrade JAILNAME install

Reboot the jail:

bastille stop JAILNAME
bastille start JAILNAME

Continue with the update:

bastille upgrade JAILNAME install

We reinstall all software depending on whether we use binary packages or ports.

bastille console JAILNAME

If we see the following error message, we should change the shell to the default before accessing the jail:

>ld-elf.so.1: Shared object "libncursesw.so.8" not found, required by "bash"
bastille cmd JAILNAME chsh -s /bin/sh
pkg-static upgrade -f
exit
git -C /usr/ports pull
cd /usr/ports
make fetchindex
for PORT in $(pkg info|awk \'{print$1}\'); do PORT_PATH=$(pkg info $PORT|grep Origin|awk \'{print$3}\') && echo PORT: $PORT - $PORT_PATH && cd /usr/ports/$PORT_PATH && export BATCH=\"yes\" && make clean reinstall clean; done
exit

We revert the shell change.

bastille cmd JAILNAME chsh -s /usr/local/bin/bash

We conclude the update.

bastille upgrade JAILNAME install

We update in case any patches have been released since the last release.

bastille update JAILNAME

We restart the jail to ensure everything works correctly.

bastille stop JAILNAME
bastille start JAILNAME


Update PACKAGES/PORTS

Update binary packages of a jail:

bastille pkg aliasJail upgrade
bastille pkg ALL upgrade

bastille pkg aliasJail autoremove
bastille pkg ALL autoremove

Update ports of a jail:

bastille console JAILNAME
git -C /usr/ports pull
for PORT in $(pkg info|awk '{print$1}'); do PORT_PATH=$(pkg info $PORT|grep Origin|awk '{print$3}') && echo PORT: $PORT - $PORT_PATH && cd /usr/ports/$PORT_PATH && export BATCH="yes" && make clean reinstall clean; done


Update script

Here is my update script in case it is useful to someone, keep in mind that all my jails are Thick type:
cecho.sh script

#!/usr/local/bin/bash
source /root/.scripts/cecho.sh

function sendTelegram {
	message=${@:1}
	curl -s -X POST https://api.telegram.org/bot5XXXXXXXXXXXXXXXX/sendMessage -d chat_id=XXXXXXXXXX -d text="$message"
}

clear
cecho "Cyan" "<== Bastille Jail updater by kr0m ==>"

echo ""
echo ""
FREEBSD_VERSION=$(freebsd-version -u|awk -F "-RELEASE" '{print$1}')
cecho "Cyan" ">> Updating Bastille RELEASE: $FREEBSD_VERSION"
bastille update $FREEBSD_VERSION-RELEASE

echo ""
cecho "Green" "----------------------------------------------------"

DATE=$(date +%d_%m_%Y-%H:%M:%S)
for JAIL in $(bastille list jail); do
    cecho "Cyan" ">> Snapshooting: $JAIL@$DATE"
    bastille zfs $JAIL snap UPDATE-$DATE
done

echo ""
cecho "Green" "----------------------------------------------------"

cecho "Cyan" ">> Snapshots check:"
for JAIL in $(bastille list jail); do
    N=$(bastille zfs $JAIL df|grep UPDATE|awk -F '@' '{print$2}'|awk '{print$1}'|sort -u|wc -l|awk '{print$1}')
    if [ $N -le 1 ]; then
        continue
    fi
    let N=$N-1
    for SNAPSHOT_TOREMOVE in $(bastille zfs $JAIL df|grep UPDATE|grep -v "root@UPDATE"|awk -F '@' '{print$2}'|awk '{print$1}'|head -n $N); do
        cecho "Cyan" ">> Clearing $JAIL@$SNAPSHOT_TOREMOVE"
        zfs destroy zroot/bastille/jails/$JAIL@$SNAPSHOT_TOREMOVE
        zfs destroy zroot/bastille/jails/$JAIL/root@$SNAPSHOT_TOREMOVE
    done
done

echo ""
cecho "Green" "----------------------------------------------------"

for JAIL in $(bastille list jail); do
    cecho "Cyan" ">> Updating base: $JAIL"
    bastille update $JAIL
done

echo ""
cecho "Green" "----------------------------------------------------"

cecho "Cyan" ">> Updating ALL jails packages"
bastille cmd ALL env ASSUME_ALWAYS_YES=YES pkg upgrade

echo ""
cecho "Green" "----------------------------------------------------"

cecho "Cyan" ">> AutoRemoving ALL jails packages"
bastille cmd ALL env ASSUME_ALWAYS_YES=YES pkg autoremove

echo ""
cecho "Green" "----------------------------------------------------"

cecho "Cyan" ">> Updating ALL jails PIP"
bastille cmd ALL pip install --upgrade pip

echo ""
cecho "Green" "----------------------------------------------------"

cecho "Cyan" ">> Updating SpamAssasin: HellStorm"
bastille cmd HellStorm sa-update -v
bastille service HellStorm sa-spamd restart

cecho "Cyan" ">> Restarting Mail services: HellStorm"
bastille service HellStorm sendmail restart
bastille service HellStorm dovecot restart

echo ""
cecho "Green" "----------------------------------------------------"

cecho "Cyan" ">> Visit: https://mail.alfaexploit.com/?admin#/about"
echo ""
cecho "Cyan" ">> Checking if Mail system still works: HellStorm"
DATE=$(date "+%d/%m/%Y %H:%M:%S")
echo -e "Subject: HellStorm updated: $DATE" | sendmail -f root@alfaexploit.com kr0m@alfaexploit.com
sleep 10
grep -r "HellStorm updated: $DATE" /usr/local/bastille/jails/HellStorm/root/var/mail/kr0m 1>/dev/null
if [ $? -ne 0 ]; then
	cecho "Red" "++ ERROR: Mail system is not working"
	MESSAGE="ERROR: Mail system is not working"
	sendTelegram $MESSAGE
else
	cecho "Cyan" ">> Mail system is working"
fi

echo ""
cecho "Green" "----------------------------------------------------"

echo ""
echo ""
cecho "Cyan" ">> Updating owasp-modsecurity: MetaCortex"
bastille cmd MetaCortex bash -c 'cd /usr/local/owasp-modsecurity-crs/ && git pull'

echo ""
cecho "Cyan" ">> Updating Hugo theme: MetaCortex"
bastille cmd MetaCortex bash -c "su -l kr0m -c 'cd /home/kr0m/AlfaExploit/alfaexploit_zzo/themes/zzo && git submodule update --remote --merge'"

echo ""
cecho "Cyan" ">> Redeploying Alfaexploit: MetaCortex"
bastille cmd MetaCortex bash -c "su -l kr0m -c 'cd /home/kr0m/AlfaExploit/alfaexploit_zzo/ && hugo && rm -rf /usr/local/www/alfaexploit/* && cp -r public/* /usr/local/www/alfaexploit/'"

echo ""
cecho "Green" "----------------------------------------------------"

cecho "Cyan" ">> Security check:"
bastille cmd ALL pkg audit -F

Binary packages security

Check for security issues in a jail’s packages:

bastille cmd aliasJail pkg audit -F
bastille cmd ALL pkg audit -F


Limiting resources

Using the limits command, we can limit the resources available to a jail. Bastille relies on rctl for this.
We enable rctl:

echo ‘kern.racct.enable=1’ » /boot/loader.conf
shutdown -r now

bastille limits aliasJail memoryuse 1g

We can check the resources to limit at this link :

RESOURCES
        cputime            CPU time, in seconds
        datasize           data size, in bytes
        stacksize          stack size, in bytes
        coredumpsize       core dump size, in bytes
        memoryuse          resident set size, in bytes
        memorylocked       locked memory, in bytes
        maxproc            number of processes
        openfiles          file descriptor table size
        vmemoryuse         address space limit, in bytes
        pseudoterminals    number of PTYs
        swapuse            swap space that may be reserved or used, in bytes
        nthr               number of threads
        msgqqueued         number of queued SysV messages
        msgqsize           SysV message queue size, in bytes
        nmsgq              number of SysV message queues
        nsem               number of SysV semaphores
        nsemop             number of SysV semaphores modified in a single
                           semop(2) call
        nshm               number of SysV shared memory segments
        shmsize            SysV shared memory size, in bytes
        wallclock          wallclock time, in seconds
        pcpu               %CPU, in percents of a single CPU core
        readbps            filesystem reads, in bytes per second
        writebps           filesystem writes, in bytes per second
        readiops           filesystem reads, in operations per second
        writeiops          filesystem writes, in operations per second

If we want to check the limits:

cat /usr/local/bastille/jails/aliasJail/rctl.conf

jail:aliasJail:memoryuse:deny=1g/jail
jail:aliasJail:memoryuse:log=1g/jail

Or for all jails using the command:

bastille list limits

jail:aliasJail:memoryuse:log=1024M
jail:aliasJail:memoryuse:deny=1024M

If we want to remove them:

rm /usr/local/bastille/jails/aliasJail/rctl.conf
rctl -r jail:aliasJail:memoryuse:log=1024M
rctl -r jail:aliasJail:memoryuse:deny=1024M

We can also remove all restrictions:

rm /usr/local/bastille/jails/*/rctl.conf
rctl -r :

NOTE: Keep in mind that through ZFS parameters we can also limit certain aspects such as disk quota.


ZFS Parameters: Quotas/Snapshots/df

The zfs command allows us to assign any zfs parameter to the dataset of a specific jail, for example, we can limit the available disk space:

bastille zfs aliasJail set quota=1G

If we check the available space, we will see that it is close to 1G:

bastille zfs aliasJail df

[aliasJail]:
NAME                                  USED  AVAIL     REFER  MOUNTPOINT                                COMPRESS        RATIO
zroot/bastille/jails/aliasJail       88.2M   936M      108K  /usr/local/bastille/jails/aliasJail       lz4             1.75x
zroot/bastille/jails/aliasJail/root  88.1M   936M     88.1M  /usr/local/bastille/jails/aliasJail/root  lz4             1.75x

We can also assign values to ZFS parameters massively:

bastille zfs ALL set quota=1G
bastille zfs ALL df

Bastille allows us to create snapshots:

bastille zfs aliasJail snap SNAP0

To check them:

bastille zfs aliasJail df

[aliasJail]:
NAME                                        USED  AVAIL     REFER  MOUNTPOINT                                COMPRESS        RATIO
zroot/bastille/jails/aliasJail             88.2M   936M      108K  /usr/local/bastille/jails/aliasJail       lz4             1.75x
zroot/bastille/jails/aliasJail@SNAP0          0B      -      108K  -                                         -               1.02x
zroot/bastille/jails/aliasJail/root        88.1M   936M     88.1M  /usr/local/bastille/jails/aliasJail/root  lz4             1.75x
zroot/bastille/jails/aliasJail/root@SNAP0     0B      -     88.1M  -                                         -               1.75x

This command can also be applied to all jails:

bastille zfs ALL snap SNAP0
bastille zfs ALL df

If we want to check the date on which the snapshots were created, we must do it directly through ZFS commands:

zfs list -r -t snapshot -o name,creation zroot/bastille/jails/Paradox

NAME                                                          CREATION
zroot/bastille/jails/Paradox@UPDATE-18_01_2023-22:50:51       Wed Jan 18 22:50 2023
zroot/bastille/jails/Paradox/root@UPDATE-18_01_2023-22:50:51  Wed Jan 18 22:50 2023

Deleting snapshots is a manual process, first, we must locate the name of the snapshot using the df command and then execute the ZFS commands:

zfs destroy zroot/bastille/jails/aliasJail@SNAP0
zfs destroy zroot/bastille/jails/aliasJail/root@SNAP0

As there are two datasets per jail, a quick way to delete both would be to use the following loop:

for DATASET in $(bastille zfs ALL df|awk ‘{print$1}’|grep ‘@SNAP0’); do echo Deleting SNAPSHOT: $DATASET && zfs destroy $DATASET; done

Restoring snapshots is also a manual task, first, we must locate the name of the snapshot using the df command and then execute the ZFS commands:

zfs rollback -r zroot/bastille/jails/aliasJail@SNAP0
zfs rollback -r zroot/bastille/jails/aliasJail/root@SNAP0

NOTE: Remember that in ZFS, if we have, for example, 5 snapshots (0,1,2,3,4) and we want to revert to the first one (0), snapshots 1,2,3,4 will be lost.


Jails Configuration

We can edit the configuration of a jail using the edit command:

bastille edit aliasJail

aliasJail {
  devfs_ruleset = 4;
  enforce_statfs = 2;
  exec.clean;
  exec.consolelog = /var/log/bastille/aliasJail_console.log;
  exec.start = '/bin/sh /etc/rc';
  exec.stop = '/bin/sh /etc/rc.shutdown';
  host.hostname = aliasJail;
  mount.devfs;
  mount.fstab = /usr/local/bastille/jails/aliasJail/fstab;
  path = /usr/local/bastille/jails/aliasJail/root;
  securelevel = 2;

  interface = bge1;
  ip4.addr = 192.168.40.136;
  ip6 = disable;
}

Or consult/modify specific parameters using the config get/set command:

bastille config aliasJail get ip4.addr

192.168.40.136
bastille config aliasJail set ip4.addr 192.168.40.140
bastille restart aliasJail

Migration from IOCage to Bastille

It should be noted that jail migration has caused problems, at least when migrating an HAProxy. Refer to the troubleshooting section in this same article for more details.

Stop the jail in IOCage:

iocage stop HAProxy

Export it:

iocage export HAProxy

Copy the image to the Bastille server:

scp /zroot/iocage/images/HAProxy_2022-12-22.* BASTILLE:

Import the image:

bastille import /home/kr0m/HAProxy_2022-12-22.zip

We can see that there is a new jail called HAProxy:

bastille list -a

 JID         State  IP Address          Published Ports  Hostname    Release          Path
 HAProxy     Down   nfe0|192.168.69.11  -                HAProxy     13.1-RELEASE-p5  /usr/local/bastille/jails/HAProxy/root
 aliasJail   Up     192.168.40.136      -                aliasJail   13.1-RELEASE-p5  /usr/local/bastille/jails/aliasJail/root
 aliasJail2  Up     192.168.40.139      -                aliasJail2  13.1-RELEASE-p5  /usr/local/bastille/jails/aliasJail2/root
 jailVnet2   Up     192.168.40.138      -                jailVnet2   13.1-RELEASE-p5  /usr/local/bastille/jails/jailVnet2/root
 natJail     Up     10.17.89.50         -                natJail     13.1-RELEASE-p5  /usr/local/bastille/jails/natJail/root
 nginx       Up     192.168.40.140      -                nginx       13.1-RELEASE-p5  /usr/local/bastille/jails/nginx/root

Depending on the network configuration we want to use in the jail, we will make the relevant adjustments:
NAT:

  interface = bastille0;
  ip4.addr = 10.17.89.50;

ALIAS:

  interface = bge1;
  ip4.addr = 192.168.40.136;

BRIDGE:

  vnet;
  vnet.interface = "e0b_jailVnet2";
  exec.prestart += "ifconfig epair0 create";
  exec.prestart += "ifconfig bridge0 addm epair0a";
  exec.prestart += "ifconfig epair0a up name e0a_jailVnet2";
  exec.prestart += "ifconfig epair0b up name e0b_jailVnet2";
  exec.poststop += "ifconfig bridge0 deletem e0a_jailVnet2";
  exec.poststop += "ifconfig e0a_jailVnet2 destroy";

In bridge mode, we will also have to perform the configuration inside the jail.

In my case, it is a jail with an IP address configured as an alias:

vi /usr/local/bastille/jails/HAProxy/jail.conf

interface = bge1;
ip4.addr = 192.168.40.198;

Start the jail:

bastille start HAProxy

Now it has the correct IP address:

bastille list -a

 JID         State  IP Address      Published Ports  Hostname    Release          Path
 HAProxy     Up     192.168.40.198  -                HAProxy.alfaexploit.com           13.1-RELEASE-p5  /usr/local/bastille/jails/HAProxy/root
 aliasJail   Up     192.168.40.136  -                aliasJail   13.1-RELEASE-p5  /usr/local/bastille/jails/aliasJail/root
 aliasJail2  Up     192.168.40.139  -                aliasJail2  13.1-RELEASE-p5  /usr/local/bastille/jails/aliasJail2/root
 jailVnet2   Up     192.168.40.138  -                jailVnet2   13.1-RELEASE-p5  /usr/local/bastille/jails/jailVnet2/root
 natJail     Up     10.17.89.50     -                natJail     13.1-RELEASE-p5  /usr/local/bastille/jails/natJail/root
 nginx       Up     192.168.40.140  -                nginx       13.1-RELEASE-p5  /usr/local/bastille/jails/nginx/root

Bash autoCompletion

We install the software:

pkg install bash-completion

We tell Bash to load the autocompletion:

vi .bashrc

[[ $PS1 && -f /usr/local/share/bash-completion/bash_completion.sh ]] && source /usr/local/share/bash-completion/bash_completion.sh

The autocompletion script would be the following:

vi /usr/local/share/bash-completion/completions/bastille

_bastille () {
    local cur
    # cur: Current word where cursor is located
    cur=${COMP_WORDS[COMP_CWORD]}

    # Bastille first level commands:
    if [ ${#COMP_WORDS[@]} -eq 2 ]; then
        # Show only the options that start with $cur
        COMPREPLY=($(compgen -W "$(bastille --help|grep '^  '|grep -v 'bastille command TARGET \[args\]'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')" -- $cur))
        return 0
    # Bastille second level commands:
    elif [ ${#COMP_WORDS[@]} -eq 3 ]; then
        case ${COMP_WORDS[1]} in
            cmd)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            clone)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            config)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            console)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            convert)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            cp)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            create)
                CREATE_OPTIONS=$(bastille create 2>/dev/null|grep -v 'Options:'|awk '{print$1"\n"$3}'|sed '/^[[:space:]]*$/d')
                COMPREPLY=($(compgen -W "$CREATE_OPTIONS" -- $cur))
            ;;
            destroy)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            edit)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            export)
                EXPORT_OPTIONS=$(bastille export 2>/dev/null|grep -v 'Options:'|grep -v 'Note: If no export option specified, the container should be redirected to standard output.'|grep -v '|'|sed '/^[[:space:]]*$/d'|awk '{print$1}')
                EXPORT_OPTIONS=$EXPORT_OPTIONS" "$(bastille export 2>/dev/null|grep -v 'Options:'|grep -v 'Note: If no export option specified, the container should be redirected to standard output.'|grep '|'|sed '/^[[:space:]]*$/d'|awk '{print$1"\n"$3}')
                COMPREPLY=($(compgen -W "$EXPORT_OPTIONS" -- $cur))
            ;;
            htop)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            import)
                IMPORT_OPTIONS=$(bastille import 2>/dev/null|grep -v 'Options:'|grep -v 'Tip: If no option specified, container should be imported from standard input.'|awk '{print$1"\n"$3}'|sed '/^[[:space:]]*$/d')
                COMPREPLY=($(compgen -W "$IMPORT_OPTIONS" -- $cur))
            ;;
            limits)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            list)
                OPTIONS_LIST='-j -a release template log limit import export backup'
                COMPREPLY=($(compgen -W "$OPTIONS_LIST" -- $cur))
            ;;
            mount)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            pkg)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            rdr)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            rename)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            restart)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            service)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            start)
                JAILS=$(bastille list -a|grep 'Down'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            stop)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            sysrc)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            template)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            top)
                JAILS=$(bastille list|grep -v 'JID             IP Address      Hostname                      Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            umount)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            update)
                UPDATE_LIST=$(bastille list release)
                UPDATE_LIST_TMP=$(bastille list container)
		UPDATE_LIST=$UPDATE_LIST" "$UPDATE_LIST_TMP
                UPDATE_LIST_TMP=$(for DIR in $(ls -l /usr/local/bastille/templates|grep -v default|awk '{print$9}'); do TEMPLATENAME=$(ls -l /usr/local/bastille/templates/$DIR|awk '{print$9}') && echo "$DIR/$TEMPLATENAME"; done)
		UPDATE_LIST=$UPDATE_LIST" "$UPDATE_LIST_TMP
                COMPREPLY=($(compgen -W "$UPDATE_LIST" -- $cur))
            ;;
            upgrade)
                UPGRADE_LIST=$(bastille list release)
                COMPREPLY=($(compgen -W "$UPGRADE_LIST" -- $cur))
            ;;
            verify)
                VERIFY_LIST=$(bastille list release)
                VERIFY_LIST_TMP=$(for DIR in $(ls -l /usr/local/bastille/templates|grep -v default|awk '{print$9}'); do TEMPLATENAME=$(ls -l /usr/local/bastille/templates/$DIR|awk '{print$9}') && echo "$DIR/$TEMPLATENAME"; done)
		VERIFY_LIST=$VERIFY_LIST" "$VERIFY_LIST_TMP
                COMPREPLY=($(compgen -W "$VERIFY_LIST" -- $cur))
            ;;
            zfs)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
        esac
    # Bastille third level commands:
    elif [ ${#COMP_WORDS[@]} -eq 4 ]; then
        case ${COMP_WORDS[1]} in
            config)
                CONFIG_COMMANDS="set get"
                COMPREPLY=($(compgen -W "$CONFIG_COMMANDS" -- $cur))
            ;;
            export)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;; 
            pkg)
                JAILS=$(bastille list -a|grep -v 'JID         State  IP Address      Published Ports  Hostname    Release          Path'|sed 's/^[[:space:]]*//g'|sed 's/[[:space:]].*//g')
                COMPREPLY=($(compgen -W "$JAILS" -- $cur))
            ;;
            template)
                TEMPLATE_LIST=$(for DIR in $(ls -l /usr/local/bastille/templates|grep -v default|awk '{print$9}'); do TEMPLATENAME=$(ls -l /usr/local/bastille/templates/$DIR|awk '{print$9}') && echo "$DIR/$TEMPLATENAME"; done)
                COMPREPLY=($(compgen -W "$TEMPLATE_LIST" -- $cur))
            ;;
            zfs)
                ZFS_LIST='set get snap'
                COMPREPLY=($(compgen -W "$ZFS_LIST" -- $cur))
            ;;
        esac
    else
        return 0
    fi
}

complete -F _bastille bastille

Jails auto start

By default, it starts all of them:

: ${bastille_list:="ALL"}

We can define a list of jails to start:

sysrc bastille_list=‘nginx aliasJail’

To disable the auto start of all jails, it is best to disable the entire service:

sysrc bastille_enable=‘NO’

If we want to start them all again, we just have to remove the bastille_list parameter:

sysrc -x bastille_list


Linux Jails

This functionality is in experimental phase, some things fail and the system works partially so I do not recommend under any circumstances to put a Linux in production through this system, if we need a Linux by force it is preferable to mount it through vm-bhyve since being a complete virtual machine it should not give any problem, in case of using Bastille/VM-Bhyve I recommend having two physical network interfaces to be able to have a bridge for the jails and another for the virtual machines and thus avoid problems between softwares.

Some drawbacks found in a basic test are:

  • Only allows configuring the network in NAT/Alias mode

  • dmesg does not work:

root@aliasLinux:~# dmesg 
dmesg: read kernel buffer failed: Operation not permitted
  • Some network tools fail:
root@aliasLinux:~# netstat -nputa
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
netstat: no support for `AF INET (tcp)' on this system.
  • Reboot fails:
root@aliasLinux:~# reboot
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
Failed to talk to init daemon.
  • SSH access does not work due to something related to /dev/pts:
ssh kr0m@192.168.40.141 -p22
kr0m@192.168.40.141's password: 
PTY allocation request failed on channel 0
Welcome to Ubuntu 20.04 LTS (GNU/Linux 3.17.0 x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
  • It only supports a very limited number of distributions:
#adding Ubuntu Bionic as valid "RELEASE" for POC @hackacad
ubuntu_bionic|bionic|ubuntu-bionic)
    PLATFORM_OS="Ubuntu/Linux"
    LINUX_FLAVOR="bionic"
    DIR_BOOTSTRAP="Ubuntu_1804"
    ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX}
    debootstrap_release
    ;;
ubuntu_focal|focal|ubuntu-focal)
    PLATFORM_OS="Ubuntu/Linux"
    LINUX_FLAVOR="focal"
    DIR_BOOTSTRAP="Ubuntu_2004"
    ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX}
    debootstrap_release
    ;;
debian_stretch|stretch|debian-stretch)
    PLATFORM_OS="Debian/Linux"
    LINUX_FLAVOR="stretch"
    DIR_BOOTSTRAP="Debian9"
    ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX}
    debootstrap_release
    ;;
debian_buster|buster|debian-buster)
    PLATFORM_OS="Debian/Linux"
    LINUX_FLAVOR="buster"
    DIR_BOOTSTRAP="Debian10"
    ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX}
    debootstrap_release
    ;;
debian_bullseye|bullseye|debian-bullseye)
    PLATFORM_OS="Debian/Linux"
    LINUX_FLAVOR="bullseye"
    DIR_BOOTSTRAP="Debian11"
    ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX}
    debootstrap_release
    ;;

If, despite all the problems, we decide to try Linux jails, the first step is to download the image:

bastille bootstrap focal

It will ask several questions about the necessary configuration in the system for Linux to run, we answer yes to everything:

fdescfs not enabled in /boot/loader.conf, Should I do that for you?  (N|y)
y
Loading kernel module: fdescfs
Loaded fdescfs, id=17
Persisting module: fdescfs
fdescfs_load:  -> YES
linprocfs not enabled in /boot/loader.conf, Should I do that for you?  (N|y)
y
Loading kernel module: linprocfs
Loaded linprocfs, id=18
Persisting module: linprocfs
linprocfs_load:  -> YES
linsysfs not enabled in /boot/loader.conf, Should I do that for you?  (N|y)
y
Loading kernel module: linsysfs
Loaded linsysfs, id=20
Persisting module: linsysfs
linsysfs_load:  -> YES
tmpfs not enabled in /boot/loader.conf, Should I do that for you?  (N|y)
y
Persisting module: tmpfs
tmpfs_load:  -> YES
Loading kernel module: linux
Loaded linux, id=21
Loading kernel module: linux64
Loaded linux64, id=22
linux_enable: NO -> YES
Debootstrap not found. Should it be installed? (N|y)
y

...
...

debootstrap bionic /compat/ubuntu
Bootstrapping Ubuntu/Linux distfiles...
...
...
Bootstrap successful.

Now we can see a new “release”:

bastille list releases

13.1-RELEASE
Ubuntu_2004

We create the jail indicating that it will be of type linux:

bastille create -L aliasLinux focal 192.168.40.141 bge1

We can see that it has started correctly:

bastille list -a

 JID         State  IP Address      Published Ports  Hostname    Release               Path
 HAProxy     Up     192.168.40.198  -                HAProxy.alfaexploit.com           13.1-RELEASE-p5       /usr/local/bastille/jails/HAProxy/root
 NATJail     Up     10.17.89.50     tcp/2001:22      NATJail     13.1-RELEASE-p5       /usr/local/bastille/jails/NATJail/root
 aliasJail   Up     192.168.40.136  -                aliasJail   13.1-RELEASE-p5       /usr/local/bastille/jails/aliasJail/root
 aliasJail2  Up     192.168.40.139  -                aliasJail2  13.1-RELEASE-p5       /usr/local/bastille/jails/aliasJail2/root
 aliasLinux  Up     192.168.40.141  -                aliasLinux  focal (Ubuntu 20.04)  /usr/local/bastille/jails/aliasLinux/root
 jailVnet2   Up     192.168.40.138  -                jailVnet2   13.1-RELEASE-p5       /usr/local/bastille/jails/jailVnet2/root
 nginx       Up     192.168.40.140  -                nginx       13.1-RELEASE-p5       /usr/local/bastille/jails/nginx/root
 thickJail   Up     192.168.40.190  -                thickJail   13.1-RELEASE-p5       /usr/local/bastille/jails/thickJail/root

We access the jail:

bastille console aliasLinux

[aliasLinux]:
Welcome to Ubuntu 20.04 LTS (GNU/Linux 3.17.0 x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

root@aliasLinux:~# uname -a
Linux aliasLinux 3.17.0 FreeBSD 13.1-RELEASE-p3 GENERIC x86_64 x86_64 x86_64 GNU/Linux

root@aliasLinux:~# lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04 LTS
Release:	20.04
Codename:	focal

Troubleshooting:

Bastille works quite well, I have only detected some anomalous behavior very punctually when doing more complicated things.
Logs
An easy way to see if a jail is causing problems is to consult its log file:

tail -f /var/log/bastille/JAILNAME_console.log

Import problems

  • We import a jail that already exists, logically it gives an error.
  • We delete the jail.
  • We import the jail again, this time successfully.
  • We delete the jail, it gives an RO error of the base, a bastille list DOES NOT show the jail.

The problem is that it left remains and this causes problems.

mount |grep aliasJail2

zroot/bastille/jails/aliasJail2 on /usr/local/bastille/jails/aliasJail2 (zfs, local, noatime, nfsv4acls)
zroot/bastille/jails/aliasJail2/root on /usr/local/bastille/jails/aliasJail2/root (zfs, local, noatime, nfsv4acls)
/usr/local/bastille/releases/13.1-RELEASE on /usr/local/bastille/jails/aliasJail2/root/.bastille (nullfs, local, noatime, read-only, nfsv4acls)

We unmount all mount points:

umount /usr/local/bastille/jails/aliasJail2/root/.bastille
umount /usr/local/bastille/jails/aliasJail2/root
umount /usr/local/bastille/jails/aliasJail2

If an umount gives problems, we force it from ZFS:

zfs umount -f /usr/local/bastille/jails/aliasJail2

We delete the directory:

rm -rf /usr/local/bastille/jails/aliasJail2

We destroy the dataset associated with the jail:

zfs destroy -r zroot/bastille/jails/aliasJail2

Import issues from IOCage
While importing IOCage jails to Bastille, we detected a problem. If we restart the parent server, it starts the jails before the network is functioning, causing problems when shutting down the jails:

bastille stop HAProxy

[HAProxy]:
jail: HAProxy: /bin/sh /etc/rc.shutdown: exited on signal 9

If we access the jail, we can see the HAProxy process:

ps aux|grep ha

nobody 22059  0.0  0.5 31152 10956  -  SsJ  08:38   0:00.19 /usr/local/sbin/haproxy -q -f /usr/local/etc/haproxy.conf -p /var/run/haproxy.pid

But it doesn’t have any socket listening:

sockstat -46 -l -s

USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS       PATH STATE   CONN STATE  
root     sendmail   18066 5  tcp4   192.168.40.198:25     *:*                                LISTEN
root     sendmail   18066 6  tcp4   192.168.40.198:587    *:*                                LISTEN
root     sshd       17328 3  tcp4   192.168.40.198:22     *:*                                LISTEN
root     syslogd    398   5  udp4   192.168.40.198:514    *:*

If we kill the service and start it again, everything works normally, but to avoid doing this on every startup, we can configure netwait. This way, services wait for the network interface to be up and the IP we indicate to be reachable by ping before starting the services.

man rc.conf | less -p netwait

If set to β€œYES”, delays the start of network-reliant services until netwait_if is up and ICMP packets to a
destination defined in netwait_ip are flowing.  Link state is examined first, followed by β€œpinging” an IP address to verify
network usability.  If no destination can be reached or timeouts are exceeded, network services are started anyway
with no guarantee that the network is usable.  Use of this variable requires both netwait_ip and netwait_if to be set.

The operating system will wait for a while, but if it exceeds the time, the services will start anyway, with no guarantee that the network is usable. In our case, it is enough to avoid the problem of starting Bastille jails.

If no destination can be reached or timeouts are exceeded, network services are started anyway with no guarantee that the network is usable.

We proceed with the configuration:

sysrc netwait_enable=‘YES’
sysrc netwait_if=‘bge1’
sysrc netwait_ip=‘192.168.40.1’

Orphaned IP alias issues
Sometimes, when stopping and destroying a jail, the IP remains configured as an alias. When reusing the IP in a new jail, Bastille complains:

bastille create aliasJail 13.1-RELEASE 192.168.40.136 bge1

Warning: IP address already in use (192.168.40.136).
Valid: (bge1).

Creating a thinjail...

Error: IP address (192.168.40.136) already in use.
[aliasJail]: Not started. See 'bastille start aliasJail'.
[aliasJail]: Not started. See 'bastille start aliasJail'.
Error: IP address (192.168.40.136) already in use.

We check that there is no other jail started with that IP:

bastille list -a

 JID         State  IP Address      Published Ports  Hostname    Release          Path
 HAProxy     Up     192.168.40.198  -                HAProxy.alfaexploit.com           13.1-RELEASE-p5  /usr/local/bastille/jails/HAProxy/root
 NATJail     Up     10.17.89.50     -                NATJail     13.1-RELEASE-p5  /usr/local/bastille/jails/NATJail/root
 aliasJail   Down   192.168.40.136  -                aliasJail   13.1-RELEASE-p5  /usr/local/bastille/jails/aliasJail/root
 aliasJail2  Up     192.168.40.139  -                aliasJail2  13.1-RELEASE-p5  /usr/local/bastille/jails/aliasJail2/root
 jailVnet2   Up     192.168.40.138  -                jailVnet2   13.1-RELEASE-p5  /usr/local/bastille/jails/jailVnet2/root
 nginx       Up     192.168.40.140  -                nginx       13.1-RELEASE-p5  /usr/local/bastille/jails/nginx/root
 thickJail   Down   192.168.40.190  -                thickJail   13.1-RELEASE-p5  /usr/local/bastille/jails/thickJail/root

As there is no jail configured with the IP, we can safely remove the alias manually:

ifconfig bge1 inet 192.168.40.136 delete

We start the jail:

bastille start aliasJail

Issues with partially created jails
If for some reason a VNET jail cannot be created, the network interfaces may be partially configured. In such a case, we must unconfigure them and run the command again:

ifconfig INTERFACE_NAME destroy

Inmortal jail
If any jail cant be stopped, we should execute a KILL command from inside jail:

jls

   JID  IP Address      Hostname                      Path
   135  192.168.69.22   PostgreSQL00-test             /usr/local/bastille/jails/PostgreSQL00-test/root
   136  192.168.69.23   PostgreSQL01-test             /usr/local/bastille/jails/PostgreSQL01-test/root
   138  192.168.69.26   test                          /usr/local/bastille/jails/test/root
   140  192.168.69.19   Atlas                         /usr/local/bastille/jails/Atlas/root
   141  192.168.69.25   DataDyne                      /usr/local/bastille/jails/DataDyne/root
   142  192.168.69.17   HellStorm                     /usr/local/bastille/jails/HellStorm/root
   143  192.168.69.20   MetaCortex                    /usr/local/bastille/jails/MetaCortex/root
   144  192.168.69.18   Paradox                       /usr/local/bastille/jails/Paradox/root
   145  192.168.69.21   RECLog                        /usr/local/bastille/jails/RECLog/root

We execute the KILL command:

jexec 135 csh
kill -KILL -1
exit

The jail should be stopped:

bastille list -a

 JID                State  IP Address     Published Ports  Hostname           Release          Path
 Atlas              Up     192.168.69.19  -                Atlas              13.2-RELEASE-p1  /usr/local/bastille/jails/Atlas/root
 DataDyne           Up     192.168.69.25  -                DataDyne           13.2-RELEASE-p1  /usr/local/bastille/jails/DataDyne/root
 HellStorm          Up     192.168.69.17  -                HellStorm          13.2-RELEASE-p1  /usr/local/bastille/jails/HellStorm/root
 MetaCortex         Up     192.168.69.20  -                MetaCortex         13.2-RELEASE-p1  /usr/local/bastille/jails/MetaCortex/root
 Paradox            Up     192.168.69.18  -                Paradox            13.2-RELEASE-p1  /usr/local/bastille/jails/Paradox/root
 PostgreSQL00-test  Down   192.168.69.22  -                PostgreSQL00-test  13.2-RELEASE-p1  /usr/local/bastille/jails/PostgreSQL00-test/root
 PostgreSQL01-test  Up     192.168.69.23  -                PostgreSQL01-test  13.2-RELEASE-p1  /usr/local/bastille/jails/PostgreSQL01-test/root
 RECLog             Up     192.168.69.21  -                RECLog             13.2-RELEASE-p1  /usr/local/bastille/jails/RECLog/root
 test               Up     192.168.69.26  -                test               13.2-RELEASE-p1  /usr/local/bastille/jails/test/root

If we are going to deletes a jail it is recomended to stop it and before deleting it, execute the sync command:

bastille stop JAILNAME
sync
bastille destroy JAILNAME

If you liked the article, you can treat me to a RedBull here