Esta pagina se ve mejor con JavaScript habilitado

Gestión de jails en FreeBSD con Bastille

 ·  🎃 kr0m

Bastille es un gestor de contenedores(jails) con 0 dependencias ya que está escrito en Bourne Shell, este hace uso de las mejores funcionalidades y tecnologías que nos ofrece FreeBSD facilitándonos de este modo la gestión, creación, destrucción y actualización de jails en todo su ciclo de vida.
Algunas ventajas respecto a otros sistemas de virtualización como IOCage son:

  • Proyecto mejor mantenido, recibe updates mas a menudo.
  • 0 dependencias por estar escrito en Bourne Shell.
  • Sistema de templates mas completo.
  • Posibilidad de reutilizar las recetas de los templates mediante Rocinante .
  • Nateo de jails con redirección de puertos integrada.

Podemos encontrar una tabla muy ilustrativa en la web de Bastille.


El artículo se compone de varias partes:


Instalación

Instalamos las dependencias:

pkg install vim git-lite bash ca_root_nss

Instalamos Bastille:

pkg install bastille

Configuramos si queremos utilizar ZFS o no:

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

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

Habilitamos el servicio:

sysrc bastille_enable=YES
service bastille start

Bajamos los ficheros base de la versión de FreeBSD que desemos utilizar y aplicamos los últimos parches de seguridad:

bastille bootstrap 13.1-RELEASE update

Opcionalmente podemos verificar la imagen:

bastille verify 13.1-RELEASE


Networking

Bastille tiene algunas limitaciones en cuanto a los nombres de las jails, por ejemplo:

  • Los nombres de las jails no pueden contener el carácter “.”
  • Los nombres no pueden ser muy largos o los comandos de configuración de red fallarán:
ifconfig: ioctl SIOCSIFNAME (set name): File name too long
jail: alcatrazVnet2: ifconfig epair0a up name e0a_alcatrazVnet2: failed

Toda la documentación sobre networking puede ser encontrada en este enlace .

Para ver el tipo de red de cada jail debemos consultar directamente su configuración:

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

Las reglas de PF que veremos mas adelante en este mismo artículo, solo afectan a los modos de red NAT y Alias, ya que en VNET y VNET-BRIDGE-PROPIO deshabilitamos explicitamente el filtrado de tráfico en bridges, por lo tanto no les afectará.


Networking-NAT

Mediante NAT tendremos una o varias redes internas las cuales serán nateadas mediante PF , para ello debemos realizar la siguiente configuración:

sysrc cloned_interfaces+=lo1
sysrc ifconfig_lo1_name="bastille0\O"

Aplicamos la configuración:

service netif cloneup

Escribimos un sendillo script de PF donde natearemos la tabla “jails”, redirigiremos los puertos del ancho rdr y daremos barra libre para el puerto 22-ssh:

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

Habilitamos y arrancamos el servicio:

sysrc pf_enable=YES
service pf start

Podemos consultar las reglas con:

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

Los rangos internos recomendados por Bastille son:

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

Por defecto Bastille crea las jails nateadas:

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

Si accedemos a la jail podemos ver la 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>

Habilitamos y arrancamos el servicio SSH en la jail:

bastille sysrc natJail sshd_enable=YES
bastille service natJail sshd start

Podemos ver como el socket está a la escucha:

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

Entramos en la jail para añadir un usuario con el que comprobar la conectividad SSH:

bastille console natJail
adduser

Conectamos:


Networking-Alias

Como su nombre indica este modo utiliza alias ips en el host padre para asignarlas a las jails.

Creamos la jail indicando la interfaz sobre la que configurar el 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

Podemos ver como la interfaz del padre ahora tiene varias ips configuradas:

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>

En modo Alias debemos tener en cuenta que las ips están configuradas en el padre por lo tanto si queremos bindear un servicio a la jail debemos configurar el padre para que solo utilice su ip específica y no utilizar wildcards:

vi /etc/ssh/sshd_config

ListenAddress 192.168.40.113
service sshd restart

Habilitamos y arrancamos el servicio SSH en la jail:

bastille sysrc aliasJail sshd_enable=YES
bastille service aliasJail sshd start

Entramos en la jail para añadir un usuario con el que comprobar la conectividad SSH:

bastille console aliasJail
adduser

Conectamos:


Networking-VNET

Bastille crea un bridge y asigna la interfaz de la jail y la interfaz WAN a este bridge de forma automática.

