Проект

Общее

Профиль

Freebsd Jail

FreeBSD Jail (англ. jail — «тюрьма») — механизм виртуализации в системе FreeBSD, позволяющий создавать внутри одной операционной системы FreeBSD несколько независимо работающих FreeBSD на том же ядре операционной системы, но совершенно независимо настраиваемых с независимым набором установленных приложений.

В основу FreeBSD Jail вошёл системный вызов chroot(2), при котором для текущего процесса и всех его потомков, корневой каталог переносится в определённое место на файловой системе. При этом это место для процесса становится корневым каталогом. Таким образом, изолированный процесс может иметь доступ только к низлежащему дереву каталогов.

Однако FreeBSD Jail также имеет поддержку на уровне ядра, что позволяет ограничивать доступ к сети, общей памяти, переменным ядра sysctl и ограничивать видимость процессов вне jail.

Процесс, заключённый в Jail, может иметь доступ только к определённым IP-адресам операционной системы и использовать определённый hostname. Такой процесс называется «изолированный процесс» или «Jailed-процесс».

Таким образом, создаётся безопасная «клетка», внутри которой можно исполнять даже потенциально опасное программное обеспечение, которое не сможет никак повредить основной системе или другим таким же «клеткам». До версии 9.0-RELEASE, FreeBSD Jail не имела средств контроля по использованию ресурсов (как это делает, например, OpenVZ под Linux). С версии 9.0-RELEASE, подобные механизмы были введены через утилиту rctl(8) и фреймворк RACCT.
see: FreeBSD Jail

sysctl привилегии Jailed-процессов:

Идентификатор sysctl Контролируемая функциональность
security.jail.chflags_allowed Возможность менять системные флаги файлов
security.jail.allow_raw_sockets Возможность создавать низкоуровневые сокеты
security.jail.sysvipc_allowed Возможность использовать System V IPC
security.jail.set_hostname_allowed Возможность задавать собственные hostname внутри Jailed-процессов (обычно hostname задаётся при вызове jail)
security.jail.enforce_statfs Возможность видеть все монтированные файловые системы внутри Jailed-процессов
security.jail.socket_unixiproute_only Ограничение по возможности создания UNIX/IPv4/route сокетов
security.jail.list Список запущеных JAIL'ов

Примерный конфиг:

# https://gist.github.com/sdebnath/086874c5df8b68e0df69

exec.clean;
path="/usr/local/jail/run/${name}";
host.hostname="${name}.lan";
$bridge="bridge1";
exec.system_user = "root";
exec.jail_user = "root";
exec.prestart = "/sbin/mount -t nullfs /usr/ports ${path}/usr/ports";
exec.poststop = "/sbin/umount -f ${path}/usr/ports";
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
# Commands to run on host before jail is created
exec.prestart  += "ifconfig epair${if} create up";
exec.prestart  += "ifconfig epair${if}a up";
exec.prestart  += "ifconfig ${bridge} addm epair${if}a up";
# Commands to run in jail after it is created
exec.start  += "/sbin/ifconfig lo0 127.0.0.1 up";
exec.start  += "/sbin/ifconfig epair${if}b inet ${ip_addr} up";
exec.start  += "/sbin/ifconfig vlan4 create";
exec.start  += "/sbin/ifconfig vlan4 vlan 4 vlandev epair${if}b up";
exec.start  += "/sbin/ifconfig vlan5 create";
exec.start  += "/sbin/ifconfig vlan5 vlan 5 vlandev epair${if}b up";
exec.start  += "/sbin/ifconfig vlan6 create";
exec.start  += "/sbin/ifconfig vlan6 vlan 6 vlandev epair${if}b up";
exec.start  += "/sbin/route add default ${ip_route}";
exec.start  += "/bin/sh /etc/rc";
exec.poststop  += "ifconfig ${bridge} deletem epair${if}a";
exec.poststop  += "ifconfig epair${if}a destroy";
exec.consolelog = "/var/log/jail/${name}_console.log";
mount.fstab = "${path}/etc/fstab";
mount += "dev ${path}/dev devfs rw,ruleset=50";
mount += "fdesc ${path}/dev/fd fdescfs rw", "proc ${path}/proc procfs rw";
mount += "tmpfs ${path}/dev/shm tmpfs rw";

