#!/bin/bash # # lxc: linux Container library # Authors: # Daniel Lezcano # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA LXC_MAPPED_UID= LXC_MAPPED_GID= SSH= # Make sure the usual locations are in PATH export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin am_in_userns() { [ -e /proc/self/uid_map ] || { echo no; return; } [ "$(wc -l /proc/self/uid_map | awk '{ print $1 }')" -eq 1 ] || { echo yes; return; } line=$(awk '{ print $1 " " $2 " " $3 }' /proc/self/uid_map) [ "$line" = "0 0 4294967295" ] && { echo no; return; } echo yes } in_userns=0 [ $(am_in_userns) = "yes" ] && in_userns=1 copy_binary() { binary_path=`which $1` if [ $? -ne 0 ]; then echo "Unable to find $1 binary on the system" return 1 fi dir_path="${binary_path%/*}" echo /{,usr/}{,s}bin | grep $dir_path >/dev/null 2>&1 if [ $? -ne 0 ]; then echo "Binary $1 is located at $binary_path and will not be copied" echo "($dir_path not supported)" return 1 fi cp $binary_path $rootfs/$binary_path if [ $? -ne 0 ]; then echo "Failed to copy $binary_path to rootfs" return 1 fi return 0 } install_busybox() { rootfs=$1 name=$2 res=0 tree="\ $rootfs/selinux \ $rootfs/dev \ $rootfs/home \ $rootfs/root \ $rootfs/etc \ $rootfs/etc/init.d \ $rootfs/bin \ $rootfs/usr/bin \ $rootfs/sbin \ $rootfs/usr/sbin \ $rootfs/proc \ $rootfs/sys \ $rootfs/mnt \ $rootfs/tmp \ $rootfs/var/log \ $rootfs/usr/share/udhcpc \ $rootfs/dev/pts \ $rootfs/dev/shm \ $rootfs/lib \ $rootfs/usr/lib \ $rootfs/lib64 \ $rootfs/usr/lib64" mkdir -p $tree || return 1 chmod 755 $tree || return 1 pushd $rootfs/dev > /dev/null || return 1 # minimal devices needed for busybox if [ $in_userns -eq 1 ]; then for dev in tty console tty0 tty1 ram0 null urandom; do echo "lxc.mount.entry = /dev/$dev dev/$dev none bind,optional,create=file 0 0" >> $path/config done else mknod -m 666 tty c 5 0 || res=1 mknod -m 666 console c 5 1 || res=1 mknod -m 666 tty0 c 4 0 || res=1 mknod -m 666 tty1 c 4 0 || res=1 mknod -m 666 tty5 c 4 0 || res=1 mknod -m 600 ram0 b 1 0 || res=1 mknod -m 666 null c 1 3 || res=1 mknod -m 666 zero c 1 5 || res=1 mknod -m 666 urandom c 1 9 || res=1 fi popd > /dev/null # root user defined cat <> $rootfs/etc/passwd root:x:0:0:root:/root:/bin/sh EOF cat <> $rootfs/etc/group root:x:0:root EOF # mount everything cat <> $rootfs/etc/init.d/rcS #!/bin/sh /bin/syslogd /bin/mount -a /bin/udhcpc EOF # executable chmod 744 $rootfs/etc/init.d/rcS || return 1 # launch rcS first then make a console available # and propose a shell on the tty, the last one is # not needed cat <> $rootfs/etc/inittab ::sysinit:/etc/init.d/rcS tty1::respawn:/bin/getty -L tty1 115200 vt100 console::askfirst:/bin/sh EOF # writable and readable for other chmod 644 $rootfs/etc/inittab || return 1 cat <> $rootfs/usr/share/udhcpc/default.script #!/bin/sh case "\$1" in deconfig) ip addr flush dev \$interface ;; renew|bound) # flush all the routes if [ -n "\$router" ]; then ip route del default 2> /dev/null fi # check broadcast if [ -n "\$broadcast" ]; then broadcast="broadcast \$broadcast" fi # add a new ip address ip addr add \$ip/\$mask \$broadcast dev \$interface if [ -n "\$router" ]; then ip route add default via \$router dev \$interface fi [ -n "\$domain" ] && echo search \$domain > /etc/resolv.conf for i in \$dns ; do echo nameserver \$i >> /etc/resolv.conf done ;; esac exit 0 EOF chmod 744 $rootfs/usr/share/udhcpc/default.script return $res } install_dropbear() { # copy dropbear binary copy_binary dropbear || return 1 # make symlinks to various ssh utilities utils="\ $rootfs/usr/bin/dbclient \ $rootfs/usr/bin/scp \ $rootfs/usr/bin/ssh \ $rootfs/usr/sbin/dropbearkey \ $rootfs/usr/sbin/dropbearconvert \ " echo $utils | xargs -n1 ln -s /usr/sbin/dropbear # add necessary config files mkdir $rootfs/etc/dropbear dropbearkey -t rsa -f $rootfs/etc/dropbear/dropbear_rsa_host_key > /dev/null 2>&1 dropbearkey -t dss -f $rootfs/etc/dropbear/dropbear_dss_host_key > /dev/null 2>&1 echo "'dropbear' ssh utility installed" return 0 } install_openssh() { # tools to be installed server_utils="sshd" client_utils="\ ssh \ scp \ " client_optional_utils="\ sftp \ ssh-add \ ssh-agent \ ssh-keygen \ ssh-keyscan \ ssh-argv0 \ ssh-copy-id \ " # new folders used by ssh ssh_tree="\ $rootfs/etc/ssh \ $rootfs/var/empty/sshd \ $rootfs/var/lib/empty/sshd \ $rootfs/var/run/sshd \ " # create folder structure mkdir -p $ssh_tree if [ $? -ne 0 ]; then return 1 fi # copy binaries for bin in $server_utils $client_utils; do copy_binary $bin || return 1 done for bin in $client_optional_utils; do tool_path=`which $bin` && copy_binary $bin done # add user and group cat <> $rootfs/etc/passwd sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin EOF cat <> $rootfs/etc/group sshd:x:74: EOF # generate container keys ssh-keygen -t rsa -N "" -f $rootfs/etc/ssh/ssh_host_rsa_key >/dev/null 2>&1 ssh-keygen -t dsa -N "" -f $rootfs/etc/ssh/ssh_host_dsa_key >/dev/null 2>&1 # by default setup root password with no password cat < $rootfs/etc/ssh/sshd_config Port 22 Protocol 2 HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_host_dsa_key UsePrivilegeSeparation yes KeyRegenerationInterval 3600 ServerKeyBits 768 SyslogFacility AUTH LogLevel INFO LoginGraceTime 120 PermitRootLogin yes StrictModes yes RSAAuthentication yes PubkeyAuthentication yes IgnoreRhosts yes RhostsRSAAuthentication no HostbasedAuthentication no PermitEmptyPasswords yes ChallengeResponseAuthentication no EOF echo "'OpenSSH' utility installed" return 0 } configure_busybox() { rootfs=$1 which busybox >/dev/null 2>&1 if [ $? -ne 0 ]; then echo "busybox executable is not accessible" return 1 fi file -L $(which busybox) | grep -q "statically linked" if [ $? -ne 0 ]; then echo "warning : busybox is not statically linked." echo "warning : The template script may not correctly" echo "warning : setup the container environment." fi # copy busybox in the rootfs cp $(which busybox) $rootfs/bin if [ $? -ne 0 ]; then echo "failed to copy busybox in the rootfs" return 1 fi # symlink busybox for the commands it supports # it would be nice to just use "chroot $rootfs busybox --install -s /bin" # but that only works right in a chroot with busybox >= 1.19.0 pushd $rootfs/bin > /dev/null || return 1 ./busybox --help | grep 'Currently defined functions:' -A300 | \ grep -v 'Currently defined functions:' | tr , '\n' | \ xargs -n1 ln -s busybox popd > /dev/null # relink /sbin/init ln $rootfs/bin/busybox $rootfs/sbin/init # passwd exec must be setuid chmod +s $rootfs/bin/passwd touch $rootfs/etc/shadow # setting passwd for root CHPASSWD_FILE=$rootfs/root/chpasswd.sh cat <$CHPASSWD_FILE echo "setting root password to \"root\"" mount -n --bind /lib $rootfs/lib if [ \$? -ne 0 ]; then echo "Failed bind-mounting /lib at $rootfs/lib" exit 1 fi chroot $rootfs chpasswd </dev/null root:root EOFF if [ \$? -ne 0 ]; then echo "Failed to change root password" exit 1 fi umount $rootfs/lib EOF lxc-unshare -s MOUNT -- /bin/sh < $CHPASSWD_FILE rm $CHPASSWD_FILE return 0 } copy_configuration() { path=$1 rootfs=$2 name=$3 grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config cat <> $path/config lxc.haltsignal = SIGUSR1 lxc.rebootsignal = SIGTERM lxc.utsname = $name lxc.tty = 1 lxc.pts = 1 lxc.cap.drop = sys_module mac_admin mac_override sys_time # When using LXC with apparmor, uncomment the next line to run unconfined: #lxc.aa_profile = unconfined lxc.mount.auto = cgroup:mixed proc:mixed sys:mixed lxc.mount.entry = shm /dev/shm tmpfs defaults 0 0 EOF libdirs="\ lib \ usr/lib \ lib64 \ usr/lib64" for dir in $libdirs; do if [ -d "/$dir" ] && [ -d "$rootfs/$dir" ]; then echo "lxc.mount.entry = /$dir $dir none ro,bind 0 0" >> $path/config fi done echo "lxc.mount.entry = /sys/kernel/security sys/kernel/security none ro,bind,optional 0 0" >>$path/config } remap_userns() { path=$1 if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then chown $LXC_MAPPED_UID $path/config >/dev/null 2>&1 chown -R root $path/rootfs >/dev/null 2>&1 fi if [ -n "$LXC_MAPPED_GID" ] && [ "$LXC_MAPPED_GID" != "-1" ]; then chgrp $LXC_MAPPED_GID $path/config >/dev/null 2>&1 chgrp -R root $path/rootfs >/dev/null 2>&1 fi } usage() { cat < -s|--ssh={dropbear,openssh} EOF return 0 } options=$(getopt -o hp:n:s: -l help,rootfs:,path:,name:,mapped-uid:,mapped-gid:,ssh: -- "$@") if [ $? -ne 0 ]; then usage $(basename $0) exit 1 fi eval set -- "$options" while true do case "$1" in -h|--help) usage $0 && exit 0;; -p|--path) path=$2; shift 2;; --rootfs) rootfs=$2; shift 2;; -n|--name) name=$2; shift 2;; --mapped-uid) LXC_MAPPED_UID=$2; shift 2;; --mapped-gid) LXC_MAPPED_GID=$2; shift 2;; -s|--ssh) SSH=$2; shift 2;; --) shift 1; break ;; *) break ;; esac done if [ "$(id -u)" != "0" ]; then echo "This script should be run as 'root'" exit 1 fi if [ -z "$path" ]; then echo "'path' parameter is required" exit 1 fi # detect rootfs config="$path/config" if [ -z "$rootfs" ]; then if grep -q '^lxc.rootfs' $config 2>/dev/null ; then rootfs=$(awk -F= '/^lxc.rootfs =/{ print $2 }' $config) else rootfs=$path/rootfs fi fi install_busybox $rootfs $name if [ $? -ne 0 ]; then echo "failed to install busybox's rootfs" exit 1 fi configure_busybox $rootfs if [ $? -ne 0 ]; then echo "failed to configure busybox template" exit 1 fi copy_configuration $path $rootfs $name if [ $? -ne 0 ]; then echo "failed to write configuration file" exit 1 fi remap_userns $path if [ $? -ne 0 ]; then echo "failed to remap files to user" exit 1 fi if [ -n "$SSH" ]; then case "$SSH" in "dropbear") install_dropbear if [ $? -ne 0 ]; then echo "Unable to install 'dropbear' ssh utility" exit 1 fi ;; "openssh") install_openssh if [ $? -ne 0 ]; then echo "Unable to install 'OpenSSH' utility" exit 1 fi ;; *) echo "$SSH: unrecognized ssh utility" exit 1 esac else which dropbear >/dev/null 2>&1 if [ $? -eq 0 ]; then install_dropbear fi fi