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

View File

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

View File

@@ -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,127 @@ EOF
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() {
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 +175,7 @@ while [ "$#" -gt 0 ]; do
esac
done
if [ "$#" -lt 2 ]; then
if [ "$#" -lt 2 ] || [ "$#" -gt 4 ]; then
usage
fi
@@ -111,21 +199,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 +207,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 +238,4 @@ for _jail in ${JAILS}; do
bastille_running_jobs "${bastille_process_limit}"
done
wait
wait