From c222d602aae52f870856774adfe412712ca39ba9 Mon Sep 17 00:00:00 2001 From: tschettervictor <85497460+tschettervictor@users.noreply.github.com> Date: Fri, 9 May 2025 10:33:19 -0600 Subject: [PATCH 1/7] Support jailing datasets --- usr/local/share/bastille/start.sh | 12 +++++++++++- usr/local/share/bastille/stop.sh | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/usr/local/share/bastille/start.sh b/usr/local/share/bastille/start.sh index 81046c67..93805d99 100644 --- a/usr/local/share/bastille/start.sh +++ b/usr/local/share/bastille/start.sh @@ -186,6 +186,16 @@ for _jail in ${JAILS}; do # Start jail jail ${OPTION} -f "${bastille_jailsdir}/${_jail}/jail.conf" -c "${_jail}" + # Add ZFS jailed datasets + if [ -s "${bastille_jailsdir}/${_jail}/zfs.conf" ]; then + while read _dataset _mount; do + zfs set jailed=on "${_dataset}" + zfs jail ${_jail} "${_dataset}" + jexec -l -U root "${_jail}" zfs set mountpoint="${_mount}" "${_dataset}" + jexec -l -U root "${_jail}" zfs mount "${_dataset}" 2>/dev/null + done < "${bastille_jailsdir}/${_jail}/zfs.conf" + fi + # Add rctl limits if [ -s "${bastille_jailsdir}/${_jail}/rctl.conf" ]; then while read _limits; do @@ -215,4 +225,4 @@ for _jail in ${JAILS}; do bastille_running_jobs "${bastille_process_limit}" done -wait +wait \ No newline at end of file diff --git a/usr/local/share/bastille/stop.sh b/usr/local/share/bastille/stop.sh index 9d1329f8..8e6e7286 100644 --- a/usr/local/share/bastille/stop.sh +++ b/usr/local/share/bastille/stop.sh @@ -126,6 +126,14 @@ for _jail in ${JAILS}; do bastille limits "${_jail}" clear fi + # Unmount and jailed ZFS datasets + if [ -s "${bastille_jailsdir}/${_jail}/zfs.conf" ]; then + while read _dataset _mount; do + jexec -l -U root "${_jail}" zfs umount "${_dataset}" + zfs unjail "${_jail}" "${_dataset}" + done < "${bastille_jailsdir}/${_jail}/zfs.conf" + fi + # Stop jail jail ${OPTION} -f "${bastille_jailsdir}/${_jail}/jail.conf" -r "${_jail}" From 2d35e5960c7a3f7cfd6c1639bf0d631cb53aa29f Mon Sep 17 00:00:00 2001 From: tschettervictor <85497460+tschettervictor@users.noreply.github.com> Date: Fri, 9 May 2025 12:09:47 -0600 Subject: [PATCH 2/7] zfs: Support jailing and unjailing --- usr/local/share/bastille/zfs.sh | 140 +++++++++++++++++++++++++++----- 1 file changed, 119 insertions(+), 21 deletions(-) diff --git a/usr/local/share/bastille/zfs.sh b/usr/local/share/bastille/zfs.sh index bddd675a..0a81e498 100644 --- a/usr/local/share/bastille/zfs.sh +++ b/usr/local/share/bastille/zfs.sh @@ -33,7 +33,10 @@ . /usr/local/share/bastille/common.sh usage() { - error_notify "Usage: bastille zfs TARGET [set|get|snap|destroy_snap|df|usage] [key=value|date]" + error_notify "Usage: bastille zfs TARGET [destroy_snap|df|get|set|(snap|snapshot)|usage] [key=value|date]" + error_notify " [jail pool/dataset /jail/path]" + error_notify " [unjail pool/dataset]" + cat << EOF Options: @@ -44,42 +47,130 @@ EOF exit 1 } +zfs_jail_dataset() { + + # Validate jail state + check_target_is_stopped "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${_jail}" + else + info "\n[${_jail}]:" + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." + fi + + info "\n[${_jail}]:" + + # Exit if MOUNT or DATASET is empty + if [ -z "${MOUNT}" ] || [ -z "${DATASET}" ]; then + usage + # Exit if datset does not exist + elif ! zfs list "${DATASET}" >/dev/null 2>/dev/null; then + error_exit "[ERROR]: Dataset does not exist: ${DATASET}" + fi + + # Ensure dataset is not already present in *zfs.conf* + if grep -hoqsw "${DATASET}" ${bastille_jailsdir}/*/zfs.conf; then + error_exit "[ERROR]: Dataset already assigned." + fi + + # Add necessary config variables to jail + bastille config ${_jail} set enforce_statfs 1 + bastille config ${_jail} set allow.mount + bastille config ${_jail} set allow.mount.devfs + bastille config ${_jail} set allow.mount.zfs + + # Add dataset to zfs.conf + echo "${DATASET} ${MOUNT}" >> "${bastille_jailsdir}/${_jail}/zfs.conf" + + if [ "${AUTO}" -eq 1 ]; then + bastille start "${_jail}" + fi +} + +zfs_unjail_dataset() { + + # Validate jail state + check_target_is_stopped "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${_jail}" + else + info "\n[${_jail}]:" + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." + fi + + info "\n[${_jail}]:" + + # Exit if DATASET is empty + if [ -z "${DATASET}" ]; then + usage + # Warn if datset does not exist + elif ! zfs list "${DATASET}" >/dev/null 2>/dev/null; then + warn "[WARNING]: Dataset does not exist: ${DATASET}" + fi + + # Remove dataset from zfs.conf + if ! grep -hoqsw "${DATASET}" ${bastille_jailsdir}/${_jail}/zfs.conf; then + error_exit "[ERROR]: Dataset not present in zfs.conf." + else + sed -i '' "\#.*${DATASET}.*#d" "${bastille_jailsdir}/${_jail}/zfs.conf" + fi + + if [ "${AUTO}" -eq 1 ]; then + bastille start "${_jail}" + fi +} + zfs_snapshot() { + info "\n[${_jail}]:" # shellcheck disable=SC2140 zfs snapshot -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"@"${TAG}" } zfs_destroy_snapshot() { + info "\n[${_jail}]:" # shellcheck disable=SC2140 zfs destroy -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"@"${TAG}" } zfs_set_value() { + info "\n[${_jail}]:" zfs "${ATTRIBUTE}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}" } zfs_get_value() { + info "\n[${_jail}]:" zfs get "${ATTRIBUTE}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}" } zfs_disk_usage() { + info "\n[${_jail}]:" zfs list -t all -o name,used,avail,refer,mountpoint,compress,ratio -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}" } - # Handle options. +AUTO=0 while [ "$#" -gt 0 ]; do case "${1}" in -h|--help|help) usage ;; + -a|--auto) + AUTO=1 + shift + ;; -x|--debug) enable_debug shift ;; - -*) - error_notify "[ERROR]: Unknown Option: \"${1}\"" - usage + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "[ERROR]: Unknown Option: \"${1}\"" ;; + esac + done + shift ;; *) break @@ -87,7 +178,7 @@ while [ "$#" -gt 0 ]; do esac done -if [ "$#" -lt 2 ]; then +if [ "$#" -lt 2 ] || [ "$#" -gt 4 ]; then usage fi @@ -111,21 +202,7 @@ for _jail in ${JAILS}; do ( - info "\n[${_jail}]:" - case "${ACTION}" in - set) - ATTRIBUTE="${3}" - zfs_set_value - ;; - get) - ATTRIBUTE="${3}" - zfs_get_value - ;; - snap|snapshot) - TAG="${3}" - zfs_snapshot - ;; destroy_snap|destroy_snapshot) TAG="${3}" zfs_destroy_snapshot @@ -133,6 +210,27 @@ for _jail in ${JAILS}; do df|usage) zfs_disk_usage ;; + get) + ATTRIBUTE="${3}" + zfs_get_value + ;; + jail) + DATASET="${3}" + MOUNT="${4}" + zfs_jail_dataset + ;; + unjail) + DATASET="${3}" + zfs_unjail_dataset + ;; + set) + ATTRIBUTE="${3}" + zfs_set_value + ;; + snap|snapshot) + TAG="${3}" + zfs_snapshot + ;; *) usage ;; @@ -143,4 +241,4 @@ for _jail in ${JAILS}; do bastille_running_jobs "${bastille_process_limit}" done -wait \ No newline at end of file +wait From d118b802ff2c143e273710635155b6617f60d1c3 Mon Sep 17 00:00:00 2001 From: tschettervictor <85497460+tschettervictor@users.noreply.github.com> Date: Fri, 9 May 2025 12:28:06 -0600 Subject: [PATCH 3/7] docs: Add jailing datasets --- docs/chapters/zfs-support.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/chapters/zfs-support.rst b/docs/chapters/zfs-support.rst index b00fcf62..13825bcd 100644 --- a/docs/chapters/zfs-support.rst +++ b/docs/chapters/zfs-support.rst @@ -58,3 +58,27 @@ dataset for bastille. Bastille will mount the datasets it creates at ``bastille_prefix`` which defaults to ``/usr/local/bastille`` If this is not desirable, you can change it at the top of the config file. + +Jailing a Dataset +----------------- + +It is possible to "jail" a dataset. This means mounting a datset into a jail, and being +able to fully manage it from within the jail. + +To add a dataset to a jail, we can run ``bastille zfs TARGET jail pool/dataset /path/inside/jail``. +This will mount ``pool/dataset`` into the jail at ``/path/inside/jail`` when the jail is started, and +unmount and unjail it when the jail is stopped. + +You can manually change the path where the dataset will be mounted by ``bastille edit TARGET zfs.conf`` and +adjusting the path after you have added it, bearing in mind the warning below. + +WARNING: Adding or removing datasets to this file can result in permission errors with your jail. It is +important that the jail is first stopped before attempting to manually configure this file. The format inside +the file is simple. + +.. code-block:: shell + + pool/dataset /path/in/jail + pool/other/dataset /other/path/in/jail + +To remove a dataset from being jailed, we can run ``bastille zfs TARGET unjail pool/dataset``. From a55a26d5e103be1bab3d8904709a194a38f44d57 Mon Sep 17 00:00:00 2001 From: tschettervictor <85497460+tschettervictor@users.noreply.github.com> Date: Fri, 9 May 2025 12:29:58 -0600 Subject: [PATCH 4/7] Update zfs-support.rst --- docs/chapters/zfs-support.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/chapters/zfs-support.rst b/docs/chapters/zfs-support.rst index 13825bcd..43387001 100644 --- a/docs/chapters/zfs-support.rst +++ b/docs/chapters/zfs-support.rst @@ -72,7 +72,7 @@ unmount and unjail it when the jail is stopped. You can manually change the path where the dataset will be mounted by ``bastille edit TARGET zfs.conf`` and adjusting the path after you have added it, bearing in mind the warning below. -WARNING: Adding or removing datasets to this file can result in permission errors with your jail. It is +WARNING: Adding or removing datasets to the ``zfs.conf`` file can result in permission errors with your jail. It is important that the jail is first stopped before attempting to manually configure this file. The format inside the file is simple. From ce1fb2bb6d4877a9aac7a767f05d1a39d6611c36 Mon Sep 17 00:00:00 2001 From: tschettervictor <85497460+tschettervictor@users.noreply.github.com> Date: Fri, 9 May 2025 12:36:23 -0600 Subject: [PATCH 5/7] zfs: Reorder checks --- usr/local/share/bastille/zfs.sh | 43 +++++++++++++++------------------ 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/usr/local/share/bastille/zfs.sh b/usr/local/share/bastille/zfs.sh index 0a81e498..fb030383 100644 --- a/usr/local/share/bastille/zfs.sh +++ b/usr/local/share/bastille/zfs.sh @@ -49,17 +49,8 @@ EOF zfs_jail_dataset() { - # Validate jail state - check_target_is_stopped "${_jail}" || if [ "${AUTO}" -eq 1 ]; then - bastille stop "${_jail}" - else - info "\n[${_jail}]:" - error_notify "Jail is running." - error_exit "Use [-a|--auto] to auto-stop the jail." - fi - info "\n[${_jail}]:" - + # Exit if MOUNT or DATASET is empty if [ -z "${MOUNT}" ] || [ -z "${DATASET}" ]; then usage @@ -72,12 +63,19 @@ zfs_jail_dataset() { if grep -hoqsw "${DATASET}" ${bastille_jailsdir}/*/zfs.conf; then error_exit "[ERROR]: Dataset already assigned." fi + # Validate jail state + check_target_is_stopped "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${_jail}" + else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." + fi # Add necessary config variables to jail - bastille config ${_jail} set enforce_statfs 1 - bastille config ${_jail} set allow.mount - bastille config ${_jail} set allow.mount.devfs - bastille config ${_jail} set allow.mount.zfs + bastille config ${_jail} set enforce_statfs 1 >/dev/null + bastille config ${_jail} set allow.mount >/dev/null + bastille config ${_jail} set allow.mount.devfs >/dev/null + bastille config ${_jail} set allow.mount.zfs >/dev/null # Add dataset to zfs.conf echo "${DATASET} ${MOUNT}" >> "${bastille_jailsdir}/${_jail}/zfs.conf" @@ -89,15 +87,6 @@ zfs_jail_dataset() { zfs_unjail_dataset() { - # Validate jail state - check_target_is_stopped "${_jail}" || if [ "${AUTO}" -eq 1 ]; then - bastille stop "${_jail}" - else - info "\n[${_jail}]:" - error_notify "Jail is running." - error_exit "Use [-a|--auto] to auto-stop the jail." - fi - info "\n[${_jail}]:" # Exit if DATASET is empty @@ -108,6 +97,14 @@ zfs_unjail_dataset() { warn "[WARNING]: Dataset does not exist: ${DATASET}" fi + # Validate jail state + check_target_is_stopped "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${_jail}" + else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." + fi + # Remove dataset from zfs.conf if ! grep -hoqsw "${DATASET}" ${bastille_jailsdir}/${_jail}/zfs.conf; then error_exit "[ERROR]: Dataset not present in zfs.conf." From 4b6a4c14ae985b75d853bcd73d8f7e69b2124b59 Mon Sep 17 00:00:00 2001 From: tschettervictor <85497460+tschettervictor@users.noreply.github.com> Date: Fri, 9 May 2025 12:43:47 -0600 Subject: [PATCH 6/7] Typo --- usr/local/share/bastille/stop.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/local/share/bastille/stop.sh b/usr/local/share/bastille/stop.sh index 8e6e7286..680c4ec0 100644 --- a/usr/local/share/bastille/stop.sh +++ b/usr/local/share/bastille/stop.sh @@ -126,7 +126,7 @@ for _jail in ${JAILS}; do bastille limits "${_jail}" clear fi - # Unmount and jailed ZFS datasets + # Unmount any jailed ZFS datasets if [ -s "${bastille_jailsdir}/${_jail}/zfs.conf" ]; then while read _dataset _mount; do jexec -l -U root "${_jail}" zfs umount "${_dataset}" From 4058446e547ed85cea2f86ccc1a26f4f6fa4b902 Mon Sep 17 00:00:00 2001 From: tschettervictor <85497460+tschettervictor@users.noreply.github.com> Date: Sun, 11 May 2025 15:04:05 -0600 Subject: [PATCH 7/7] docs: Add docs about jailing a dataset using the template approach --- docs/chapters/zfs-support.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/chapters/zfs-support.rst b/docs/chapters/zfs-support.rst index 43387001..e29690eb 100644 --- a/docs/chapters/zfs-support.rst +++ b/docs/chapters/zfs-support.rst @@ -82,3 +82,34 @@ the file is simple. pool/other/dataset /other/path/in/jail To remove a dataset from being jailed, we can run ``bastille zfs TARGET unjail pool/dataset``. + +Template Approach +^^^^^^^^^^^^^^^^^ + +While it is possible to "jail" a dataset using a template, it is a bit more "hacky" than the above apporach. +Below is a template that you can use that will add the necessary bits to the ``jail.conf`` file to "jail" a +dataset. + +.. code-block:: shell + + ARG JAIL_NAME + ARG DATASET + ARG MOUNT + + CONFIG set allow.mount + CONFIG set allow.mount.devfs + CONFIG set allow.mount.zfs + CONFIG set enforce_statfs 1 + + CONFIG set "exec.created += '/sbin/zfs jail ${JAIL_NAME} ${DATASET}'" + CONFIG set "exec.start += '/sbin/zfs set mountpoint=${MOUNT} ${DATASET}'" + + RESTART + + CONFIG set "exec.prestop += 'jexec -l -U root ${JAIL_NAME} /sbin/zfs umount ${DATASET}'" + CONFIG set "exec.prestop += '/sbin/zfs unjail ${JAIL_NAME} ${DATASET}'" + + RESTART + +This template can be applied using ``bastille template TARGET project/template --arg DATASET=zpool/dataset --arg MOUNT=/path/inside/jail``. +We do not need the ``JAIL_NAME`` arg, as it will be auto-filled from the supplied ``TARGET`` name.