here are the scripts i wrote to make this easier. these really were made for my own use, but i hope others may find them useful. i would be interested to know if anyone else actually does find them useful. would also be glad to know of any errors/problems/things that can go wrong i didn't think of.
the first one (jail_new) creates a new jail (and possibly the user). the second one (jail_pkgadd) adds a package and its dependencies to an existing jail. they are expected to be in the same directory (jail_new cannot add packages (-p) otherwise). to relate to my earlier examples: $ jail_new -tu _inmate:_chaingang /home/jail will create the jail in /home/jail and also the user _inmate and group _chaingang. this case it will be just be a regular shell account (just chrooted). $ jail_new -t _inmate:_chaingang /home/jail will create the jail, but will not create the user:group. a real case: $ jail_new -tux -k /home/null/.ssh/id_rsa.pub -p w3m,feh:/usr/release/pkg browse /home/browse w3m -B this command sets up the terminal (-t) and X (-x) in a directory (here /home/browse), creates a user (-u) (in this case 'browse'), uses the given key file (-k) for the authorized keys, installs the packages (-p) w3m and feh (and all of their dependencies) from directory /usr/release/pkg, and sets 'w3m -B' to run automatically via ForceCommand in sshd_config. this is the equivalent of: $ jail_new -tux -k /home/null/.ssh/id_rsa.pub browse /home/browse w3m -B $ jail_pkgadd -p /usr/release/pkg w3m /home/browse $ jail_pkgadd -p /usr/release/pkg feh /home/browse if you want bzip2 in there as well, you can always add it later: $ jail_pkgadd -p /usr/release/pkg bzip2 /home/browse or, if PKG_PATH is set (and not remote) you can omit -p $ jail_pkgadd bzip2 /home/browse if PKG_PATH is set, and is remote, you need: $ jail_pkgadd -r bzip2 /home/browse (note: will only allow a single directory for PKG_PATH) this can be used by running: $ Xephyr :1 & env DISPLAY=:1 ssh -X browse@localhost (side note: w3m runs 'display' to display an image, so i create a symlink to feh to view images) another case: $ jail_new -tuxr -k /home/null/.ssh/id_rsa.pub -p xpdf:scp://null@node02/usr/release/pkg pdf /home/pdf you need to specify -r (remote) directly to use remote pkg src. which is the equivalent of: $ jail_new -tux -k /home/null/.ssh/id_rsa.pub pdf /home/pdf $ jail_pkgadd -r -p scp://null@node02/usr/release/pkg xpdf /home/pdf which can be used: $ cp test.pdf /home/pdf/tmp $ Xephyr :1 & env DISPLAY=:1 ssh -X browse@localhost xpdf -fullscreen /tmp/test.pdf (in this case it may be best not to use ForceCommand, since you may want to open multiple documents.) WARNING use at your own peril. if you can't read the scripts, you probably shouldn't use them, and then i am certain there are other glaring security flaws you need to know about. i include these because it is a dull pain in the ass to do this manually, and hopefully someone may get some use out of them. other than that, do with it what you wish. they are as fool-proof as i could make them, so that i don't shoot myself in the foot accidently (and i have been around long enough to have done that a few times, even while being careful). but you never know. jail_new: -------------------------------------------------- #!/bin/ksh USAGE="${0##*/} [-jrtux] [-k authkeys] [-p pkg[,pkg2...][:pkgpath]] user[:group] path [cmd [args ...]]" [[ "$1" = -h ]] && { echo "USAGE $USAGE"; return 0; } #-t sets PermitTTY and copies files for term #-x sets X11Forwarding and copies files for X (fonts,xauth) #-u creates user; fails if user exists #-j joins group; needed to join existing group #-p pkg[,pkg2...][:pkgpath] #-r allows remote pkg access #uses existing PKG_PATH #pkgpath arg overrides PKG_PATH #only accepts a lone pkgpath PATH=/sbin:/bin:/usr/sbin:/usr/bin echov() { eval echo \"\$$1\"; } isemptyv() { eval [ \${#$1} -eq 0 ]; } notemptyv() { eval [ \${#$1} -gt 0 ]; } alias xt='set -o xtrace' alias xt-='set +o xtrace' if [ $(id -u) -eq 0 ];then echo "ERR cannot run as root" return 1 fi _sshd_config=/etc/ssh/sshd_config _sshd_config_tmp=/tmp/sshd_config trap "rm -f $_sshd_config_tmp" 0 2 #for convenience _fontdir=/usr/X11R6/lib/X11/fonts _terminfo=/usr/share/misc/terminfo.db _termcap=/usr/share/misc/termcap _do_x=no _do_tty=no _do_useradd= _do_joingrp= _do_remote= _authkeys= _pkg= _pkgpath= _userhome=/home/cell while getopts :jrtuxk:p: _opt;do case "$_opt" in j) _do_joingrp=yes ;; r) _do_remote=-r ;; t) _do_tty=yes ;; u) _do_useradd=yes ;; x) _do_x=yes ;; k) _authkeys=$OPTARG if [ ! -f "$_authkeys" ];then echo "ERR no such file '$_authkeys'" return 1 fi ;; p) _pkg=$OPTARG if [[ "$_pkg" = *:* ]];then _pkgpath=${_pkg#*:} _pkg=${_pkg%%:*} export PKG_PATH=$_pkgpath else if isemptyv PKG_PATH;then echo "ERR PKG_PATH not set and none given" return 1 fi _pkgpath=$PKG_PATH fi if [[ "$_pkgpath" = *://* ]];then if isemptyv _do_remote;then echo "ERR pkgpath is remote; use -r" return 1 fi if [[ "$_pkgpath" = *:*:* ]];then echo "ERR invalid pkgpath '$_pkgpath'; only one dir permitted" return 1 fi else if [[ "$_pkgpath" = *:* ]];then echo "ERR invalid pkgpath '$_pkgpath'; only one dir permitted" return 1 fi fi if isemptyv _do_remote && [ ! -d "$_pkgpath" ];then echo "ERR no such dir '$_pkgpath'" return 1 fi _cmd_jailpkgadd=$(dirname $0)/jail_pkgadd if [ ! -f $_cmd_jailpkgadd ];then echo "ERR cannot locate jail_pkgadd script" return 1 fi ;; :) echo "ERR no arg for '-$OPTARG'"; return 1 ;; *) echo "ERR invalid arg '-$OPTARG'"; return 1 ;; esac done shift $((OPTIND-1)) _user=$1 _jailroot=$2 _cmd=$3 if isemptyv _user;then echo "ERR user not given" return 1 fi if isemptyv _jailroot;then echo "ERR jailroot path not given" return 1 fi shift 2 if [ -e $_jailroot ];then echo "ERR $_jailroot already exists" return 1 fi _authkeysfile=$_jailroot.authkeys _dbdir=$_jailroot/var/db/pkg #check/setup user/group getfreeuid() { local _uid=1000 local _uid_list="$(awk -F : '{printf $3"\n"}' /etc/passwd)" local _gid_list="$(awk -F : '{printf $3"\n"}' /etc/group)" while [ $_uid -gt 900 ];do _uid=$((_uid-1)) echov _uid_list | grep -q "^$_uid$" && continue echov _gid_list | grep -q "^$_uid$" && continue break done [ $_uid -lt 900 ] && return 1 echo $_uid } _group=${_user#*:} _user=${_user%:*} if grep -q ^$_user: /etc/passwd;then if notemptyv _do_useradd;then echo "ERR user '$_user' already exists" return 1 fi _userhome=$(grep ^$_user: /etc/passwd | awk -F : '{printf $4":"$6}') _group=${_userhome%:*} _userhome=${_userhome#*:} _jailhome=$_jailroot$_userhome else if isemptyv _do_useradd;then echo "ERR user '$_user' does not exist; use -u to create" return 1 fi _uid=$(getfreeuid) || { echo "ERR no uid free"; return 1; } if [[ "$_user" = "$_group" ]];then _group="=uid" else if grep -q ^$_group: /etc/group;then if isemptyv _do_joingrp;then echo "ERR group '$_group' exists; use -j to join" return 1 fi else if notemptyv _do_joingrp;then echo "ERR group '$_group' doesn't exist; cannot use -j to join" return 1 fi if ! sudo groupadd -g $_uid $_group;then echo "ERR failed to add group '$_group' gid '$_uid'" return 1 fi fi fi if ! sudo useradd -u $_uid -g $_group -c "public jail" \ -s /bin/sh -d $_userhome $_user 2>&1 then echo "ERR useradd failed" return 1 fi | grep -v "Warning: home" _group=$_uid fi _jailhome=$_jailroot$_userhome sudo mkdir -p $_jailhome || return 1 cat >$_sshd_config_tmp <<CONF Match User $_user ChrootDirectory $_jailroot AuthorizedKeysFile $_authkeysfile X11Forwarding $_do_x PermitTTY $_do_tty CONF set -f notemptyv _cmd && echo " ForceCommand $*" >>$_sshd_config_tmp set +f #setup filesystem isemptyv _authkeys && _authkeys=/dev/null sudo sh -c "cat $_authkeys >$_authkeysfile" || return 1 sudo chown $_user:$_group $_jailhome $_authkeysfile || return 1 sudo mkdir -p $_jailroot/dev || return 1 if [[ "$_do_tty" = yes ]];then sudo mknod -m 644 $_jailroot/dev/arandom c 45 3 || return 1 sudo mknod -m 666 $_jailroot/dev/null c 2 2 || return 1 sudo mknod -m 666 $_jailroot/dev/stderr c 22 2 || return 1 sudo mknod -m 666 $_jailroot/dev/stdin c 22 0 || return 1 sudo mknod -m 666 $_jailroot/dev/stdout c 22 1 || return 1 sudo mknod -m 666 $_jailroot/dev/tty c 1 0 || return 1 sudo mknod -m 666 $_jailroot/dev/zero c 2 12 || return 1 #sudo mknod -m 666 $_jailroot/dev/ttyp0 c 5 0 || return 1 #might want to add ttypN to make sshd happy fi sudo mkdir -p $_jailroot/{bin,sbin,etc,tmp,var/run} $_dbdir sudo mkdir -p $_jailroot/usr/{libexec,{,X11R6,local}/{bin,lib}} sudo cp -p /sbin/ldconfig $_jailroot/sbin sudo cp -p /usr/libexec/ld.so $_jailroot/usr/libexec sudo chroot $_jailroot ldconfig /usr/{,X11R6,local}/lib sudo chmod 1777 $_jailroot/tmp sudo cp -p /bin/sh $_jailroot/bin sudo cp -pR /etc/{hosts,resolv.conf,localtime} $_jailroot/etc if [[ "$_do_x" = yes ]];then _xauth=/usr/X11R6/bin/xauth sudo cp -p /bin/sh $_jailroot/bin sudo cp -p $_xauth $_jailroot$_xauth for _lib in $(ldd $_xauth | grep ' rlib ' | awk '{printf $7" "}');do [ -f $_jailroot/$_lib ] && continue sudo cp -p $_lib $_jailroot/$_lib || return 1 done sudo cp -rp /etc/fonts $_jailroot/etc || return 1 sudo mkdir -p $_jailroot/${_fontdir%/*} || return 1 sudo cp -rp $_fontdir $_jailroot/$_fontdir || return 1 fi if [[ "$_do_tty" = yes ]];then #for terminal eg w3m sudo mkdir -p $_jailroot${_terminfo%/*} || return 1 sudo cp -p $_terminfo $_termcap $_jailroot${_terminfo%/*} || return 1 #may still need /etc/termcap #sudo cp -R /etc/termcap $_jailroot/etc || return 1 fi echo "WARNING will update /etc/ssh/sshd_config with the following:\n" cat $_sshd_config_tmp echo read _update_ssh?"update sshd_config and restart sshd? (type 'yes') " if notemptyv _update_ssh && [[ "$_update_ssh" = yes ]];then sudo sh -c "cat $_sshd_config_tmp >>$_sshd_config" [ -f /var/run/sshd.pid ] && sudo kill -1 $(</var/run/sshd.pid) fi if notemptyv _pkg;then IFS=, for _thispkg in $_pkg;do $_cmd_jailpkgadd $_do_remote -p $_pkgpath $_thispkg $_jailroot done fi -------------------------------------------------- jail_pkgadd: -------------------------------------------------- #!/bin/ksh USAGE="${0##*/} [-r] [-p pkg_path] pkg [jailroot]" [[ "$1" = -h ]] && { echo "USAGE $USAGE"; return 0; } PATH=/sbin:/bin:/usr/sbin:/usr/bin #a few functions of my own #these are easier to type, and save me debugging time due to common things #like quotes, $, etc #takes a variable name as an arg, hence 'v' suffix echov() { eval echo \"\$$1\"; } isemptyv() { eval [ \${#$1} -eq 0 ]; } notemptyv() { eval [ \${#$1} -gt 0 ]; } alias usage='echo "USAGE $USAGE"' alias xt='set -o xtrace' alias xt-='set +o xtrace' if [ $(id -u) -eq 0 ];then echo "ERR should not run as root" return 1 fi if [[ "$(id -nG)" != *wsrc* ]];then echo "ERR user not in wsrc; needed for pkg_add" return 1 fi #_pkg_dir read from PKG_PATH #-p overrides PKG_PATH #if neither set, uses default _do_remote= _pkg_dir=$PKG_PATH while getopts :rp: _opt;do case "$_opt" in r) _do_remote=true ;; p) _pkg_dir=$OPTARG ;; :) echo "ERR no arg for '-$OPTARG'"; return 1 ;; *) echo "ERR invalid arg '-$OPTARG'"; return 1 ;; esac done shift $((OPTIND-1)) _pkg_dir=${_pkg_dir:-/usr/ports/packages/`arch -s`/all} _pkg_dbdir=/var/db/pkg export PKG_PATH="$_pkg_dir" if [[ "$_pkg_dir" = *://* ]];then if isemptyv _do_remote;then echo "ERR invalid pkg dir; need -r for remote" return 1 fi if [[ "$_pkg_dir" = *:*:* ]];then echo "ERR invalid pkg dir; only one dir allowed" return 1 fi else _do_remote= if [[ "$_pkg_dir" = *:* ]];then echo "ERR invalid pkg dir; only one dir allowed" return 1 fi if [ ! -d "$_pkg_dir" ];then echo "ERR pkg dir '$_pkg_dir' does not exist" return 1 fi fi _pkg=$1 _jailroot=$2 if isemptyv _pkg;then echo "ERR no package given" usage return 1 fi _jailroot=${_jailroot:-/home/jail} if [ ! -d "$_jailroot" ];then echo "ERR no such dir '$_jailroot'" return 1 fi _jail_dbdir=$_jailroot$_pkg_dbdir #set to /tmp to ignore installed package and use PKG_PATH _pkg_file=$(PKG_DBDIR=/tmp pkg_info -c $_pkg | sed -n 's!^Information for.*/!!p') if isemptyv _pkg_file;then echo "ERR could not find pkg '$_pkg'" return 1 fi ##get list of required packages ##only works if /usr/ports is in sync with packages in PKG_PATH #_pkgpath=$(pkg_info -P $_pkg | grep ^[a-z]) #if isemptyv _pkgpath;then # echo "ERR cannot find port '$_pkg'" # return 1 #fi #_pkgpath=/usr/ports/$_pkgpath #cd $_pkgpath #_pkg_list=$(make print-run-depends) || return $? #_pkg_list=$(echov _pkg_list | sed -e 's/.*"\([^"]*\)".*/\1 /' -e 's/ /.tgz /g') #_pkg_list="${_pkg_list}$_pkg_file" _depend_list= function get_depends { local _pkg=$1 _depend for _depend in $(pkg_info -f $_pkg | sed -n 's/^@depend.*://p');do echo $_pkg $_depend [[ "$_depend_list" = *\ $_depend\ * ]] && continue get_depends $_depend _depend_list="$_depend_list $_depend " done } _pkg_list="$(get_depends ${_pkg_file%.tgz} | tsort -r)" isemptyv _pkg_list && _pkg_list=${_pkg_file%.tgz} #if local go thru list to make sure pkgs are all there if isemptyv _do_remote;then for _pkg in $_pkg_list;do [ -f $_pkg_dir/$_pkg.tgz ] && continue echo "ERR pkg '$_pkg' not found" return 1 done fi #keep list of libraries to avoid repeating find _lib_tree=$(find /usr/{,X11R6}/lib -type f) for _pkg in $_pkg_list;do #check if package already installed [ -d $_jail_dbdir/$_pkg ] && continue #get list of @wantlib's that need copying _lib_list=$(pkg_info -f $_pkg | sed -n 's/^@wantlib //p') #get list of binaries in the pkg to check for more dependencies _bin_list=$(pkg_info -f $_pkg | sed -n 's/^@bin //p') #hopefully this handles all the quicks for @lib for _lib in $_lib_list;do _lib_relpath= if [[ "$_lib" = */* ]];then _lib_relpath=${_lib%/*} _lib=${_lib##*/} fi _lib_ver_min=${_lib##*.} _lib_name=${_lib%.*} _lib_ver_maj=${_lib_name##*.} _lib_name=${_lib_name%.*} _lib_fn=lib$_lib_name.so.$_lib_ver_maj.$_lib_ver_min if isemptyv _lib_relpath;then #ASSUME want libraries only -maxdepth 1 _lib_pathn=$(find $_jailroot/usr/{,X11R6,local}/lib -maxdepth 1 -name $_lib_fn) else _lib_pathn=$_jailroot/usr/local/$_lib_relpath/$_lib_fn fi [ -f "$_lib_pathn" ] && continue #lib not in jail _lib_pathn=$(echov _lib_tree | grep $_lib_fn$) if isemptyv _lib_pathn || notemptyv _lib_relpath;then #never copy from /usr/local echo "WARN could not find $_lib_fn" else sudo cp -p $_lib_pathn $_jailroot/$_lib_pathn fi done #ignore unsigned packages (-D unsigned) since i built them #sudo env PKG_DBDIR=$_jail_dbdir pkg_add -m -B $_jailroot -D unsigned $_pkg sudo env PKG_DBDIR=$_jail_dbdir pkg_add -m -B $_jailroot $_pkg #rebuild ld.so.hints with new libraries sudo chroot $_jailroot ldconfig /usr/{,X11R6,local}/lib #go thru binaries in package and get additional dependencies (cd $_jailroot/usr/local for _bin in $_bin_list;do for _lib in $(ldd $_bin | grep ' rlib ' | grep -v /usr/local/lib | awk '{printf $7" "}');do [ -f $_jailroot/$_lib ] || sudo cp -p $_lib $_jailroot/$_lib done done) done #env PKG_DBDIR=$_dbdir pkg_add -B /home/chroot $_pkg #env PKG_DBDIR=$_dbdir pkg_delete -B /home/chroot $_pkg