Para que las jails VNET puedan funcionar correctamente se requieren de reglas devd que solo deberán ser configuradas una vez:

vi /etc/devfs.rules

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

Reiniciamos devd:

service devd restart

Deshabilitamos el filtrado de tráfico en los bridges, de este modo cada jail VNET realizará su propio filtrado:

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

Creamos la 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

Podemos ver en el padre el bridge con la interfaz de la jail(e0a_bastille0) y la inetrfaz WAN(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>

Habilitamos y arrancamos el servicio SSH en la jail:

bastille sysrc jailVnet sshd_enable=YES
bastille service jailVnet sshd start

Si accedemos a la jail podemos ver la 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>

Añadimos un usuario con el que comprobar la conectividad SSH:

adduser

A veces la ruta por defecto no es detectada correctamente, si se diese el caso podemos ajustarla por cada jail:

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

O desde Bastille para las nuevas jails:

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

bastille_network_gateway=aa.bb.cc.dd

Networking-VNET-Bridge-Propio

Se debe seguir la misma configuración que VNET.

vi /etc/devfs.rules

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

Reiniciamos devd:

service devd restart

Deshabilitamos el filtrado de tráfico en los bridges, de este modo cada jail VNET realizará su propio filtrado:

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

En el modo VNET automático hemos metido la interfaz WAN en un bridge y las interfaces no pueden estar en dos bridges simultáneamente, así que primero eliminaremos la jail VNET que hemos creado en el paso anterior:

bastille stop jailVnet
bastille destroy jailVnet
ifconfig bge1bridge destroy

Creamos el bridge en el padre:

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

Aplicamos la configuración:

service netif cloneup

Creamos la 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

Podemos ver en el padre el bridge con la interfaz de la jail(e0a_jailVnet2) y la inetrfaz WAN(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>

Habilitamos y arrancamos el servicio SSH en la jail:

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

Si accedemos a la jail podemos ver la 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>

Añadimos un usuario con el que comprobar la conectividad SSH:

adduser


Redirección de puertos:

Mediante el comando rdr podremos redirigir puertos a las jails Nateadas:

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

Bastille hace uso de un anchor de PF, de este modo las reglas pueden ser dinámicas en tiempo real:

bastille rdr NATJail tcp 2001 22

Consultamos las redirecciones de la jail:

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

Si queremos eliminar las redirecciones de una jail:

bastille rdr NATJail clear


Script de firewall final

En mi caso el script de firewall final quedaría del siguiente modo:

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
block in quick from <badguys>

pass in proto tcp to 192.168.69.2 port 7777

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

# HTTP/HTTPS -> Atlas
pass in proto tcp to 192.168.69.19 port 80
pass in proto tcp 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

pass in proto tcp to any port 22

Tipos de jails

Bastille permite crear jails de varios tipos distintos:

    -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.

Pero las mas utilizadas son Thin(modo por defecto) y Thick:

  • Thin:

    bastille create thinJail 13.1-RELEASE 192.168.40.190 bge1

    • Todas las jails comparten una base en modo RO.
    • Cuando se actualiza la RELEASE se actualizan todas las jails que comparten la base.
    • Recomendado para jails en las que no se planea actualizar de una major RELEASE a otra, si no que solo se actualizará dentro de la misma RELEASE.
  • Thick:

    bastille create -T thickJail 13.1-RELEASE 192.168.40.190 bge1

    • Cada jail tiene una copia de la RELEASE, se actualizan de forma independiente y todo el sistema de ficheros es RW.
    • Recomendado para jails que irán actualizando entre major RELEASEs a lo largo del tiempo.

Es posible convertir una jail de Thin a Thick, pero debemos tener en cuenta que el proceso es irreversible, es decir NO se puede pasar de Thick a Thin:

bastille stop aliasJail2
bastille convert aliasJail2

La única manera de saber si es una jail Thin o Thick es comprobar si tiene montada la base:

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

Visualización de jails/releases/templates/logs

Lista de jails arrancadas:

bastille list jail

aliasJail
aliasJail2
jailVnet2
natJail

Lista de jails arrancadas con mas información:

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

Para ver todas las jails y sus detalles incluidas las ips de la jails VNET:

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

Lista de releases:

bastille list release

13.1-RELEASE

Lista de templates:

bastille list template

/usr/local/bastille/templates

Lista de ficheros de log de las jails:

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

Los templates son una forma de confeccionar “recetas”, un poco al estilo Ansible pero para las jails de Bastille, en estos templates debemos indicar los pasos a realizar sobre la jail, por ejemplo instala X paquete, arranca cierto servicio, etc.
https://gitlab.com/bastillebsd-templates
https://docs.bastillebsd.org/en/latest/chapters/template.html

A modo de ejemplo bajamos el template de Nginx:

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.

Ahora veremos que tenemos un template mas:

bastille list template

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

Creamos una jail a la que aplicaremos el template:

bastille create nginx 13.1-RELEASE 192.168.40.140 bge1

Aplicamos el template:

bastille template nginx bastillebsd-templates/nginx

Podemos ver como tiene Nginx a la escucha:

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

Y podemos conectar sin problemas:

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

Por supuesto podemos crear nuestros propios templates, no utiliza ningún lenguaje de marcas ni ningún lenguaje exótico tan solo comandos reales.
La mejor manera es copiar uno existente e ir modificándolo:

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

Un aspecto muy interesante de los templates es que no se limitan a las jails creadas por Bastille si no que también se pueden aplicar a cualquier equipo ya sea físico, máquina virtual o jail mediante un software llamado Rocinante , de este modo las recetas que tengamos en formato template serán reutilizables:

pkg install rocinante

Instalamos Nginx mediante Rocinante:

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


Exportar/Importar jails

Cuando exportamos una jail estamos generando una imagen comprimida que luego puede ser importada en otro sistema, de este modo podemos mover jails entre hosts y realizar backups de forma rápida y sencilla.
El comando export funciona tanto sobre UFS como ZFS:

  • UFS: Hay que parar la jail, solo soporta txz.
  • ZFS: Realizará un snapshot y sacará el fichero comprimido desde este sin necesidad de parar la 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.

Exportamos una jail a modo de ejemplo:

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.

Podemos ver lo que ocupa el 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

También podemos obtener una lista de backups, los subcomandos export/import/backup son equivalentes:

bastille list export
bastille list import
bastille list backup

aliasJail_2022-12-27-121707.tgz

La restauración se realiza con el mismo nombre, así que para restaurar primero eliminamos la jail original:

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


Montar directorios

Mediante el comando mount podremos montar directorios del host padre en la propia jail:

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

Montamos un directorio de prueba:

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

Podemos ver como la configuración de la jail ahora tiene una entrada adicional:

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

Creamos desde el padre un fichero:

touch /mnt/storage/AA

Accedemos a la jail y comprobamos que efectivamente el fichero resulta 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

Para desmontarlo es tan sencillo como ejecutar el comando umount:

bastille umount aliasJail2 /mnt/storage

[aliasJail2]:
Unmounted: /mnt/storage

NOTA: El comando umount NO eliminará la entrada del fichero /usr/local/bastille/jails/JAIL_NAME/fstab, pero ya no estará disponible dentro de la jail.


Comandos útiles

Hacer login con otro usuario:

bastille console aliasJail kr0m

Podemos instalar directamente software:

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

Ejecutar top/htop es una tarea tan común que Bastille incorpora un comando para ello:

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

Para eliminar una jail, primero la paramos, luego la destruimos:

bastille stop alcatraz
bastille destroy alcatraz

Ejecutar comandos sysrc en la jail:

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

Gestionar servicios:

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

Podemos copiar ficheros directamente a la jail:

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

Clonar una jail:

bastille clone aliasJail aliasJail2 192.168.40.139
bastille start aliasJail2

Renombrar una jail:

bastille stop natJail
bastille rename natJail NATJail

Reiniciar una jail:

bastille restart natJail
bastille restart ALL


Update

Como en cualquier sistema FreeBSD la actualización del sistema operativo se divide en dos partes, el sistema base y los paquetes/ports instalados.


Update BASE

Además el sistema base puede actualizarse dentro de la misma versión, la misma versión principal o a otra versión principal.
Thin jail:
Si se trata de una Thin jail estas comparten la base, por lo tanto actualizando la RELEASE desde Bastille estaremos actualizando el sistema base de todas las jails que compartan dicha RELEASE:

bastille update RELEASENAME

Recomiendan reiniciar las jails que hayan sido actualizadas:

To be safe, you may want to restart any containers that have been updated live.

Si vamos a actualizar a otra version fuera de la que estamos o a otra major version, el comando sería:

bastille upgrade 13.0-RELEASE 13.1-RELEASE
bastille upgrade 13.1-RELEASE 14.0-RELEASE

Thick jail:
Por el contrario si se trata de un Thick jail habrá que hacerlo para cada jail, pero siempre desde Bastille ya que las jails arrancan en securelevel 2:

sysctl kern.securelevel

kern.securelevel: 2
bastille update JAILNAME

Si vamos a actualizar a otra version fuera de la que estamos o a otra major version, el comando sería:

bastille upgrade 13.1-RELEASE 14.0-RELEASE JAILNAME

Si hemos actualizado a otra major version, debemos ejecutar los comandos adicionales:

pkg-static upgrade -f -> Binarios
portmaster -af -> Ports
freebsd-update install


Update PAQUETES/PORTS

Actualizar los paquetes binarios de una jail:

bastille pkg aliasJail upgrade
bastille pkg ALL upgrade

bastille pkg aliasJail autoremove
bastille pkg ALL autoremove

Script actualización

Dejo aquí mi script de actualización por si a alguien le resulta útil, hay que tener en cuenta que todas mis jails son de tipo Thick:
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|awk -F '@' '{print$2}'|awk '{print$1}'|sort -u|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 update test: $DATE\r\HellStorm updated: $DATE" | sendmail -f mightymax@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

Seguridad paquetes binarios

Comprobar paquetes con problemas de seguridad de una jail:

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


Limitar recursos

Mediante el comando limits podremos limitar los recursos de los que dispondrá una jail, Bastille se apoya sobre rctl para ello.
Habilitamos rctl:

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

bastille limits aliasJail memoryuse 1g

Podemos consultar en este enlace los recursos a limitar:

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

Si queremos comprobar los límites:

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

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

O de todas las jails mediante el comando:

bastille list limits

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

Si queremos eliminarlos:

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

Tambien podemos eliminar todas las restricciones:

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

NOTA: Tened en cuenta que mediante parámetros ZFS también podemos limitar ciertos aspectos como la cuota de disco.


ZFS Parameters: Quotas/Snapshots/df

El comando zfs nos permite asignar cualquier parámetro zfs al dataset de una jail en concreto, por ejemplo podemos acotar el espacio de disco disponible:

bastille zfs aliasJail set quota=1G

Si consultamos el espacio disponible veremos que es cercano al 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

También podemos asignar valores a los parámetros ZFS de forma masiva:

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

Bastille nos permite crear snapshots:

bastille zfs aliasJail snap SNAP0

Para consultarlos:

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

Este comando también se puede aplicar a todas las jails:

bastille zfs ALL snap SNAP0
bastille zfs ALL df

Eliminar snapshots es un proceso manual, primero debemos localizar mediante el comando df el nombre del snapshot y luego ejecutar los comandos ZFS:

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

Como hay dos datasets por jail, una manera rápida de eliminar los dos sería utilizar el siguiente bucle:

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

Restaurar snapshots también es una tarea manual, primero debemos localizar mediante el comando df el nombre del snapshot y luego ejecutar los comandos ZFS:

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

NOTA: Recordad que en ZFS si tenemos por ejemplo 5 snapshots(0,1,2,3,4) y queremos revertir al primero(0), los snapshots 1,2,3,4 se perderán.


Configuración jails

Podemos editar la configuración de una jail mediante el comando edit:

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;
}

O consultar/modificar parámetros específicos mediante el comando config get/set:

bastille config aliasJail get ip4.addr

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

Migración de IOCage a Bastille

Hay que destacar que la migración de jails ha dado problemas, al menos migrando un HAProxy, consultar la sección de troubleshooting en este mismo artículo para mas detalles.

Paramos la jail en IOCage:

iocage stop HAProxy

La exportamos:

iocage export HAProxy

Copiamos la imagen al servidor Bastille:

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

Importamos la imagen:

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

Podemos ver que hay una jail nueva llamada 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

Según la confguración de red que queramos utilizar en la jail, haremos los ajustes pertinentes:
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";

En modo bridge además tendremos que realizar la configuracón dentro de la jail.

En mi caso se trata de una jail con una dirección ip configurada como alias:

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

interface = bge1;
ip4.addr = 192.168.40.198;

Arrancamos la jail:

bastille start HAProxy

Ahora ya teiene la dirección ip correcta:

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

Instalamos el software:

pkg install bash-completion

Le indicamos a Bash que cargue el autocompletion:

vi .bashrc

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

El script de autocompletion sería el siguiente:

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

Auto arranque jails

Por defecto las arranca todas:

: ${bastille_list:="ALL"}

Podemos definir una lista de jails a arrancar con el servicio:

sysrc bastille_list=‘nginx aliasJail’

Para deshabilitar el auto arranque de todas las jails lo mejor es dehabilitar el servicio entero:

sysrc bastille_enable=‘NO’

Si queremos arrancarlas todas de nuevo tan solo debemos eliminar el parámetro bastille_list:

sysrc -x bastille_list


Jails Linux

Esta funcionalidad está en fase experimental, fallan algunas cosas y el sistema funciona parcialmente por lo que no recomiendo bajo ninguna circunstancia poner en producción un Linux mediante este sistema, si necesitamos un Linux a la fuerza es preferible montarlo mediante vm-bhyve ya que al tratarse de una máquina virtual completa no debe dar ningún problema, en caso de utilizar Bastille/VM-Bhyve recomiendo tener dos interfaces de red físicas para poder tener un bridge para las jails y otro para las máquinas virtuales y de este modo evitar problemas entre softwares.

Algunas pegas encontradas en un testeo básico son:

  • Solo permite configurar la red en modo NAT/Alias

  • dmesg no funciona:

root@aliasLinux:~# dmesg 
dmesg: read kernel buffer failed: Operation not permitted
  • Algunas tools de red fallan:
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 falla:
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.
  • El acceso por ssh no funciona por algo relacionado con /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
  • Solo soporta un número muy limitado de distribuciones:
#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
    ;;

Si a pesar de todos los problemas decidimos probar las jails Linux, el primer paso es bajarnos la imagen:

bastille bootstrap focal

No preguntará varios aspectos sobre la configuración necesaria en el sistema para que Linux pueda correr, respondemos a todo que si:

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.

Ahora podremos ver una “release” nueva:

bastille list releases

13.1-RELEASE
Ubuntu_2004

Creamos la jail indicando que será de tipo linux:

bastille create -L aliasLinux focal 192.168.40.141 bge1

Podemos ver que ha arrancado correctamente:

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

Accedemos a la 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 funciona bastante bien, solo he detectado algún comportamiento anómalo de forma muy puntual al hacer cosas un poco mas enrevesadas.
Logs
Una manera fácil de ver si una jail está causando problemas es consultando su fichero de log:

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

Problemas importación

  • Importamos una jail que ya existe, como es lógico da error.
  • Borramos la jail.
  • Volvemos a importar la jail, esta vez satisfactoriamente.
  • Borramos la jail, da error de RO de la base, un bastille list NO muestra la jail.

El problema es que dejó restos y esto causa problemas.

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)

