freebsd_jail
freebsd_jail
Ansible role. FreeBSD Jails' Management.
Feel free to share your feedback and report issues.
Synopsis
This role uses ezjail to manage FreeBSD jails. In most cases it is more efficient to use iocage. See the Ansible module iocage.
Supported platforms
This role has been developed and tested with FreeBSD Supported Production Releases.
Requirements
Roles
Collections
- ansible.posix
- community.general
Other
- Preconfigured network, ZFS, firewall and NAT is required.
Recommended roles
- Configure Network vbotka.freebsd_network
- Configure PF firewall vbotka.freebsd_pf
- Configure ZFS vbotka.freebsd_zfs
- Configure Poudriere vbotka.freebsd_poudriere
Jail type
This role is tested with jailtype zfs only.
Variables
See the defaults and examples in vars.
Parameters of the jails are configured in the the variable bsd_jail_jails
bsd_jail_jails:
- jailname: test_01
present: true # default=true
start: true # default=true
jailtype: zfs
flavour: ansible # optional
interface:
- {dev: lo1, ip4: 127.0.2.1}
- {dev: em0, ip4: 10.1.0.51}
parameters: # default([])
- {key: allow.raw_sockets, val: "true"}
- {key: allow.set_hostname, val: "true"}
jail_conf: # default([])
- {key: mount.devfs}
ezjail_conf: [] # default([])
firstboot: /root/firstboot.sh # optional
firstboot_owner: root # default=root
firstboot_group: wheel # default=wheel
firstboot_mode: '0750' # default='0755'
,or in the files stored in the directory bsd_jail_objects_dir. For example,
bsd_jail_objects_dir: "{{ playbook_dir }}/jails/jail.d"
See the example of the configuration file below
shell> cat test_02.yml
---
objects:
- jailname: test_02
present: true
start: true
jailtype: zfs
flavour: ansible
interface:
- {dev: lo1, ip4: 127.0.2.2}
- {dev: em0, ip4: 10.1.0.52}
parameters:
- {key: allow.raw_sockets, val: "true"}
- {key: allow.set_hostname, val: "true"}
jail_conf:
- {key: mount.devfs}
ezjail_conf: []
firstboot: /root/firstboot.sh
firstboot_owner: root
firstboot_group: wheel
firstboot_mode: '0750'
To remove a jail keep the entry in the variable, or in the file and set
objects:
- jailname: test_02
start: false
present: false
...
See contrib/jail-objects playbook and template to create the YAML files with the jail objects.
Flavours
See the chapter Flavours from the ezjail – Jail administration framework. Quoting:
A set of files to copy, packages to install and scripts to execute is called "flavour".
See contrib/jail-flavours on how to create and configure ezjail flavours.
Portsnap cron (optional)
See the chapter The basejail from the ezjail – Jail administration framework. The command ezjail-admin install may ask portsnap to (-p) fetch and extract a FreeBSD ports tree (see man ezjail-admin). This may take a while. You might want to speedup the execution of the role and fetch the ports tree by cron (see the cron command in man portsnap). Optionally, use the role vbotka.ansible-freebsd-ports to configure portsnap cron.
Workflow
- Change shell to /bin/sh if necessary
shell> ansible server -e 'ansible_shell_type=csh ansible_shell_executable=/bin/csh' -a 'sudo pw usermod admin -s /bin/sh'
- Install roles
shell> ansible-galaxy role install vbotka.freebsd_jail
shell> ansible-galaxy role install vbotka.freebsd_postinstall
- Fit variables. For example, in vars/main.yml
shell> editor vbotka.freebsd_jail/vars/main.yml
- Create playbook and inventory
shell> cat jail.yml
- hosts: server
roles:
- vbotka.freebsd_jail
# cat hosts
[server]
<server1_ip-or-fqdn>
<server2_ip-or-fqdn>
[server:vars]
ansible_connection=ssh
ansible_user=admin
ansible_python_interpreter=/usr/local/bin/python3.9
ansible_perl_interpreter=/usr/local/bin/perl
- Install and configure ezjail
Check syntax
shell> ansible-playbook freebsd-jail.yml --syntax-check
Take a look at the variables
shell> ansible-playbook jail.yml -t bsd_jail_debug -e bsd_jail_debug=true
Install packages
shell> ansible-playbook jail.yml -t bsd_jail_packages -e bsd_jail_install=true
Create directory flavours and unarchive files
shell> ansible-playbook jail.yml -t bsd_jail_ezjail_flavours -e bsd_ezjail=true
Dry-run the play and show the changes
shell> ansible-playbook jail.yml --check --diff
Run the play
shell> ansible-playbook jail.yml
- Create inventory and test the connection to the jails
shell> cat hosts
[test]
test_01
test_02
test_03
[test:vars]
ansible_connection=ssh
ansible_user=admin
ansible_become=yes
ansible_become_user=root
ansible_become_method=sudo
ansible_python_interpreter=/usr/local/bin/python3.8
ansible_perl_interpreter=/usr/local/bin/perl
shell> ansible test_01 -m setup | grep ansible_distribution_release
"ansible_distribution_release": "13.2-RELEASE",
List jails
shell> ezjail-admin list
STA JID IP Hostname Root Directory
--- ---- --------------- ------------------------------ ------------------------
ZR 13 127.0.2.3 test_03 /local/jails/test_03
13 em0|10.1.0.53
ZR 12 127.0.2.2 test_02 /local/jails/test_02
12 em0|10.1.0.52
ZR 11 127.0.2.1 test_01 /local/jails/test_01
11 em0|10.1.0.51
Archive jail
shell> jexec 11 /etc/rc.shutdown
shell> ezjail-admin stop test_01
...
shell> ezjail-admin archive -A
shell> ls -1 /export/archive/jails/ezjail_archives/
test_01-202311060342.38.tar.gz
test_02-202311060342.18.tar.gz
test_03-202311060341.58.tar.gz
Stop and delete jail
shell> jexec 12 /etc/rc.shutdown
Stopping sshd.
Waiting for PIDS: 6759.
Stopping cron.
Waiting for PIDS: 6769.
.
Terminated
shell> ezjail-admin delete -wf test_02
Stopping jails:/etc/rc.d/jail: WARNING: /var/run/jail.test_02.conf is created and used for jail test_02.
test_02.
# ezjail-admin list
STA JID IP Hostname Root Directory
--- ---- --------------- ------------------------------ ------------------------
ZR 13 127.0.2.3 test_03 /local/jails/test_03
13 em0|10.1.0.53
ZR 11 127.0.2.1 test_01 /local/jails/test_01
11 em0|10.1.0.51
Restore and Start jail
shell> ezjail-admin restore test_02-202311060342.18.tar.gz
shell> ezjail-admin start test_02
shell> ezjail-admin list
STA JID IP Hostname Root Directory
--- ---- --------------- ------------------------------ ------------------------
ZR 13 127.0.2.3 test_03 /local/jails/test_03
13 em0|10.1.0.53
ZR 14 127.0.2.2 test_02 /local/jails/test_02
14 em0|10.1.0.52
ZR 11 127.0.2.1 test_01 /local/jails/test_01
11 em0|10.1.0.51
Restore a jail with Ansible
Set the attribute archive. For example,
shell> cat test_02.conf
---
objects:
- jailname: test_02
present: true
start: true
archive: test_02-202311060342.18.tar.gz
jailtype: zfs
flavour: ansible
interface:
- {dev: lo1, ip4: 127.0.2.2}
- {dev: em0, ip4: 10.1.0.52}
parameters:
- {key: allow.raw_sockets, val: "true"}
- {key: allow.set_hostname, val: "true"}
jail_conf:
- {key: mount.devfs}
ezjail_conf: []
firstboot: /root/firstboot.sh
firstboot_owner: root
firstboot_group: wheel
firstboot_mode: '0750'
If the jail does not exist it will be restored from the archive if the parameter bsd_ezjail_admin_restore is set true (default=false). Speedup the play by selecting the tag bsd_jail_ezjail_jails
ansible-playbook jail.yml -e bsd_ezjail_admin_restore=true -t bsd_jail_ezjail_jails
If the jail is restored from the archive a jail-stamp is created. This prevents the play from running the script in the attribute firstboot. See the stamps
shell> ls -1 /var/db/jail-stamps/
test_01-firstboot
test_02-firstboot
test_03-firstboot
Start the jail
shell> ezjail-admin start test_02
# ezjail-admin list
STA JID IP Hostname Root Directory
--- ---- --------------- ------------------------------ ------------------------
ZR 13 127.0.2.3 test_03 /local/jails/test_03
13 em0|10.1.0.53
ZR 17 127.0.2.2 test_02 /local/jails/test_02
17 em0|10.1.0.52
ZR 11 127.0.2.1 test_01 /local/jails/test_01
11 em0|10.1.0.51
If the restoration is disabled or the attribute archive is not defined new jail will be created.
my-jail-admin.sh
my-jail-admin.sh is a script to facilitate the automation of jail's management. Once a jail has been created, configured and archived it's easier to use my-jail-admin.sh to delete and restore the jail. my-jail-admin.sh is not installed by default and should be manually copied if needed.
Examples
shell> my-jail-admin.sh delete test_01
[Logging: /tmp/my-jail-admin.test_01]
2019-03-20 12:23:58: test_01: delete: [OK] jail-rcd:
Stopping jails: test_01.
2019-03-20 12:23:58: test_01: delete: [OK] jail: test_01 stopped
2019-03-20 12:23:58: test_01: delete: [OK] ezjail-admin:
2019-03-20 12:23:58: test_01: delete: [OK] jail: test_01 deleted
2019-03-20 12:23:58: test_01: delete: [OK] lock: /var/db/jail-stamps/test_01-firstboot removed
shell> my-jail-admin.sh restore test_01 test_01-firstboot
2019-03-20 12:25:32: test_01: restore: [OK] ezjail-admin:
Warning: Some services already seem to be listening on IP 127.0.2.1
...
2019-03-20 12:25:32: test_01: restore: [OK] jail: test_01 restored from test_01-firstboot
2019-03-20 12:25:32: test_01: restore: [OK] lock: /var/db/jail-stamps/test_01-firstboot created
2019-03-20 12:25:33: test_01: restore: [OK] jail-rcd:
Starting jails: test_01.
2019-03-20 12:25:33: test_01: restore: [OK] jail: test_01 started
Example 1. Variables of recommended roles
fn_gateway_enable: true
fn_defaultrouter: 10.1.0.10
fn_interfaces:
- {interface: em0, options: "inet 10.1.0.75 netmask 255.255.255.0"}
fn_aliases:
- interface: em0
aliases:
- {alias: alias1, options: "inet 10.1.0.51 netmask 255.255.255.255"}
- {alias: alias2, options: "inet 10.1.0.52 netmask 255.255.255.255"}
- {alias: alias3, options: "inet 10.1.0.53 netmask 255.255.255.255"}
fn_cloned_interfaces:
- {interface: lo1, state: present}
shell> ansible-playbook freebsd-network.yml
fzfs_enable: true
fzfs_manage:
- name: zroot/jails
state: present
extra_zfs_properties:
compression: 'on'
mountpoint: /local/jails
fzfs_mountpoints:
- mountpoint: /local/jails
owner: root
group: wheel
mode: '0700'
shell> ansible-playbook freebsd-zfs.yml
pf_blacklistd_enable: true
pf_fail2ban_enable: true
pf_relayd_enable: false
pf_sshguard_enable: true
pfconf_only: false
pfconf_validate: true
# blacklistd
pf_blacklistd_flags: '-r'
pf_blacklistd_conf_remote: []
pf_blacklistd_conf_local:
- {adr: ssh, type: stream, proto: '*', owner: '*', name: '*', nfail: '3', disable: 24h}
- {adr: ftp, type: stream, proto: '*', owner: '*', name: '*', nfail: '3', disable: 24h}
- {adr: smtp, type: stream, proto: '*', owner: '*', name: '*', nfail: '3', disable: 24h}
- {adr: smtps, type: stream, proto: '*', owner: '*', name: '*', nfail: '3', disable: 24h}
- {adr: submission, type: stream, proto: '*', owner: '*', name: '*', nfail: '3', disable: 24h}
- {adr: '*', type: '*', proto: '*', owner: '*', name: '*', nfail: '3', disable: '60'}
pf_blacklistd_rcconf:
- {regexp: blacklistd_flags, line: "{{ pf_blacklistd_flags }}"}
# /etc/pf.conf
pf_type: default
pf_ext_if: em0
pf_logall_blocked: log
pf_pass_icmp_types: [echoreq, unreach]
pf_pass_icmp6_types: [echoreq, unreach]
pf_jails_net: 10.1.0.0/24
pf_rules_rdr:
- rdr pass on $ext_if proto tcp from any to 10.1.0.51 port { 80 443 8080 8081 } -> 127.0.2.1 # test_01
- rdr pass on $ext_if proto tcp from any to 10.1.0.52 port { 80 443 } -> 127.0.2.2 # test_02
- rdr pass on $ext_if proto tcp from any to 10.1.0.53 port { 80 443 } -> 127.0.2.3 # test_03
- rdr pass on $ext_if proto tcp from any to 10.1.0.54 port { 80 443 } -> 127.0.2.4 # test_04
- rdr pass on $ext_if proto tcp from any to 10.1.0.55 port { 80 443 } -> 127.0.2.5 # test_05
- rdr pass on $ext_if proto tcp from any to 10.1.0.56 port { 80 443 } -> 127.0.2.6 # test_06
- rdr pass on $ext_if proto tcp from any to 10.1.0.57 port { 80 443 } -> 127.0.2.7 # test_07
- rdr pass on $ext_if proto tcp from any to 10.1.0.58 port { 80 443 } -> 127.0.2.8 # test_08
- rdr pass on $ext_if proto tcp from any to 10.1.0.59 port { 80 443 } -> 127.0.2.9 # test_09
- rdr pass on $ext_if proto tcp from any to 10.1.0.60 port { 80 443 } -> 127.0.2.10 # test_10
pf_macros:
ext_if: "{{ pf_ext_if }}"
localnet: "{{ pf_jails_net }}"
logall: "{{ pf_logall_blocked }}"
icmp_types: "{{ pf_pass_icmp_types }}"
icmp6_types: "{{ pf_pass_icmp6_types }}"
pf_options:
- set skip on lo0
- set block-policy return
- set loginterface $ext_if
pf_tables:
- table <sshabuse> persist
pf_normalization:
- scrub in on $ext_if all fragment reassemble
pf_translation:
- "{{ pf_rules_rdr }}"
- nat on $ext_if from $localnet to any -> ($ext_if)
pf_filtering:
- antispoof for $ext_if
- "{{ pf_anchors }}"
- block $logall all
- pass inet proto icmp all icmp-type $icmp_types
- pass inet6 proto icmp6 all icmp6-type $icmp6_types
- pass from { self, $localnet } to any keep state
shell> ansible-playbook freebsd-pf.yml
fp_sysctl:
- {name: net.inet.ip.forwarding, value: 1}
- {name: vfs.zfs.prefetch.disable, value: 0}
- {name: security.jail.set_hostname_allowed, value: 1}
- {name: security.jail.socket_unixiproute_only, value: 1}
- {name: security.jail.sysvipc_allowed, value: 0}
- {name: security.jail.allow_raw_sockets, value: 0}
- {name: security.jail.chflags_allowed, value: 0}
- {name: security.jail.jailed, value: 0}
- {name: security.jail.enforce_statfs, value: 2}
To manage ZFS inside the jail add the following states
- {name: security.jail.mount_allowed, value: '1'}
- {name: security.jail.mount_devfs_allowed, value: '1'}
- {name: security.jail.mount_zfs_allowed, value: '1'}
shell> ansible-playbook freebsd-postinstall.yml -t fp_sysctl
Example 2. Ansible flavour tarball
shell> tar tvf ansible.tar
-rw------- admin/admin 1475 2023-11-04 14:52 home/admin/.ssh/authorized_keys
-rwxr-x--- root/wheel 855 2023-11-04 14:52 root/firstboot.sh
-r--r----- root/wheel 3978 2023-11-04 14:54 usr/local/etc/sudoers
-rw-r--r-- root/wheel 39 2023-11-04 14:52 etc/resolv.conf
-rw-r--r-- root/wheel 39 2023-11-04 14:52 etc/rc.conf
-rwxr-xr-x root/wheel 1821 2023-11-04 14:52 etc/rc.d/ezjail.flavour.default
shell> tree -a /local/jails/flavours/ansible
/local/jails/flavours/ansible
├── etc
│ ├── rc.conf
│ ├── rc.d
│ │ └── ezjail.flavour.default
│ └── resolv.conf
├── home
│ └── admin
│ └── .ssh
│ └── authorized_keys
├── root
│ └── firstboot.sh
└── usr
└── local
└── etc
└── sudoers
Example 3. Ansible firstboot.sh
See contrib/jail-flavours/firstboot-1.0.1
#!/bin/sh
# Install packages
env ASSUME_ALWAYS_YES=YES pkg install sudo
env ASSUME_ALWAYS_YES=YES pkg install perl5
env ASSUME_ALWAYS_YES=YES pkg install python38
env ASSUME_ALWAYS_YES=YES pkg install gtar
# Create user admin
pw useradd -n admin -s /bin/sh -m
chown -R admin:admin /home/admin
chmod 0700 /home/admin/.ssh
chmod 0600 /home/admin/.ssh/authorized_keys
# Configure sudoers
cp /usr/local/etc/sudoers.dist /usr/local/etc/sudoers
chown root:wheel /usr/local/etc/sudoers
chmod 0440 /usr/local/etc/sudoers
echo "admin ALL=(ALL) NOPASSWD: ALL" >> /usr/local/etc/sudoers
# Configure root
chown root:wheel root/firstboot.sh
# Configure etc
chown root:wheel etc/rc.conf
chmod 0644 etc/rc.conf
chown root:wheel etc/resolv.conf
chmod 0644 etc/resolv.conf
chown root:wheel etc/rc.d/ezjail.flavour.default
chmod 0755 etc/rc.d/ezjail.flavour.default
# EOF
Known issues
'Error: No archive for pattern __ can be found.'
Restoration of a jail from an archive fails
TASK [vbotka.freebsd_jail : ezjail-jails:create: Debug ezjail-admin command] ***************************************
ok: [srv.example.com] =>
local_command: ezjail-admin restore test_13-202201162054.07.tar.gz && touch /var/db/jail-stamps/test_13-firstboot
TASK [vbotka.freebsd_jail : ezjail-jails:create: Create or Restore jail test_13] ***********************************
fatal: [srv.example.com]: FAILED! => changed=true
cmd:
- ezjail-admin
- restore
- test_13-202201162054.07.tar.gz
- '&&'
- touch
- /var/db/jail-stamps/test_13-firstboot
delta: '0:01:05.721641'
end: '2022-01-16 23:58:13.516263'
msg: non-zero return code
rc: 1
start: '2022-01-16 23:57:07.794622'
stderr: 'Error: No archive for pattern __ can be found.'
References
- Jails - FreeBSD Handbook
- ezjail Jail administration framework - erdgeist.org
- jail - FreeBSD man
- Quick setup of jail on ZFS using ezjail with PF NAT - FreeBSD Forums
- Trying to understand jail networking - FreeBSD Forums
- How to create a ZFS dataset within a jail? - FreeBSD Forums
- Best practice: jails - FreeBSD Forums
- Can't get iocage jail to have internet connectivity - FreeNAS Forums
- FreeBSD Mastery: Jails, Michael W. Lucas
License
Author Information
ansible-galaxy install vbotka/ansible-freebsd-jail