You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
8.7 KiB
Bash
284 lines
8.7 KiB
Bash
#!/bin/bash
|
|
#
|
|
# Privilege Escalation Helper (PE Helper) example - Fileset manipulation
|
|
#
|
|
# The purpose of this script is to aid in creating and manipulating filesets by using elevated privileges (root) in a
|
|
# controlled way. It works around the problem that - on Linux - only root may manipulate the global filesystem
|
|
# namespace. That means that only root may mount and umount filesystems, even if a less privileged user has been
|
|
# granted permission to mount using "zfs allow".
|
|
#
|
|
#############
|
|
# This script is an example, and by no means complete or guaranteed to be secure. Use at your own risk!
|
|
# There *are* race conditions, always make sure to only pass clean arguments to the script and run only one instance.
|
|
#############
|
|
#
|
|
# The theory of operation for this script is that it allows the caller to create and delete filesets with mountpoints
|
|
# and to manipulate the mountpoint property of existing filesets within certain restrictions:
|
|
# * The fileset be a (grand...)child of a configured parent dataset, i.e. the caller can only manipulate filesets in
|
|
# a subtree of the hierarchy.
|
|
# * The mountpoint must be below a certain filesystem node, i.e. the caller can mount or unmount filesets below a
|
|
# configured parent directory.
|
|
#
|
|
# Note that it is not currently possible to use non-ascii characters in the path or fileset name
|
|
#
|
|
####################
|
|
# Calling convention
|
|
#
|
|
# The first argument specifies the action such as "create" for creating a fileset. Depending on the action, one or
|
|
# more arguments are required, most take two:
|
|
#
|
|
# * "create" has 2 args: fileset name and mountpoint
|
|
# * "destroy" has 1 arg: fileset name
|
|
# * "set_mountpoint" has 2 args: fileset name and new mountpoint
|
|
#
|
|
# The exit code is the primary way to communicate back if something happened. Additionally, messages are written to
|
|
# stdout or stderr to provide more insight.
|
|
#
|
|
# * 0: Everything went well
|
|
# * 1: Parameter or general error (such as commands not found)
|
|
# * 2: Parent directory does not exist or is not a directory
|
|
# * 3: Parent dataset does not exist
|
|
# * 4: Target fileset is not a (grand)child of parent dataset or parent does not exist
|
|
# * 5: Mountpoint is not inside the parent directory or otherwise invalid
|
|
# * 6: Calling the zfs utility failed
|
|
#
|
|
###############
|
|
# Configuration
|
|
#
|
|
# The section following this comment block is for configuration. The following can be configured:
|
|
#
|
|
# * "PARENT_DATASET" is a string pointing to the dataset below which the caller can create and destroy filesets.
|
|
# * "PARENT_DIRECTORY" is a string pointing to the directory below which filesets can be mounted/unmounted by the
|
|
# caller.
|
|
# * "ZFS_BIN": full path to the zfs(8) utility.
|
|
# * "USE_SUDO": Whether to use sudo for the manipulation commands. The lookups will be done without sudo regardless of
|
|
# this setting.
|
|
#
|
|
# It is not required for the parent dataset to be mounted at or below the parent directory, but it must exist prior to
|
|
# calling the helper. The parent directory must also exist.
|
|
#
|
|
|
|
PARENT_DATASET=rpool/test
|
|
PARENT_DIRECTORY=/data
|
|
ZFS_BIN=/usr/bin/zfs
|
|
USE_SUDO=false
|
|
|
|
######################################################################################################################
|
|
# End of configuration section - Do NOT modify anything below this line
|
|
######################################################################################################################
|
|
|
|
if [ ! -x $ZFS_BIN ]
|
|
then
|
|
echo "'zfs' binary can't be executed"
|
|
exit 1
|
|
fi
|
|
|
|
# check the parameters
|
|
if [ $# -lt 1 ]
|
|
then
|
|
echo "not enough parameters"
|
|
exit 1
|
|
fi
|
|
|
|
case $1 in
|
|
create|set_mountpoint)
|
|
if [ $# -ne 3 ]
|
|
then
|
|
echo "incorrect number of arguments, expected 3: ${1} <fileset> <mountpoint>"
|
|
exit 1
|
|
fi
|
|
;;
|
|
destroy)
|
|
if [ $# -ne 2 ]
|
|
then
|
|
echo "incorrect number of arguments, expected 2: ${1} <fileset>"
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "unknown command, chose from: create | destroy | set_mountpoint"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Execute the given parameters with ZFS, optionally using sudo if USE_SUDO is true.
|
|
function exec_zfs()
|
|
{
|
|
params=$*
|
|
# Simple and not at all sufficient check to limit the commands that can be run
|
|
# It also limits the characters allowed as names to the ASCII set, so unicode directory names are not possible
|
|
if [[ $params =~ [a-zA-Z0-9=_\ /-] ]]
|
|
then
|
|
if [[ $USE_SUDO == true ]]
|
|
then
|
|
echo "Issuing sudo command: sudo ${ZFS_BIN} ${params}"
|
|
# shellcheck disable=SC2086
|
|
sudo $ZFS_BIN $params
|
|
else
|
|
echo "Issuing command: ${ZFS_BIN} ${params}"
|
|
# shellcheck disable=SC2086
|
|
$ZFS_BIN $params
|
|
fi
|
|
else
|
|
echo "Command string contains disallowed characters"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Test parent directory
|
|
######
|
|
# test that the parent directory exists
|
|
if ! readlink -qe ${PARENT_DIRECTORY} > /dev/null
|
|
then
|
|
echo "Parent directory ${PARENT_DIRECTORY} does not exist."
|
|
exit 2
|
|
fi
|
|
|
|
# and is a directory
|
|
if [ ! -d ${PARENT_DIRECTORY} ]
|
|
then
|
|
echo "Parent directory ${PARENT_DIRECTORY} is not a directory."
|
|
exit 2
|
|
fi
|
|
|
|
# Test parent dataset
|
|
######
|
|
# Test that the parent dataset exists
|
|
if ! $ZFS_BIN list -d 2 "${PARENT_DATASET}" -o name -H > /dev/null
|
|
then
|
|
echo "Parent dataset ${PARENT_DATASET} does not exist."
|
|
exit 3
|
|
fi
|
|
|
|
fileset=$2
|
|
|
|
# Check that the fileset is below the PARENT_DATASET. It does not need to exist.
|
|
pds_tmp="${PARENT_DATASET}/"
|
|
if [[ $fileset != $pds_tmp* ]]
|
|
then
|
|
echo "Fileset does not appear to be a child of parent dataset ${PARENT_DATASET}"
|
|
exit 4
|
|
fi
|
|
|
|
# Branch off for the different actions. At this point, we know that the parent dataset exists and the fileset argument
|
|
# points to a path inside the parent dataset.
|
|
if [[ $1 == 'create' ]]
|
|
then
|
|
echo create
|
|
mountpoint=${3%/}
|
|
|
|
# make sure the direct parent of our soon-to-be fileset exists
|
|
new_fileset_parent=$(dirname "$fileset")
|
|
if ! $ZFS_BIN list -d 2 "$new_fileset_parent" -o name -H > /dev/null
|
|
then
|
|
echo "Parent dataset of new fileset does not exist"
|
|
exit 4
|
|
fi
|
|
|
|
# resolve the mountpoint through symlinks, then try to make sure it is below PARENT_DIRECTORY
|
|
if ! mp_dir=$(readlink -qf "$mountpoint")
|
|
then
|
|
echo "Parent of mountpoint does not exist."
|
|
exit 5
|
|
fi
|
|
|
|
if [[ $mp_dir != $PARENT_DIRECTORY* ]]
|
|
then
|
|
echo "Mountpoint does not appear to be located somewhere below ${PARENT_DIRECTORY}"
|
|
exit 5
|
|
fi
|
|
|
|
# create the fileset
|
|
exec_zfs create -o "mountpoint=${mountpoint}" "$fileset"
|
|
|
|
elif [[ $1 == 'set_mountpoint' ]]
|
|
then
|
|
echo set_mountpoint
|
|
|
|
# check that the fileset exists
|
|
if ! $ZFS_BIN list -d 2 "$fileset" -o name -H > /dev/null
|
|
then
|
|
echo "Fileset does not exist"
|
|
exit 4
|
|
fi
|
|
|
|
# get the type and bail out if it is not a "filesystem"
|
|
if [[ $($ZFS_BIN get -H -o value type "$fileset") != 'filesystem' ]]
|
|
then
|
|
echo "Fileset parameter does not point to a filesystem"
|
|
exit 5
|
|
fi
|
|
|
|
# get the current mountpoint
|
|
if ! mp=$($ZFS_BIN get -H -o value mountpoint "$fileset")
|
|
then
|
|
echo "The old value of the mountpoint property can't be retrieved"
|
|
exit 5
|
|
fi
|
|
|
|
# check that the mountpoint resides below the PARENT_DIRECTORY
|
|
if ! mp_dir=$(readlink -qe "$mp")
|
|
then
|
|
echo "The filesets mountpoint does not exist"
|
|
exit 5
|
|
fi
|
|
|
|
if [[ $mp_dir != $PARENT_DIRECTORY* ]]
|
|
then
|
|
echo "The current mountpoint is outside of ${PARENT_DIRECTORY}"
|
|
exit 5
|
|
fi
|
|
|
|
# check that the parent directory of the new mountpoint exists
|
|
if ! mp_dir_new=$(readlink -qf "$mountpoint")
|
|
then
|
|
echo "The parent of the new mountpoint does not exist"
|
|
exit 5
|
|
fi
|
|
|
|
# check that the new mountpoint is below the PARENT_DIRECTORY, too
|
|
if [[ $mp_dir_new != $PARENT_DIRECTORY* ]]
|
|
then
|
|
echo "The new mountpoint is outside of $PARENT_DIRECTORY"
|
|
exit 5
|
|
fi
|
|
|
|
# change the mountpoint property
|
|
exec_zfs set "mountpoint=${mountpoint}" "$fileset"
|
|
|
|
elif [[ $1 == 'destroy' ]]
|
|
then
|
|
echo destroy
|
|
|
|
if ! $ZFS_BIN list -d 2 "$fileset" -o name -H > /dev/null
|
|
then
|
|
echo "Fileset does not exist"
|
|
exit 4
|
|
fi
|
|
|
|
# get the mountpoint value
|
|
mp=$($ZFS_BIN get -H -o value mountpoint "$fileset")
|
|
|
|
# check that the mountpoint resides below the PARENT_DIRECTORY
|
|
if ! mp_dir=$(readlink -qe "$mp")
|
|
then
|
|
echo "Mountpoint does not exist"
|
|
exit 5
|
|
fi
|
|
|
|
if [[ $mp_dir != $PARENT_DIRECTORY* ]]
|
|
then
|
|
echo "Mountpoint appears to be outside of $PARENT_DIRECTORY"
|
|
exit 5
|
|
fi
|
|
|
|
# remove the fileset
|
|
exec_zfs destroy "$fileset"
|
|
|
|
else
|
|
echo "Unknown action, this should not be reachable"
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|