Merge pull request #1035 from BastilleBSD/zfs-jailed

Support jailing datasets
This commit is contained in:
tschettervictor
2025-05-11 15:04:51 -06:00
committed by GitHub
4 changed files with 190 additions and 22 deletions

View File

@@ -58,3 +58,58 @@ dataset for bastille.
Bastille will mount the datasets it creates at ``bastille_prefix`` which Bastille will mount the datasets it creates at ``bastille_prefix`` which
defaults to ``/usr/local/bastille`` defaults to ``/usr/local/bastille``
If this is not desirable, you can change it at the top of the config file. 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 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.
.. 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``.
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.

View File

@@ -186,6 +186,16 @@ for _jail in ${JAILS}; do
# Start jail # Start jail
jail ${OPTION} -f "${bastille_jailsdir}/${_jail}/jail.conf" -c "${_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 # Add rctl limits
if [ -s "${bastille_jailsdir}/${_jail}/rctl.conf" ]; then if [ -s "${bastille_jailsdir}/${_jail}/rctl.conf" ]; then
while read _limits; do while read _limits; do
@@ -215,4 +225,4 @@ for _jail in ${JAILS}; do
bastille_running_jobs "${bastille_process_limit}" bastille_running_jobs "${bastille_process_limit}"
done done
wait wait

View File

@@ -126,6 +126,14 @@ for _jail in ${JAILS}; do
bastille limits "${_jail}" clear bastille limits "${_jail}" clear
fi fi
# 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}"
zfs unjail "${_jail}" "${_dataset}"
done < "${bastille_jailsdir}/${_jail}/zfs.conf"
fi
# Stop jail # Stop jail
jail ${OPTION} -f "${bastille_jailsdir}/${_jail}/jail.conf" -r "${_jail}" jail ${OPTION} -f "${bastille_jailsdir}/${_jail}/jail.conf" -r "${_jail}"

View File

@@ -33,7 +33,10 @@
. /usr/local/share/bastille/common.sh . /usr/local/share/bastille/common.sh
usage() { 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 cat << EOF
Options: Options:
@@ -44,42 +47,127 @@ EOF
exit 1 exit 1
} }
zfs_jail_dataset() {
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
# 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 >/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"
if [ "${AUTO}" -eq 1 ]; then
bastille start "${_jail}"
fi
}
zfs_unjail_dataset() {
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
# 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."
else
sed -i '' "\#.*${DATASET}.*#d" "${bastille_jailsdir}/${_jail}/zfs.conf"
fi
if [ "${AUTO}" -eq 1 ]; then
bastille start "${_jail}"
fi
}
zfs_snapshot() { zfs_snapshot() {
info "\n[${_jail}]:"
# shellcheck disable=SC2140 # shellcheck disable=SC2140
zfs snapshot -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"@"${TAG}" zfs snapshot -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"@"${TAG}"
} }
zfs_destroy_snapshot() { zfs_destroy_snapshot() {
info "\n[${_jail}]:"
# shellcheck disable=SC2140 # shellcheck disable=SC2140
zfs destroy -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"@"${TAG}" zfs destroy -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"@"${TAG}"
} }
zfs_set_value() { zfs_set_value() {
info "\n[${_jail}]:"
zfs "${ATTRIBUTE}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}" zfs "${ATTRIBUTE}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"
} }
zfs_get_value() { zfs_get_value() {
info "\n[${_jail}]:"
zfs get "${ATTRIBUTE}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}" zfs get "${ATTRIBUTE}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"
} }
zfs_disk_usage() { 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}" zfs list -t all -o name,used,avail,refer,mountpoint,compress,ratio -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"
} }
# Handle options. # Handle options.
AUTO=0
while [ "$#" -gt 0 ]; do while [ "$#" -gt 0 ]; do
case "${1}" in case "${1}" in
-h|--help|help) -h|--help|help)
usage usage
;; ;;
-a|--auto)
AUTO=1
shift
;;
-x|--debug) -x|--debug)
enable_debug enable_debug
shift shift
;; ;;
-*) -*)
error_notify "[ERROR]: Unknown Option: \"${1}\"" for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do
usage case ${_opt} in
a) AUTO=1 ;;
x) enable_debug ;;
*) error_exit "[ERROR]: Unknown Option: \"${1}\"" ;;
esac
done
shift
;; ;;
*) *)
break break
@@ -87,7 +175,7 @@ while [ "$#" -gt 0 ]; do
esac esac
done done
if [ "$#" -lt 2 ]; then if [ "$#" -lt 2 ] || [ "$#" -gt 4 ]; then
usage usage
fi fi
@@ -111,21 +199,7 @@ for _jail in ${JAILS}; do
( (
info "\n[${_jail}]:"
case "${ACTION}" in 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) destroy_snap|destroy_snapshot)
TAG="${3}" TAG="${3}"
zfs_destroy_snapshot zfs_destroy_snapshot
@@ -133,6 +207,27 @@ for _jail in ${JAILS}; do
df|usage) df|usage)
zfs_disk_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 usage
;; ;;
@@ -143,4 +238,4 @@ for _jail in ${JAILS}; do
bastille_running_jobs "${bastille_process_limit}" bastille_running_jobs "${bastille_process_limit}"
done done
wait wait