diff --git a/usr/local/bin/bastille b/usr/local/bin/bastille index c5442dafe..be16d4492 100755 --- a/usr/local/bin/bastille +++ b/usr/local/bin/bastille @@ -75,6 +75,10 @@ bastille_perms_check() { fi } +## we only load the config if conf_check passes +. /usr/local/etc/bastille/bastille.conf + +bastille_conf_check bastille_perms_check ## version @@ -99,6 +103,7 @@ Available Commands: create Create a new thin container or a thick container if -T|--thick option specified. destroy Destroy a stopped container or a FreeBSD release. edit Edit container configuration files (advanced). + etcupdate Update container /etc directory to specified release. export Exports a specified container. help Help about any command. htop Interactive process viewer (requires htop). @@ -106,6 +111,7 @@ Available Commands: limits Apply resources limits to targeted container(s). See rctl(8). list List containers (running). mount Mount a volume inside the targeted container(s). + network Add or remove interfaces from targeted container(s). pkg Manipulate binary packages within targeted container(s). See pkg(8). rcp reverse cp(1) files from a single container to the host. rdr Redirect host port to container port. @@ -132,104 +138,62 @@ EOF exit 1 } -[ $# -lt 1 ] && usage - -CMD=$1 -shift - -target_all_jails() { - _JAILS=$(/usr/sbin/jls name) - JAILS="" - for _jail in ${_JAILS}; do - _JAILPATH=$(/usr/sbin/jls -j "${_jail}" path) - if [ -z ${_JAILPATH##${bastille_jailsdir}*} ]; then - JAILS="${JAILS} ${_jail}" - fi - done -} - -check_target_is_running() { - if [ ! "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - error_exit "[${TARGET}]: Not started. See 'bastille start ${TARGET}'." - fi -} +if [ "$#" -lt 1 ]; then + usage +else + CMD="${1}" + shift +fi # Handle special-case commands first. case "${CMD}" in -version|-v|--version) - info "${BASTILLE_VERSION}" - exit 0 - ;; -help|-h|--help) - usage - ;; -bootstrap|create|destroy|export|htop|import|list|mount|rdr|restart|setup|start|top|umount|update|upgrade|verify) - # Nothing "extra" to do for these commands. -- cwells - ;; -clone|config|cmd|console|convert|cp|edit|limits|pkg|rcp|rename|service|stop|sysrc|tags|template|zfs) - # Parse the target and ensure it exists. -- cwells - if [ $# -eq 0 ]; then # No target was given, so show the command's help. -- cwells - PARAMS='help' - elif [ "${1}" != 'help' ] && [ "${1}" != '-h' ] && [ "${1}" != '--help' ]; then - TARGET="${1}" - shift - - # This is needed to handle the special case of 'bastille rcp' and 'bastille cp' with the '-q' or '--quiet' - # option specified before the TARGET. Also seems the cp and rcp commands does not support ALL as a target, so - # that's why is handled here. Maybe this behaviour needs an improvement later. -- yaazkal - if { [ "${CMD}" = 'rcp' ] || [ "${CMD}" = 'cp' ]; } && \ - { [ "${TARGET}" = '-q' ] || [ "${TARGET}" = '--quiet' ]; }; then - TARGET="${1}" - JAILS="${TARGET}" - OPTION="-q" - export OPTION - shift - fi - - if [ "${TARGET}" = 'ALL' ]; then - target_all_jails - elif [ "${CMD}" = "pkg" ] && [ "${TARGET}" = '-H' ] || [ "${TARGET}" = '--host' ]; then - TARGET="${1}" - USE_HOST_PKG=1 - if [ "${TARGET}" = 'ALL' ]; then - target_all_jails - else - JAILS="${TARGET}" - check_target_is_running - fi - shift - elif [ "${CMD}" = 'template' ] && [ "${TARGET}" = '--convert' ]; then - # This command does not act on a jail, so we are temporarily bypassing the presence/started - # checks. The command will simply convert a template from hooks to a Bastillefile. -- cwells - : - else - JAILS="${TARGET}" - - # Ensure the target exists. -- cwells - if [ ! -d "${bastille_jailsdir}/${TARGET}" ]; then - error_exit "[${TARGET}]: Not found." - fi - - case "${CMD}" in - cmd|console|pkg|service|stop|sysrc|template) - check_target_is_running - ;; - convert|rename) - # Require the target to be stopped. -- cwells - if [ "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - error_exit "${TARGET} is running. See 'bastille stop ${TARGET}'." - fi - ;; - esac - fi - export USE_HOST_PKG - export TARGET - export JAILS - fi - ;; -*) # Filter out all non-commands - usage - ;; + version|-v|--version) + info "${BASTILLE_VERSION}" + exit 0 + ;; + help|-h|--help) + usage + ;; + bootstrap| \ + clone| \ + cmd| \ + config| \ + console| \ + convert| \ + cp| \ + create| \ + destroy| \ + edit| \ + etcupdate| \ + export| \ + htop| \ + import| \ + limits| \ + list| \ + mount| \ + network| \ + pkg| \ + rcp| \ + rdr| \ + rename| \ + restart| \ + service| \ + setup| \ + start| \ + stop| \ + sysrc| \ + tags| \ + template| \ + top| \ + umount| \ + update| \ + upgrade| \ + verify| \ + zfs) + ;; + *) + usage + ;; esac # shellcheck disable=SC2154 @@ -237,14 +201,8 @@ SCRIPTPATH="${bastille_sharedir}/${CMD}.sh" if [ -f "${SCRIPTPATH}" ]; then : "${UMASK:=022}" umask "${UMASK}" - : "${SH:=sh}" - - if [ -n "${PARAMS}" ]; then - exec "${SH}" "${SCRIPTPATH}" "${PARAMS}" - else - exec "${SH}" "${SCRIPTPATH}" "$@" - fi + exec "${SH}" "${SCRIPTPATH}" "$@" else error_exit "${SCRIPTPATH} not found." -fi +fi \ No newline at end of file diff --git a/usr/local/share/bastille/bootstrap.sh b/usr/local/share/bastille/bootstrap.sh index b981e9abb..e0f957b5b 100644 --- a/usr/local/share/bastille/bootstrap.sh +++ b/usr/local/share/bastille/bootstrap.sh @@ -34,48 +34,47 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille bootstrap [release|template] [update|arch]" -} + error_notify "Usage: bastille bootstrap [option(s)] [RELEASE|TEMPLATE] [update|arch]" + cat << EOF + Options: -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac + -r | --repo Bootstrap multiple templates inside one repository. + -x | --debug Enable debug mode. -bastille_root_check +EOF + exit 1 +} -#Validate if ZFS is enabled in rc.conf and bastille.conf. -if [ "$(sysrc -n zfs_enable)" = "YES" ] && ! checkyesno bastille_zfs_enable; then - warn "ZFS is enabled in rc.conf but not bastille.conf. Do you want to continue? (N|y)" - read answer - case $answer in - no|No|n|N|"") - error_exit "ERROR: Missing ZFS parameters. See bastille_zfs_enable." +# Handle options. +MULTI_REPO=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -r|--repo) + MULTI_REPO=1 + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + r) MULTI_REPO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break ;; - yes|Yes|y|Y) ;; esac -fi - -# Validate ZFS parameters. -if checkyesno bastille_zfs_enable; then - ## check for the ZFS pool and bastille prefix - if [ -z "${bastille_zfs_zpool}" ]; then - error_exit "ERROR: Missing ZFS parameters. See bastille_zfs_zpool." - elif [ -z "${bastille_zfs_prefix}" ]; then - error_exit "ERROR: Missing ZFS parameters. See bastille_zfs_prefix." - elif ! zfs list "${bastille_zfs_zpool}" > /dev/null 2>&1; then - error_exit "ERROR: ${bastille_zfs_zpool} is not a ZFS pool." - fi - - ## check for the ZFS dataset prefix if already exist - if [ -d "/${bastille_zfs_zpool}/${bastille_zfs_prefix}" ]; then - if ! zfs list "${bastille_zfs_zpool}/${bastille_zfs_prefix}" > /dev/null 2>&1; then - error_exit "ERROR: ${bastille_zfs_zpool}/${bastille_zfs_prefix} is not a ZFS dataset." - fi - fi -fi +done validate_release_url() { ## check upstream url, else warn user @@ -214,13 +213,12 @@ bootstrap_directories() { } bootstrap_release() { + # shellcheck disable=SC2010 ## if release exists quit, else bootstrap additional distfiles if [ -f "${bastille_releasesdir}/${RELEASE}/COPYRIGHT" ]; then ## check distfiles list and skip existing cached files bastille_bootstrap_archives=$(echo "${bastille_bootstrap_archives}" | sed "s/base//") - # TODO check how to handle this - # shellcheck disable=SC2010 - bastille_cached_files=$(ls "${bastille_cachedir}/${RELEASE}" | grep -v "MANIFEST" | tr -d ".txz") + bastille_cached_files="$(ls "${bastille_cachedir}/${RELEASE}" | grep -v "MANIFEST" | tr -d ".txz")" for distfile in ${bastille_cached_files}; do bastille_bootstrap_archives=$(echo "${bastille_bootstrap_archives}" | sed "s/${distfile}//") done @@ -350,7 +348,7 @@ debootstrap_release() { ;; esac else - # If already set in /boot/loader.conf, check and try to load the module. + # If already set in /boot/loader.conf, check and try to load the module. if ! kldstat -m ${_req_kmod} >/dev/null 2>&1; then info "Loading kernel module: ${_req_kmod}" kldload -v ${_req_kmod} @@ -432,8 +430,12 @@ bootstrap_template() { _url=${BASTILLE_TEMPLATE_URL} _user=${BASTILLE_TEMPLATE_USER} _repo=${BASTILLE_TEMPLATE_REPO%.*} # Remove the trailing ".git" - _template=${bastille_templatesdir}/${_user}/${_repo} - + if [ "${MULTI_REPO}" -eq 1 ]; then + _template=${bastille_templatesdir}/${_repo} + else + _template=${bastille_templatesdir}/${_user}/${_repo} + fi +set -x ## support for non-git if ! which -s git; then error_notify "Git not found." @@ -447,13 +449,65 @@ bootstrap_template() { error_notify "Template update unsuccessful." fi fi - - bastille verify "${_user}/${_repo}" + if [ "${MULTI_REPO}" -eq 1 ]; then + # shellcheck disable=SC2045 + for _template_dir in $(ls ${_template}); do + if [ -f ${_template}/${_template_dir}/Bastillefile ]; then + bastille verify "${_repo}/${_template_dir}" + fi + done + else + bastille verify "${_user}/${_repo}" + fi } +# Handle special-case commands first. +case "$1" in + help|-h|--help) + usage + ;; +esac + +RELEASE="${1}" +OPTION="${2}" +NOCACHEDIR= HW_MACHINE=$(sysctl hw.machine | awk '{ print $2 }') HW_MACHINE_ARCH=$(sysctl hw.machine_arch | awk '{ print $2 }') +bastille_root_check + +#Validate if ZFS is enabled in rc.conf and bastille.conf. +if [ "$(sysrc -n zfs_enable)" = "YES" ] && ! checkyesno bastille_zfs_enable; then + warn "ZFS is enabled in rc.conf but not bastille.conf. Do you want to continue? (N|y)" + read answer + case $answer in + no|No|n|N|"") + error_exit "ERROR: Missing ZFS parameters. See bastille_zfs_enable." + ;; + yes|Yes|y|Y) + ;; + esac +fi + +# Validate ZFS parameters. +if checkyesno bastille_zfs_enable; then + ## check for the ZFS pool and bastille prefix + if [ -z "${bastille_zfs_zpool}" ]; then + error_exit "ERROR: Missing ZFS parameters. See bastille_zfs_zpool." + elif [ -z "${bastille_zfs_prefix}" ]; then + error_exit "ERROR: Missing ZFS parameters. See bastille_zfs_prefix." + elif ! zfs list "${bastille_zfs_zpool}" > /dev/null 2>&1; then + error_exit "ERROR: ${bastille_zfs_zpool} is not a ZFS pool." + fi + + ## check for the ZFS dataset prefix if already exist + if [ -d "/${bastille_zfs_zpool}/${bastille_zfs_prefix}" ]; then + if ! zfs list "${bastille_zfs_zpool}/${bastille_zfs_prefix}" > /dev/null 2>&1; then + error_exit "ERROR: ${bastille_zfs_zpool}/${bastille_zfs_prefix} is not a ZFS dataset." + fi + fi +fi + # bootstrapping from aarch64/arm64 Debian or Ubuntu require a different value for ARCH # create a new variable if [ "${HW_MACHINE_ARCH}" = "aarch64" ]; then @@ -462,10 +516,6 @@ else HW_MACHINE_ARCH_LINUX=${HW_MACHINE_ARCH} fi -NOCACHEDIR= -RELEASE="${1}" -OPTION="${2}" - # Alternate RELEASE/ARCH fetch support(experimental) if [ -n "${OPTION}" ] && [ "${OPTION}" != "${HW_MACHINE}" ] && [ "${OPTION}" != "update" ]; then # Supported architectures @@ -484,133 +534,133 @@ fi ## Filter sane release names case "${1}" in -2.[0-9]*) - ## check for MidnightBSD releases name - NAME_VERIFY=$(echo "${RELEASE}") - UPSTREAM_URL="${bastille_url_midnightbsd}${HW_MACHINE_ARCH}/${NAME_VERIFY}" - PLATFORM_OS="MidnightBSD" - validate_release_url - ;; -*-CURRENT|*-current) - ## check for FreeBSD releases name - NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '^([1-9]{2,2})\.[0-9](-CURRENT)$' | tr '[:lower:]' '[:upper:]') - UPSTREAM_URL=$(echo "${bastille_url_freebsd}${HW_MACHINE}/${HW_MACHINE_ARCH}/${NAME_VERIFY}" | sed 's/releases/snapshots/') - PLATFORM_OS="FreeBSD" - validate_release_url - ;; -*-RELEASE|*-release|*-RC[1-9]|*-rc[1-9]|*-BETA[1-9]) - ## check for FreeBSD releases name - NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '^([0-9]{1,2})\.[0-9](-RELEASE|-RC[1-9]|-BETA[1-9])$' | tr '[:lower:]' '[:upper:]') - UPSTREAM_URL="${bastille_url_freebsd}${HW_MACHINE}/${HW_MACHINE_ARCH}/${NAME_VERIFY}" - PLATFORM_OS="FreeBSD" - validate_release_url - ;; -*-stable-LAST|*-STABLE-last|*-stable-last|*-STABLE-LAST) - ## check for HardenedBSD releases name(previous infrastructure, keep for reference) - NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '^([1-9]{2,2})(-stable-last)$' | sed 's/STABLE/stable/g' | sed 's/last/LAST/g') - UPSTREAM_URL="${bastille_url_hardenedbsd}${HW_MACHINE}/${HW_MACHINE_ARCH}/hardenedbsd-${NAME_VERIFY}" - PLATFORM_OS="HardenedBSD" - validate_release_url - ;; -*-stable-build-[0-9]*|*-STABLE-BUILD-[0-9]*) - ## check for HardenedBSD(specific stable build releases) - NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '([0-9]{1,2})(-stable-build)-([0-9]{1,3})$' | sed 's/BUILD/build/g' | sed 's/STABLE/stable/g') - NAME_RELEASE=$(echo "${NAME_VERIFY}" | sed 's/-build-[0-9]\{1,3\}//g') - NAME_BUILD=$(echo "${NAME_VERIFY}" | sed 's/[0-9]\{1,2\}-stable-//g') - UPSTREAM_URL="${bastille_url_hardenedbsd}${NAME_RELEASE}/${HW_MACHINE}/${HW_MACHINE_ARCH}/${NAME_BUILD}" - PLATFORM_OS="HardenedBSD" - validate_release_url - ;; -*-stable-build-latest|*-stable-BUILD-LATEST|*-STABLE-BUILD-LATEST) - ## check for HardenedBSD(latest stable build release) - NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '([0-9]{1,2})(-stable-build-latest)$' | sed 's/STABLE/stable/g' | sed 's/build/BUILD/g' | sed 's/latest/LATEST/g') - NAME_RELEASE=$(echo "${NAME_VERIFY}" | sed 's/-BUILD-LATEST//g') - NAME_BUILD=$(echo "${NAME_VERIFY}" | sed 's/[0-9]\{1,2\}-stable-BUILD-//g') - UPSTREAM_URL="${bastille_url_hardenedbsd}${NAME_RELEASE}/${HW_MACHINE}/${HW_MACHINE_ARCH}/installer/${NAME_BUILD}" - PLATFORM_OS="HardenedBSD" - validate_release_url - ;; -current-build-[0-9]*|CURRENT-BUILD-[0-9]*) - ## check for HardenedBSD(specific current build releases) - NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '(current-build)-([0-9]{1,3})' | sed 's/BUILD/build/g' | sed 's/CURRENT/current/g') - NAME_RELEASE=$(echo "${NAME_VERIFY}" | sed 's/current-.*/current/g') - NAME_BUILD=$(echo "${NAME_VERIFY}" | sed 's/current-//g') - UPSTREAM_URL="${bastille_url_hardenedbsd}${NAME_RELEASE}/${HW_MACHINE}/${HW_MACHINE_ARCH}/${NAME_BUILD}" - PLATFORM_OS="HardenedBSD" - validate_release_url - ;; -current-build-latest|current-BUILD-LATEST|CURRENT-BUILD-LATEST) - ## check for HardenedBSD(latest current build release) - NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '(current-build-latest)' | sed 's/CURRENT/current/g' | sed 's/build/BUILD/g' | sed 's/latest/LATEST/g') - NAME_RELEASE=$(echo "${NAME_VERIFY}" | sed 's/current-.*/current/g') - NAME_BUILD=$(echo "${NAME_VERIFY}" | sed 's/current-BUILD-//g') - UPSTREAM_URL="${bastille_url_hardenedbsd}${NAME_RELEASE}/${HW_MACHINE}/${HW_MACHINE_ARCH}/installer/${NAME_BUILD}" - PLATFORM_OS="HardenedBSD" - validate_release_url - ;; -http?://*/*/*) - BASTILLE_TEMPLATE_URL=${1} - BASTILLE_TEMPLATE_USER=$(echo "${1}" | awk -F / '{ print $4 }') - BASTILLE_TEMPLATE_REPO=$(echo "${1}" | awk -F / '{ print $5 }') - bootstrap_template - ;; -git@*:*/*) - BASTILLE_TEMPLATE_URL=${1} - git_repository=$(echo "${1}" | awk -F : '{ print $2 }') - BASTILLE_TEMPLATE_USER=$(echo "${git_repository}" | awk -F / '{ print $1 }') - BASTILLE_TEMPLATE_REPO=$(echo "${git_repository}" | awk -F / '{ print $2 }') - bootstrap_template - ;; -#adding Ubuntu Bionic as valid "RELEASE" for POC @hackacad -ubuntu_bionic|bionic|ubuntu-bionic) - PLATFORM_OS="Ubuntu/Linux" - LINUX_FLAVOR="bionic" - DIR_BOOTSTRAP="Ubuntu_1804" - ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} - debootstrap_release - ;; -ubuntu_focal|focal|ubuntu-focal) - PLATFORM_OS="Ubuntu/Linux" - LINUX_FLAVOR="focal" - DIR_BOOTSTRAP="Ubuntu_2004" - ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} - debootstrap_release - ;; -ubuntu_jammy|jammy|ubuntu-jammy) - PLATFORM_OS="Ubuntu/Linux" - LINUX_FLAVOR="jammy" - DIR_BOOTSTRAP="Ubuntu_2204" - ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} - debootstrap_release - ;; -debian_buster|buster|debian-buster) - PLATFORM_OS="Debian/Linux" - LINUX_FLAVOR="buster" - DIR_BOOTSTRAP="Debian10" - ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} - debootstrap_release - ;; -debian_bullseye|bullseye|debian-bullseye) - PLATFORM_OS="Debian/Linux" - LINUX_FLAVOR="bullseye" - DIR_BOOTSTRAP="Debian11" - ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} - debootstrap_release - ;; -debian_bookworm|bookworm|debian-bookworm) - PLATFORM_OS="Debian/Linux" - LINUX_FLAVOR="bookworm" - DIR_BOOTSTRAP="Debian12" - ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} - debootstrap_release - ;; -*) - usage - ;; + 2.[0-9]*) + ## check for MidnightBSD releases name + NAME_VERIFY=$(echo "${RELEASE}") + UPSTREAM_URL="${bastille_url_midnightbsd}${HW_MACHINE_ARCH}/${NAME_VERIFY}" + PLATFORM_OS="MidnightBSD" + validate_release_url + ;; + *-CURRENT|*-current) + ## check for FreeBSD releases name + NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '^([1-9]{2,2})\.[0-9](-CURRENT)$' | tr '[:lower:]' '[:upper:]') + UPSTREAM_URL=$(echo "${bastille_url_freebsd}${HW_MACHINE}/${HW_MACHINE_ARCH}/${NAME_VERIFY}" | sed 's/releases/snapshots/') + PLATFORM_OS="FreeBSD" + validate_release_url + ;; + *-RELEASE|*-release|*-RC[1-9]|*-rc[1-9]|*-BETA[1-9]) + ## check for FreeBSD releases name + NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '^([0-9]{1,2})\.[0-9](-RELEASE|-RC[1-9]|-BETA[1-9])$' | tr '[:lower:]' '[:upper:]') + UPSTREAM_URL="${bastille_url_freebsd}${HW_MACHINE}/${HW_MACHINE_ARCH}/${NAME_VERIFY}" + PLATFORM_OS="FreeBSD" + validate_release_url + ;; + *-stable-LAST|*-STABLE-last|*-stable-last|*-STABLE-LAST) + ## check for HardenedBSD releases name(previous infrastructure, keep for reference) + NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '^([1-9]{2,2})(-stable-last)$' | sed 's/STABLE/stable/g' | sed 's/last/LAST/g') + UPSTREAM_URL="${bastille_url_hardenedbsd}${HW_MACHINE}/${HW_MACHINE_ARCH}/hardenedbsd-${NAME_VERIFY}" + PLATFORM_OS="HardenedBSD" + validate_release_url + ;; + *-stable-build-[0-9]*|*-STABLE-BUILD-[0-9]*) + ## check for HardenedBSD(specific stable build releases) + NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '([0-9]{1,2})(-stable-build)-([0-9]{1,3})$' | sed 's/BUILD/build/g' | sed 's/STABLE/stable/g') + NAME_RELEASE=$(echo "${NAME_VERIFY}" | sed 's/-build-[0-9]\{1,3\}//g') + NAME_BUILD=$(echo "${NAME_VERIFY}" | sed 's/[0-9]\{1,2\}-stable-//g') + UPSTREAM_URL="${bastille_url_hardenedbsd}${NAME_RELEASE}/${HW_MACHINE}/${HW_MACHINE_ARCH}/${NAME_BUILD}" + PLATFORM_OS="HardenedBSD" + validate_release_url + ;; + *-stable-build-latest|*-stable-BUILD-LATEST|*-STABLE-BUILD-LATEST) + ## check for HardenedBSD(latest stable build release) + NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '([0-9]{1,2})(-stable-build-latest)$' | sed 's/STABLE/stable/g' | sed 's/build/BUILD/g' | sed 's/latest/LATEST/g') + NAME_RELEASE=$(echo "${NAME_VERIFY}" | sed 's/-BUILD-LATEST//g') + NAME_BUILD=$(echo "${NAME_VERIFY}" | sed 's/[0-9]\{1,2\}-stable-BUILD-//g') + UPSTREAM_URL="${bastille_url_hardenedbsd}${NAME_RELEASE}/${HW_MACHINE}/${HW_MACHINE_ARCH}/installer/${NAME_BUILD}" + PLATFORM_OS="HardenedBSD" + validate_release_url + ;; + current-build-[0-9]*|CURRENT-BUILD-[0-9]*) + ## check for HardenedBSD(specific current build releases) + NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '(current-build)-([0-9]{1,3})' | sed 's/BUILD/build/g' | sed 's/CURRENT/current/g') + NAME_RELEASE=$(echo "${NAME_VERIFY}" | sed 's/current-.*/current/g') + NAME_BUILD=$(echo "${NAME_VERIFY}" | sed 's/current-//g') + UPSTREAM_URL="${bastille_url_hardenedbsd}${NAME_RELEASE}/${HW_MACHINE}/${HW_MACHINE_ARCH}/${NAME_BUILD}" + PLATFORM_OS="HardenedBSD" + validate_release_url + ;; + current-build-latest|current-BUILD-LATEST|CURRENT-BUILD-LATEST) + ## check for HardenedBSD(latest current build release) + NAME_VERIFY=$(echo "${RELEASE}" | grep -iwE '(current-build-latest)' | sed 's/CURRENT/current/g' | sed 's/build/BUILD/g' | sed 's/latest/LATEST/g') + NAME_RELEASE=$(echo "${NAME_VERIFY}" | sed 's/current-.*/current/g') + NAME_BUILD=$(echo "${NAME_VERIFY}" | sed 's/current-BUILD-//g') + UPSTREAM_URL="${bastille_url_hardenedbsd}${NAME_RELEASE}/${HW_MACHINE}/${HW_MACHINE_ARCH}/installer/${NAME_BUILD}" + PLATFORM_OS="HardenedBSD" + validate_release_url + ;; + http?://*/*/*) + BASTILLE_TEMPLATE_URL=${1} + BASTILLE_TEMPLATE_USER=$(echo "${1}" | awk -F / '{ print $4 }') + BASTILLE_TEMPLATE_REPO=$(echo "${1}" | awk -F / '{ print $5 }') + bootstrap_template + ;; + git@*:*/*) + BASTILLE_TEMPLATE_URL=${1} + git_repository=$(echo "${1}" | awk -F : '{ print $2 }') + BASTILLE_TEMPLATE_USER=$(echo "${git_repository}" | awk -F / '{ print $1 }') + BASTILLE_TEMPLATE_REPO=$(echo "${git_repository}" | awk -F / '{ print $2 }') + bootstrap_template + ;; + #adding Ubuntu Bionic as valid "RELEASE" for POC @hackacad + ubuntu_bionic|bionic|ubuntu-bionic) + PLATFORM_OS="Ubuntu/Linux" + LINUX_FLAVOR="bionic" + DIR_BOOTSTRAP="Ubuntu_1804" + ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} + debootstrap_release + ;; + ubuntu_focal|focal|ubuntu-focal) + PLATFORM_OS="Ubuntu/Linux" + LINUX_FLAVOR="focal" + DIR_BOOTSTRAP="Ubuntu_2004" + ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} + debootstrap_release + ;; + ubuntu_jammy|jammy|ubuntu-jammy) + PLATFORM_OS="Ubuntu/Linux" + LINUX_FLAVOR="jammy" + DIR_BOOTSTRAP="Ubuntu_2204" + ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} + debootstrap_release + ;; + debian_buster|buster|debian-buster) + PLATFORM_OS="Debian/Linux" + LINUX_FLAVOR="buster" + DIR_BOOTSTRAP="Debian10" + ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} + debootstrap_release + ;; + debian_bullseye|bullseye|debian-bullseye) + PLATFORM_OS="Debian/Linux" + LINUX_FLAVOR="bullseye" + DIR_BOOTSTRAP="Debian11" + ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} + debootstrap_release + ;; + debian_bookworm|bookworm|debian-bookworm) + PLATFORM_OS="Debian/Linux" + LINUX_FLAVOR="bookworm" + DIR_BOOTSTRAP="Debian12" + ARCH_BOOTSTRAP=${HW_MACHINE_ARCH_LINUX} + debootstrap_release + ;; + *) + usage + ;; esac case "${OPTION}" in -update) - bastille update "${RELEASE}" - ;; -esac + update) + bastille update "${RELEASE}" + ;; +esac \ No newline at end of file diff --git a/usr/local/share/bastille/clone.sh b/usr/local/share/bastille/clone.sh index 9dae7f447..b24882bf1 100644 --- a/usr/local/share/bastille/clone.sh +++ b/usr/local/share/bastille/clone.sh @@ -34,24 +34,79 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille clone TARGET NEW_NAME IPADDRESS" + error_notify "Usage: bastille clone [option(s)] TARGET NEW_NAME IP_ADDRESS" + cat << EOF + Options: + + -f | --force Stop the jail if it is running. Cannot be used with [-l|--live]. + -l | --live Clone a running jail. ZFS only. Jail must be running. Cannot be used with [-f|--force]. + -s | --start Start jail(s) when complete. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first -case "$1" in -help|-h|--help) - usage - ;; -esac +# Handle options. +AUTO=0 +LIVE=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -l|--live) + if ! checkyesno bastille_zfs_enable; then + error_exit "[-l|--live] can only be used with ZFS." + else + LIVE=1 + shift + fi + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + l) LIVE=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -ne 2 ]; then +if [ "${AUTO}" -eq 1 ] && [ "${LIVE}" -eq 1 ]; then + error_exit "[-a|--auto] cannot be used with [-l|--live]" +fi + +if [ $# -ne 3 ]; then usage fi +TARGET="${1}" +NEWNAME="${2}" +IP="${3}" + bastille_root_check +set_target_single "${TARGET}" -NEWNAME="${1}" -IP="${2}" +## don't allow for dots(.) in container names +if echo "${NEWNAME}" | grep -q "[.]"; then + error_exit "Container names may not contain a dot(.)!" +fi validate_ip() { IPX_ADDR="ip4.addr" @@ -89,79 +144,132 @@ update_jailconf() { JAIL_CONFIG="${bastille_jailsdir}/${NEWNAME}/jail.conf" if [ -f "${JAIL_CONFIG}" ]; then if ! grep -qw "path = ${bastille_jailsdir}/${NEWNAME}/root;" "${JAIL_CONFIG}"; then - sed -i '' "s|host.hostname = ${TARGET};|host.hostname = ${NEWNAME};|" "${JAIL_CONFIG}" + sed -i '' "s|host.hostname = ${TARGET};|host.hostname = ${NEWNAME};|" "${JAIL_CONFIG}" sed -i '' "s|exec.consolelog = .*;|exec.consolelog = ${bastille_logsdir}/${NEWNAME}_console.log;|" "${JAIL_CONFIG}" sed -i '' "s|path = .*;|path = ${bastille_jailsdir}/${NEWNAME}/root;|" "${JAIL_CONFIG}" sed -i '' "s|mount.fstab = .*;|mount.fstab = ${bastille_jailsdir}/${NEWNAME}/fstab;|" "${JAIL_CONFIG}" sed -i '' "s|${TARGET} {|${NEWNAME} {|" "${JAIL_CONFIG}" - sed -i '' "s|${IPX_ADDR} = .*;|${IPX_ADDR} = ${IP};|" "${JAIL_CONFIG}" fi fi if grep -qw "vnet;" "${JAIL_CONFIG}"; then update_jailconf_vnet + else + _ip4="$(bastille config ${TARGET} get ip4.addr | sed 's/,/ /g')" + _ip6="$(bastille config ${TARGET} get ip6.addr | sed 's/,/ /g')" + # IP4 + if [ "${_ip4}" != "not set" ]; then + for _ip in ${_ip4}; do + _ip="$(echo ${_ip} | awk -F"|" '{print $2}')" + sed -i '' "/${IPX_ADDR} = .*/ s/${_ip}/${IP}/" "${JAIL_CONFIG}" + sed -i '' "/${IPX_ADDR} += .*/ s/${_ip}/127.0.0.1/" "${JAIL_CONFIG}" + done + fi + # IP6 + if [ "${_ip6}" != "not set" ]; then + for _ip in ${_ip6}; do + _ip="$(echo ${_ip} | awk -F"|" '{print $2}')" + sed -i '' "/${IPX_ADDR} = .*/ s/${_ip}/${IP}/" "${JAIL_CONFIG}" + sed -i '' "/${IPX_ADDR} += .*/ s/${_ip}/127.0.0.1/" "${JAIL_CONFIG}" + done + fi fi } update_jailconf_vnet() { bastille_jail_rc_conf="${bastille_jailsdir}/${NEWNAME}/root/etc/rc.conf" - - # Determine number of containers and define an uniq_epair - local list_jails_num="$(bastille list jails | wc -l | awk '{print $1}')" - local num_range="$(expr "${list_jails_num}" + 1)" - jail_list=$(bastille list jail) - for _num in $(seq 0 "${num_range}"); do - if [ -n "${jail_list}" ]; then - if ! grep -q "e0b_bastille${_num}" "${bastille_jailsdir}"/*/jail.conf; then - if ! grep -q "epair${_num}" "${bastille_jailsdir}"/*/jail.conf; then - local uniq_epair="bastille${_num}" + # Determine number of interfaces and define a uniq_epair + local _if_list="$(grep -Eo 'epair[0-9]+|bastille[0-9]+' ${JAIL_CONFIG} | sort -u)" + for _if in ${_if_list}; do + local _epair_if_count="$(grep -Eo 'epair[0-9]+' ${bastille_jailsdir}/*/jail.conf | sort -u | wc -l | awk '{print $1}')" + local _bastille_if_count="$(grep -Eo 'bastille[0-9]+' ${bastille_jailsdir}/*/jail.conf | sort -u | wc -l | awk '{print $1}')" + local epair_num_range=$((_epair_if_count + 1)) + local bastille_num_range=$((_vnet_if_count + 1)) + if echo ${_if} | grep -Eoq 'epair[0-9]+'; then + # Update bridged VNET config + for _num in $(seq 0 "${epair_num_range}"); do + if ! grep -oq "epair${_num}" ${bastille_jailsdir}/*/jail.conf; then + # Update jail.conf epair name local uniq_epair_bridge="${_num}" - # since we don't have access to the external_interface variable, we cat the jail.conf file to retrieve the mac prefix + local _if_epaira="$(grep "${_if}" ${JAIL_CONFIG} | grep -Eo -m 1 "epair[1-9]+a")" + local _if_epairb="$(grep "${_if}" ${JAIL_CONFIG} | grep -Eo -m 1 "epair[1-9]+b")" + local _if_vnet="$(grep ${_if_epairb} "${bastille_jail_rc_conf}" | grep -Eo -m 1 "vnet[1-9]+")" + sed -i '' "s|${_if}|epair${uniq_epair_bridge}|g" "${JAIL_CONFIG}" + # since we don't have access to the external_interface variable, we cat the jail.conf file to retrieve the mac prefix # we also do not use the main generate_static_mac function here - local macaddr_prefix="$(cat ${JAIL_CONFIG} | grep -m 1 ether | grep -oE '([0-9a-f]{2}(:[0-9a-f]{2}){5})' | awk -F: '{print $1":"$2":"$3}')" - local macaddr_suffix="$(echo -n ${NEWNAME} | sha256 | cut -b -5 | sed 's/\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F]\)/\1:\2:\3/')" - local macaddr="${macaddr_prefix}:${macaddr_suffix}" - # Update the exec.* with uniq_epair when cloning jails. - # for VNET jails - sed -i '' "s|bastille\([0-9]\{1,\}\)|${uniq_epair}|g" "${JAIL_CONFIG}" - sed -i '' "s|e\([0-9]\{1,\}\)a_${NEWNAME}|e${uniq_epair_bridge}a_${NEWNAME}|g" "${JAIL_CONFIG}" - sed -i '' "s|e\([0-9]\{1,\}\)b_${NEWNAME}|e${uniq_epair_bridge}b_${NEWNAME}|g" "${JAIL_CONFIG}" - sed -i '' "s|epair\([0-9]\{1,\}\)|epair${uniq_epair_bridge}|g" "${JAIL_CONFIG}" - sed -i '' "s|exec.prestart += \"ifconfig e0a_bastille\([0-9]\{1,\}\).*description.*|exec.prestart += \"ifconfig e0a_${uniq_epair} description \\\\\"vnet host interface for Bastille jail ${NEWNAME}\\\\\"\";|" "${JAIL_CONFIG}" - sed -i '' "s|ether.*:.*:.*:.*:.*:.*a\";|ether ${macaddr}a\";|" "${JAIL_CONFIG}" - sed -i '' "s|ether.*:.*:.*:.*:.*:.*b\";|ether ${macaddr}b\";|" "${JAIL_CONFIG}" + if grep -oq ${_if} ${JAIL_CONFIG} | grep -oq ether; then + local macaddr_prefix="$(cat ${JAIL_CONFIG} | grep ${_if} | grep -m 1 ether | grep -oE '([0-9a-f]{2}(:[0-9a-f]{2}){5})' | awk -F: '{print $1":"$2":"$3}')" + local macaddr_suffix="$(echo -n ${NEWNAME} | sha256 | cut -b -5 | sed 's/\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F]\)/\1:\2:\3/')" + local macaddr="${macaddr_prefix}:${macaddr_suffix}" + sed -i '' "s|epair${uniq_epair}a ether.*:.*:.*:.*:.*:.*a\";|epair${uniq_epair}a ether ${macaddr}a\";|" "${JAIL_CONFIG}" + sed -i '' "s|epair${uniq_epair}b ether.*:.*:.*:.*:.*:.*b\";|epair${uniq_epair}b ether ${macaddr}b\";|" "${JAIL_CONFIG}" + fi + sed -i '' "s|vnet host interface for Bastille jail ${TARGET}|vnet host interface for Bastille jail ${NEWNAME}|g" "${JAIL_CONFIG}" + # Update /etc/rc.conf + sed -i '' "s|${_if_epairb}_name|epair${uniq_epair_bridge}b_name|" "${bastille_jail_rc_conf}" + if grep "vnet0" "${bastille_jail_rc_conf}" | grep -q "epair${uniq_epair_bridge}b_name"; then + if [ "${IP}" = "0.0.0.0" ]; then + sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0="SYNCDHCP" + else + sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0="inet ${IP}" + fi + else + sysrc -f "${bastille_jail_rc_conf}" ifconfig_${_if_vnet}="SYNCDHCP" + fi break fi - fi + done + elif echo ${_if} | grep -Eoq 'bastille[0-9]+'; then + # Update VNET config + for _num in $(seq 0 "${bastille_num_range}"); do + if ! grep -oq "bastille${_num}" ${bastille_jailsdir}/*/jail.conf; then + # Update jail.conf epair name + local uniq_epair="bastille${_num}" + local _if_vnet="$(grep ${_if} "${bastille_jail_rc_conf}" | grep -Eo -m 1 "vnet[0-9]+")" + sed -i '' "s|${_if}|${uniq_epair}|g" "${JAIL_CONFIG}" + # since we don't have access to the external_interface variable, we cat the jail.conf file to retrieve the mac prefix + # we also do not use the main generate_static_mac function here + if grep -oq ${_if} ${JAIL_CONFIG} | grep -oq ether; then + local macaddr_prefix="$(cat ${JAIL_CONFIG} | grep ${_if} | grep -m 1 ether | grep -oE '([0-9a-f]{2}(:[0-9a-f]{2}){5})' | awk -F: '{print $1":"$2":"$3}')" + local macaddr_suffix="$(echo -n ${NEWNAME} | sha256 | cut -b -5 | sed 's/\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F]\)/\1:\2:\3/')" + local macaddr="${macaddr_prefix}:${macaddr_suffix}" + sed -i '' "s|${uniq_epair} ether.*:.*:.*:.*:.*:.*a\";|${uniq_epair} ether ${macaddr}a\";|" "${JAIL_CONFIG}" + sed -i '' "s|${uniq_epair} ether.*:.*:.*:.*:.*:.*b\";|${uniq_epair} ether ${macaddr}b\";|" "${JAIL_CONFIG}" + fi + sed -i '' "s|vnet host interface for Bastille jail ${TARGET}|vnet host interface for Bastille jail ${NEWNAME}|g" "${JAIL_CONFIG}" + # Update /etc/rc.conf + sed -i '' "s|ifconfig_e0b_${_if}_name|ifconfig_e0b_${uniq_epair}_name|" "${bastille_jail_rc_conf}" + if grep "vnet0" "${bastille_jail_rc_conf}" | grep -q ${uniq_epair}; then + if [ "${IP}" = "0.0.0.0" ]; then + sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0="SYNCDHCP" + else + sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0=" inet ${IP} " + fi + else + sysrc -f "${bastille_jail_rc_conf}" ifconfig_${_if_vnet}="SYNCDHCP" + fi + break + fi + done fi done - - # Rename interface to new uniq_epair - sed -i '' "s|ifconfig_e0b_bastille.*_name|ifconfig_e0b_${uniq_epair}_name|" "${bastille_jail_rc_conf}" - sed -i '' "s|ifconfig_e.*b_${TARGET}_name|ifconfig_e${uniq_epair_bridge}b_${NEWNAME}_name|" "${bastille_jail_rc_conf}" - - # If 0.0.0.0 set DHCP, else set static IP address - if [ "${IP}" = "0.0.0.0" ]; then - sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0="SYNCDHCP" - else - sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0="inet ${IP}" - fi -} - -update_fstab() { - # Update fstab to use the new name - FSTAB_CONFIG="${bastille_jailsdir}/${NEWNAME}/fstab" - if [ -f "${FSTAB_CONFIG}" ]; then - # Update additional fstab paths with new jail path - sed -i '' "s|${bastille_jailsdir}/${TARGET}/root/|${bastille_jailsdir}/${NEWNAME}/root/|" "${FSTAB_CONFIG}" - fi } clone_jail() { - # Attempt container clone - info "Attempting to clone '${TARGET}' to ${NEWNAME}..." + + info "Attempting to clone ${TARGET} to ${NEWNAME}..." + if ! [ -d "${bastille_jailsdir}/${NEWNAME}" ]; then if checkyesno bastille_zfs_enable; then + if [ "${LIVE}" -eq 1 ]; then + check_target_is_running "${TARGET}" || error_exit "[-l|--live] can only be used with a running jail." + else check_target_is_stopped "${TARGET}" || if [ "${FORCE}" -eq 1 ]; then + bastille stop "${TARGET}" + else + error_notify "Jail is running." + error_exit "Use [-f|--force] to force stop the jail, or [-l|--live] (ZFS only) to clone a running jail." + fi + fi if [ -n "${bastille_zfs_zpool}" ]; then # Replicate the existing container DATE=$(date +%F-%H%M%S) @@ -177,13 +285,13 @@ clone_jail() { zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${NEWNAME}@bastille_clone_${DATE}" fi else - # Just clone the jail directory - # Check if container is running - if [ -n "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - error_exit "${TARGET} is running. See 'bastille stop ${TARGET}'." + # Perform container file copy (archive mode) + check_target_is_stopped "${TARGET}" || if [ "${FORCE}" -eq 1 ]; then + bastille stop "${TARGET}" + else + error_notify "Jail is running." + error_exit "Use [-f|--force] to force stop the jail." fi - - # Perform container file copy(archive mode) cp -a "${bastille_jailsdir}/${TARGET}" "${bastille_jailsdir}/${NEWNAME}" fi else @@ -192,7 +300,7 @@ clone_jail() { # Generate jail configuration files update_jailconf - update_fstab + update_fstab "${TARGET}" "${NEWNAME}" # Display the exist status if [ "$?" -ne 0 ]; then @@ -200,18 +308,19 @@ clone_jail() { else info "Cloned '${TARGET}' to '${NEWNAME}' successfully." fi + if [ "${START}" -eq 1 ]; then + if [ "${LIVE}" -eq 0 ]; then + bastille start "${TARGET}" + fi + bastille start "${NEWNAME}" + fi } -## don't allow for dots(.) in container names -if echo "${NEWNAME}" | grep -q "[.]"; then - error_exit "Container names may not contain a dot(.)!" -fi - -## check if ip address is valid +# Check if IP address is valid. if [ -n "${IP}" ]; then validate_ip else usage fi -clone_jail +clone_jail \ No newline at end of file diff --git a/usr/local/share/bastille/cmd.sh b/usr/local/share/bastille/cmd.sh index 277791d13..7dcad9227 100644 --- a/usr/local/share/bastille/cmd.sh +++ b/usr/local/share/bastille/cmd.sh @@ -34,15 +34,47 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille cmd TARGET command" + error_notify "Usage: bastille cmd [option(s)] TARGET command" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# 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 + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done if [ $# -eq 0 ]; then usage @@ -50,30 +82,40 @@ fi bastille_root_check +TARGET="${1}" +shift 1 COUNT=0 RETURN=0 +set_target "${TARGET}" + for _jail in ${JAILS}; do - COUNT=$(($COUNT+1)) + info "[${_jail}]:" + check_target_is_running "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${_jail}" + else + error_notify "Jail is not running." + error_continue "Use [-a|--auto] to auto-start the jail." + fi + + COUNT=$(($COUNT+1)) if grep -qw "linsysfs" "${bastille_jailsdir}/${_jail}/fstab"; then # Allow executing commands on Linux jails. jexec -l -u root "${_jail}" "$@" else jexec -l -U root "${_jail}" "$@" fi - ERROR_CODE=$? - info "[${_jail}]: ${ERROR_CODE}" - + if [ "${ERROR_CODE}" -ne 0 ]; then + warn "[${_jail}]: ${ERROR_CODE}" + fi if [ "$COUNT" -eq 1 ]; then RETURN=${ERROR_CODE} else RETURN=$(($RETURN+$ERROR_CODE)) fi - - echo done # Check when a command is executed in all running jails. (bastille cmd ALL ...) @@ -81,4 +123,4 @@ if [ "${COUNT}" -gt 1 ] && [ "${RETURN}" -gt 0 ]; then RETURN=1 fi -return "${RETURN}" +return "${RETURN}" \ No newline at end of file diff --git a/usr/local/share/bastille/common.sh b/usr/local/share/bastille/common.sh index 4189f07b4..fbf99bc42 100644 --- a/usr/local/share/bastille/common.sh +++ b/usr/local/share/bastille/common.sh @@ -30,7 +30,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# Source config file . /usr/local/etc/bastille/bastille.conf COLOR_RED= @@ -50,24 +49,30 @@ enable_color() { . /usr/local/share/bastille/colors.pre.sh } +enable_debug() { + # Enable debug mode. + warn "***DEBUG MODE START***" + set -x +} + # If "NO_COLOR" environment variable is present, or we aren't speaking to a # tty, disable output colors. if [ -z "${NO_COLOR}" ] && [ -t 1 ]; then enable_color fi -# Error/Info functions -error_notify() { - echo -e "${COLOR_RED}$*${COLOR_RESET}" 1>&2 -} - +# Notify message on error, and continue to next jail error_continue() { error_notify "$@" - # Disabling this shellcheck as we only ever call it inside of a loop # shellcheck disable=SC2104 continue } +# Notify message on error, but do not exit +error_notify() { + echo -e "${COLOR_RED}$*${COLOR_RESET}" 1>&2 +} + # Notify message on error and exit error_exit() { error_notify "$@" @@ -84,7 +89,8 @@ warn() { check_target_exists() { local _TARGET="${1}" - if [ ! -d "${bastille_jailsdir}"/"${_TARGET}" ]; then + local _jaillist="$(bastille list jails)" + if ! echo "${_jaillist}" | grep -Eq "^${_TARGET}$"; then return 1 else return 0 @@ -93,7 +99,7 @@ check_target_exists() { check_target_is_running() { local _TARGET="${1}" - if [ ! "$(/usr/sbin/jls name | awk "/^${_TARGET}$/")" ]; then + if ! jls name | grep -Eq "^${_TARGET}$"; then return 1 else return 0 @@ -102,13 +108,118 @@ check_target_is_running() { check_target_is_stopped() { local _TARGET="${1}" - if [ "$(/usr/sbin/jls name | awk "/^${_TARGET}$/")" ]; then + if jls name | grep -Eq "^${_TARGET}$"; then return 1 else return 0 fi } +get_jail_name() { + local _JID="${1}" + local _jailname="$(jls -j ${_JID} name 2>/dev/null)" + if [ -z "${_jailname}" ]; then + return 1 + else + echo "${_jailname}" + fi +} + +jail_autocomplete() { + local _TARGET="${1}" + local _jaillist="$(bastille list jails)" + local _AUTOTARGET="$(echo "${_jaillist}" | grep -E "^${_TARGET}")" + if [ -n "${_AUTOTARGET}" ]; then + if [ "$(echo "${_AUTOTARGET}" | wc -l)" -eq 1 ]; then + echo "${_AUTOTARGET}" + else + error_continue "Multiple jails found for ${_TARGET}:\n${_AUTOTARGET}" + return 1 + fi + else + return 2 + fi +} + +set_target() { + local _TARGET=${1} + JAILS="" + TARGET="" + if [ "${_TARGET}" = ALL ] || [ "${_TARGET}" = all ]; then + target_all_jails + else + for _jail in ${_TARGET}; do + if [ ! -d "${bastille_jailsdir}/${_TARGET}" ] && echo "${_jail}" | grep -Eq '^[0-9]+$'; then + if get_jail_name "${_jail}" > /dev/null; then + _jail="$(get_jail_name ${_jail})" + else + error_continue "Error: JID \"${_jail}\" not found. Is jail running?" + fi + elif ! check_target_exists "${_jail}"; then + if jail_autocomplete "${_jail}" > /dev/null; then + _jail="$(jail_autocomplete ${_jail})" + elif [ $? -eq 2 ]; then + error_continue "Jail not found \"${_jail}\"" + else + exit 1 + fi + fi + TARGET="${TARGET} ${_jail}" + JAILS="${JAILS} ${_jail}" + done + export TARGET + export JAILS + fi +} + +set_target_single() { + local _TARGET="${1}" + if [ "${_TARGET}" = ALL ] || [ "${_TARGET}" = all ]; then + error_exit "[all|ALL] not supported with this command." + elif [ "$(echo ${_TARGET} | wc -w)" -gt 1 ]; then + error_exit "Error: Command only supports a single TARGET." + elif [ ! -d "${bastille_jailsdir}/${_TARGET}" ] && echo "${_TARGET}" | grep -Eq '^[0-9]+$'; then + if get_jail_name "${_TARGET}" > /dev/null; then + _TARGET="$(get_jail_name ${_TARGET})" + else + error_exit "Error: JID \"${_TARGET}\" not found. Is jail running?" + fi + elif + ! check_target_exists "${_TARGET}"; then + if jail_autocomplete "${_TARGET}" > /dev/null; then + _TARGET="$(jail_autocomplete ${_TARGET})" + elif [ $? -eq 2 ]; then + error_exit "Jail not found \"${_TARGET}\"" + else + exit 1 + fi + fi + TARGET="${_TARGET}" + JAILS="${_TARGET}" + export TARGET + export JAILS +} + +target_all_jails() { + local _JAILS="$(bastille list jails)" + JAILS="" + for _jail in ${_JAILS}; do + if [ -d "${bastille_jailsdir}/${_jail}" ]; then + JAILS="${JAILS} ${_jail}" + fi + done + export JAILS +} + +update_fstab() { + local _oldname="${1}" + local _newname="${2}" + local _fstab="${bastille_jailsdir}/${_newname}/fstab" + if [ -f "${_fstab}" ]; then + sed -i '' "s|${bastille_jailsdir}/${_oldname}/root/|${bastille_jailsdir}/${_newname}/root/|" "${_fstab}" + fi +} + generate_static_mac() { local jail_name="${1}" local external_interface="${2}" @@ -123,47 +234,73 @@ generate_static_mac() { } generate_vnet_jail_netblock() { - local jail_name="$1" - local use_unique_bridge="$2" - local external_interface="$3" - generate_static_mac "${jail_name}" "${external_interface}" - ## determine number of containers + 1 + local jail_name="${1}" + local use_unique_bridge="${2}" + local external_interface="${3}" + local static_mac="${4}" + ## determine number of interfaces + 1 ## iterate num and grep all jail configs ## define uniq_epair - local jail_list="$(bastille list jails)" - if [ -n "${jail_list}" ]; then - local list_jails_num="$(echo "${jail_list}" | wc -l | awk '{print $1}')" - local num_range=$((list_jails_num + 1)) - for _num in $(seq 0 "${num_range}"); do - if ! grep -q "e[0-9]b_bastille${_num}" "${bastille_jailsdir}"/*/jail.conf; then - if ! grep -q "epair${_num}" "${bastille_jailsdir}"/*/jail.conf; then - local uniq_epair="bastille${_num}" + local _epair_if_count="$(grep -Eos 'epair[0-9]+' ${bastille_jailsdir}/*/jail.conf | sort -u | wc -l | awk '{print $1}')" + local _vnet_if_count="$(grep -Eos '_bastille[0-9]+' ${bastille_jailsdir}/*/jail.conf | sort -u | wc -l | awk '{print $1}')" + local epair_num_range=$((_epair_if_count + 1)) + local vnet_num_range=$((_vnet_if_count + 1)) + if [ -n "${use_unique_bridge}" ]; then + if [ "${_epair_if_count}" -gt 0 ]; then + for _num in $(seq 0 "${epair_num_range}"); do + if ! grep -Eosq "epair${_num}" ${bastille_jailsdir}/*/jail.conf; then local uniq_epair_bridge="${_num}" break fi - fi - done + done + else + local uniq_epair_bridge="0" + fi else - local uniq_epair="bastille0" - local uniq_epair_bridge="0" + if [ "${_vnet_if_count}" -gt 0 ]; then + for _num in $(seq 0 "${vnet_num_range}"); do + if ! grep -Eosq "_bastille${_num}" ${bastille_jailsdir}/*/jail.conf; then + local uniq_epair="bastille${_num}" + break + fi + done + else + local uniq_epair="bastille0" + fi fi + ## If BRIDGE is enabled, generate bridge config, else generate VNET config if [ -n "${use_unique_bridge}" ]; then - ## generate bridge config - cat <<-EOF + if [ -n "${static_mac}" ]; then + ## Generate bridged VNET config with static MAC address + generate_static_mac "${jail_name}" "${external_interface}" + cat <<-EOF vnet; - vnet.interface = e${uniq_epair_bridge}b_${jail_name}; + vnet.interface = epair${uniq_epair_bridge}b; exec.prestart += "ifconfig epair${uniq_epair_bridge} create"; exec.prestart += "ifconfig ${external_interface} addm epair${uniq_epair_bridge}a"; - exec.prestart += "ifconfig epair${uniq_epair_bridge}a up name e${uniq_epair_bridge}a_${jail_name}"; - exec.prestart += "ifconfig epair${uniq_epair_bridge}b up name e${uniq_epair_bridge}b_${jail_name}"; - exec.prestart += "ifconfig e${uniq_epair_bridge}a_${jail_name} ether ${macaddr}a"; - exec.prestart += "ifconfig e${uniq_epair_bridge}b_${jail_name} ether ${macaddr}b"; - exec.poststop += "ifconfig ${external_interface} deletem e${uniq_epair_bridge}a_${jail_name}"; - exec.poststop += "ifconfig e${uniq_epair_bridge}a_${jail_name} destroy"; + exec.prestart += "ifconfig epair${uniq_epair_bridge}a ether ${macaddr}a"; + exec.prestart += "ifconfig epair${uniq_epair_bridge}b ether ${macaddr}b"; + exec.prestart += "ifconfig epair${uniq_epair_bridge}a description \"vnet host interface for Bastille jail ${jail_name}\""; + exec.poststop += "ifconfig ${external_interface} deletem epair${uniq_epair_bridge}a"; + exec.poststop += "ifconfig epair${uniq_epair_bridge}a destroy"; EOF + else + ## Generate bridged VNET config without static MAC address + cat <<-EOF + vnet; + vnet.interface = epair${uniq_epair_bridge}b; + exec.prestart += "ifconfig epair${uniq_epair_bridge} create"; + exec.prestart += "ifconfig ${external_interface} addm epair${uniq_epair_bridge}a"; + exec.prestart += "ifconfig epair${uniq_epair_bridge}a description \"vnet host interface for Bastille jail ${jail_name}\""; + exec.poststop += "ifconfig ${external_interface} deletem epair${uniq_epair_bridge}a"; + exec.poststop += "ifconfig epair${uniq_epair_bridge}a destroy"; +EOF + fi else - ## generate config - cat <<-EOF + if [ -n "${static_mac}" ]; then + ## Generate VNET config with static MAC address + generate_static_mac "${jail_name}" "${external_interface}" + cat <<-EOF vnet; vnet.interface = e0b_${uniq_epair}; exec.prestart += "jib addm ${uniq_epair} ${external_interface}"; @@ -172,44 +309,17 @@ EOF exec.prestart += "ifconfig e0a_${uniq_epair} description \"vnet host interface for Bastille jail ${jail_name}\""; exec.poststop += "jib destroy ${uniq_epair}"; EOF - fi -} - -set_target() { - local _TARGET="${1}" - if [ "${_TARGET}" = ALL ] || [ "${_TARGET}" = all ]; then - target_all_jails - else - check_target_exists "${_TARGET}" || error_exit "Jail not found \"${_TARGET}\"" - JAILS="${_TARGET}" - TARGET="${_TARGET}" - export JAILS - export TARGET - fi -} - -set_target_single() { - local _TARGET="${1}" - if [ "${_TARGET}" = ALL ] || [ "${_TARGET}" = all ]; then - error_exit "[all|ALL] not supported with this command." - else - check_target_exists "${_TARGET}" || error_exit "Jail not found \"${_TARGET}\"" - JAILS="${_TARGET}" - TARGET="${_TARGET}" - export JAILS - export TARGET - fi -} - -target_all_jails() { - local _JAILS="$(bastille list jails)" - JAILS="" - for _jail in ${_JAILS}; do - if [ -d "${bastille_jailsdir}/${_jail}" ]; then - JAILS="${JAILS} ${_jail}" + else + ## Generate VNET config without static MAC address + cat <<-EOF + vnet; + vnet.interface = e0b_${uniq_epair}; + exec.prestart += "jib addm ${uniq_epair} ${external_interface}"; + exec.prestart += "ifconfig e0a_${uniq_epair} description \"vnet host interface for Bastille jail ${jail_name}\""; + exec.poststop += "jib destroy ${uniq_epair}"; +EOF fi - done - export JAILS + fi } checkyesno() { @@ -231,5 +341,4 @@ checkyesno() { return 1 ;; esac -} - +} \ No newline at end of file diff --git a/usr/local/share/bastille/config.sh b/usr/local/share/bastille/config.sh index 68fe1135d..4ff21b22d 100644 --- a/usr/local/share/bastille/config.sh +++ b/usr/local/share/bastille/config.sh @@ -34,49 +34,55 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille config TARGET get|set propertyName [newValue]" + error_exit "Usage: bastille config TARGET [get|set] PROPERTY_NAME NEW_VALUE" } -# we need jail(8) to parse the config file so it can expand variables etc -print_jail_conf() { - - # we need to pass a literal \n to jail to get each parameter on its own - # line - jail -f "$1" -e ' -' -} - # Handle special-case commands first. -case "$1" in +case "${1}" in help|-h|--help) usage ;; esac -if [ $# -eq 1 ] || [ $# -gt 3 ]; then +if [ "$#" -lt 3 ] || [ "$#" -gt 4 ]; then usage fi bastille_root_check -ACTION=$1 -shift +TARGET="${1}" +ACTION="${2}" +shift 2 + +set_target "${TARGET}" -case $ACTION in +case "${ACTION}" in get) - if [ $# -ne 1 ]; then + if [ "$#" -ne 1 ]; then error_notify 'Too many parameters for a "get" operation.' usage fi ;; - set) ;; - *) error_exit 'Only get and set are supported.' ;; + set) + ;; + *) + error_exit 'Only get and set are supported.' + ;; esac -PROPERTY=$1 +PROPERTY="${1}" shift VALUE="$@" +# we need jail(8) to parse the config file so it can expand variables etc +print_jail_conf() { + + # we need to pass a literal \n to jail to get each parameter on its own + # line + jail -f "${1}" -e ' +' +} + for _jail in ${JAILS}; do FILE="${bastille_jailsdir}/${_jail}/jail.conf" if [ ! -f "${FILE}" ]; then @@ -93,6 +99,7 @@ for _jail in ${JAILS}; do # check if there is a value for this property if (NF == 2) { # remove any quotes surrounding the string + #sub(",[^|]*\\|", ",", $2); sub(/^"/, "", $2); sub(/"$/, "", $2); print $2; @@ -171,4 +178,4 @@ if [ "${ACTION}" = 'set' ]; then info "A restart is required for the changes to be applied. See 'bastille restart ${TARGET}'." fi -exit 0 +exit 0 \ No newline at end of file diff --git a/usr/local/share/bastille/console.sh b/usr/local/share/bastille/console.sh index 9131b221b..749de4fe3 100644 --- a/usr/local/share/bastille/console.sh +++ b/usr/local/share/bastille/console.sh @@ -34,26 +34,66 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille console TARGET [user]" + error_notify "Usage: bastille console [option(s)] TARGET [user]" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# 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 + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -gt 1 ]; then +if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then usage fi -bastille_root_check +TARGET="${1}" +USER="${2}" -USER="${1}" +bastille_root_check +set_target_single "${TARGET}" +check_target_is_running "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${TARGET}" +else + error_notify "Jail is not running." + error_exit "Use [-a|--auto] to auto-start the jail." +fi validate_user() { - if jexec -l "${_jail}" id "${USER}" >/dev/null 2>&1; then + if jexec -l "${TARGET}" id "${USER}" >/dev/null 2>&1; then USER_SHELL="$(jexec -l "${_jail}" getent passwd "${USER}" | cut -d: -f7)" if [ -n "${USER_SHELL}" ]; then if jexec -l "${_jail}" grep -qwF "${USER_SHELL}" /etc/shells; then @@ -78,15 +118,12 @@ check_fib() { fi } -for _jail in ${JAILS}; do - info "[${_jail}]:" - LOGIN="$(jexec -l "${_jail}" which login)" - if [ -n "${USER}" ]; then - validate_user - else - check_fib - LOGIN="$(jexec -l "${_jail}" which login)" - ${_setfib} jexec -l "${_jail}" $LOGIN -f root - fi - echo -done +info "[${TARGET}]:" +LOGIN="$(jexec -l "${TARGET}" which login)" +if [ -n "${USER}" ]; then + validate_user +else + check_fib + LOGIN="$(jexec -l "${TARGET}" which login)" + ${_setfib} jexec -l "${TARGET}" $LOGIN -f root +fi \ No newline at end of file diff --git a/usr/local/share/bastille/convert.sh b/usr/local/share/bastille/convert.sh index d22c97083..eef383f2b 100644 --- a/usr/local/share/bastille/convert.sh +++ b/usr/local/share/bastille/convert.sh @@ -34,21 +34,63 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille convert TARGET" + error_notify "Usage: bastille convert [option(s)] TARGET" + + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# 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 + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -ne 0 ]; then +if [ "$#" -ne 1 ]; then usage fi +TARGET="${1}" + bastille_root_check +set_target_single "${TARGET}" +check_target_is_stopped "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${TARGET}" +else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." +fi convert_symlinks() { # Work with the symlinks, revert on first cp error @@ -116,7 +158,7 @@ start_convert() { HASPORTS=$(grep -w ${bastille_releasesdir}/${RELEASE}/usr/ports ${bastille_jailsdir}/${TARGET}/fstab) if [ -n "${RELEASE}" ]; then - cd "${bastille_jailsdir}/${TARGET}/root" || error_exit "Failed to change directory to ${bastille_jailsdir}/${TARGET}/root" + cd "${bastille_jailsdir}/${TARGET}/root" || error_exit "Could not cd to ${bastille_jailsdir}/${TARGET}/root" # Work with the symlinks convert_symlinks @@ -151,11 +193,9 @@ fi # Be interactive here since this cannot be easily undone while :; do error_notify "Warning: container conversion from thin to thick can't be undone!" - # shellcheck disable=SC2162 - # shellcheck disable=SC3045 - read -p "Do you really wish to convert '${TARGET}' into a thick container? [y/N]:" yn + read "Do you really wish to convert '${TARGET}' into a thick container? [y/N]:" yn case ${yn} in [Yy]) start_convert;; [Nn]) exit 0;; esac -done +done \ No newline at end of file diff --git a/usr/local/share/bastille/cp.sh b/usr/local/share/bastille/cp.sh index d7fc174bb..36268ad5d 100644 --- a/usr/local/share/bastille/cp.sh +++ b/usr/local/share/bastille/cp.sh @@ -34,49 +34,63 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille cp [OPTION] TARGET HOST_PATH CONTAINER_PATH" -} + error_notify "Usage: bastille cp [option(s)] TARGET HOST_PATH JAIL_PATH" + cat << EOF + Options: -CPSOURCE="${1}" -CPDEST="${2}" + -q | --quiet Suppress output. + -x | --debug Enable debug mode. -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; --q|--quiet) - OPTION="${1}" - CPSOURCE="${2}" - CPDEST="${3}" - ;; -esac +EOF + exit 1 +} + +# Handle options. +OPTION="-av" +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -q|--quiet) + OPTION="-a" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + q) OPTION="-a" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -ne 2 ]; then +if [ "$#" -ne 3 ]; then usage fi -bastille_root_check +TARGET="${1}" +CPSOURCE="${2}" +CPDEST="${3}" -case "${OPTION}" in - -q|--quiet) - OPTION="-a" - ;; - *) - OPTION="-av" - ;; -esac +bastille_root_check +set_target "${TARGET}" for _jail in ${JAILS}; do info "[${_jail}]:" bastille_jail_path="${bastille_jailsdir}/${_jail}/root" - cp "${OPTION}" "${CPSOURCE}" "${bastille_jail_path}/${CPDEST}" - RETURN="$?" - if [ "${TARGET}" = "ALL" ]; then - # Display the return status for reference - echo -e "Returned: ${RETURN}\n" - else - echo - return "${RETURN}" + if ! cp "${OPTION}" "${CPSOURCE}" "${bastille_jail_path}${CPDEST}"; then + error_continue "CP failed: ${CPSOURCE} -> ${bastille_jail_path}${CPDEST}" fi -done +done \ No newline at end of file diff --git a/usr/local/share/bastille/create.sh b/usr/local/share/bastille/create.sh index 77169e8ff..8d7f8c6ea 100644 --- a/usr/local/share/bastille/create.sh +++ b/usr/local/share/bastille/create.sh @@ -36,30 +36,24 @@ usage() { # Build an independent usage for the create command # If no option specified, will create a thin container by default - error_notify "Usage: bastille create [option(s)] name release ip [interface]" + error_notify "Usage: bastille create [option(s)] NAME RELEASE IP_ADDRESS [interface]" cat << EOF Options: - -E | --empty -- Creates an empty container, intended for custom jail builds (thin/thick/linux or unsupported). - -L | --linux -- This option is intended for testing with Linux jails, this is considered experimental. - -T | --thick -- Creates a thick container, they consume more space as they are self contained and independent. - -V | --vnet -- Enables VNET, VNET containers are attached to a virtual bridge interface for connectivity. - -C | --clone -- Creates a clone container, they are duplicates of the base release, consume low space and preserves changing data. - -B | --bridge -- Enables VNET, VNET containers are attached to a specified, already existing external bridge. + -D | --dual Creates the jails with bot IPv4 and IPv6 networking ('inherit' and 'ip_hostname' only). + -M | --static-mac Generate a static MAC address for jail (VNET only). + -E | --empty Creates an empty container, intended for custom jail builds (thin/thick/linux or unsupported). + -L | --linux This option is intended for testing with Linux jails, this is considered experimental. + -T | --thick Creates a thick container, they consume more space as they are self contained and independent. + -V | --vnet Enables VNET, VNET containers are attached to a virtual bridge interface for connectivity. + -C | --clone Creates a clone container, they are duplicates of the base release, consume low space and preserves changing data. + -B | --bridge Enables VNET, VNET containers are attached to a specified, already existing external bridge. EOF exit 1 } -running_jail() { - if [ -n "$(/usr/sbin/jls name | awk "/^${NAME}$/")" ]; then - error_exit "A running jail matches name." - elif [ -d "${bastille_jailsdir}/${NAME}" ]; then - error_exit "Jail: ${NAME} already created." - fi -} - validate_name() { local NAME_VERIFY=${NAME} local NAME_SANITY="$(echo "${NAME_VERIFY}" | tr -c -d 'a-zA-Z0-9-_')" @@ -71,54 +65,90 @@ validate_name() { } validate_ip() { - ipx_addr="ip4.addr" - ip="$1" - ip6=$(echo "${ip}" | grep -E '^(([a-fA-F0-9:]+$)|([a-fA-F0-9:]+\/[0-9]{1,3}$)|SLAAC)') - if [ -n "${ip6}" ]; then - info "Valid: (${ip6})." + _ip="${1}" + _ip6=$(echo "${_ip}" | grep -E '^(([a-fA-F0-9:]+$)|([a-fA-F0-9:]+\/[0-9]{1,3}$)|SLAAC)') + if [ -n "${_ip6}" ]; then + info "Valid: (${_ip6})." ipx_addr="ip6.addr" - IP6_MODE="new" else - if [ "${ip}" = "DHCP" ]; then - info "Valid: (${ip})." + if [ "${_ip}" = "inherit" ] || [ "${_ip}" = "ip_hostname" ]; then + info "Valid: (${_ip})." else local IFS - if echo "${ip}" | grep -Eq '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))?$'; then - TEST_IP=$(echo "${ip}" | cut -d / -f1) + if echo "${_ip}" | grep -Eq '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))?$'; then + TEST_IP=$(echo "${_ip}" | cut -d / -f1) IFS=. set ${TEST_IP} for quad in 1 2 3 4; do if eval [ \$$quad -gt 255 ]; then - echo "Invalid: (${TEST_IP})" - exit 1 + error_continue "Invalid: (${TEST_IP})" fi done if ifconfig | grep -qwF "${TEST_IP}"; then warn "Warning: IP address already in use (${TEST_IP})." else - info "Valid: (${ip})." + ipx_addr="ip4.addr" + info "Valid: (${_ip})." fi else - error_exit "Invalid: (${ip})." + error_continue "Invalid: (${_ip})." fi fi fi - if echo "${ip}" | grep -qvE '(SLAAC|DHCP|0[.]0[.]0[.]0)'; then - if [ "${ipx_addr}" = "ip4.addr" ]; then - IP4_ADDR="${ip}" - IP4_DEFINITION="${ipx_addr} = ${ip};" + # Set interface value + if [ ! -f "${bastille_jail_conf}" ]; then + if [ -z "${bastille_network_loopback}" ] && [ -n "${bastille_network_shared}" ]; then + local bastille_jail_conf_interface=${bastille_network_shared} + fi + if [ -n "${bastille_network_loopback}" ] && [ -z "${bastille_network_shared}" ]; then + local bastille_jail_conf_interface=${bastille_network_loopback} + fi + if [ -n "${INTERFACE}" ]; then + local bastille_jail_conf_interface=${INTERFACE} + fi + fi + # Determine IP/Interface mode + if [ "${_ip}" = "inherit" ]; then + if [ -n "${DUAL_STACK}" ]; then + IP4_DEFINITION="ip4 = ${_ip};" + IP6_DEFINITION="ip6 = ${_ip};" + IP6_MODE="new" else - IP6_ADDR="${ip}" - IP6_DEFINITION="${ipx_addr} = ${ip};" + IP4_DEFINITION="ip4 = ${_ip};" + IP6_DEFINITION="" + IP6_MODE="disable" + fi + elif [ "${_ip}" = "ip_hostname" ]; then + if [ -n "${DUAL_STACK}" ]; then + IP_HOSTNAME="${_ip}" + IP4_DEFINITION="${IP_HOSTNAME};" + IP6_DEFINITION="${IP_HOSTNAME};" + IP6_MODE="new" + else + IP_HOSTNAME="${_ip}" + IP4_DEFINITION="${IP_HOSTNAME};" + IP6_DEFINITION="" + IP6_MODE="disable" + fi + elif echo "${_ip}" | grep -qvE '(SLAAC|DHCP|0[.]0[.]0[.]0)'; then + if [ "${ipx_addr}" = "ip4.addr" ]; then + IP4_ADDR="${_ip}" + IP4_DEFINITION="${ipx_addr} = ${bastille_jail_conf_interface}|${_ip};" + elif [ "${ipx_addr}" = "ip6.addr" ]; then + IP6_ADDR="${_ip}" + IP6_DEFINITION="${ipx_addr} = ${bastille_jail_conf_interface}|${_ip};" + IP6_MODE="new" fi fi } + validate_ips() { IP6_MODE="disable" IP4_DEFINITION="" IP6_DEFINITION="" IP4_ADDR="" IP6_ADDR="" + IP_HOSTNAME="" for ip in ${IP}; do validate_ip "${ip}" done @@ -167,15 +197,10 @@ EOF } generate_jail_conf() { - if [ "$(sysctl -n security.jail.jailed)" -eq 1 ]; then - devfs_ruleset_value=0 - else - devfs_ruleset_value=4 - fi cat << EOF > "${bastille_jail_conf}" ${NAME} { + devfs_ruleset = 4; enforce_statfs = 2; - devfs_ruleset = ${devfs_ruleset_value}; exec.clean; exec.consolelog = ${bastille_jail_log}; exec.start = '/bin/sh /etc/rc'; @@ -187,7 +212,6 @@ ${NAME} { securelevel = 2; osrelease = ${RELEASE}; - interface = ${bastille_jail_conf_interface}; ${IP4_DEFINITION} ${IP6_DEFINITION} ip6 = ${IP6_MODE}; @@ -196,17 +220,12 @@ EOF } generate_linux_jail_conf() { - if [ "$(sysctl -n security.jail.jailed)" -eq 1 ]; then - devfs_ruleset_value=0 - else - devfs_ruleset_value=4 - fi cat << EOF > "${bastille_jail_conf}" ${NAME} { host.hostname = ${NAME}; mount.fstab = ${bastille_jail_fstab}; path = ${bastille_jail_path}; - devfs_ruleset = ${devfs_ruleset_value}; + devfs_ruleset = 4; enforce_statfs = 1; exec.start = '/bin/true'; @@ -216,24 +235,19 @@ ${NAME} { allow.mount; allow.mount.devfs; - interface = ${bastille_jail_conf_interface}; - ${ipx_addr} = ${IP}; + ${IP4_DEFINITION} + ${IP6_DEFINITION} ip6 = ${IP6_MODE}; } EOF } generate_vnet_jail_conf() { - if [ "$(sysctl -n security.jail.jailed)" -eq 1 ]; then - devfs_ruleset_value=0 - else - devfs_ruleset_value=13 - fi - NETBLOCK=$(generate_vnet_jail_netblock "$NAME" "${VNET_JAIL_BRIDGE}" "${bastille_jail_conf_interface}") + NETBLOCK=$(generate_vnet_jail_netblock "${NAME}" "${VNET_JAIL_BRIDGE}" "${bastille_jail_conf_interface}" "${STATIC_MAC}") cat << EOF > "${bastille_jail_conf}" ${NAME} { + devfs_ruleset = 13; enforce_statfs = 2; - devfs_ruleset = ${devfs_ruleset_value}; exec.clean; exec.consolelog = ${bastille_jail_log}; exec.start = '/bin/sh /etc/rc'; @@ -255,8 +269,7 @@ post_create_jail() { # Using relative paths here. # MAKE SURE WE'RE IN THE RIGHT PLACE. - cd "${bastille_jail_path}" || error_exit "Failed to change directory." - echo + cd "${bastille_jail_path}" || error_exit "Could not cd to ${bastille_jail_path}" if [ ! -f "${bastille_jail_conf}" ]; then if [ -z "${bastille_network_loopback}" ] && [ -n "${bastille_network_shared}" ]; then @@ -379,7 +392,7 @@ create_jail() { if [ -z "${THICK_JAIL}" ] && [ -z "${CLONE_JAIL}" ]; then LINK_LIST="bin boot lib libexec rescue sbin usr/bin usr/include usr/lib usr/lib32 usr/libdata usr/libexec usr/sbin usr/share usr/src" - info "Creating a thinjail...\n" + info "Creating a thinjail..." for _link in ${LINK_LIST}; do ln -sf /.bastille/${_link} ${_link} done @@ -413,11 +426,11 @@ create_jail() { info "Creating a clonejail...\n" ## clone the release base to the new basejail SNAP_NAME="bastille-clone-$(date +%Y-%m-%d-%H%M%S)" - # shellcheck disable=SC2140 + # shellcheck disable=SC2140 zfs snapshot "${bastille_zfs_zpool}/${bastille_zfs_prefix}/releases/${RELEASE}"@"${SNAP_NAME}" - - # shellcheck disable=SC2140 - zfs clone -p "${bastille_zfs_zpool}/${bastille_zfs_prefix}/releases/${RELEASE}"@"${SNAP_NAME}" \ + + # shellcheck disable=SC2140 + zfs clone -p "${bastille_zfs_zpool}/${bastille_zfs_prefix}/releases/${RELEASE}"@"${SNAP_NAME}" \ "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${NAME}/root" # Check and apply required settings. @@ -431,20 +444,20 @@ create_jail() { ## take a temp snapshot of the base release SNAP_NAME="bastille-$(date +%Y-%m-%d-%H%M%S)" - # shellcheck disable=SC2140 + # shellcheck disable=SC2140 zfs snapshot "${bastille_zfs_zpool}/${bastille_zfs_prefix}/releases/${RELEASE}"@"${SNAP_NAME}" ## replicate the release base to the new thickjail and set the default mountpoint - # shellcheck disable=SC2140 + # shellcheck disable=SC2140 zfs send -R "${bastille_zfs_zpool}/${bastille_zfs_prefix}/releases/${RELEASE}"@"${SNAP_NAME}" | \ zfs receive "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${NAME}/root" zfs set ${ZFS_OPTIONS} mountpoint=none "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${NAME}/root" zfs inherit mountpoint "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${NAME}/root" ## cleanup temp snapshots initially - # shellcheck disable=SC2140 + # shellcheck disable=SC2140 zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/releases/${RELEASE}"@"${SNAP_NAME}" - # shellcheck disable=SC2140 + # shellcheck disable=SC2140 zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${NAME}/root"@"${SNAP_NAME}" fi @@ -523,6 +536,12 @@ create_jail() { fi fi + # Exit if jail was not started, which means something is wrong. + if ! check_target_is_running "${NAME}"; then + bastille destroy "${NAME}" + error_exit "[${NAME}]: Failed to create jail..." + fi + if [ -n "${VNET_JAIL}" ]; then if [ -n "${bastille_template_vnet}" ]; then ## rename interface to generic vnet0 @@ -588,7 +607,7 @@ create_jail() { jexec -l "${NAME}" /bin/bash -c "DEBIAN_FRONTEND=noninteractive rm /var/cache/apt/archives/rsyslog*.deb" jexec -l "${NAME}" /bin/bash -c "DEBIAN_FRONTEND=noninteractive dpkg --force-depends --force-confdef --force-confold -i /var/cache/apt/archives/*.deb" jexec -l "${NAME}" /bin/bash -c "DEBIAN_FRONTEND=noninteractive dpkg --force-depends --force-confdef --force-confold -i /var/cache/apt/archives/*.deb" - jexec -l "${NAME}" /bin/bash -c "chmod 777 /tmp" + jexec -l "${NAME}" /bin/bash -c "chmod 1777 /tmp" jexec -l "${NAME}" /bin/bash -c "apt update" else # Thin jail. @@ -608,32 +627,36 @@ create_jail() { fi } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac - bastille_root_check -if echo "$3" | grep '@'; then +if echo "${3}" | grep '@'; then # shellcheck disable=SC2034 BASTILLE_JAIL_IP=$(echo "$3" | awk -F@ '{print $2}') # shellcheck disable=SC2034 BASTILLE_JAIL_INTERFACES=$( echo "$3" | awk -F@ '{print $1}') fi -## reset this options +# Handle options. EMPTY_JAIL="" THICK_JAIL="" CLONE_JAIL="" VNET_JAIL="" LINUX_JAIL="" - -# Handle and parse options +STATIC_MAC="" +DUAL_STACK="" while [ $# -gt 0 ]; do case "${1}" in + -h|--help|help) + usage + ;; + -D|--dual) + DUAL_STACK="1" + shift + ;; + -M|--static-mac) + STATIC_MAC="1" + shift + ;; -E|--empty) EMPTY_JAIL="1" shift @@ -659,61 +682,30 @@ while [ $# -gt 0 ]; do CLONE_JAIL="1" shift ;; - -CV|-VC|--clone-vnet) - CLONE_JAIL="1" - VNET_JAIL="1" - shift - ;; - -CB|-BC|--clone-bridge) - CLONE_JAIL="1" - VNET_JAIL="1" - VNET_JAIL_BRIDGE="1" - shift - ;; - -TV|-VT|--thick-vnet) - THICK_JAIL="1" - VNET_JAIL="1" - shift - ;; - -TB|-BT|--thick-bridge) - THICK_JAIL="1" - VNET_JAIL="1" - VNET_JAIL_BRIDGE="1" - shift - ;; - -EB|-BE|--empty-bridge) - EMPTY_JAIL="1" - VNET_JAIL="1" - VNET_JAIL_BRIDGE="1" - shift - ;; - -EV|-VE|--empty-vnet) - EMPTY_JAIL="1" - VNET_JAIL="1" - shift - ;; - -LV|-VL|--linux-vnet) - LINUX_JAIL="1" - VNET_JAIL="1" - shift - ;; - -LB|-BL|--linux-bridge) - LINUX_JAIL="1" - VNET_JAIL="1" - VNET_JAIL_BRIDGE="1" + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + B) VNET_JAIL=1 VNET_JAIL_BRIDGE=1 ;; + C) CLONE_JAIL=1 ;; + D) DUAL_STACK=1 ;; + E) EMPTY_JAIL=1 ;; + L) LINUX_JAIL=1 ;; + M) STATIC_MAC=1 ;; + T) THICK_JAIL=1 ;; + V) VNET_JAIL=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done shift ;; - --*|-*) - error_notify "Unknown Option." - usage - ;; *) break ;; esac done -## validate for combined options +# Validate options if [ -n "${EMPTY_JAIL}" ]; then if [ -n "${CLONE_JAIL}" ] || [ -n "${THICK_JAIL}" ] || [ -n "${VNET_JAIL}" ] || [ -n "${LINUX_JAIL}" ]; then error_exit "Error: Empty jail option can't be used with other options." @@ -892,11 +884,6 @@ else info "Creating empty jail: ${NAME}." fi -## check if a running jail matches name or already exist -if [ -n "${NAME}" ]; then - running_jail -fi - # May not exist on deployments created before Bastille 0.7.20200714, so creating it. -- cwells if [ ! -e "${bastille_templatesdir}/default" ]; then ln -s "${bastille_sharedir}/templates/default" "${bastille_templatesdir}/default" @@ -926,4 +913,7 @@ if [ -z ${bastille_template_vnet+x} ]; then bastille_template_vnet='default/vnet' fi -create_jail "${NAME}" "${RELEASE}" "${IP}" "${INTERFACE}" +if check_target_exists "${NAME}"; then + error_exit "Error: Existing jail found: ${NAME}" +fi +create_jail "${NAME}" "${RELEASE}" "${IP}" "${INTERFACE}" \ No newline at end of file diff --git a/usr/local/share/bastille/destroy.sh b/usr/local/share/bastille/destroy.sh index 56d8d7f3e..798b50295 100644 --- a/usr/local/share/bastille/destroy.sh +++ b/usr/local/share/bastille/destroy.sh @@ -34,30 +34,35 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille destroy [force] | [container|release]" + error_notify "Usage: bastille destroy [option(s)] [JAIL|RELEASE]" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -f | --force Jails - Force unmount any mounted datasets (ZFS only). + Releases - Destroy cache as well as release. + -x | --debug Enable debug mode. + +EOF + exit 1 } destroy_jail() { - local OPTIONS + local TARGET="${1}" + local OPTIONS bastille_jail_base="${bastille_jailsdir}/${TARGET}" ## dir bastille_jail_log="${bastille_logsdir}/${TARGET}_console.log" ## file - - if [ "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - if [ "${FORCE}" = "1" ]; then - bastille stop "${TARGET}" - else - error_notify "Jail running." - error_exit "See 'bastille stop ${TARGET}'." - fi - fi - - if [ ! -d "${bastille_jail_base}" ]; then - error_exit "Jail not found." + + check_target_is_stopped "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${TARGET}" + else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." fi if [ -d "${bastille_jail_base}" ]; then ## make sure no filesystem is currently mounted in the jail directory - mount_points=$(mount | cut -d ' ' -f 3 | grep "${bastille_jail_base}"/root/) + mount_points="$(mount | cut -d ' ' -f 3 | grep ${bastille_jail_base}/root/)" if [ -n "${mount_points}" ]; then error_notify "Failed to destroy jail: ${TARGET}" error_exit "Jail has mounted filesystems:\n$mount_points" @@ -102,7 +107,6 @@ destroy_jail() { info "Clearing RDR rules:" pfctl -a "rdr/${TARGET}" -Fn fi - echo fi } @@ -180,95 +184,110 @@ destroy_rel() { if [ "${FORCE}" = "1" ]; then ## remove cache on force if [ -d "${bastille_cachedir}/${TARGET}" ]; then - rm -rf "${bastille_cachedir:?}/${TARGET}" + rm -rf "${bastille_cachedir:?}/${TARGET:?}" fi fi - echo else error_notify "Cannot destroy base with child containers." fi fi } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac - -## reset this options -FORCE="" - -## handle additional options -case "${1}" in - -f|--force|force) - FORCE="1" - shift - ;; - -*) - error_notify "Unknown Option." - usage - ;; -esac - -TARGET="${1}" +# Handle options. +AUTO=0 +FORCE=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -f|--force) + FORCE=1 + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + f) FORCE=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -gt 1 ] || [ $# -lt 1 ]; then +if [ "$#" -ne 1 ]; then usage fi +TARGET="${1}" + bastille_root_check ## check what should we clean case "${TARGET}" in -*-CURRENT|*-CURRENT-I386|*-CURRENT-i386|*-current) - ## check for FreeBSD releases name - NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '^([1-9]{2,2})\.[0-9](-CURRENT|-CURRENT-i386)$' | tr '[:lower:]' '[:upper:]' | sed 's/I/i/g') - destroy_rel - ;; -*-RELEASE|*-RELEASE-I386|*-RELEASE-i386|*-release|*-RC[1-9]|*-rc[1-9]|*-BETA[1-9]) - ## check for FreeBSD releases name - NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '^([1-9]{2,2})\.[0-9](-RELEASE|-RELEASE-i386|-RC[1-9]|-BETA[1-9])$' | tr '[:lower:]' '[:upper:]' | sed 's/I/i/g') - destroy_rel - ;; -*-stable-LAST|*-STABLE-last|*-stable-last|*-STABLE-LAST) - ## check for HardenedBSD releases name - NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '^([1-9]{2,2})(-stable-last)$' | sed 's/STABLE/stable/g;s/last/LAST/g') - destroy_rel - ;; -*-stable-build-[0-9]*|*-STABLE-BUILD-[0-9]*) - ## check for HardenedBSD(specific stable build releases) - NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '([0-9]{1,2})(-stable-build)-([0-9]{1,3})$' | sed 's/BUILD/build/g;s/STABLE/stable/g') - destroy_rel - ;; -*-stable-build-latest|*-stable-BUILD-LATEST|*-STABLE-BUILD-LATEST) - ## check for HardenedBSD(latest stable build release) - NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '([0-9]{1,2})(-stable-build-latest)$' | sed 's/STABLE/stable/;s/build/BUILD/g;s/latest/LATEST/g') - destroy_rel - ;; -current-build-[0-9]*|CURRENT-BUILD-[0-9]*) - ## check for HardenedBSD(specific current build releases) - NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '(current-build)-([0-9]{1,3})' | sed 's/BUILD/build/g;s/CURRENT/current/g') - destroy_rel - ;; -current-build-latest|current-BUILD-LATEST|CURRENT-BUILD-LATEST) - ## check for HardenedBSD(latest current build release) - NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '(current-build-latest)$' | sed 's/CURRENT/current/;s/build/BUILD/g;s/latest/LATEST/g') - destroy_rel - ;; -Ubuntu_1804|Ubuntu_2004|Ubuntu_2204|UBUNTU_1804|UBUNTU_2004|UBUNTU_2204) - ## check for Linux releases - NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '(Ubuntu_1804)$|(Ubuntu_2004)$|(Ubuntu_2204)$' | sed 's/UBUNTU/Ubuntu/g;s/ubuntu/Ubuntu/g') - destroy_rel - ;; -Debian10|Debian11|Debian12|DEBIAN10|DEBIAN11|DEBIAN12) - ## check for Linux releases - NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '(Debian10)$|(Debian11)$|(Debian12)$' | sed 's/DEBIAN/Debian/g') - destroy_rel - ;; -*) - ## just destroy a jail - destroy_jail - ;; -esac + *-CURRENT|*-CURRENT-I386|*-CURRENT-i386|*-current) + ## check for FreeBSD releases name + NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '^([1-9]{2,2})\.[0-9](-CURRENT|-CURRENT-i386)$' | tr '[:lower:]' '[:upper:]' | sed 's/I/i/g') + destroy_rel + ;; + *-RELEASE|*-RELEASE-I386|*-RELEASE-i386|*-release|*-RC[1-9]|*-rc[1-9]|*-BETA[1-9]) + ## check for FreeBSD releases name + NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '^([1-9]{2,2})\.[0-9](-RELEASE|-RELEASE-i386|-RC[1-9]|-BETA[1-9])$' | tr '[:lower:]' '[:upper:]' | sed 's/I/i/g') + destroy_rel + ;; + *-stable-LAST|*-STABLE-last|*-stable-last|*-STABLE-LAST) + ## check for HardenedBSD releases name + NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '^([1-9]{2,2})(-stable-last)$' | sed 's/STABLE/stable/g;s/last/LAST/g') + destroy_rel + ;; + *-stable-build-[0-9]*|*-STABLE-BUILD-[0-9]*) + ## check for HardenedBSD(specific stable build releases) + NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '([0-9]{1,2})(-stable-build)-([0-9]{1,3})$' | sed 's/BUILD/build/g;s/STABLE/stable/g') + destroy_rel + ;; + *-stable-build-latest|*-stable-BUILD-LATEST|*-STABLE-BUILD-LATEST) + ## check for HardenedBSD(latest stable build release) + NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '([0-9]{1,2})(-stable-build-latest)$' | sed 's/STABLE/stable/;s/build/BUILD/g;s/latest/LATEST/g') + destroy_rel + ;; + current-build-[0-9]*|CURRENT-BUILD-[0-9]*) + ## check for HardenedBSD(specific current build releases) + NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '(current-build)-([0-9]{1,3})' | sed 's/BUILD/build/g;s/CURRENT/current/g') + destroy_rel + ;; + current-build-latest|current-BUILD-LATEST|CURRENT-BUILD-LATEST) + ## check for HardenedBSD(latest current build release) + NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '(current-build-latest)$' | sed 's/CURRENT/current/;s/build/BUILD/g;s/latest/LATEST/g') + destroy_rel + ;; + Ubuntu_1804|Ubuntu_2004|Ubuntu_2204|UBUNTU_1804|UBUNTU_2004|UBUNTU_2204) + ## check for Linux releases + NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '(Ubuntu_1804)$|(Ubuntu_2004)$|(Ubuntu_2204)$' | sed 's/UBUNTU/Ubuntu/g;s/ubuntu/Ubuntu/g') + destroy_rel + ;; + Debian10|Debian11|Debian12|DEBIAN10|DEBIAN11|DEBIAN12) + ## check for Linux releases + NAME_VERIFY=$(echo "${TARGET}" | grep -iwE '(Debian10)$|(Debian11)$|(Debian12)$' | sed 's/DEBIAN/Debian/g') + destroy_rel + ;; + *) + ## just destroy a jail + set_target_single "${TARGET}" + destroy_jail "${TARGET}" + ;; +esac \ No newline at end of file diff --git a/usr/local/share/bastille/edit.sh b/usr/local/share/bastille/edit.sh index 4442cad0d..8bb9c64bb 100644 --- a/usr/local/share/bastille/edit.sh +++ b/usr/local/share/bastille/edit.sh @@ -34,33 +34,52 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille edit TARGET [filename]" + error_notify "Usage: bastille edit [option(s)] TARGET [filename]" + cat << EOF + Options: + + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# Handle options. +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + error_notify "Unknown Option: \"${1}\"" + usage + ;; + *) + break + ;; + esac +done -if [ $# -gt 1 ]; then +if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then usage -elif [ $# -eq 1 ]; then - TARGET_FILENAME="${1}" +fi + +TARGET="${1}" +if [ "$#" -eq 2 ]; then + TARGET_FILENAME="${2}" +else + TARGET_FILENAME="jail.conf" fi bastille_root_check +set_target_single "${TARGET}" if [ -z "${EDITOR}" ]; then - # shellcheck disable=SC2209 - EDITOR=vi + EDITOR=nano fi -for _jail in ${JAILS}; do - if [ -n "${TARGET_FILENAME}" ]; then - "${EDITOR}" "${bastille_jailsdir}/${_jail}/${TARGET_FILENAME}" - else - "${EDITOR}" "${bastille_jailsdir}/${_jail}/jail.conf" - fi -done +"${EDITOR}" "${bastille_jailsdir}/${TARGET}/${TARGET_FILENAME}" \ No newline at end of file diff --git a/usr/local/share/bastille/etcupdate.sh b/usr/local/share/bastille/etcupdate.sh new file mode 100644 index 000000000..b98ff6e3a --- /dev/null +++ b/usr/local/share/bastille/etcupdate.sh @@ -0,0 +1,191 @@ +#!/bin/sh +# Copyright (c) 2018-2024, Christer Edwards +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +. /usr/local/share/bastille/common.sh +. /usr/local/etc/bastille/bastille.conf + +usage() { + error_notify "Usage: bastille etcupdate [option(s)] [bootstrap|TARGET] [diff|resolve|update RELEASE]" + cat << EOF + Options: + + -d | --dry-run Show output, but do not apply. + -f | --force Force a re-bootstrap of a RELEASE. + -x | --debug Enable debug mode. + +EOF + exit 1 +} + +bootstrap_etc_release() { + local _release="${1}" + local _current="$(sysrc -f /usr/local/etc/bastille/bastille.conf bastille_bootstrap_archives | awk -F': ' '{print $2}')" + if ! ls -A "${bastille_releasesdir}/${_release}/usr/src" 2>/dev/null; then + sysrc -f /usr/local/etc/bastille/bastille.conf bastille_bootstrap_archives=src + if ! bastille bootstrap "${_release}" > /dev/null; then + sysrc -f /usr/local/etc/bastille/bastille.conf bastille_bootstrap_archives="${_current}" + error_exit "Failed to bootstrap etcupdate: ${_release}" + else + sysrc -f /usr/local/etc/bastille/bastille.conf bastille_bootstrap_archives="${_current}" + fi + fi +} + +bootstrap_etc_tarball() { + local _release="${1}" + if [ ! -f ${bastille_cachedir}/${_release}.tbz2 ]; then + echo "Building tarball, please wait..." + if ! etcupdate build -d /tmp/etcupdate -s ${bastille_releasesdir}/${_release}/usr/src ${bastille_cachedir}/${_release}.tbz2; then + error_exit "Failed to build etcupdate tarball \"${_release}.tbz2\"" + else + info "Etcupdate bootstrap complete: ${_release}" + fi + elif [ -f ${bastille_cachedir}/${_release}.tbz2 ] && [ "${FORCE}" -eq 1 ]; then + rm -f "${bastille_cachedir}/${_release}.tbz2" + echo "Building tarball, please wait..." + if ! etcupdate build -d /tmp/etcupdate -s ${bastille_releasesdir}/${_release}/usr/src ${bastille_cachedir}/${_release}.tbz2; then + error_exit "Failed to build etcupdate tarball: ${_release}.tbz2" + else + info "Etcupdate bootstrap complete: ${_release}" + fi + else + info "Etcupdate release has already been prepared for application: ${_release}" + fi +} + +diff_review() { + local _jail="${1}" + info "[${_jail}]: etcupdate --diff mode" + etcupdate diff -D "${bastille_jailsdir}/${_jail}/root" +} + +resolve_conflicts() { + local _jail="${1}" + info "[${_jail}]: etcupdate resolve" + etcupdate resolve -D "${bastille_jailsdir}/${_jail}/root" +} + +update_jail_etc() { + local _jail="${1}" + local _release="${2}" + if [ ! -f ${bastille_cachedir}/${_release}.tbz2 ]; then + error_exit "Error: Please run \"bastille etcupdate bootstrap RELEASE\" first." + fi + if [ "${DRY_RUN}" -eq 1 ]; then + info "[${_jail}]: etcupdate update --dry-run" + etcupdate -n -D "${bastille_jailsdir}/${_jail}/root" -t ${bastille_cachedir}/${_release}.tbz2 + else + info "[${_jail}]: etcupdate update" + etcupdate -D "${bastille_jailsdir}/${_jail}/root" -t ${bastille_cachedir}/${_release}.tbz2 + fi +} + +# Handle options. +DRY_RUN=0 +FORCE=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -d|--dry-run) + DRY_RUN=1 + shift + ;; + -f|--force) + FORCE=1 + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + d) DRY_RUN=1 ;; + f) FORCE=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done + +if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then + usage +fi + +# Main commands +while [ "$#" -gt 0 ]; do + case "${1}" in + bootstrap) + if [ -z "${2}" ]; then + usage + else + RELEASE="${2}" + bootstrap_etc_release "${RELEASE}" + bootstrap_etc_tarball "${RELEASE}" + shift $# + fi + ;; + *) + TARGET="${1}" + ACTION="${2}" + RELEASE="${3}" + set_target_single "${TARGET}" + case "${ACTION}" in + diff) + diff_review "${TARGET}" + shift "$#" + ;; + resolve) + resolve_conflicts "${TARGET}" + shift "$#" + ;; + update) + if [ -z "${RELEASE}" ]; then + usage + else + update_jail_etc "${TARGET}" "${RELEASE}" + shift "$#" + fi + ;; + *) + error_exit "Unknown action: \"${ACTION}\"" + ;; + esac + ;; + esac +done \ No newline at end of file diff --git a/usr/local/share/bastille/export.sh b/usr/local/share/bastille/export.sh index 86898b52c..3e1622308 100644 --- a/usr/local/share/bastille/export.sh +++ b/usr/local/share/bastille/export.sh @@ -38,18 +38,18 @@ usage() { # Valid compress/options for ZFS systems are raw, .gz, .tgz, .txz and .xz # Valid compress/options for non ZFS configured systems are .tgz and .txz # If no compression option specified, user must redirect standard output - error_notify "Usage: bastille export | option(s) | TARGET | PATH" - + error_notify "Usage: bastille export [option(s)] TARGET PATH" cat << EOF Options: - --gz -- Export a ZFS jail using GZIP(.gz) compressed image. - -r | --raw -- Export a ZFS jail to an uncompressed RAW image. - -s | --safe -- Safely stop and start a ZFS jail before the exporting process. - --tgz -- Export a jail using simple .tgz compressed archive instead. - --txz -- Export a jail using simple .txz compressed archive instead. - -v | --verbose -- Be more verbose during the ZFS send operation. - --xz -- Export a ZFS jail using XZ(.xz) compressed image. + --gz Export a ZFS jail using GZIP(.gz) compressed image. + -r | --raw Export a ZFS jail to an uncompressed RAW image. + -s | --safe Safely stop and start a ZFS jail before the exporting process. + --tgz Export a jail using simple .tgz compressed archive instead. + --txz Export a jail using simple .txz compressed archive instead. + -v | --verbose Be more verbose during the ZFS send operation. + --xz Export a ZFS jail using XZ(.xz) compressed image. + -x | --debug Enable debug mode. Note: If no export option specified, the container should be redirected to standard output. @@ -57,44 +57,18 @@ EOF exit 1 } -# Handle special-case commands first -case "$1" in -help|-h|--help) - usage - ;; -esac - -# Check for unsupported actions -if [ "${TARGET}" = "ALL" ]; then - error_exit "Batch export is unsupported." -fi - if [ $# -gt 5 ] || [ $# -lt 1 ]; then usage fi -bastille_root_check - zfs_enable_check() { # Temporarily disable ZFS so we can create a standard backup archive + # shellcheck disable=SC2034 if checkyesno bastille_zfs_enable; then - # shellcheck disable=SC2034 bastille_zfs_enable="NO" fi } -TARGET="${1}" -GZIP_EXPORT= -XZ_EXPORT= -SAFE_EXPORT= -USER_EXPORT= -RAW_EXPORT= -DIR_EXPORT= -TXZ_EXPORT= -TGZ_EXPORT= -OPT_ZSEND="-R" -COMP_OPTION="0" - opt_count() { COMP_OPTION=$(expr ${COMP_OPTION} + 1) } @@ -107,7 +81,7 @@ if [ -n "${bastille_export_options}" ]; then DEFAULT_EXPORT_OPTS="${bastille_export_options}" info "Default export option(s): '${DEFAULT_EXPORT_OPTS}'" - + # Handle default options. for opt in ${DEFAULT_EXPORT_OPTS}; do case "${opt}" in --gz) @@ -138,14 +112,17 @@ if [ -n "${bastille_export_options}" ]; then --verbose) OPT_ZSEND="-Rv" shift;; - --*|-*) error_notify "Unknown Option." + -*) error_notify "Unknown Option." usage;; esac done else - # Handle and parse option args + # Handle options. while [ $# -gt 0 ]; do case "${1}" in + -h|--help|help) + usage + ;; --gz) GZIP_EXPORT="1" TARGET="${2}" @@ -188,8 +165,12 @@ else TARGET="${2}" shift ;; - --*|-*) - error_notify "Unknown Option." + -x|--debug) + enable_debug + shift + ;; + -*) + error_notify "Unknown Option: \"${1}\"" usage ;; *) @@ -206,30 +187,39 @@ else done fi +TARGET="${1}" +GZIP_EXPORT= +XZ_EXPORT= +SAFE_EXPORT= +USER_EXPORT= +RAW_EXPORT= +DIR_EXPORT= +TXZ_EXPORT= +TGZ_EXPORT= +OPT_ZSEND="-R" +COMP_OPTION="0" + +bastille_root_check +set_target_single "${TARGET}" + # Validate for combined options if [ "${COMP_OPTION}" -gt "1" ]; then error_exit "Error: Only one compression format can be used during export." fi -if { [ -n "${TXZ_EXPORT}" ] || [ -n "${TGZ_EXPORT}" ]; } && [ -n "${SAFE_EXPORT}" ]; then +if [ -n "${TXZ_EXPORT}" ] || [ -n "${TGZ_EXPORT}" ] && [ -n "${SAFE_EXPORT}" ]; then error_exit "Error: Simple archive modes with safe ZFS export can't be used together." fi if ! checkyesno bastille_zfs_enable; then - if [ -n "${XZ_EXPORT}" ] || - [ -n "${GZIP_EXPORT}" ] || - [ -n "${RAW_EXPORT}" ] || - [ -n "${SAFE_EXPORT}" ] || - [ "${OPT_ZSEND}" = "-Rv" ]; then + if [ -n "${XZ_EXPORT}" ] || [ -n "${GZIP_EXPORT}" ] || [ -n "${RAW_EXPORT}" ] || [ -n "${SAFE_EXPORT}" ] || [ "${OPT_ZSEND}" = "-Rv" ]; then error_exit "Options --xz, --gz, --raw, --safe, --verbose are valid for ZFS configured systems only." fi fi if [ -n "${SAFE_EXPORT}" ]; then # Check if container is running, otherwise just ignore - if [ -z "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - SAFE_EXPORT= - fi + check_target_is_stopped "${TARGET}" || SAFE_EXPORT="" fi # Export directory check @@ -367,13 +357,12 @@ jail_export() { fi fi - # shellcheck disable=SC2181 if [ "$?" -ne 0 ]; then error_exit "Failed to export '${TARGET}' container." else if [ -z "${USER_EXPORT}" ]; then # Generate container checksum file - cd "${bastille_backupsdir}" || error_exit "Failed to change directory." + cd "${bastille_backupsdir}" || error_exit "Could not cd to ${bastille_backupsdir}" sha256 -q "${TARGET}_${DATE}${FILE_EXT}" > "${TARGET}_${DATE}.sha256" info "Exported '${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}' successfully." fi @@ -399,4 +388,4 @@ if [ -n "${TARGET}" ]; then fi fi jail_export -fi +fi \ No newline at end of file diff --git a/usr/local/share/bastille/htop.sh b/usr/local/share/bastille/htop.sh index 4449edefc..828ab439c 100644 --- a/usr/local/share/bastille/htop.sh +++ b/usr/local/share/bastille/htop.sh @@ -34,29 +34,41 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille htop [option(s)] TARGET" + error_notify "Usage: bastille htop [option(s)] TARGET" cat << EOF Options: - -f | --force -- Start the jail if it is stopped. + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. EOF exit 1 } # Handle options. -FORCE=0 +AUTO=0 while [ "$#" -gt 0 ]; do case "${1}" in -h|--help|help) usage ;; - -f|--force) - FORCE=1 + -a|--auto) + AUTO=1 + shift + ;; + -x|--debug) + enable_debug shift ;; -*) - error_exit "Unknown option: \"${1}\"" + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift ;; *) break @@ -74,11 +86,11 @@ bastille_root_check set_target_single "${TARGET}" info "[${TARGET}]:" -check_target_is_running "${TARGET}" || if [ "${FORCE}" -eq 1 ]; then +check_target_is_running "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then bastille start "${TARGET}" else error_notify "Jail is not running." - error_continue "Use [-f|--force] to force start the jail." + error_continue "Use [-a|--auto] to auto-start the jail." fi bastille_jail_path="${bastille_jailsdir}/${TARGET}/root" @@ -86,4 +98,4 @@ if [ ! -x "${bastille_jail_path}/usr/local/bin/htop" ]; then error_notify "htop not found on ${TARGET}." elif [ -x "${bastille_jail_path}/usr/local/bin/htop" ]; then jexec -l ${TARGET} /usr/local/bin/htop -fi +fi \ No newline at end of file diff --git a/usr/local/share/bastille/import.sh b/usr/local/share/bastille/import.sh index ade9f648b..a40b558c8 100644 --- a/usr/local/share/bastille/import.sh +++ b/usr/local/share/bastille/import.sh @@ -37,12 +37,12 @@ usage() { # Build an independent usage for the import command # If no file/extension specified, will import from standard input error_notify "Usage: bastille import [option(s)] FILE" - cat << EOF Options: - -f | --force -- Force an archive import regardless if the checksum file does not match or missing. - -v | --verbose -- Be more verbose during the ZFS receive operation. + -f | --force Force an archive import regardless if the checksum file does not match or missing. + -v | --verbose Be more verbose during the ZFS receive operation. + -x | --debug Enable debug mode. Tip: If no option specified, container should be imported from standard input. @@ -50,50 +50,52 @@ EOF exit 1 } -# Handle special-case commands first -case "$1" in -help|-h|--help) - usage - ;; -esac - -if [ $# -gt 3 ] || [ $# -lt 1 ]; then - usage -fi - -bastille_root_check - -TARGET="${1}" -OPT_FORCE= +# Handle options. +FORCE=0 +ZRECV="-u" USER_IMPORT= -OPT_ZRECV="-u" - -# Handle and parse option args -while [ $# -gt 0 ]; do +while [ "$#" -gt 0 ]; do case "${1}" in + -h|--help|help) + usage + ;; -f|--force) - OPT_FORCE="1" - TARGET="${2}" + FORCE="1" shift ;; -v|--verbose) - OPT_ZRECV="-u -v" - TARGET="${2}" + ZRECV="-u -v" shift ;; - --*|-*) - error_notify "Unknown Option." - usage + -x|--debug) + enable_debug + shift ;; - *) - if [ $# -gt 1 ] || [ $# -lt 1 ]; then - usage - fi + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + f) FORCE=1 ;; + v) ZRECV="-u -v" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done shift ;; + *) + break + ;; esac done +if [ $# -gt 3 ] || [ $# -lt 1 ]; then + usage +fi + +TARGET="${1}" + +bastille_root_check + # Fallback to default if missing config parameters if [ -z "${bastille_decompress_xz_options}" ]; then bastille_decompress_xz_options="-c -d -v" @@ -117,7 +119,7 @@ validate_archive() { fi else # Check if user opt to force import - if [ -n "${OPT_FORCE}" ]; then + if [ -n "${FORCE}" ]; then warn "Warning: Skipping archive validation!" else error_exit "Checksum file not found. See 'bastille import [option(s)] FILE'." @@ -283,7 +285,7 @@ EOF >> "${bastille_jailsdir}/${TARGET_TRIM}/fstab" # Work with the symlinks - cd "${bastille_jailsdir}/${TARGET_TRIM}/root" || error_exit "Failed to change directory." + cd "${bastille_jailsdir}/${TARGET_TRIM}/root" || error_exit "Could not cd to ${bastille_jailsdir}/${TARGET_TRIM}/root" update_symlinks else # Generate new empty fstab file @@ -326,7 +328,7 @@ update_config() { >> "${bastille_jailsdir}/${TARGET_TRIM}/fstab" # Work with the symlinks - cd "${bastille_jailsdir}/${TARGET_TRIM}/root" || error_exit "Failed to change directory." + cd "${bastille_jailsdir}/${TARGET_TRIM}/root" || error_exit "Could not cd to ${bastille_jailsdir}/${TARGET_TRIM}/root" update_symlinks } @@ -420,7 +422,7 @@ jail_import() { info "Importing '${TARGET_TRIM}' from compressed ${FILE_EXT} image." info "Receiving ZFS data stream..." xz ${bastille_decompress_xz_options} "${bastille_backupsdir}/${TARGET}" | \ - zfs receive ${OPT_ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" + zfs receive ${ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" # Update ZFS mountpoint property if required update_zfsmount @@ -430,7 +432,7 @@ jail_import() { info "Importing '${TARGET_TRIM}' from compressed ${FILE_EXT} image." info "Receiving ZFS data stream..." gzip ${bastille_decompress_gz_options} "${bastille_backupsdir}/${TARGET}" | \ - zfs receive ${OPT_ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" + zfs receive ${ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" # Update ZFS mountpoint property if required update_zfsmount @@ -473,9 +475,9 @@ jail_import() { rm -f "${FILE_TRIM}" "${FILE_TRIM}_root" fi info "Receiving ZFS data stream..." - zfs receive ${OPT_ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" < "${FILE_TRIM}" + zfs receive ${ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" < "${FILE_TRIM}" zfs set ${ZFS_OPTIONS} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" - zfs receive ${OPT_ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}/root" < "${FILE_TRIM}_root" + zfs receive ${ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}/root" < "${FILE_TRIM}_root" # Update ZFS mountpoint property if required update_zfsmount @@ -530,14 +532,14 @@ jail_import() { # Import from uncompressed image file info "Importing '${TARGET_TRIM}' from uncompressed image archive." info "Receiving ZFS data stream..." - zfs receive ${OPT_ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" < "${bastille_backupsdir}/${TARGET}" + zfs receive ${ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" < "${bastille_backupsdir}/${TARGET}" # Update ZFS mountpoint property if required update_zfsmount else # Based on the file name, looks like we are importing from previous redirected bastille image # Quietly import from previous redirected bastille image - if ! zfs receive ${OPT_ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}"; then + if ! zfs receive ${ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}"; then exit 1 else # Update ZFS mountpoint property if required @@ -639,4 +641,4 @@ fi if [ -n "${TARGET}" ]; then jail_import -fi +fi \ No newline at end of file diff --git a/usr/local/share/bastille/limits.sh b/usr/local/share/bastille/limits.sh index 03af4690d..64ae7b1a1 100644 --- a/usr/local/share/bastille/limits.sh +++ b/usr/local/share/bastille/limits.sh @@ -35,36 +35,75 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_notify "Usage: bastille limits TARGET option value" + error_notify "Usage: bastille limits [option(s)] TARGET OPTION VALUE" echo -e "Example: bastille limits JAILNAME memoryuse 1G" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. + +EOF exit 1 } -RACCT_ENABLE=$(sysctl -n kern.racct.enable) -if [ "${RACCT_ENABLE}" != '1' ]; then - echo "Racct not enabled. Append 'kern.racct.enable=1' to /boot/loader.conf and reboot" -# exit 1 -fi +# 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 + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -# Handle special-case commands first. -case "$1" in -help|-h|--help) +if [ $# -ne 3 ]; then usage - ;; -esac +fi -if [ $# -ne 2 ]; then - usage +TARGET="${1}" +OPTION="${2}" +VALUE="${3}" +RACCT_ENABLE="$(sysctl -n kern.racct.enable)" +if [ "${RACCT_ENABLE}" != '1' ]; then + error_exit "Racct not enabled. Append 'kern.racct.enable=1' to /boot/loader.conf and reboot" fi bastille_root_check - -OPTION="${1}" -VALUE="${2}" +set_target "${TARGET}" for _jail in ${JAILS}; do + info "[${_jail}]:" + check_target_is_running "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${_jail}" + else + error_notify "Jail is not running." + error_continue "Use [-a|--auto] to auto-start the jail." + fi + _rctl_rule="jail:${_jail}:${OPTION}:deny=${VALUE}/jail" _rctl_rule_log="jail:${_jail}:${OPTION}:log=${VALUE}/jail" @@ -80,5 +119,5 @@ for _jail in ${JAILS}; do echo -e "${OPTION} ${VALUE}" rctl -a "${_rctl_rule}" "${_rctl_rule_log}" - echo -e "${COLOR_RESET}" -done + +done \ No newline at end of file diff --git a/usr/local/share/bastille/list.sh b/usr/local/share/bastille/list.sh index 3b1845b7b..ec71c4aac 100644 --- a/usr/local/share/bastille/list.sh +++ b/usr/local/share/bastille/list.sh @@ -34,24 +34,22 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille list [-j|-a] [release [-p]|template|(jail|container)|log|limit|(import|export|backup)]" -} + error_notify "Usage: bastille list [option(s)] [-j|-a] [release [-p]|template|(jail|container)|log|limit|(import|export|backup)]" + cat << EOF + Options: + + -x | --debug Enable debug mode. -if [ "${1}" = help ] || [ "${1}" = "-h" ] || [ "${1}" = "--help" ]; then - usage -fi +EOF + exit 1 +} bastille_root_check -if [ $# -eq 0 ]; then +if [ "$#" -eq 0 ]; then /usr/sbin/jls fi -if [ "${1}" = "-j" ]; then - /usr/sbin/jls -N --libxo json - exit 0 -fi - TARGET= list_all(){ @@ -118,28 +116,28 @@ list_all(){ JAIL_RELEASE=$(jexec -l ${JAIL_NAME} freebsd-version -u 2> /dev/null) fi if [ "${IS_LINUX_JAIL}" -eq 1 ]; then - JAIL_RELEASE=$(grep -hE "^NAME=.*$|^VERSION_ID=.*$|^VERSION_CODENAME=.*$" "${JAIL_PATH}/etc/os-release" 2> /dev/null | sed "s/\"//g" | sed "s/ GNU\/Linux//g" | awk -F'=' '{ a[$1] = $2; o++ } o%3 == 0 { print a["VERSION_CODENAME"] " (" a["NAME"] " " a["VERSION_ID"] ")" }') + JAIL_RELEASE=$(grep -hEs "^NAME=.*$|^VERSION_ID=.*$|^VERSION_CODENAME=.*$" "${JAIL_PATH}/etc/os-release" | sed "s/\"//g" | sed "s/ GNU\/Linux//g" | awk -F'=' '{ a[$1] = $2; o++ } o%3 == 0 { print a["VERSION_CODENAME"] " (" a["NAME"] " " a["VERSION_ID"] ")" }') fi else JAIL_STATE=$(if [ "$(sed -n "/^${JAIL_NAME} {$/,/^}$/p" "${bastille_jailsdir}/${JAIL_NAME}/jail.conf" 2> /dev/null | awk '$0 ~ /^'${JAIL_NAME}' \{|\}/ { printf "%s",$0 }')" = "${JAIL_NAME} {}" ]; then echo "Down"; else echo "n/a"; fi) if [ "$(awk '$1 == "vnet;" { print $1 }' "${bastille_jailsdir}/${JAIL_NAME}/jail.conf" 2> /dev/null)" ]; then - JAIL_IP=$(sed -n 's/^ifconfig_vnet0="\(.*\)"$/\1/p' "${bastille_jailsdir}/${JAIL_NAME}/root/etc/rc.conf" 2> /dev/null | sed "s/\// /g" | awk '{ if ($1 ~ /^[inet|inet6]/) print $2; else print $1 }') + JAIL_IP=$(sed -n 's/^ifconfig_vnet0="\(.*\)"$/\1/p' "${bastille_jailsdir}/${JAIL_NAME}/root/etc/rc.conf" 2> /dev/null | sed "s/\// /g" | awk -F"|" '{print $2}' | awk '{ if ($1 ~ /^[inet|inet6]/) print $2; else print $1 }') else - JAIL_IP=$(sed -n "s/^[ ]*ip[4,6].addr[ ]*=[ ]*\(.*\);$/\1/p" "${bastille_jailsdir}/${JAIL_NAME}/jail.conf" 2> /dev/null | sed "s/\// /g" | awk '{ print $1 }') + JAIL_IP=$(sed -n "s/^[ ]*ip[4,6].addr[ ]*=[ ]*\(.*\);$/\1/p" "${bastille_jailsdir}/${JAIL_NAME}/jail.conf" 2> /dev/null | sed "s/\// /g" | awk -F"|" '{print $2}' | awk '{ print $1 }') fi JAIL_HOSTNAME=$(sed -n "s/^[ ]*host.hostname[ ]*=[ ]*\(.*\);$/\1/p" "${bastille_jailsdir}/${JAIL_NAME}/jail.conf" 2> /dev/null) - if [ -f "${bastille_jailsdir}/${JAIL_NAME}/rdr.conf" ]; then JAIL_PORTS=$(awk '$1 ~ /^[tcp|udp]/ { printf "%s/%s:%s,",$1,$2,$3 }' "${bastille_jailsdir}/${JAIL_NAME}/rdr.conf" 2> /dev/null | sed "s/,$//"); else JAIL_PORTS=""; fi + if [ -f "${bastille_jailsdir}/${JAIL_NAME}/rdr.conf" ]; then JAIL_PORTS=$(awk '$0 ~ /(tcp|udp)/ { printf "%s/%s:%s,",$5,$6,$7 }' "${bastille_jailsdir}/${JAIL_NAME}/rdr.conf" 2> /dev/null | sed "s/,$//"); else JAIL_PORTS=""; fi JAIL_PATH=$(sed -n "s/^[ ]*path[ ]*=[ ]*\(.*\);$/\1/p" "${bastille_jailsdir}/${JAIL_NAME}/jail.conf" 2> /dev/null) if [ "${JAIL_PATH}" ]; then if [ "${IS_FREEBSD_JAIL}" -eq 1 ]; then if [ -f "${JAIL_PATH}/bin/freebsd-version" ]; then - JAIL_RELEASE=$(grep -hE "^USERLAND_VERSION=" "${JAIL_PATH}/bin/freebsd-version" 2> /dev/null | sed "s/[\"\'\^]//g;s/ .*$//g" | sed -n "s/^USERLAND_VERSION=\(.*\)$/\1/p") + JAIL_RELEASE=$(grep -hEs "^USERLAND_VERSION=" "${JAIL_PATH}/bin/freebsd-version" | sed "s/[\"\'\^]//g;s/ .*$//g" | sed -n "s/^USERLAND_VERSION=\(.*\)$/\1/p") else - JAIL_RELEASE=$(grep -h "/releases/.*/root/.bastille.*nullfs" "${bastille_jailsdir}/${JAIL_NAME}/fstab" 2> /dev/null | grep -hE "^USERLAND_VERSION=" "$(sed -n "s/^\(.*\) \/.*$/\1\/bin\/freebsd-version/p" | awk '!_[$0]++')" | sed "s/[\"\'\^]//g;s/ .*$//g" | sed -n "s/^USERLAND_VERSION=\(.*\)$/\1/p") + JAIL_RELEASE=$(grep -h "/releases/.*/root/.bastille.*nullfs" "${bastille_jailsdir}/${JAIL_NAME}/fstab" 2> /dev/null | grep -hEs "^USERLAND_VERSION=" "$(sed -n "s/^\(.*\) \/.*$/\1\/bin\/freebsd-version/p" | awk '!_[$0]++')" | sed "s/[\"\'\^]//g;s/ .*$//g" | sed -n "s/^USERLAND_VERSION=\(.*\)$/\1/p") fi fi if [ "${IS_LINUX_JAIL}" -eq 1 ]; then - JAIL_RELEASE=$(grep -hE "^NAME=.*$|^VERSION_ID=.*$|^VERSION_CODENAME=.*$" "${JAIL_PATH}/etc/os-release" 2> /dev/null | sed "s/\"//g" | sed "s/ GNU\/Linux//g" | awk -F'=' '{ a[$1] = $2; o++ } o%3 == 0 { print a["VERSION_CODENAME"] " (" a["NAME"] " " a["VERSION_ID"] ")" }') + JAIL_RELEASE=$(grep -hEs "^NAME=.*$|^VERSION_ID=.*$|^VERSION_CODENAME=.*$" "${JAIL_PATH}/etc/os-release" | sed "s/\"//g" | sed "s/ GNU\/Linux//g" | awk -F'=' '{ a[$1] = $2; o++ } o%3 == 0 { print a["VERSION_CODENAME"] " (" a["NAME"] " " a["VERSION_ID"] ")" }') fi else JAIL_RELEASE="" @@ -164,13 +162,14 @@ list_all(){ # 10.10.10.11 # 10.10.10.12 FIRST_IP="$(echo "${JAIL_IP}" | head -n 1)" - printf " ${JAIL_NAME}%*s${JAIL_STATE}%*s${FIRST_IP}%*s${JAIL_PORTS}%*s${JAIL_HOSTNAME}%*s${JAIL_RELEASE}%*s${JAIL_PATH}\n" "$((${MAX_LENGTH_JAIL_NAME} - ${#JAIL_NAME} + ${SPACER}))" "" "$((5 - ${#JAIL_STATE} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_IP} - ${#FIRST_IP} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_PORTS} - ${#JAIL_PORTS} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_HOSTNAME} - ${#JAIL_HOSTNAME} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_RELEASE} - ${#JAIL_RELEASE} + ${SPACER}))" "" + printf " ${JID}%*s${JAIL_STATE}%*s${FIRST_IP}%*s${JAIL_PORTS}%*s${JAIL_HOSTNAME}%*s${JAIL_RELEASE}%*s${JAIL_PATH}\n" "$((${MAX_LENGTH_JID} - ${#JID} + ${SPACER}))" "" "$((5 - ${#JAIL_STATE} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_IP} - ${#FIRST_IP} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_PORTS} - ${#JAIL_PORTS} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_HOSTNAME} - ${#JAIL_HOSTNAME} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_RELEASE} - ${#JAIL_RELEASE} + ${SPACER}))" "" for IP in $(echo "${JAIL_IP}" | tail -n +2); do - printf "%*s %*s${IP}\n" "$((${MAX_LENGTH_JAIL_NAME} + ${SPACER}))" "" "$((5 + ${SPACER}))" "" + printf "%*s %*s${IP}\n" "$((${MAX_LENGTH_JID} + ${SPACER}))" "" "$((5 + ${SPACER}))" "" done else printf " ${JID}%*s${JAIL_STATE}%*s${JAIL_IP}%*s${JAIL_PORTS}%*s${JAIL_HOSTNAME}%*s${JAIL_RELEASE}%*s${JAIL_PATH}\n" "$((${MAX_LENGTH_JID} - ${#JID} + ${SPACER}))" "" "$((5 - ${#JAIL_STATE} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_IP} - ${#JAIL_IP} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_PORTS} - ${#JAIL_PORTS} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_HOSTNAME} - ${#JAIL_HOSTNAME} + ${SPACER}))" "" "$((${MAX_LENGTH_JAIL_RELEASE} - ${#JAIL_RELEASE} + ${SPACER}))" "" fi + fi done else @@ -227,39 +226,78 @@ list_import(){ ls "${bastille_backupsdir}" | grep -v ".sha256$" } -if [ $# -gt 0 ]; then - # Handle special-case commands first. +list_ports() { + if [ -d "${bastille_jailsdir}" ]; then + JAIL_LIST="$(bastille list jails)" + for _jail in ${JAIL_LIST}; do + if [ -f "${bastille_jailsdir}/${_jail}/rdr.conf" ]; then + _ports="$(cat ${bastille_jailsdir}/${_jail}/rdr.conf)" + info "[$_jail]:" + echo "${_ports}" + fi + done + fi +} + +# Handle options. +while [ "$#" -gt 0 ]; do case "${1}" in - all|-a|--all) - list_all - ;; - release|releases) - list_release "${2}" - ;; - template|templates) - list_template - ;; - jail|jails|container|containers) - list_jail - ;; - log|logs) - list_log - ;; - limit|limits) - list_limit - ;; - import|imports|export|exports|backup|backups) - list_import - exit 0 - ;; - *) - # Check if we want to query all info for a specific jail instead. - if [ -f "${bastille_jailsdir}/${1}/jail.conf" ]; then - TARGET="${1}" + -h|--help|help) + usage + ;; + -a|--all|all) list_all - else - usage - fi - ;; + exit 0 + ;; + -j|--json) + /usr/sbin/jls -N --libxo json + exit 0 + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + error_exit "Unknown option: \"${1}\"" + ;; + *) + break + ;; esac -fi +done + +if [ "$#" -gt 0 ]; then + case "${1}" in + port|ports) + list_ports + ;; + release|releases) + list_release "${2}" + ;; + template|templates) + list_template + ;; + jail|jails|container|containers) + list_jail + ;; + log|logs) + list_log + ;; + limit|limits) + list_limit + ;; + import|imports|export|exports|backup|backups) + list_import + exit 0 + ;; + *) + # Check if we want to query all info for a specific jail instead. + if [ -f "${bastille_jailsdir}/${1}/jail.conf" ]; then + TARGET="${1}" + list_all + else + usage + fi + ;; + esac +fi \ No newline at end of file diff --git a/usr/local/share/bastille/mount.sh b/usr/local/share/bastille/mount.sh index b2aeb4382..92fb6580a 100644 --- a/usr/local/share/bastille/mount.sh +++ b/usr/local/share/bastille/mount.sh @@ -34,15 +34,34 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille mount TARGET HOST_PATH JAIL_PATH [filesystem_type options dump pass_number]" + error_notify "Usage: bastille mount TARGET HOST_PATH JAIL_PATH [filesystem_type options dump pass_number]" + cat << EOF + Options: + + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "${1}" in - help|-h|--help) - usage - ;; -esac +# Handle options. +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + error_exit "Unknown option: \"${1}\"" + ;; + *) + break + ;; + esac +done if [ "$#" -lt 3 ] || [ "$#" -gt 7 ]; then usage @@ -91,7 +110,7 @@ elif [ ! -e "${_hostpath}" ] || [ "${_type}" != "nullfs" ]; then usage fi -# Mount permissions,options need to start with "ro" or "rw" +# Mount permissions/options need to start with "ro" or "rw" if ! echo "${_perms}" | grep -Eq 'r[w|o],.*$'; then error_notify "Detected invalid mount permissions in FSTAB." warn "Format: /host/path /jail/path nullfs ro 0 0" @@ -123,7 +142,6 @@ for _jail in ${JAILS}; do continue fi - # Create mount point if it does not exist if [ -d "${_hostpath}" ] && [ ! -d "${_fullpath}" ]; then mkdir -p "${_fullpath}" || error_continue "Failed to create mount point." @@ -157,4 +175,4 @@ for _jail in ${JAILS}; do echo "${_fstab_entry}" >> "${bastille_jailsdir}/${_jail}/fstab" || error_continue "Failed to create fstab entry: ${_fstab_entry}" mount -F "${bastille_jailsdir}/${_jail}/fstab" -a || error_continue "Failed to mount volume: ${_fullpath}" echo "Added: ${_fstab_entry}" -done +done \ No newline at end of file diff --git a/usr/local/share/bastille/network.sh b/usr/local/share/bastille/network.sh new file mode 100644 index 000000000..f7519d2cb --- /dev/null +++ b/usr/local/share/bastille/network.sh @@ -0,0 +1,453 @@ +#!/bin/sh +# +# Copyright (c) 2018-2024, Victor Tschetter +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +. /usr/local/share/bastille/common.sh +. /usr/local/etc/bastille/bastille.conf + +usage() { + error_notify "Usage: bastille network [option(s)] TARGET [remove|add|change] INTERFACE [IP_ADDRESS]" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -b | --bridge Add a bridged VNET interface to an existing VNET jail. + -c | --classic Add an interface to a classic (non-VNET) jail. + -m | --static-mac Generate a static MAC address for the VNET interface. + -s | --start Start jail on completion. + -v | --vnet Add a VNET interface to an existing VNET jail. + -x | --debug Enable debug mode. + +EOF + exit 1 +} + +# Handle options. +AUTO=0 +BRIDGE_VNET_JAIL=0 +CLASSIC_JAIL=0 +STATIC_MAC=0 +VNET_JAIL=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -b|-B|--bridge) + BRIDGE_VNET_JAIL=1 + shift + ;; + -c|--classic) + CLASSIC_JAIL=1 + shift + ;; + -m|-M|--static-mac) + STATIC_MAC=1 + shift + ;; + -v|-V|--vnet) + VNET_JAIL=1 + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} 2>/dev/null | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + b|B) BRIDGE_VNET_JAIL=1 ;; + c|C) CLASSIC_JAIL=1 ;; + m|M) STATIC_MAC=1 ;; + v|V) VNET_JAIL=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done + +TARGET="${1}" +ACTION="${2}" +INTERFACE="${3}" +IP="${4}" + +if [ "${ACTION}" = "add" ]; then + if { [ "${VNET_JAIL}" -eq 1 ] && [ "${BRIDGE_VNET_JAIL}" -eq 1 ]; } || \ + { [ "${VNET_JAIL}" -eq 1 ] && [ "${CLASSIC_JAIL}" -eq 1 ]; } || \ + { [ "${CLASSIC_JAIL}" -eq 1 ] && [ "${BRIDGE_VNET_JAIL}" -eq 1 ]; } then + error_notify "Error: Only one of [-b|-B|--bridge], [-c|--classic] or [-v|-V|--vnet] should be set." + usage + elif [ "${VNET_JAIL}" -eq 0 ] && [ "${BRIDGE_VNET_JAIL}" -eq 0 ] && [ "${CLASSIC_JAIL}" -eq 0 ]; then + error_notify "Error: [-b|-B|--bridge], [-c|--classic] or [-v|-V|--vnet] must be set." + usage + fi +fi + +if [ "$#" -lt 2 ] || [ "$#" -gt 4 ]; then + usage +fi + +bastille_root_check +set_target_single "${TARGET}" +check_target_is_stopped "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${TARGET}" +else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." +fi + +validate_ip() { + IP6="" + local _ip="${1}" + local _jail_config="${bastille_jailsdir}/${TARGET}/jail.conf" + if [ -z "${IP}" ] || [ "${IP}" = "0.0.0.0" ]; then + IP="SYNCDHCP" + else + if grep -Eqo ${_ip} ${_jail_config}; then + error_exit "Error: IP already present in jail.conf" + fi + local _ip6="$( echo "${_ip}" 2>/dev/null | grep -E '^(([a-fA-F0-9:]+$)|([a-fA-F0-9:]+\/[0-9]{1,3}$)|SLAAC)' )" + if [ -n "${_ip6}" ]; then + info "Valid: (${_ip6})." + # shellcheck disable=SC2034 + IP6="${_ip6}" + else + local IFS + if echo "${_ip}" 2>/dev/null | grep -Eq '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))?$'; then + TEST_IP=$(echo "${_ip}" | cut -d / -f1) + IFS=. + set ${TEST_IP} + for quad in 1 2 3 4; do + if eval [ \$$quad -gt 255 ]; then + error_exit "Invalid: (${TEST_IP})" + fi + done + info "Valid: (${_ip})." + else + error_exit "Invalid: (${_ip})." + fi + fi + fi +} + +validate_netif() { + local _interface="${1}" + if ifconfig -l | grep -qwo ${_interface}; then + info "Valid: (${_interface})." + else + error_exit "Invalid: (${_interface})." + fi +} + +check_interface_added() { + local _jailname="${1}" + local _if="${2}" + local _jail_config="${bastille_jailsdir}/${_jailname}/jail.conf" + if grep -qo "${_if}" "${_jail_config}"; then + return 0 + else + return 1 + fi +} + +change_ip() { + local _jailname="${1}" + local _if="${2}" + local _ip="${3}" + local _jail_config="${bastille_jailsdir}/${_jailname}/jail.conf" + local _jail_rc_config="${bastille_jailsdir}/${_jailname}/root/etc/rc.conf" + local _epair="$(grep -E ${_if} ${_jail_config} | grep -Eo -m 1 'epair[0-9]+|bastille[0-9]+')" + local _jail_vnet="$(grep -E ${_epair} ${_jail_rc_config} | grep -Eo -m 1 'vnet[0-9]+')" + sysrc -f "${_jail_rc_config}" ifconfig_${_jail_vnet}=" inet ${_ip} " +} + +add_interface() { + local _jailname="${1}" + local _if="${2}" + local _ip="${3}" + local _jail_config="${bastille_jailsdir}/${_jailname}/jail.conf" + if [ "${VNET_JAIL}" -eq 1 ] || [ "${BRIDGE_VNET_JAIL}" -eq 1 ]; then + local _jail_rc_config="${bastille_jailsdir}/${_jailname}/root/etc/rc.conf" + local _epair_if_count="$(grep -Eo 'epair[0-9]+' ${bastille_jailsdir}/*/jail.conf | sort -u | wc -l | awk '{print $1}')" + local _bastille_if_count="$(grep -Eo 'bastille[0-9]+' ${bastille_jailsdir}/*/jail.conf | sort -u | wc -l | awk '{print $1}')" + local _vnet_if_count="$(grep -Eo 'vnet[1-9]+' ${_jail_rc_config} | sort -u | wc -l | awk '{print $1}')" + local _if_vnet="vnet$((_vnet_if_count + 1))" + local epair_num_range=$((_epair_if_count + 1)) + local bastille_num_range=$((_bastille_if_count + 1)) + fi + if [ "${BRIDGE_VNET_JAIL}" -eq 1 ]; then + for _num in $(seq 0 "${epair_num_range}"); do + if ! grep -Eq "epair${_num}" "${bastille_jailsdir}"/*/jail.conf; then + local bridge_epair="epair${_num}" + break + fi + done + # Remove ending brace (it is added again with the netblock) + sed -i '' '/}/d' "${_jail_config}" + if [ "${STATIC_MAC}" -eq 1 ]; then + # Generate NETBLOCK with static MAC + generate_static_mac "${_jailname}" "${_if}" + cat << EOF >> "${_jail_config}" + ## ${bridge_epair} interface + vnet.interface += ${bridge_epair}b; + exec.prestart += "ifconfig ${bridge_epair} create"; + exec.prestart += "ifconfig ${_if} addm ${bridge_epair}a"; + exec.prestart += "ifconfig ${bridge_epair}a ether ${macaddr}a"; + exec.prestart += "ifconfig ${bridge_epair}b ether ${macaddr}b"; + exec.prestart += "ifconfig ${bridge_epair}a description \"vnet host interface for Bastille jail ${_jailname}\""; + exec.poststop += "ifconfig ${_if} deletem ${bridge_epair}a"; + exec.poststop += "ifconfig ${bridge_epair}a destroy"; +} +EOF + else + # Generate NETBLOCK without static MAC + cat << EOF >> "${_jail_config}" + ## ${bridge_epair} interface + vnet.interface += ${bridge_epair}b; + exec.prestart += "ifconfig ${bridge_epair} create"; + exec.prestart += "ifconfig ${_if} addm ${bridge_epair}a"; + exec.prestart += "ifconfig ${bridge_epair}a description \"vnet host interface for Bastille jail ${_jailname}\""; + exec.poststop += "ifconfig ${_if} deletem ${bridge_epair}a"; + exec.poststop += "ifconfig ${bridge_epair}a destroy"; +} +EOF + fi + # Add config to /etc/rc.conf + sysrc -f "${_jail_rc_config}" ifconfig_${bridge_epair}b_name="${_if_vnet}" + # If 0.0.0.0 set DHCP, else set static IP address + if [ "${_ip}" = "0.0.0.0" ]; then + sysrc -f "${_jail_rc_config}" ifconfig_${_if_vnet}="SYNCDHCP" + else + sysrc -f "${_jail_rc_config}" ifconfig_${_if_vnet}=" inet ${_ip} " + fi + + info "[${_jailname}]:" + echo "Added interface: \"${_if}\"" + + elif [ "${VNET_JAIL}" -eq 1 ]; then + for _num in $(seq 0 "${bastille_num_range}"); do + if ! grep -Eq "bastille${_num}" "${bastille_jailsdir}"/*/jail.conf; then + local bastille_epair="bastille${_num}" + break + fi + done + # Remove ending brace (it is added again with the netblock) + sed -i '' '/}/d' "${_jail_config}" + if [ "${STATIC_MAC}" -eq 1 ]; then + # Generate NETBLOCK with static MAC + generate_static_mac "${_jailname}" "${_if}" + cat << EOF >> "${_jail_config}" + ## ${bastille_epair} interface + vnet.interface += e0b_${bastille_epair}; + exec.prestart += "jib addm ${bastille_epair} ${_if}"; + exec.prestart += "ifconfig e0a_${bastille_epair} ether ${macaddr}a"; + exec.prestart += "ifconfig e0b_${bastille_epair} ether ${macaddr}b"; + exec.prestart += "ifconfig e0a_${bastille_epair} description \"vnet host interface for Bastille jail ${_jailname}\""; + exec.poststop += "jib destroy ${bastille_epair}"; +} +EOF + else + # Generate NETBLOCK without static MAC + cat << EOF >> "${_jail_config}" + ## ${bastille_epair} interface + vnet.interface += e0b_${bastille_epair}; + exec.prestart += "jib addm ${bastille_epair} ${_if}"; + exec.prestart += "ifconfig e0a_${bastille_epair} description \"vnet host interface for Bastille jail ${_jailname}\""; + exec.poststop += "jib destroy ${bastille_epair}"; +} +EOF + fi + # Add config to /etc/rc.conf + sysrc -f "${_jail_rc_config}" ifconfig_e0b_${bastille_epair}_name="${_if_vnet}" + # If 0.0.0.0 set DHCP, else set static IP address + if [ "${_ip}" = "0.0.0.0" ]; then + sysrc -f "${_jail_rc_config}" ifconfig_${_if_vnet}="SYNCDHCP" + else + sysrc -f "${_jail_rc_config}" ifconfig_${_if_vnet}=" inet ${_ip} " + fi + + info "[${_jailname}]:" + echo "Added VNET interface: \"${_if}\"" + + elif [ "${CLASSIC_JAIL}" -eq 1 ]; then + if [ -n "${IP6_ENABLE}" ]; then + if [ "$(bastille config ${TARGET} get ip6)" = "disable" ]; then + error_notify "Error: IPv6 is not enabled for this jail." + error_exit "Enable it by setting \"ip6 = new;\" in jail.conf." + else + sed -i '' "s/ip6.addr = .*/&\n ip6.addr += ${_if}|${_ip};/" "${_jail_config}" + fi + else + if [ "$(bastille config ${TARGET} get ip4)" = "disable" ]; then + error_exit "Error: IPv4 is not enabled for this jail." + else + sed -i '' "s/ip4.addr = .*/&\n ip4.addr += ${_if}|${_ip};/" "${_jail_config}" + fi + fi + fi + + info "[${_jailname}]:" + echo "Added interface: \"${_if}\"" +} + +remove_interface() { + local _jailname="${1}" + local _if="${2}" + local _jail_config="${bastille_jailsdir}/${_jailname}/jail.conf" + # Skip next block in case of classic jail + if [ "$(bastille config ${TARGET} get vnet)" != "not set" ]; then + local _jail_rc_config="${bastille_jailsdir}/${_jailname}/root/etc/rc.conf" + local _if_jail="$(grep ${_if} ${_jail_config} | grep -Eo -m 1 'epair[0-9]+|bastille[0-9]+')" + + if grep -o "${_if_jail}" ${_jail_rc_config}; then + local _if_vnet="$(grep ${_if_jail} ${_jail_rc_config} | grep -Eo 'vnet[0-9]+')" + else + error_exit "Interface not found: ${_if_jail}" + fi + + # Do not allow removing default vnet0 interface + if [ "${_if_vnet}" = "vnet0" ]; then + error_exit "Default interface cannot be removed." + fi + + # Avoid removing entire file contents if variables aren't set for some reason + if [ -z "${_if_jail}" ]; then + error_exit "Error: Could not find specifed interface." + fi + + # Remove interface from /etc/rc.conf + if [ -n "${_if_vnet}" ] && echo ${_if_vnet} 2>/dev/null | grep -Eo 'vnet[0-9]+'; then + sed -i '' "/.*${_if_vnet}.*/d" "${_jail_rc_config}" + else + error_exit "Failed to remove interface from /etc/rc.conf" + fi + + # Remove VNET interface from jail.conf (VNET) + if [ -n "${_if_jail}" ]; then + sed -i '' "/.*${_if_jail}.*/d" "${_jail_config}" + else + error_exit "Failed to remove interface from jail.conf" + fi + else + # Remove interface from jail.conf (non-VNET) + if [ -n "${_if}" ]; then + if grep ${_if} ${_jail_config} 2>/dev/null | grep -qo " = "; then + error_exit "Default interface cannot be removed." + else + sed -i '' "/.*${_if}.*/d" "${_jail_config}" + fi + else + error_exit "Failed to remove interface from jail.conf" + fi + fi + + info "[${_jailname}]:" + echo "Removed interface: \"${_if}\"" +} + +case "${ACTION}" in + add) + validate_netif "${INTERFACE}" + validate_ip "${IP}" + if [ "${VNET_JAIL}" -eq 1 ]; then + ! check_interface_added "${TARGET}" "${INTERFACE}" || error_exit "Interface is already added: \"${INTERFACE}\"" + if ifconfig | grep "${INTERFACE}" | grep -q bridge; then + error_exit "\"${INTERFACE}\" is a bridge interface." + else + add_interface "${TARGET}" "${INTERFACE}" "${IP}" + if [ "${START}" -eq 1 ]; then + bastille start "${TARGET}" + fi + fi + elif [ "${BRIDGE_VNET_JAIL}" -eq 1 ]; then + ! check_interface_added "${TARGET}" "${INTERFACE}" || error_exit "Interface is already added: \"${INTERFACE}\"" + if ! ifconfig | grep "${INTERFACE}" | grep -q bridge; then + error_exit "\"${INTERFACE}\" is not a bridge interface." + else + add_interface "${TARGET}" "${INTERFACE}" "${IP}" + if [ "${START}" -eq 1 ]; then + bastille start "${TARGET}" + fi + fi + elif [ "${CLASSIC_JAIL}" -eq 1 ]; then + if [ "$(bastille config ${TARGET} get vnet)" != "not set" ]; then + error_exit "Error: ${TARGET} is a VNET jail." + elif [ "$(bastille config ${TARGET} get ip4)" = "inherit" ] || [ "$(bastille config ${TARGET} get ip6)" = "inherit" ] || \ + [ "$(bastille config ${TARGET} get ip4)" = "ip_hostname" ] || [ "$(bastille config ${TARGET} get ip6)" = "ip_hostname" ]; then + error_exit "Error: Jail IP mode must not [inherit|ip_hostname]." + elif [ "${IP}" = "SYNCDHCP" ]; then + error_exit "Error: Valid IP is required for non-VNET jails." + else + add_interface "${TARGET}" "${INTERFACE}" "${IP}" + if [ "${AUTO}" -eq 1 ]; then + bastille start "${TARGET}" + fi + fi + fi + ;; + remove|delete) + check_interface_added "${TARGET}" "${INTERFACE}" || error_exit "Interface not found in jail.conf: \"${INTERFACE}\"" + validate_netif "${INTERFACE}" + remove_interface "${TARGET}" "${INTERFACE}" + if [ "${AUTO}" -eq 1 ]; then + bastille start "${TARGET}" + fi + ;; + change) + validate_netif "${INTERFACE}" + check_interface_added "${TARGET}" "${INTERFACE}" || error_exit "Interface not found in jail.conf: \"${INTERFACE}\"" + if grep -qo "vnet;" "${bastille_jailsdir}/${TARGET}/jail.conf"; then + if [ -z "${IP}" ] || [ "${IP}" = "0.0.0.0" ]; then + IP="SYNCDHCP" + fi + validate_ip "${IP}" + change_ip "${TARGET}" "${INTERFACE}" "${IP}" + if [ "${AUTO}" -eq 1 ]; then + bastille start "${TARGET}" + fi + else + error_notify "Error: Changing IP is not supported for non-VNET jails." + error_exit "Please use [add] to add an additional IP to a classic, non-VNET jail." + fi + ;; + *) + error_exit "Only [add|remove|change] are supported." + ;; +esac \ No newline at end of file diff --git a/usr/local/share/bastille/pkg.sh b/usr/local/share/bastille/pkg.sh index e7cf23d75..af96ad70c 100644 --- a/usr/local/share/bastille/pkg.sh +++ b/usr/local/share/bastille/pkg.sh @@ -31,29 +31,82 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. . /usr/local/share/bastille/common.sh +. /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille pkg [-H|--host] TARGET command [args]" + error_notify "Usage: bastille pkg [option(s)] TARGET COMMAND [args]" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -H | --host Use the hosts pkg command. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# Handle options. +AUTO=0 +USE_HOST_PKG=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -H|--host) + USE_HOST_PKG=1 + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + H) USE_HOST_PKG=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -lt 1 ]; then +if [ $# -lt 2 ]; then usage fi +TARGET="${1}" +shift + bastille_root_check +set_target "${TARGET}" errors=0 for _jail in ${JAILS}; do + info "[${_jail}]:" - bastille_jail_path=$(/usr/sbin/jls -j "${_jail}" path) + + check_target_is_running "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${_jail}" + else + error_notify "Jail is not running." + error_continue "Use [-a|--auto] to auto-start the jail." + fi + + bastille_jail_path="${bastille_jailsdir}/${_jail}/root" if [ -f "/usr/sbin/mport" ]; then if ! jexec -l -U root "${_jail}" /usr/sbin/mport "$@"; then errors=1 @@ -71,10 +124,8 @@ for _jail in ${JAILS}; do errors=1 fi fi - echo done if [ $errors -ne 0 ]; then error_exit "Failed to apply on some jails, please check logs" - exit 1 -fi +fi \ No newline at end of file diff --git a/usr/local/share/bastille/rcp.sh b/usr/local/share/bastille/rcp.sh index a3b1cda1c..61b1a7352 100644 --- a/usr/local/share/bastille/rcp.sh +++ b/usr/local/share/bastille/rcp.sh @@ -34,46 +34,63 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille rcp [OPTION] TARGET CONTAINER_PATH HOST_PATH" -} + error_notify "Usage: bastille rcp [option(s)] TARGET JAIL_PATH HOST_PATH" + cat << EOF + Options: -CPSOURCE="${1}" -CPDEST="${2}" + -q | --quiet Suppress output. + -x | --debug Enable debug mode. -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; --q|--quiet) - OPTION="${1}" - CPSOURCE="${2}" - CPDEST="${3}" - ;; -esac +EOF + exit 1 +} -if [ $# -ne 2 ]; then - usage -fi +# Handle options. +OPTION="-av" +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -q|--quiet) + OPTION="-a" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + q) OPTION="-a" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ "${TARGET}" = "ALL" ]; then +if [ "$#" -ne 3 ]; then usage fi -case "${OPTION}" in - -q|--quiet) - OPTION="-a" - ;; - *) - OPTION="-av" - ;; -esac +TARGET="${1}" +CPSOURCE="${2}" +CPDEST="${3}" + +bastille_root_check +set_target "${TARGET}" for _jail in ${JAILS}; do info "[${_jail}]:" bastille_jail_path="${bastille_jailsdir}/${_jail}/root" - cp "${OPTION}" "${bastille_jail_path}/${CPSOURCE}" "${CPDEST}" - RETURN="$?" - echo - return "${RETURN}" -done + if ! cp "${OPTION}" "${bastille_jail_path}${CPSOURCE}" "${CPDEST}"; then + error_continue "RCP failed: ${CPSOURCE} -> ${bastille_jail_path}${CPDEST}" + fi +done \ No newline at end of file diff --git a/usr/local/share/bastille/rdr.sh b/usr/local/share/bastille/rdr.sh index f5f426d58..00dcf7d49 100644 --- a/usr/local/share/bastille/rdr.sh +++ b/usr/local/share/bastille/rdr.sh @@ -34,172 +34,328 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille rdr TARGET [clear|list|(tcp|udp host_port jail_port [log ['(' logopts ')'] ] )]" -} - -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac + error_notify "Usage: bastille rdr [option(s)] TARGET [clear|reset|list|(tcp|udp)] HOST_PORT JAIL_PORT [log ['(' logopts ')'] ] )]" + cat << EOF + Options: -if [ $# -lt 2 ]; then - usage -fi + -d | --destination [destination ip] Limit rdr to a destination IP. Useful if you have multiple IPs on one interface. + -i | --interface [interface] Set the interface to create the rdr rule on. Useful if you have multiple interfaces. + -s | --source [source ip] Limit rdr to a source IP. Useful to only allow access from a certian IP or subnet. + -t | --type [ipv4|ipv6] Specify IP type. Must be used if -s or -d are used. Defaults to both. + -x | --debug Enable debug mode. -bastille_root_check - -TARGET="${1}" -JAIL_NAME="" -JAIL_IP="" -JAIL_IP6="" -EXT_IF="" -shift +EOF + exit 1 +} check_jail_validity() { - # Can only redirect to single jail - if [ "${TARGET}" = 'ALL' ]; then - error_exit "Can only redirect to a single jail." - fi - # Check if jail name is valid - JAIL_NAME=$(/usr/sbin/jls -j "${TARGET}" name 2>/dev/null) - if [ -z "${JAIL_NAME}" ]; then - error_exit "Jail not found: ${TARGET}" - fi - - # Check if jail ip4 address (ip4.addr) is valid (non-VNET only) - if [ "$(bastille config $TARGET get vnet)" != 'enabled' ]; then - JAIL_IP=$(/usr/sbin/jls -j "${TARGET}" ip4.addr 2>/dev/null) - if [ -z "${JAIL_IP}" ] || [ "${JAIL_IP}" = "-" ]; then - error_exit "Jail IP not found: ${TARGET}" + # Validate jail network type and set IP4/6 + if [ "$( bastille config ${TARGET} get vnet )" != 'enabled' ]; then + _ip4_interfaces="$(bastille config ${TARGET} get ip4.addr | sed 's/,/ /g')" + _ip6_interfaces="$(bastille config ${TARGET} get ip6.addr | sed 's/,/ /g')" + # Check if jail ip4.addr is valid (non-VNET only) + if [ "${_ip4_interfaces}" != "not set" ] && [ "${_ip4_interfaces}" != "disable" ]; then + JAIL_IP="$(echo ${_ip4_interfaces} | awk '{print $1}' | awk -F"|" '{print $2}')" fi - fi - # Check if jail ip6 address (ip6.addr) is valid (non-VNET only) - if [ "$(bastille config $TARGET get vnet)" != 'enabled' ]; then - if [ "$(bastille config $TARGET get ip6)" != 'disable' ] && [ "$(bastille config $TARGET get ip6)" != 'not set' ]; then - JAIL_IP6=$(/usr/sbin/jls -j "${TARGET}" ip6.addr 2>/dev/null) + # Check if jail ip6.addr is valid (non-VNET only) + if [ "${_ip6_interfaces}" != "not set" ] && [ "${_ip6_interfaces}" != "disable" ]; then + JAIL_IP6="$(echo ${_ip6_interfaces} | awk '{print $1}' | awk -F"|" '{print $2}')" fi + else + error_exit "VNET jails do not support rdr." fi - - + # Check if rdr-anchor is defined in pf.conf if ! (pfctl -sn | grep rdr-anchor | grep 'rdr/\*' >/dev/null); then error_exit "rdr-anchor not found in pf.conf" fi +} - # Check if ext_if is defined in pf.conf - if [ -n "${bastille_pf_conf}" ]; then - EXT_IF=$(grep "^[[:space:]]*${bastille_network_pf_ext_if}[[:space:]]*=" ${bastille_pf_conf}) - if [ -z "${EXT_IF}" ]; then - error_exit "bastille_network_pf_ext_if (${bastille_network_pf_ext_if}) not defined in pf.conf" +check_rdr_ip_validity() { + local ip="${1}" + local ip6="$( echo "${ip}" | grep -E '^(([a-fA-F0-9:]+$)|([a-fA-F0-9:]+\/[0-9]{1,3}$)|SLAAC)' )" + if [ -n "${ip6}" ]; then + info "Valid: (${ip6})." + else + local IFS + if echo "${ip}" | grep -Eq '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))?$'; then + TEST_IP=$(echo "${ip}" | cut -d / -f1) + IFS=. + set ${TEST_IP} + for quad in 1 2 3 4; do + if eval [ \$$quad -gt 255 ]; then + error_exit "Invalid: (${TEST_IP})" + fi + done + info "Valid: (${ip})." + else + error_exit "Invalid: (${ip})." fi fi } -# function: write rule to rdr.conf +validate_rdr_rule() { + local if="${1}" + local src="${2}" + local dst="${3}" + local proto="${4}" + local host_port="${5}" + local jail_port="${6}" + if grep -qs "$if $src $dst $proto $host_port $jail_port" "${bastille_jailsdir}/${TARGET}/rdr.conf"; then + error_notify "Error: Ports already in use on this interface." + error_exit "See 'bastille list ports' or 'bastille rdr TARGET clear'." + fi +} + persist_rdr_rule() { -if ! grep -qs "$1 $2 $3" "${bastille_jailsdir}/${JAIL_NAME}/rdr.conf"; then - echo "$1 $2 $3" >> "${bastille_jailsdir}/${JAIL_NAME}/rdr.conf" -fi + local inet="${1}" + local if="${2}" + local src="${3}" + local dst="${4}" + local proto="${5}" + local host_port="${6}" + local jail_port="${7}" + if ! grep -qs "$inet $if $src $dst $proto $host_port $jail_port" "${bastille_jailsdir}/${TARGET}/rdr.conf"; then + echo "$inet $if $src $dst $proto $host_port $jail_port" >> "${bastille_jailsdir}/${TARGET}/rdr.conf" + fi } persist_rdr_log_rule() { -proto=$1;host_port=$2;jail_port=$3; -shift 3; -log=$@; -if ! grep -qs "$proto $host_port $jail_port $log" "${bastille_jailsdir}/${JAIL_NAME}/rdr.conf"; then - echo "$proto $host_port $jail_port $log" >> "${bastille_jailsdir}/${JAIL_NAME}/rdr.conf" -fi + local inet="${1}" + local if="${2}" + local src="${3}" + local dst="${4}" + local proto="${5}" + local host_port="${6}" + local jail_port="${7}" + shift 7; + log=$@; + if ! grep -qs "$inet $if $src $dst $proto $host_port $jail_port $log" "${bastille_jailsdir}/${TARGET}/rdr.conf"; then + echo "$inet $if $src $dst $proto $host_port $jail_port $log" >> "${bastille_jailsdir}/${TARGET}/rdr.conf" + fi } - -# function: load rdr rule via pfctl load_rdr_rule() { -( pfctl -a "rdr/${JAIL_NAME}" -Psn 2>/dev/null; - printf '%s\nrdr pass on $%s inet proto %s to port %s -> %s port %s\n' "$EXT_IF" "${bastille_network_pf_ext_if}" "$1" "$2" "$JAIL_IP" "$3" ) \ - | pfctl -a "rdr/${JAIL_NAME}" -f- -if [ -n "$JAIL_IP6" ]; then - ( pfctl -a "rdr/${JAIL_NAME}" -Psn 2>/dev/null; - printf '%s\nrdr pass on $%s inet proto %s to port %s -> %s port %s\n' "$EXT_IF" "${bastille_network_pf_ext_if}" "$1" "$2" "$JAIL_IP6" "$3" ) \ - | pfctl -a "rdr/${JAIL_NAME}" -f- -fi + local inet="${1}" + local if_name="${2}" + local if=ext_if=\"${2}\" + local src="${3}" + local dst="${4}" + local proto="${5}" + local host_port="${6}" + local jail_port="${7}" + # Create IPv4 rdr rule + # shellcheck disable=SC2193 + if { [ "${inet}" = "ipv4" ] || [ "${inet}" = "dual" ]; } then + if ! ( pfctl -a "rdr/${TARGET}" -Psn 2>/dev/null; + printf '%s\nrdr pass on $%s inet proto %s from %s to %s port %s -> %s port %s\n' "$if" "${bastille_network_pf_ext_if}" "$proto" "$src" "$dst" "$host_port" "$JAIL_IP" "$jail_port" ) \ + | pfctl -a "rdr/${TARGET}" -f-; then + error_exit "Failed to create IPv4 rdr rule \"${if_name} ${src} ${dst} ${proto} ${host_port} ${jail_port}\"" + else + info "[${TARGET}]:" + echo "IPv4 ${proto}/${host_port}:${jail_port} on ${if_name}" + fi + fi + # Create IPv6 rdr rule (if ip6.addr is enabled) + # shellcheck disable=SC2193 + if [ -n "${JAIL_IP6}" ] && { [ "${inet}" = "ipv6" ] || [ "${inet}" = "dual" ]; } then + if ! ( pfctl -a "rdr/${TARGET}" -Psn; + printf '%s\nrdr pass on $%s inet6 proto %s from %s to %s port %s -> %s port %s\n' "$if" "${bastille_network_pf_ext_if}" "$proto" "$src" "$dst" "$host_port" "$JAIL_IP6" "$jail_port" ) \ + | pfctl -a "rdr/${TARGET}" -f-; then + error_exit "Failed to create IPv6 rdr rule \"${if_name} ${src} ${dst} ${proto} ${host_port} ${jail_port}\"" + else + info "[${TARGET}]:" + echo "IPv6 ${proto}/${src}:${host_port} -> ${dst}:${jail_port} on ${if_name}" + fi + fi } -# function: load rdr rule with log via pfctl load_rdr_log_rule() { -proto=$1;host_port=$2;jail_port=$3; -shift 3; -log=$@ -( pfctl -a "rdr/${JAIL_NAME}" -Psn 2>/dev/null; - printf '%s\nrdr pass %s on $%s inet proto %s to port %s -> %s port %s\n' "$EXT_IF" "$log" "${bastille_network_pf_ext_if}" "$proto" "$host_port" "$JAIL_IP" "$jail_port" ) \ - | pfctl -a "rdr/${JAIL_NAME}" -f- -if [ -n "$JAIL_IP6" ]; then - ( pfctl -a "rdr/${JAIL_NAME}" -Psn 2>/dev/null; - printf '%s\nrdr pass %s on $%s inet proto %s to port %s -> %s port %s\n' "$EXT_IF" "$log" "${bastille_network_pf_ext_if}" "$proto" "$host_port" "$JAIL_IP6" "$jail_port" ) \ - | pfctl -a "rdr/${JAIL_NAME}" -f- + local inet="${1}" + local if_name="${2}" + local if=ext_if=\"${2}\" + local src="${3}" + local dst="${4}" + local proto="${5}" + local host_port="${6}" + local jail_port="${7}" + shift 7; + log=$@ + # Create IPv4 rule with log + # shellcheck disable=SC2193 + if { [ "${inet}" = "ipv4" ] || [ "${inet}" = "dual" ]; } then + if ! ( pfctl -a "rdr/${TARGET}" -Psn; + printf '%s\nrdr pass %s on $%s inet proto %s from %s to %s port %s -> %s port %s\n' "$if" "$log" "${bastille_network_pf_ext_if}" "$proto" "$src" "$dst" "$host_port" "$JAIL_IP" "$jail_port" ) \ + | pfctl -a "rdr/${TARGET}" -f-; then + error_exit "Failed to create logged IPv4 rdr rule \"${if_name} ${src} ${dst} ${proto} ${host_port} ${jail_port}\"" + else + info "[${TARGET}]:" + echo "IPv4 ${proto}/${src}:${host_port} -> ${dst}:${jail_port} on ${if_name}" + fi + fi + # Create IPv6 rdr rule with log (if ip6.addr is enabled) + # shellcheck disable=SC2193 + if [ -n "${JAIL_IP6}" ] && { [ "${inet}" = "ipv6" ] || [ "${inet}" = "dual" ]; } then + if ! ( pfctl -a "rdr/${TARGET}" -Psn; + printf '%s\nrdr pass %s on $%s inet6 proto %s from %s to %s port %s -> %s port %s\n' "$if" "$log" "${bastille_network_pf_ext_if}" "$proto" "$src" "$dst" "$host_port" "$JAIL_IP6" "$jail_port" ) \ + | pfctl -a "rdr/${TARGET}" -f-; then + error_exit "Failed to create logged IPv6 rdr rule \"${if_name} ${src} ${dst} ${proto} ${host_port} ${jail_port}\"" + else + info "[${TARGET}]:" + echo "IPv6 ${proto}/${src}:${host_port} -> ${dst}:${jail_port} on ${if_name}" + fi + fi +} + +# Handle options. +RDR_IF="$(grep "^[[:space:]]*${bastille_network_pf_ext_if}[[:space:]]*=" ${bastille_pf_conf} | awk -F'"' '{print $2}')" +RDR_SRC="any" +RDR_DST="any" +RDR_INET="dual" +OPTION_IF=0 +OPTION_SRC=0 +OPTION_DST=0 +OPTION_INET_TYPE=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -d|--destination) + if ifconfig | grep -owq "inet ${2}"; then + OPTION_DST=1 + RDR_DST="${2}" + shift 2 + else + error_exit "${2} is not an IP on this system." + fi + ;; + -i|--interface) + if ifconfig | grep -owq "${2}:"; then + OPTION_IF=1 + RDR_IF="${2}" + shift 2 + else + error_exit "${2} is not a valid interface." + fi + ;; + -s|--source) + check_rdr_ip_validity "${2}" + OPTION_SRC=1 + RDR_SRC="${2}" + shift 2 + ;; + -t|--type) + if [ "${2}" != "ipv4" ] && [ "${2}" != "ipv6" ]; then + error_exit "[-t|--type] must be [ipv4|ipv6]" + else + OPTION_INET_TYPE=1 + RDR_INET="${2}" + shift 2 + fi + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + error_exit "Unknown option: \"${1}\"" + ;; + *) + break + ;; + esac +done + +if [ "$#" -lt 2 ]; then + usage fi -} +TARGET="${1}" +JAIL_IP="" +JAIL_IP6="" +shift -while [ $# -gt 0 ]; do - case "$1" in +bastille_root_check +set_target_single "${TARGET}" + +while [ "$#" -gt 0 ]; do + case "${1}" in list) - if [ "${TARGET}" = 'ALL' ]; then - for JAIL_NAME in $(ls "${bastille_jailsdir}" | sed "s/\n//g"); do - echo "${JAIL_NAME} redirects:" - pfctl -a "rdr/${JAIL_NAME}" -Psn 2>/dev/null - done + if [ "${OPTION_IF}" -eq 1 ] || [ "${OPTION_SRC}" -eq 1 ] || [ "${OPTION_DST}" -eq 1 ] || [ "${OPTION_INET_TYPE}" -eq 1 ];then + error_exit "Command \"${1}\" cannot be used with options." + elif [ -n "${2}" ]; then + usage else check_jail_validity - pfctl -a "rdr/${JAIL_NAME}" -Psn 2>/dev/null + pfctl -a "rdr/${TARGET}" -Psn 2>/dev/null fi shift ;; clear) - if [ "${TARGET}" = 'ALL' ]; then - for JAIL_NAME in $(ls "${bastille_jailsdir}" | sed "s/\n//g"); do - echo "${JAIL_NAME} redirects:" - pfctl -a "rdr/${JAIL_NAME}" -Fn - done + if [ "${OPTION_IF}" -eq 1 ] || [ "${OPTION_SRC}" -eq 1 ] || [ "${OPTION_DST}" -eq 1 ] || [ "${OPTION_INET_TYPE}" -eq 1 ];then + error_exit "Command \"${1}\" cannot be used with options." + elif [ -n "${2}" ]; then + usage else check_jail_validity - pfctl -a "rdr/${JAIL_NAME}" -Fn + echo "${TARGET} redirects:" + pfctl -a "rdr/${TARGET}" -Fn fi shift ;; + reset) + if [ "${OPTION_IF}" -eq 1 ] || [ "${OPTION_SRC}" -eq 1 ] || [ "${OPTION_DST}" -eq 1 ] || [ "${OPTION_INET_TYPE}" -eq 1 ];then + error_exit "Command \"${1}\" cannot be used with options." + elif [ -n "${2}" ]; then + usage + else + check_jail_validity + echo "${TARGET} redirects:" + pfctl -a "rdr/${TARGET}" -Fn + if rm -f "${bastille_jailsdir}/${_jail}/rdr.conf"; then + info "[${TARGET}]: rdr.conf removed" + fi + fi + shift + ;; tcp|udp) - if [ $# -lt 3 ]; then + if [ "$#" -lt 3 ]; then usage - elif [ $# -eq 3 ]; then + elif [ "${OPTION_SRC}" -eq 1 ] || [ "${OPTION_DST}" -eq 1 ] && [ "${OPTION_INET_TYPE}" -ne 1 ];then + error_exit "[-t|--type] must be set when using [-s|--source] or [-d|--destination]" + elif [ "$#" -eq 3 ]; then check_jail_validity - persist_rdr_rule $1 $2 $3 - load_rdr_rule $1 $2 $3 - shift 3 + validate_rdr_rule $RDR_IF $RDR_SRC $RDR_DST $1 $2 $3 + persist_rdr_rule $RDR_INET $RDR_IF $RDR_SRC $RDR_DST $1 $2 $3 + load_rdr_rule $RDR_INET $RDR_IF $RDR_SRC $RDR_DST $1 $2 $3 + shift "$#" else - case "$4" in + case "${4}" in log) proto=$1 host_port=$2 jail_port=$3 shift 3 - if [ $# -gt 3 ]; then + if [ "$#" -gt 3 ]; then for last in "$@"; do true done - if [ "$2" = "(" ] && [ "$last" = ")" ] ; then + if [ "${2}" = "(" ] && [ "${last}" = ")" ] ; then check_jail_validity - persist_rdr_log_rule "$proto" "$host_port" "$jail_port" "$@" - load_rdr_log_rule "$proto" "$host_port" "$jail_port" "$@" + validate_rdr_rule $RDR_IF $RDR_SRC $RDR_DST $1 $2 $3 + persist_rdr_log_rule $RDR_INET $RDR_IF $RDR_SRC $RDR_DST $proto $host_port $jail_port "$@" + load_rdr_log_rule $RDR_INET $RDR_IF $RDR_SRC $RDR_DST $proto $host_port $jail_port "$@" shift $# else usage fi elif [ $# -eq 1 ]; then check_jail_validity - persist_rdr_log_rule $proto $host_port $jail_port "$@" - load_rdr_log_rule $proto $host_port $jail_port "$@" + validate_rdr_rule $RDR_IF $RDR_SRC $RDR_DST $1 $2 $3 + persist_rdr_log_rule $RDR_INET $RDR_IF $RDR_SRC $RDR_DST $proto $host_port $jail_port "$@" + load_rdr_log_rule $RDR_INET $RDR_IF $RDR_SRC $RDR_DST $proto $host_port $jail_port "$@" shift 1 else usage @@ -212,7 +368,26 @@ while [ $# -gt 0 ]; do fi ;; *) - usage + if [ "${1}" = "dual" ] || [ "${1}" = "ipv4" ] || [ "${1}" = "ipv6" ]; then + RDR_INET="${1}" + else + usage + fi + if [ "$#" -eq 7 ] && { [ "${5}" = "tcp" ] || [ "${5}" = "udp" ]; } then + check_jail_validity + validate_rdr_rule $RDR_IF $RDR_SRC $RDR_DST $1 $2 $3 + persist_rdr_rule "$@" + load_rdr_rule "$@" + shift "$#" + elif [ "$#" -ge 8 ] && [ "${8}" = "log" ]; then + check_jail_validity + validate_rdr_rule $RDR_IF $RDR_SRC $RDR_DST $1 $2 $3 + persist_rdr_log_rule "$@" + load_rdr_log_rule "$@" + shift "$#" + else + usage + fi ;; esac -done +done \ No newline at end of file diff --git a/usr/local/share/bastille/rename.sh b/usr/local/share/bastille/rename.sh index 20fb80212..b5c380a82 100644 --- a/usr/local/share/bastille/rename.sh +++ b/usr/local/share/bastille/rename.sh @@ -34,11 +34,62 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille rename TARGET NEW_NAME" + error_notify "Usage: bastille rename [option(s)] TARGET NEW_NAME" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. + +EOF + exit 1 } +# Handle options. +AUTO=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" + esac + done + shift + ;; + *) + break + ;; + esac +done + +if [ "$#" -ne 2 ]; then + usage +fi + +TARGET="${1}" +NEWNAME="${2}" + +bastille_root_check +set_target_single "${TARGET}" +check_target_is_stopped "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${TARGET}" +else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." +fi + validate_name() { - local NAME_VERIFY=${NEWNAME} + local NAME_VERIFY="${NEWNAME}" local NAME_SANITY="$(echo "${NAME_VERIFY}" | tr -c -d 'a-zA-Z0-9-_')" if [ -n "$(echo "${NAME_SANITY}" | awk "/^[-_].*$/" )" ]; then error_exit "Container names may not begin with (-|_) characters!" @@ -47,21 +98,6 @@ validate_name() { fi } -# Handle special-case commands first -case "$1" in -help|-h|--help) - usage - ;; -esac - -if [ $# -ne 1 ]; then - usage -fi - -bastille_root_check - -NEWNAME="${1}" - update_jailconf() { # Update jail.conf JAIL_CONFIG="${bastille_jailsdir}/${NEWNAME}/jail.conf" @@ -72,21 +108,12 @@ update_jailconf() { sed -i '' "s|path.*=.*;|path = ${bastille_jailsdir}/${NEWNAME}/root;|" "${JAIL_CONFIG}" sed -i '' "s|mount.fstab.*=.*;|mount.fstab = ${bastille_jailsdir}/${NEWNAME}/fstab;|" "${JAIL_CONFIG}" sed -i '' "s|${TARGET}.*{|${NEWNAME} {|" "${JAIL_CONFIG}" - # Rename vnet interface - sed -i '' "/vnet.interface/s|_${TARGET}\";|_${NEWNAME}\";|" "${JAIL_CONFIG}" - sed -i '' "/ifconfig/s|_${TARGET}|_${NEWNAME}|" "${JAIL_CONFIG}" + # update vnet config + sed -i '' "s|vnet host interface for Bastille jail ${TARGET}|vnet host interface for Bastille jail ${NEWNAME}|g" "${JAIL_CONFIG}" fi fi } -update_fstab() { - # Update fstab to use the new name - FSTAB_CONFIG="${bastille_jailsdir}/${NEWNAME}/fstab" - if [ -f "${FSTAB_CONFIG}" ]; then - sed -i '' "s|${bastille_jailsdir}/${TARGET}|${bastille_jailsdir}/${NEWNAME}|g" "${FSTAB_CONFIG}" - fi -} - change_name() { # Attempt container name change info "Attempting to rename '${TARGET}' to ${NEWNAME}..." @@ -124,26 +151,29 @@ change_name() { fi fi - # Update jail configuration files accordingly + # Update jail conf files update_jailconf - update_fstab + update_fstab "${TARGET}" "${NEWNAME}" # Check exit status and notify if [ "$?" -ne 0 ]; then error_exit "An error has occurred while attempting to rename '${TARGET}'." else info "Renamed '${TARGET}' to '${NEWNAME}' successfully." + if [ "${AUTO}" -eq 1 ]; then + bastille start "${NEWNAME}" + fi fi } -## validate jail name +# Validate NEW_NAME if [ -n "${NEWNAME}" ]; then validate_name fi -## check if a jail already exists with the new name +# Check if a jail already exists with NEW_NAME if [ -d "${bastille_jailsdir}/${NEWNAME}" ]; then error_exit "Jail: ${NEWNAME} already exists." fi -change_name +change_name \ No newline at end of file diff --git a/usr/local/share/bastille/service.sh b/usr/local/share/bastille/service.sh index 76d1edadc..149b936ee 100644 --- a/usr/local/share/bastille/service.sh +++ b/usr/local/share/bastille/service.sh @@ -31,26 +31,68 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. . /usr/local/share/bastille/common.sh +. /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille service TARGET service_name action" + error_notify "Usage: bastille service [options(s)] TARGET SERVICE_NAME ACTION" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# 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 + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -lt 1 ] || [ $# -gt 2 ]; then +if [ "$#" -ne 3 ]; then usage fi +TARGET="${1}" +shift + bastille_root_check +set_target "${TARGET}" for _jail in ${JAILS}; do info "[${_jail}]:" + check_target_is_running "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${_jail}" + else + error_notify "Jail is not running." + error_continue "Use [-a|--auto] to auto-start the jail." + fi jexec -l "${_jail}" /usr/sbin/service "$@" - echo -done +done \ No newline at end of file diff --git a/usr/local/share/bastille/setup.sh b/usr/local/share/bastille/setup.sh index 020d2cf4b..e1ee6dd6b 100644 --- a/usr/local/share/bastille/setup.sh +++ b/usr/local/share/bastille/setup.sh @@ -40,12 +40,15 @@ usage() { } # Check for too many args -if [ $# -gt 1 ]; then +if [ "$#" -gt 1 ]; then usage fi # Configure bastille loopback network interface configure_network() { + if ifconfig -l | grep -qo ${bastille_network_loopback}; then + error_notify "Error: Network has already been configured." + fi info "Configuring ${bastille_network_loopback} loopback interface" sysrc cloned_interfaces+=lo1 sysrc ifconfig_lo1_name="${bastille_network_loopback}" @@ -56,6 +59,9 @@ configure_network() { configure_vnet() { info "Configuring bridge interface" + if ifconfig -l | grep -qo bridge1; then + error_notify "Error: VNET has already been configured." + fi sysrc cloned_interfaces+=bridge1 sysrc ifconfig_bridge1_name=bastille1 @@ -82,8 +88,8 @@ configure_pf() { if [ ! -f "${bastille_pf_conf}" ]; then # shellcheck disable=SC3043 local ext_if - ext_if=$(netstat -rn | awk '/default/ {print $4}' | head -n1) - info "Determined default network interface: ($ext_if)" + ext_if="$(netstat -rn | awk '/default/ {print $4}' | head -n1)" + info "Determined default network interface: \(${ext_if}\)" info "${bastille_pf_conf} does not exist: creating..." ## creating pf.conf @@ -107,7 +113,7 @@ EOF sysrc pf_enable=YES warn "pf ruleset created, please review ${bastille_pf_conf} and enable it using 'service pf start'." else - error_exit "${bastille_pf_conf} already exists. Exiting." + error_notify "${bastille_pf_conf} already exists. Exiting." fi } @@ -115,6 +121,8 @@ fi configure_zfs() { if [ ! "$(kldstat -m zfs)" ]; then info "ZFS module not loaded; skipping..." + elif [ -n "$(sysrc -f ${bastille_config} bastille_zfs_zpool)" ] && [ -n "$(sysrc -f ${bastille_config} bastille_zfs_enable)" ]; then + error_notify "Error: ZFS has already been configured." else ## attempt to determine bastille_zroot from `zpool list` bastille_zroot=$(zpool list | grep -v NAME | awk '{print $1}') @@ -129,7 +137,7 @@ configure_zfs() { } # Run all base functions (w/o vnet) if no args -if [ $# -eq 0 ]; then +if [ "$#" -eq 0 ]; then sysrc bastille_enable=YES configure_network configure_pf @@ -137,18 +145,13 @@ if [ $# -eq 0 ]; then fi # Handle special-case commands first. -case "$1" in +case "${1}" in help|-h|--help) usage ;; pf|firewall) configure_pf ;; -bastille0) - # TODO remove in future release 0.13 - warn "'bastille setup bastille0' will be deprecated in the next 0.13 version." - configure_network - ;; network|loopback) configure_network ;; diff --git a/usr/local/share/bastille/start.sh b/usr/local/share/bastille/start.sh index 375d49c22..318acd7b9 100644 --- a/usr/local/share/bastille/start.sh +++ b/usr/local/share/bastille/start.sh @@ -34,80 +34,124 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille start TARGET" + error_notify "Usage: bastille start [option(s)] TARGET" + cat << EOF + Options: + + -v | --verbose Print every action on jail start. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# Handle options. +OPTION="" +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -v|--verbose) + OPTION="-v" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + v) OPTION="-v" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -gt 1 ] || [ $# -lt 1 ]; then +if [ "$#" -ne 1 ]; then usage fi -bastille_root_check - TARGET="${1}" -shift -if [ "${TARGET}" = 'ALL' ]; then - JAILS=$(bastille list jails) -fi -if [ "${TARGET}" != 'ALL' ]; then - JAILS=$(bastille list jails | awk "/^${TARGET}$/") - ## check if exist - if [ ! -d "${bastille_jailsdir}/${TARGET}" ]; then - error_exit "[${TARGET}]: Not found." - fi -fi +bastille_root_check +set_target "${TARGET}" for _jail in ${JAILS}; do - ## test if running - if [ "$(/usr/sbin/jls name | awk "/^${_jail}$/")" ]; then - error_notify "[${_jail}]: Already started." - ## test if not running - elif [ ! "$(/usr/sbin/jls name | awk "/^${_jail}$/")" ]; then - # Verify that the configured interface exists. -- cwells - if [ "$(bastille config $_jail get vnet)" != 'enabled' ]; then - _interface=$(bastille config $_jail get interface) - if ! ifconfig | grep "^${_interface}:" >/dev/null; then - error_notify "Error: ${_interface} interface does not exist." - continue - fi - fi + info "[${_jail}]:" + check_target_is_stopped "${_jail}" || error_continue "Jail is already running." - ## warn if matching configured (but not online) ip4.addr, ignore if there's no ip4.addr entry - _ip4=$(bastille config "${_jail}" get ip4.addr) - if [ "${_ip4}" != "not set" ]; then - if ifconfig | grep -wF "${_ip4}" >/dev/null; then - error_notify "Error: IP address (${_ip4}) already in use." - continue - fi - ## add ip4.addr to firewall table - pfctl -q -t "${bastille_network_pf_table}" -T add "${_ip4}" + # Validate interfaces and add IPs to firewall table + if [ "$(bastille config ${_jail} get vnet)" != 'enabled' ]; then + _ip4_interfaces="$(bastille config ${_jail} get ip4.addr | sed 's/,/ /g')" + _ip6_interfaces="$(bastille config ${_jail} get ip6.addr | sed 's/,/ /g')" + # IP4 + if [ "${_ip4_interfaces}" != "not set" ]; then + for _interface in ${_ip4_interfaces}; do + if echo "${_interface}" | grep -q "|"; then + _if="$(echo ${_interface} 2>/dev/null | awk -F"|" '{print $1}')" + _ip="$(echo ${_interface} 2>/dev/null | awk -F"|" '{print $2}' | sed -E 's#/[0-9]+$##g')" + else + _if="$(bastille config ${_jail} get interface)" + _ip="$(echo ${_interface} | sed -E 's#/[0-9]+$##g')" + fi + if ifconfig | grep "^${_if}:" >/dev/null; then + if ifconfig | grep -qwF "${_ip}"; then + error_continue "Error: IP address (${_ip}) already in use." + else + pfctl -q -t "${bastille_network_pf_table}" -T add "${_ip}" + fi + else + error_continue "Error: ${_if} interface does not exist." + fi + done fi + # IP6 + if [ "${_ip6_interfaces}" != "not set" ]; then + for _interface in ${_ip6_interfaces}; do + if echo "${_interface}" | grep -q "|"; then + _if="$(echo ${_interface} 2>/dev/null | awk -F"|" '{print $1}')" + _ip="$(echo ${_interface} 2>/dev/null | awk -F"|" '{print $2}' | sed -E 's#/[0-9]+$##g')" + else + _if="$(bastille config ${_jail} get interface)" + _ip="$(echo ${_interface} | sed -E 's#/[0-9]+$##g')" + fi + if ifconfig | grep "^${_if}:" >/dev/null; then + if ifconfig | grep -qwF "${_ip}"; then + error_continue "Error: IP address (${_ip}) already in use." + else + pfctl -q -t "${bastille_network_pf_table}" -T add "${_ip}" + fi + else + error_continue "Error: ${_if} interface does not exist." + fi + done + fi + fi - ## start the container - info "[${_jail}]:" - jail -f "${bastille_jailsdir}/${_jail}/jail.conf" -c "${_jail}" + # Start jail + jail ${OPTION} -f "${bastille_jailsdir}/${_jail}/jail.conf" -c "${_jail}" - ## add rctl limits - if [ -s "${bastille_jailsdir}/${_jail}/rctl.conf" ]; then - while read _limits; do - rctl -a "${_limits}" - done < "${bastille_jailsdir}/${_jail}/rctl.conf" - fi + # Add rctl limits + if [ -s "${bastille_jailsdir}/${_jail}/rctl.conf" ]; then + while read _limits; do + rctl -a "${_limits}" + done < "${bastille_jailsdir}/${_jail}/rctl.conf" + fi - ## add rdr rules - if [ -s "${bastille_jailsdir}/${_jail}/rdr.conf" ]; then - while read _rules; do - bastille rdr "${_jail}" ${_rules} - done < "${bastille_jailsdir}/${_jail}/rdr.conf" - fi + # Add rdr rules + if [ -s "${bastille_jailsdir}/${_jail}/rdr.conf" ]; then + while read _rules; do + bastille rdr ${_jail} ${_rules} + done < "${bastille_jailsdir}/${_jail}/rdr.conf" fi - echo -done +done \ No newline at end of file diff --git a/usr/local/share/bastille/stop.sh b/usr/local/share/bastille/stop.sh index efec51e11..718c87144 100644 --- a/usr/local/share/bastille/stop.sh +++ b/usr/local/share/bastille/stop.sh @@ -34,52 +34,104 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille stop TARGET" + error_notify "Usage: bastille stop [option(s)] TARGET" + cat << EOF + Options: + + -v | --verbose Print every action on jail stop. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# Handle options. +OPTION="" +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -v|--verbose) + OPTION="-v" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + v) OPTION="-v" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -ne 0 ]; then +if [ "$#" -ne 1 ]; then usage fi +TARGET="${1}" + bastille_root_check +set_target "${TARGET}" for _jail in ${JAILS}; do - ## test if running - if [ "$(/usr/sbin/jls name | awk "/^${_jail}$/")" ]; then - ## Capture ip4.addr address while still running - _ip4="$(bastille config ${_jail} get ip4.addr)" - # Check if pfctl is present - if [ "${_ip4}" != "not set" ]; then - if [ "$(bastille rdr ${_jail} list)" ]; then - bastille rdr ${_jail} clear + info "[${_jail}]:" + check_target_is_running "${_jail}" || error_continue "Jail is already stopped." + + # Remove RDR rules + if [ "$(bastille config ${_jail} get vnet)" != "enabled" ]; then + _ip4="$(bastille config ${_jail} get ip4.addr | sed 's/,/ /g')" + _ip6="$(bastille config ${_jail} get ip6.addr | sed 's/,/ /g')" + if [ "${_ip4}" != "not set" ] || [ "${_ip6}" != "not set" ]; then + if which -s pfctl; then + if [ "$(bastille rdr ${_jail} list)" ]; then + bastille rdr "${_jail}" clear + fi fi fi + fi - ## remove rctl limits - if [ -s "${bastille_jailsdir}/${_jail}/rctl.conf" ]; then - while read _limits; do - rctl -r "${_limits}" - done < "${bastille_jailsdir}/${_jail}/rctl.conf" - fi + # Remove rctl limits + if [ -s "${bastille_jailsdir}/${_jail}/rctl.conf" ]; then + while read _limits; do + rctl -r "${_limits}" + done < "${bastille_jailsdir}/${_jail}/rctl.conf" + fi - ## stop container - info "[${_jail}]:" - jail -f "${bastille_jailsdir}/${_jail}/jail.conf" -r "${_jail}" + # Stop jail + jail ${OPTION} -f "${bastille_jailsdir}/${_jail}/jail.conf" -r "${_jail}" - ## remove (captured above) ip4.addr from firewall table - if [ -n "${bastille_network_loopback}" ] && [ "${_ip4}" != "not set" ]; then - if grep -qw "interface.*=.*${bastille_network_loopback}" "${bastille_jailsdir}/${_jail}/jail.conf"; then - pfctl -q -t "${bastille_network_pf_table}" -T delete "${_ip4}" + # Remove (captured above) IPs from firewall table + if [ "${_ip4}" != "not set" ]; then + for _ip in ${_ip4}; do + if echo "${_ip}" | grep -q "|"; then + _ip="$(echo ${_ip} | awk -F"|" '{print $2}' | sed -E 's#/[0-9]+$##g')" + else + _ip="$(echo ${_ip} | sed -E 's#/[0-9]+$##g')" fi - fi + pfctl -q -t "${bastille_network_pf_table}" -T delete "${_ip}" + done fi - echo -done + if [ "${_ip6}" != "not set" ]; then + for _ip in ${_ip6}; do + if echo "${_ip}" | grep -q "|"; then + _ip="$(echo ${_ip} | awk -F"|" '{print $2}' | sed -E 's#/[0-9]+$##g')" + else + _ip="$(echo ${_ip} | sed -E 's#/[0-9]+$##g')" + fi + pfctl -q -t "${bastille_network_pf_table}" -T delete "${_ip}" + done + fi +done \ No newline at end of file diff --git a/usr/local/share/bastille/sysrc.sh b/usr/local/share/bastille/sysrc.sh index baf0d7ae1..ac76883aa 100644 --- a/usr/local/share/bastille/sysrc.sh +++ b/usr/local/share/bastille/sysrc.sh @@ -31,26 +31,68 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. . /usr/local/share/bastille/common.sh +. /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille sysrc TARGET args" + error_notify "Usage: bastille sysrc [option(s)] TARGET ARGS" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# 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 + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -lt 1 ]; then +if [ "$#" -lt 2 ]; then usage fi +TARGET="${1}" +shift + bastille_root_check +set_target "${TARGET}" for _jail in ${JAILS}; do info "[${_jail}]:" + check_target_is_running "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${_jail}" + else + error_notify "Jail is not running." + error_continue "Use [-a|--auto] to auto-start the jail." + fi jexec -l "${_jail}" /usr/sbin/sysrc "$@" - echo -e "${COLOR_RESET}" -done +done \ No newline at end of file diff --git a/usr/local/share/bastille/tags.sh b/usr/local/share/bastille/tags.sh index 4457f03d1..bfe30efd4 100644 --- a/usr/local/share/bastille/tags.sh +++ b/usr/local/share/bastille/tags.sh @@ -35,31 +35,45 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_notify "Usage: bastille tags TARGET add tag1[,tag2,...]" - error_notify " bastille tags TARGET delete tag1[,tag2,...]" - error_notify " bastille tags TARGET list [tag]" - echo -e "Example: bastille tags JAILNAME add database,mysql" - echo -e " bastille tags JAILNAME delete mysql" - echo -e " bastille tags ALL list" - echo -e " bastille tags ALL list mysql" + error_notify "Usage: bastille tags TARGET [add|delete|list] [tag1,tag2]" + cat << EOF + Options: + + -x | --debug Enable debug mode. + +EOF exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# Handle options. +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + error_exit "Unknown Option: \"${1}\"" + ;; + *) + break + ;; + esac +done -if [ $# -lt 1 ] || [ $# -gt 2 ]; then +if [ $# -lt 2 ] || [ $# -gt 3 ]; then usage fi -bastille_root_check +TARGET="${1}" +ACTION="${2}" +TAGS="${3}" -ACTION="${1}" -TAGS="${2}" +bastille_root_check +set_target "${TARGET}" for _jail in ${JAILS}; do bastille_jail_tags="${bastille_jailsdir}/${_jail}/tags" @@ -102,5 +116,4 @@ for _jail in ${JAILS}; do usage ;; esac -done - +done \ No newline at end of file diff --git a/usr/local/share/bastille/template.sh b/usr/local/share/bastille/template.sh index 3aed8664c..829fd9ea0 100644 --- a/usr/local/share/bastille/template.sh +++ b/usr/local/share/bastille/template.sh @@ -34,9 +34,18 @@ . /usr/local/etc/bastille/bastille.conf bastille_usage() { - error_exit "Usage: bastille template TARGET|--convert project/template" + error_notify "Usage: bastille template [option(s)] TARGET [--convert|project/template]" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. + +EOF + exit 1 } + post_command_hook() { _jail=$1 _cmd=$2 @@ -98,7 +107,7 @@ render() { if [ -d "${_file_path}" ]; then # Recursively render every file in this directory. -- cwells echo "Rendering Directory: ${_file_path}" find "${_file_path}" \( -type d -name .git -prune \) -o -type f - find "${_file_path}" \( -type d -name .git -prune \) -o -type f -print0 | eval "xargs -0 sed -i '' ${ARG_REPLACEMENTS}" + find "${_file_path}" \( -type d -name .git -prune \) -o -type f -print0 | "$(eval "xargs -0 sed -i '' ${ARG_REPLACEMENTS}")" elif [ -f "${_file_path}" ]; then echo "Rendering File: ${_file_path}" eval "sed -i '' ${ARG_REPLACEMENTS} '${_file_path}'" @@ -107,32 +116,57 @@ render() { fi } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - bastille_usage - ;; -esac +# 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 + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -lt 1 ]; then +if [ $# -lt 2 ]; then bastille_usage fi -bastille_root_check - -## global variables -TEMPLATE="${1}" +TARGET="${1}" +TEMPLATE="${2}" bastille_template=${bastille_templatesdir}/${TEMPLATE} if [ -z "${HOOKS}" ]; then HOOKS='LIMITS INCLUDE PRE FSTAB PF PKG OVERLAY CONFIG SYSRC SERVICE CMD RENDER' fi +bastille_root_check +set_target "${TARGET}" + # Special case conversion of hook-style template files into a Bastillefile. -- cwells if [ "${TARGET}" = '--convert' ]; then if [ -d "${TEMPLATE}" ]; then # A relative path was provided. -- cwells - cd "${TEMPLATE}" || error_exit "Failed to change to directory: ${TEMPLATE}" + cd "${TEMPLATE}" || error_exit "Could not cd to ${TEMPLATE}" elif [ -d "${bastille_template}" ]; then - cd "${bastille_template}" || error_exit "Failed to change to directory: ${TEMPLATE}" + cd "${bastille_template}" || error_exit "Could not cd to ${bastille_template}" else error_exit "Template not found: ${TEMPLATE}" fi @@ -226,17 +260,30 @@ if [ -n "${ARG_FILE}" ] && [ ! -f "${ARG_FILE}" ]; then fi for _jail in ${JAILS}; do + info "[${_jail}]:" - info "Applying template: ${TEMPLATE}..." - - ## jail-specific variables. - bastille_jail_path=$(/usr/sbin/jls -j "${_jail}" path) - if [ "$(bastille config $TARGET get vnet)" != 'enabled' ]; then - _jail_ip=$(/usr/sbin/jls -j "${_jail}" ip4.addr 2>/dev/null) - _jail_ip6=$(/usr/sbin/jls -j "${_jail}" ip6.addr 2>/dev/null) - if [ -z "${_jail_ip}" ] || [ "${_jail_ip}" = "-" ]; then - error_notify "Jail IP not found: ${_jail}" - _jail_ip='' # In case it was -. -- cwells + + check_target_is_running "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${_jail}" + else + error_notify "Jail is not running." + error_continue "Use [-a|--auto] to auto-start the jail." + fi + + echo "Applying template: ${TEMPLATE}..." + + # Get default IPv4 and IPv6 addresses + bastille_jail_path="${bastille_jailsdir}/${_jail}/root" + if [ "$(bastille config ${_jail} get vnet)" != 'enabled' ]; then + _ip4_interfaces="$(bastille config ${_jail} get ip4.addr | sed 's/,/ /g')" + _ip6_interfaces="$(bastille config ${_jail} get ip6.addr | sed 's/,/ /g')" + # IP4 + if [ "${_ip4_interfaces}" != "not set" ]; then + _jail_ip="$(echo ${_ip4_interface} 2>/dev/null | awk -F"|" '{print $2}')" + fi + # IP6 + if [ "${_ip6_interfaces}" != "not set" ]; then + _jail_ip="$(echo ${_ip6_interface} 2>/dev/null | awk -F"|" '{print $2}')" fi fi @@ -306,7 +353,7 @@ for _jail in ${JAILS}; do cp|copy) _cmd='cp' # Convert relative "from" path into absolute path inside the template directory. -- cwells - if [ "${_args%"${_args#?}"}" != '/' ] && [ "${_args%"${_args#??}"}" != '"/' ]; then + if [ "${_args%${_args#?}}" != '/' ] && [ "${_args%${_args#??}}" != '"/' ]; then _args="${bastille_template}/${_args}" fi ;; @@ -370,9 +417,9 @@ for _jail in ${JAILS}; do info "[${_jail}]:${_hook} -- START" if [ "${_hook}" = 'CMD' ] || [ "${_hook}" = 'PRE' ]; then - bastille cmd "${_jail}" /bin/sh < "${bastille_template}/${_hook}" || error_exit "Failed to execute command." + bastille cmd "${_jail}" /bin/sh < "${bastille_template}/${_hook}" || exit 1 elif [ "${_hook}" = 'PKG' ]; then - bastille pkg "${_jail}" install -y "$(cat "${bastille_template}/PKG")" || error_exit "Failed to install packages." + bastille pkg "${_jail}" install -y "$(cat "${bastille_template}/PKG")" || exit 1 bastille pkg "${_jail}" audit -F else while read _line; do @@ -382,7 +429,7 @@ for _jail in ${JAILS}; do # Replace "arg" variables in this line with the provided values. -- cwells _line=$(echo "${_line}" | eval "sed ${ARG_REPLACEMENTS}") eval "_args=\"${_args_template}\"" - bastille "${_cmd}" "${_jail}" "${_args}" || error_exit "Failed to execute command." + bastille "${_cmd}" "${_jail}" "${_args}" || exit 1 done < "${bastille_template}/${_hook}" fi info "[${_jail}]:${_hook} -- END" @@ -391,5 +438,5 @@ for _jail in ${JAILS}; do done info "Template applied: ${TEMPLATE}" - echo -done + +done \ No newline at end of file diff --git a/usr/local/share/bastille/templates/default/base/Bastillefile b/usr/local/share/bastille/templates/default/base/Bastillefile index 7418fba64..7d99dfd42 100644 --- a/usr/local/share/bastille/templates/default/base/Bastillefile +++ b/usr/local/share/bastille/templates/default/base/Bastillefile @@ -8,4 +8,4 @@ SYSRC sendmail_outbound_enable="NO" SYSRC sendmail_msp_queue_enable="NO" SYSRC cron_flags="-J 60" -CP "${HOST_RESOLV_CONF}" etc/resolv.conf +CP "${HOST_RESOLV_CONF}" /etc/resolv.conf diff --git a/usr/local/share/bastille/top.sh b/usr/local/share/bastille/top.sh index bd82d1530..8e02e706c 100644 --- a/usr/local/share/bastille/top.sh +++ b/usr/local/share/bastille/top.sh @@ -38,25 +38,37 @@ usage() { cat << EOF Options: - -f | --force -- Start the jail if it is stopped. + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. EOF exit 1 } # Handle options. -FORCE=0 +AUTO=0 while [ "$#" -gt 0 ]; do case "${1}" in -h|--help|help) usage ;; - -f|--force) - FORCE=1 + -a|--auto) + AUTO=1 + shift + ;; + -x|--debug) + enable_debug shift ;; -*) - error_exit "Unknown option: \"${1}\"" + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift ;; *) break @@ -74,10 +86,10 @@ bastille_root_check set_target_single "${TARGET}" info "[${TARGET}]:" -check_target_is_running "${TARGET}" || if [ "${FORCE}" -eq 1 ]; then +check_target_is_running "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then bastille start "${TARGET}" else error_notify "Jail is not running." - error_continue "Use [-f|--force] to force start the jail." + error_continue "Use [-a|--auto] to auto-start the jail." fi -jexec -l "${TARGET}" /usr/bin/top +jexec -l "${TARGET}" /usr/bin/top \ No newline at end of file diff --git a/usr/local/share/bastille/umount.sh b/usr/local/share/bastille/umount.sh index f5d689699..705d29c8d 100644 --- a/usr/local/share/bastille/umount.sh +++ b/usr/local/share/bastille/umount.sh @@ -34,15 +34,34 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille umount TARGET JAIL_PATH" + error_notify "Usage: bastille umount [option(s)] TARGET JAIL_PATH" + cat << EOF + Options: + + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "${1}" in - help|-h|--help) - usage - ;; -esac +# Handle options. +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + error_exit "Unknown option: \"${1}\"" + ;; + *) + break + ;; + esac +done if [ "$#" -ne 2 ]; then usage @@ -87,4 +106,4 @@ for _jail in ${JAILS}; do echo "Unmounted: ${_jailpath}" -done +done \ No newline at end of file diff --git a/usr/local/share/bastille/update.sh b/usr/local/share/bastille/update.sh index 85d632c03..d42ec0dcd 100644 --- a/usr/local/share/bastille/update.sh +++ b/usr/local/share/bastille/update.sh @@ -34,39 +34,62 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille update [release|container|template] | [force]" -} + error_notify "Usage: bastille update [option(s)] [RELEASE|JAIL|TEMPLATE]" + cat << EOF + Options: -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac + -a | --auto Auto mode. Start/stop jail(s) if required. + -f | --force Force upgrade a release. + -x | --debug Enable debug mode. + +EOF + exit 1 +} if [ $# -gt 2 ] || [ $# -lt 1 ]; then usage fi -bastille_root_check +# Handle options. +OPTION="" +AUTO=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -f|--force) + OPTION="-F" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + f) OPTION="-F" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done TARGET="${1}" -OPTION="${2}" - -# Handle options -case "${OPTION}" in - -f|--force) - OPTION="-F" - ;; - *) - OPTION= - ;; -esac - -# Check for unsupported actions -if [ "${TARGET}" = "ALL" ]; then - error_exit "Batch upgrade is unsupported." -fi + +bastille_root_check if [ -f "/bin/midnightbsd-version" ]; then echo -e "${COLOR_RED}Not yet supported on MidnightBSD.${COLOR_RESET}" @@ -86,28 +109,27 @@ arch_check() { jail_check() { # Check if the jail is thick and is running - if [ ! "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - error_exit "[${TARGET}]: Not started. See 'bastille start ${TARGET}'." - else - if grep -qw "${bastille_jailsdir}/${TARGET}/root/.bastille" "${bastille_jailsdir}/${TARGET}/fstab"; then - error_exit "${TARGET} is not a thick container." - fi + set_target_single "${TARGET}" + check_target_is_running "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${TARGET}" + else + error_notify "Jail is not running." + error_continue "Use [-a|--auto] to auto-start the jail." + fi + if grep -qw "${bastille_jailsdir}/${TARGET}/root/.bastille" "${bastille_jailsdir}/${TARGET}/fstab"; then + error_exit "${TARGET} is not a thick container." fi } jail_update() { # Update a thick container - if [ -d "${bastille_jailsdir}/${TARGET}" ]; then - jail_check - CURRENT_VERSION=$(/usr/sbin/jexec -l "${TARGET}" freebsd-version 2>/dev/null) - if [ -z "${CURRENT_VERSION}" ]; then - error_exit "Can't determine '${TARGET}' version." - else - env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_jailsdir}/${TARGET}/root" \ - fetch install --currently-running "${CURRENT_VERSION}" - fi + jail_check + CURRENT_VERSION=$(/usr/sbin/jexec -l "${TARGET}" freebsd-version 2>/dev/null) + if [ -z "${CURRENT_VERSION}" ]; then + error_exit "Can't determine '${TARGET}' version." else - error_exit "${TARGET} not found. See 'bastille bootstrap'." + env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_jailsdir}/${TARGET}/root" \ + fetch install --currently-running "${CURRENT_VERSION}" fi } @@ -145,10 +167,10 @@ template_update() { templates_update() { # Update all templates _updated_templates=0 - if [ -d "${bastille_templatesdir}" ]; then - # shellcheck disable=SC2045 - for _template_path in $(ls -d "${bastille_templatesdir}"/*/*); do - if [ -d "$_template_path"/.git ]; then + if [ -d ${bastille_templatesdir} ]; then + # shellcheck disable=SC2045 + for _template_path in $(ls -d ${bastille_templatesdir}/*/*); do + if [ -d $_template_path/.git ]; then BASTILLE_TEMPLATE=$(echo "$_template_path" | awk -F / '{ print $(NF-1) "/" $NF }') template_update @@ -175,4 +197,4 @@ elif echo "${TARGET}" | grep -q "[0-9]\{2\}.[0-9]-RELEASE"; then release_update else jail_update -fi +fi \ No newline at end of file diff --git a/usr/local/share/bastille/upgrade.sh b/usr/local/share/bastille/upgrade.sh index 5aa06905c..92f9b2e5a 100644 --- a/usr/local/share/bastille/upgrade.sh +++ b/usr/local/share/bastille/upgrade.sh @@ -34,31 +34,64 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille upgrade release newrelease | target newrelease | target install | [force]" -} + error_notify "Usage: bastille upgrade [option(s)] [RELEASE NEW_RELEASE install] [TARGET NEW_RELEASE install]" + cat << EOF + Options: -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac + -a | --auto Auto mode. Start/stop jail(s) if required. + -f | --force Force upgrade a release. + -x | --debug Enable debug mode. + +EOF + exit 1 +} -if [ $# -gt 3 ] || [ $# -lt 2 ]; then +# Handle options. +OPTION="" +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -f|--force) + OPTION="-F" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + f) OPTION="-F" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done + +if [ $# -lt 2 ] || [ $# -gt 3 ]; then usage fi -bastille_root_check - -TARGET="$1" -NEWRELEASE="$2" -OPTION="$3" +TARGET="${1}" +NEWRELEASE="${2}" -# Check for unsupported actions -if [ "${TARGET}" = "ALL" ]; then - error_exit "Batch upgrade is unsupported." -fi +bastille_root_check +# Check for unsupported actions if [ -f "/bin/midnightbsd-version" ]; then echo -e "${COLOR_RED}Not yet supported on MidnightBSD.${COLOR_RESET}" exit 1 @@ -68,24 +101,17 @@ if freebsd-version | grep -qi HBSD; then error_exit "Not yet supported on HardenedBSD." fi -# Handle options -case "${OPTION}" in - -f|--force) - OPTION="-F" - ;; - *) - OPTION= - ;; -esac - jail_check() { # Check if the jail is thick and is running - if [ ! "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - error_exit "[${TARGET}]: Not started. See 'bastille start ${TARGET}'." - else - if grep -qw "${bastille_jailsdir}/${TARGET}/root/.bastille" "${bastille_jailsdir}/${TARGET}/fstab"; then - error_exit "${TARGET} is not a thick container." - fi + set_target_single "${TARGET}" + check_target_is_running "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${TARGET}" + else + error_notify "Jail is not running." + error_continue "Use [-a|--auto] to auto-start the jail." + fi + if grep -qw "${bastille_jailsdir}/${TARGET}/root/.bastille" "${bastille_jailsdir}/${TARGET}/fstab"; then + error_exit "${TARGET} is not a thick container." fi } @@ -110,16 +136,12 @@ release_upgrade() { jail_upgrade() { # Upgrade a thick container - if [ -d "${bastille_jailsdir}/${TARGET}" ]; then - jail_check - release_check - CURRENT_VERSION=$(jexec -l ${TARGET} freebsd-version) - env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_jailsdir}/${TARGET}/root" --currently-running "${CURRENT_VERSION}" -r ${NEWRELEASE} upgrade - echo - echo -e "${COLOR_YELLOW}Please run 'bastille upgrade ${TARGET} install' to finish installing updates.${COLOR_RESET}" - else - error_exit "${TARGET} not found. See 'bastille bootstrap'." - fi + jail_check + release_check + CURRENT_VERSION=$(jexec -l ${TARGET} freebsd-version) + env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_jailsdir}/${TARGET}/root" --currently-running "${CURRENT_VERSION}" -r ${NEWRELEASE} upgrade + echo + echo -e "${COLOR_YELLOW}Please run 'bastille upgrade ${TARGET} install' to finish installing updates.${COLOR_RESET}" } jail_updates_install() { @@ -152,4 +174,4 @@ elif [ "${NEWRELEASE}" = "install" ]; then jail_updates_install else jail_upgrade -fi +fi \ No newline at end of file diff --git a/usr/local/share/bastille/verify.sh b/usr/local/share/bastille/verify.sh index ec8afa916..206c711a9 100644 --- a/usr/local/share/bastille/verify.sh +++ b/usr/local/share/bastille/verify.sh @@ -33,10 +33,18 @@ . /usr/local/share/bastille/common.sh . /usr/local/etc/bastille/bastille.conf -bastille_usage() { - error_exit "Usage: bastille verify [release|template]" +usage() { + error_notify "Usage: bastille verify [RELEASE|TEMPLATE]" + cat << EOF + Options: + + -x | --debug Enable debug mode. + +EOF + exit 1 } + verify_release() { if [ -f "/bin/midnightbsd-version" ]; then echo -e "${COLOR_RED}Not yet supported on MidnightBSD.${COLOR_RESET}" @@ -80,9 +88,8 @@ verify_template() { info "Detected ${_hook} hook." ## line count must match newline count - # shellcheck disable=SC2046 - # shellcheck disable=SC3003 - if [ $(wc -l "${_path}" | awk '{print $1}') -ne $(grep -c $'\n' "${_path}") ]; then + # shellcheck disable=SC3003 + if [ "$(wc -l "${_path}" | awk '{print $1}')" -ne "$(grep -c $'\n' "${_path}")" ]; then info "[${_hook}]:" error_notify "${BASTILLE_TEMPLATE}:${_hook} [failed]." error_notify "Line numbers don't match line breaks." @@ -136,47 +143,61 @@ verify_template() { ## remove bad templates if [ "${_hook_validate}" -lt 1 ]; then error_notify "No valid template hooks found." - error_notify "Template discarded." - rm -rf "${bastille_template}" + error_notify "Template discarded: ${BASTILLE_TEMPLATE}" + echo + rm -rf "${_template_path}" exit 1 fi ## if validated; ready to use if [ "${_hook_validate}" -gt 0 ]; then - info "Template ready to use." + info "Template ready to use: ${BASTILLE_TEMPLATE}" + echo fi } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - bastille_usage - ;; -esac +# Handle options. +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + error_exit "Unknown Option: \"${1}\"" + ;; + *) + break + ;; + esac +done -if [ $# -gt 1 ] || [ $# -lt 1 ]; then - bastille_usage +if [ "$#" -ne 1 ]; then + usage fi bastille_root_check -case "$1" in -*-RELEASE|*-release|*-RC[1-9]|*-rc[1-9]) - RELEASE=$1 - verify_release - ;; -*-stable-LAST|*-STABLE-last|*-stable-last|*-STABLE-LAST) - RELEASE=$1 - verify_release - ;; -http?*) - bastille_usage - ;; -*/*) - BASTILLE_TEMPLATE=$1 - verify_template - ;; -*) - bastille_usage - ;; +case "${1}" in + *-RELEASE|*-release|*-RC[1-9]|*-rc[1-9]) + RELEASE="${1}" + verify_release + ;; + *-stable-LAST|*-STABLE-last|*-stable-last|*-STABLE-LAST) + RELEASE="${1}" + verify_release + ;; + http?*) + bastille_usage + ;; + */*) + BASTILLE_TEMPLATE="${1}" + verify_template + ;; + *) + usage + ;; esac diff --git a/usr/local/share/bastille/zfs.sh b/usr/local/share/bastille/zfs.sh index 3ba507925..6d7c3c046 100644 --- a/usr/local/share/bastille/zfs.sh +++ b/usr/local/share/bastille/zfs.sh @@ -34,15 +34,22 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille zfs TARGET [set|get|snap] [key=value|date]'" + error_notify "Usage: bastille zfs TARGET [set|get|snap|destroy_snap|df|usage] [key=value|date]" + cat << EOF + Options: + + -x | --debug Enable debug mode. + +EOF + exit 1 } + zfs_snapshot() { for _jail in ${JAILS}; do info "[${_jail}]:" # shellcheck disable=SC2140 zfs snapshot -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"@"${TAG}" - echo done } @@ -51,7 +58,6 @@ for _jail in ${JAILS}; do info "[${_jail}]:" # shellcheck disable=SC2140 zfs destroy -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"@"${TAG}" - echo done } @@ -59,7 +65,6 @@ zfs_set_value() { for _jail in ${JAILS}; do info "[${_jail}]:" zfs "${ATTRIBUTE}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}" - echo done } @@ -81,45 +86,52 @@ done # Handle special-case commands first. case "$1" in -help|-h|--help) - usage - ;; + help|-h|--help) + usage + ;; esac +if [ "$#" -lt 2 ]; then + usage +fi + +TARGET="${1}" +ACTION="${2}" + bastille_root_check +set_target "${TARGET}" -## check ZFS enabled +# Check if ZFS is enabled if ! checkyesno bastille_zfs_enable; then error_exit "ZFS not enabled." fi -## check zpool defined +# Check if zpool is defined if [ -z "${bastille_zfs_zpool}" ]; then error_exit "ZFS zpool not defined." fi -if [ $# -lt 1 ]; then - usage -fi - -case "$1" in -set) - ATTRIBUTE=$2 - zfs_set_value - ;; -get) - ATTRIBUTE=$2 - zfs_get_value - ;; -snap|snapshot) - TAG=$2 - zfs_snapshot - ;; -destroy_snap|destroy_snapshot) - TAG=$2 - zfs_destroy_snapshot - ;; -df|usage) - zfs_disk_usage - ;; -esac +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 + ;; + df|usage) + zfs_disk_usage + ;; + *) + usage + ;; +esac \ No newline at end of file