Desmontamos todos los puntos de montaje:

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

Si un umount dá problemas lo forzamos desde ZFS:

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

Eliminamos el directorio:

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

Destruimos el dataset asociado a la jail:

zfs destroy -r zroot/bastille/jails/aliasJail2

Problemas importación desde IOCage
En la importación de jails IOCage a Bastille detectamos un problema, si reiniciamos el servidor padre este arranca las jails antes de tener la red funcionando, esto provoca problemas al apagar las jails:

bastille stop HAProxy

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

Si accedemos a la jail podemos ver el proceso del HAProxy:

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

Pero no tiene ningún socket a la escucha:

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    *:*

Si se mata el servicio con un kill y se arranca de nuevo, ya funciona todo con normalidad, pero para no tener que hacer esto en cada arranque podemos configurar el netwait, de este modo los servicios esperan a que la interfaz de red esté up y la ip que indiquemos alcanzable por ping antes de arrancar los servicios.

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.

El sistema operativo esperará un tiempo, pero si se sobrepasa los servicios arrancarán de todos modos, en nuestro caso es suficiente para evitar el problema de arranque de las jails de Bastille:

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

Procedemos con la configuración:

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

Problemas direcciones ip alias huérfanas
Alguna vez parando una jail y destuyéndola la ip se ha quedado configurada como alias, al reutilizar la ip en una jail nueva, Bastille se queja:

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.

Comprobamos que no haya ninguna otra jail arrancada con esa 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

Como no hay ninguna jail configurada con la ip, podemos quitar el alias a mano de forma segura:

ifconfig bge1 inet 192.168.40.136 delete

Arrancamos la jail:

bastille start aliasJail

Problemas jails creadas a medias
Si por algún motivo una jail VNET no puede crearse, las interfaces de red pueden quedar parcialmente configuradas, en tal caso debemos desconfigurarlas y volver a ejecutar el comando:

ifconfig INTERFACE_NAME destroy

Si te ha gustado el artículo puedes invitarme a un RedBull aquí