allow.raw_sockets = 2;
allow.chflags = 1;
allow.socket_af = 1;
#interface = "${interface}";
#securelevel = 1;
#allow.nomount;
#allow.set_hostname = 0;
#allow.sysvipc = 0;

allow.mount;
mount.devfs;
mount.procfs;
exec.clean;

#allow.raw_sockets = "1";
allow.set_hostname = "0";
allow.sysvipc = "1";
allow.mount.devfs;
mount.devfs  = "1";
devfs_ruleset  = "4";

vnet;
vnet.interface  = "epair${if}b";
persist;

$ip_addr="192.168.15.${if}/24";
$ip_route="192.168.15.1";

test {
    $if="10";
}

Примерный скрипт автоматизации с использованием zfs и снимков

#!/usr/bin/env sh

[ -z "${DEBUG}" ] || set -x

ZPOOL=${ZPOOL:-zroot}
JAIL_ROOT=${JAIL_ROOT:-/usr/local/jail}
JAIL_RELEASE=${JAIL_RELEASE:-$(sysctl -n kern.osrelease | sed -e 's/-p[0-9]*$//')}
JAIL_ARCH=${JAIL_ARCH:-$(sysctl -n hw.machine_arch)}
JAIL_DIST=${JAIL_ROOT}/dist/${JAIL_RELEASE}
JAIL_TEMPLATE=${JAIL_ROOT}/template/${JAIL_RELEASE}/root
JAIL_RUN=${JAIL_ROOT}/run
JAIL_IP=${JAIL_IP:-172.16.0.%d}
JAIL_NAME=${JAIL_NAME:-j%03d}
DIST_SRC=${DIST_SRC:-http://ftp.freebsd.org/pub/FreeBSD/releases/${JAIL_ARCH}/${JAIL_RELEASE}}

_err() {
    echo $@ >&2
    exit 1
}

_create_zfs_datasets() {
    zfs create -o compression=lz4 -o mountpoint=${JAIL_ROOT} -p ${ZPOOL}/jail
    zfs create -p ${ZPOOL}/jail/dist/${JAIL_RELEASE}
    zfs create -p ${ZPOOL}/jail/template/${JAIL_RELEASE}/root
    zfs create -p ${ZPOOL}/jail/run
}

_download_distfiles() {
    for f in base.txz lib32.txz
    do
        fetch -o ${JAIL_DIST}/${f} ${DIST_SRC}/${f}
    done
}

_unpack_template() {
    for f in base.txz lib32.txz
    do
        tar -C ${JAIL_TEMPLATE} -xf ${JAIL_DIST}/${f}
    done
}

_freebsd_update_chroot() {

    local chroot=${1}
    if [ -z "${chroot}" ]; then 
        _err ERROR: Must specify chroot directory
    fi

    UNAME_r=${JAIL_RELEASE} PAGER="/usr/bin/tail -n0" freebsd-update -b ${chroot} \
        -d ${chroot}/var/db/freebsd-update/ \
        -f ${chroot}/etc/freebsd-update.conf fetch

            UNAME_r=${JAIL_RELEASE} PAGER=/bin/cat freebsd-update -b ${chroot} \
                -d ${chroot}/var/db/freebsd-update/ \
                -f ${chroot}/etc/freebsd-update.conf install
            }

        _configure_template(){
            sysrc -f ${JAIL_TEMPLATE}/etc/rc.conf sendmail_enable=NO \
                sendmail_submit_enable=NO \
                sendmail_outbound_enable=NO \
                sendmail_msp_queue_enable=NO \
                syslogd_enable=NO 

                            /usr/sbin/pw -R ${JAIL_TEMPLATE} group add git -g 1000
                            /usr/sbin/pw -R ${JAIL_TEMPLATE} group add users -g 1003
                            /usr/sbin/pw -R ${JAIL_TEMPLATE} group add user -g 1001
                            /usr/sbin/pw -R ${JAIL_TEMPLATE} usermod -n root -L russian
                            /usr/sbin/pw -R ${JAIL_TEMPLATE} useradd -n git -g git -m -w no -L russian -u 1000 -c "GIT user"
                            /usr/sbin/pw -R ${JAIL_TEMPLATE} useradd -n user -m -w no -G wheel,users,git -L russian -u 1001 -c "Default user"

    for f in /etc/resolv.conf /etc/localtime
    do
        cp $f ${JAIL_TEMPLATE}/$f
    done

    chroot ${JAIL_TEMPLATE} env ASSUME_ALWAYS_YES=YES pkg bootstrap 
    chroot ${JAIL_TEMPLATE} env ASSUME_ALWAYS_YES=1 pkg update
}

_snapshot_template() {
    local tag=${1}
    if [ -z "${tag}" ]
    then 
        tag=$(cut -f 3,4 -d '|' ${JAIL_TEMPLATE}/var/db/freebsd-update/tag | sed -e 's/|/-p/')
    fi
    zfs snapshot ${ZPOOL}/jail/template/${JAIL_RELEASE}/root@${tag}
}

_clone_template() {
    if [ "$1" == "--tag" ] 
    then
        local tag=${2}
        if [ -z ${tag} ]
        then
            err USAGE: tag needed
        fi
        shift 2
    else    
        local tag=$(cut -f 3,4 -d '|' ${JAIL_TEMPLATE}/var/db/freebsd-update/tag | sed -e 's/|/-p/')
    fi
    local id=${1:-$(uuidgen)}
    zfs clone ${ZPOOL}/jail/template/${JAIL_RELEASE}/root@${tag} ${ZPOOL}/jail/run/${id}

    ## Local configuration

    /usr/sbin/sysrc -f ${JAIL_RUN}/${id}/etc/rc.conf hostname=${id} > /dev/null
    touch ${JAIL_RUN}/${id}/etc/fstab > /dev/null

    if [ ! -d ${JAIL_RUN}/${id}/usr/ports ]; then
        mkdir ${JAIL_RUN}/${id}/usr/ports
    fi

    ## End local configuration

    [ -t 1 ] && printf "Cloned jail: "
    echo $id
}

_delete_jail(){
    name=${1}
    id=$(jls -j ${name} jid 2>/dev/null)

    if [ -z ${name} ];then
        _err "USAGE: id needed"
    fi

    isset=$(zfs list| awk '{print $1}'|grep "${ZPOOL}/jail/run/${name}")

    if [ ! -z "${isset}" ];then
        _kill ${id}
        echo "destroy ${ZPOOL}/jail/run/${name}"
        zfs umount -f ${ZPOOL}/jail/run/${name}
        zfs destroy ${ZPOOL}/jail/run/${name}
    fi

    if [ -d ${JAIL_RUN}/${name} ]; then
        echo "delete: ${JAIL_RUN}/${name}"
        rm -Rf ${JAIL_RUN}/${name}
    fi
}

_find_jail_path() {
    local path=${1}
    if [ ! -z "${path}" -a $(ls ${JAIL_RUN} | grep "^${path}" | wc -l) -eq 1 ] 
    then
        echo ${JAIL_RUN}/$(ls ${JAIL_RUN} | grep "^${path}")
    else
        return 1
    fi
}

_find_jail_id() {
    local path=$(_find_jail_path ${1})
    local id=${path##*/}
    if [ ! -z "${id}" -a $(/usr/sbin/jls host.hostname jid | grep "^${id}" | wc -l) -eq 1 ] 
    then
        /usr/sbin/jls host.hostname jid | grep "^${id}" | cut -d' ' -f2
    else
        return 1
    fi
}

_chroot_jail() {
    local path=$(_find_jail_path ${1})
    if [ -z "${path}" ]
    then 
        _err ERROR: Cant find jail - $1
    fi
    echo "--> ${path}"
    chroot ${path}
}

_run() {

    if [ "$1" == "--tag" ] 
    then
        local tag=${2}
        if [ -z ${tag} ]
        then
            _err USAGE: tag needed
        fi
        shift 2
    else    
        local tag=$(cut -f 3,4 -d '|' ${JAIL_TEMPLATE}/var/db/freebsd-update/tag | sed -e 's/|/-p/')
    fi

    if [ $# -ne 1 ]
    then
        _err 'USAGE: _run <n>'
    fi

    local n=${1}
    local ip=$(printf ${JAIL_IP} ${n})
    local name=$(printf ${JAIL_NAME} ${n})
    local path=${JAIL_RUN}/${name}

    if jls -j ${name} >/dev/null 2>&1 
    then
        _err ERROR: Jail ${name} already running
    fi

    if zfs list ${ZPOOL}/jail/run/${name} >/dev/null 2>&1
    then
        _err ERROR: ZFS dataset ${name} exists
    fi

    zfs clone ${ZPOOL}/jail/template/${JAIL_RELEASE}/root@${tag} ${ZPOOL}/jail/run/${name}
    /usr/sbin/sysrc -f ${path}/etc/rc.conf hostname=${name} \
                                           sshd_enable=YES \
                                           sshd_flags="-o ListenAddress=${ip}" >/dev/null
    #/usr/sbin/pw -R ${path} lock root
    /usr/sbin/pw -R ${path} usermod -n user -w random

    /usr/sbin/jail -c host.hostname=${name} \
                      name=${name} \
                      path=${path} \
                      ip4.addr=${ip} \
                      exec.start="/bin/sh /etc/rc" \
                      exec.clean \
                      mount.devfs >/dev/null || _err ERROR: Unable to start jail

}

_kill() {

    if [ $# -ne 1 ]
    then
        _err 'USAGE: _run <n>'
    fi

    local n=${1}
    local name=${n} #$(printf ${JAIL_NAME} ${n})
    local path=${JAIL_RUN}/${name}
    local jid=$(jls -j ${name} jid 2>/dev/null)

    if [ -z "${jid}" ]
    then
        _err ERROR: Jail ${name} not running
    fi

    /usr/sbin/jexec $jid /bin/sh /etc/rc.shutdown
    /bin/pkill -j $jid 
    /bin/sleep 0.5

    if jls -j ${name} >/dev/null 2>&1
    then 
        echo ERROR: Jail still running - removing
        /usr/sbin/jail -r ${jid}
    fi
}

_start_jail() {

    if [ $# -ne 2 ]
    then
        _err 'USAGE: _start_jail <id> <interface|address>'
    fi

    local path=$(_find_jail_path ${1})
    if [ -z "${path}" ]
    then 
        _err ERROR: Cant find jail - $1
    fi

    local ip=${2}
    if [ -z "${ip}" ]
    then 
        _err 'ERROR: Need to specify ip4 address <interface|address>'
    fi

    # Configure SSHD
    /usr/sbin/sysrc -f ${path}/etc/rc.conf sshd_enable=YES
    /usr/sbin/sysrc -f ${path}/etc/rc.conf sshd_flags="-o ListenAddress=${ip}"
    # Set user pw
    /usr/sbin/pw -R ${path} usermod -n user -w random

    /usr/sbin/jail -c host.hostname=${path##*/} \
                      path=${path} \
                      ip4.addr="${ip}" \
                      exec.start="/bin/sh /etc/rc" \
                      exec.clean \
                      mount.devfs || _err ERROR: Unable to start jail

    [ -t 1 ] && printf "Started jail: "
    echo $id
}

_stop_jail() {
    local id=$(_find_jail_id ${1})
    local path=$(_find_jail_path ${1})

    if [ -z "${id}" ]
    then 
        _err ERROR: Jail not running - $1
    fi

    /usr/sbin/jexec $id /bin/sh /etc/rc.shutdown
    /bin/pkill -l -j $id 
    /bin/sleep 1

    if _find_jail_id ${1} >/dev/null 2>&1
    then 
        echo ERROR: Jail still running - removing
        /usr/sbin/jail -r ${id}
    fi

    mount | grep -q ${path}/dev && umount ${path}/dev
}

_shell() {
    local id=$(_find_jail_id ${1})
    shift
    if [ -z "${id}" ]
    then 
        _err ERROR: Jail not running - $1
    fi

    if [ -z "$*" ]
    then
        /usr/sbin/jexec $id /bin/tcsh
    else
        /usr/sbin/jexec $id $@
    fi
}

_list() {
    /bin/ls -1 ${JAIL_RUN}
}

_running() {
    jls path ip4.addr | sed -ne "s^${JAIL_RUN}/^^p"
}

_usage() {
    cat <<EOM
Usage: $0 [cmd]
    init                    - Initialise ZFS datasets, downlaod/upadte dist 
                              and create template (equivalent to: create/
                              download/unpack/update/configure/snapshot)
    create                  - Initialise ZFS datasets
    download                - Download distfiles 
                              (default same release/arch as host) 
    unpack                  - Unpack distfiles
    update [dir]            - Update distribution in chroot 
                              (default $JAIL_TEMPLATE)
    configure               - Configure template
    snapshot [tag]          - Snapshot template (default is osrelease)
    clone [--tag <tag> [id] - Clone template from <tag> (default latest 
                              osrelease) to create new jail with given 
                              <id> (default UUID)
    find <id>               - Find jail path for <id>
    id <id>                 - Find JID for jail <id> (if running)
    chroot <id>             - Chroot into jail <id> (for local configuration)
    start <id> <if|addr>    - Start jail <id> with specified address
    stop <id>               - Stop jail <id>
    shell <id> [cmd]        - Open shell (or run <cmd>) in jail <id>
    list                    - List jail ids
    running                 - List running jail ids
    delete <id>             - Delete jail id
    <id> is jail id (by default UUID) - can be abbreviated if unique
    System defaults can be configured by env variables (see code)
EOM
}

_main() {

    local cmd="${1:-help}"
    shift || true

    case "$cmd" in
        init)      _create_zfs_datasets
            _download_distfiles
            _unpack_template
            _freebsd_update_chroot ${JAIL_TEMPLATE}
            _configure_template
            _snapshot_template
            ;;
        create)    _create_zfs_datasets
            ;;
        download)  _download_distfiles
            ;;
        unpack)    _unpack_template
            ;;
        update)    _freebsd_update_chroot ${1:-${JAIL_TEMPLATE}}
            ;;
        configure) _configure_template
            ;;
        snapshot)  _snapshot_template $@
            ;;
        clone)     _clone_template $@
            ;;
        find)      _find_jail_path $@ || _err ERROR: Cant find jail matching $@
            ;;
        id)        _find_jail_id $@ || _err ERROR: Jail not running
            ;;
        chroot)    _chroot_jail $@
            ;;
        start)     _start_jail $@
            ;;
        stop)      _stop_jail $@
            ;;
        shell)     _shell $@
            ;;
        list)      _list $@
            ;;
        running)   _running $@
            ;;
        run)       _run $@
            ;;
        kill)      _kill $@
            ;;
        delete) _delete_jail $@
            ;;
        *|help)    _usage
            ;;
    esac
}

_main $@