Permalink
Fetching contributors…
Cannot retrieve contributors at this time
executable file 368 lines (338 sloc) 12.1 KB
#!/bin/sh
#
# Tenant (jail on nanobsd helper script) for BSD Router Project
# http://bsdrp.net
#
# Copyright (c) 2017-2018, The BSDRP Development Team
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. 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.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
#
#############################################
############ Variables definition ###########
#############################################
# Exit if error or variable undefined
set -eu
create=false
delete=false
tenant=""
nics=""
defaultgw=""
authorizedkeys=""
jailbase="/var/jails"
[ -f /etc/nanobsd.conf ] && NANOBSD=true || NANOBSD=false
#############################################
########### Function definition #############
#############################################
# A usefull function (from: http://code.google.com/p/sh-die/)
die() { echo -n "EXIT: " >&2; echo "$@" >&2; exit 1; }
# Display usage
usage () {
echo "Usage: $0 -c|-d -j name -f authorized_keys -i if/ip/mask"
echo " -c: create a jail"
echo " -d: delete a jail"
echo " -f authorized_keys: SSH authorized_keys to be installed into jail"
echo " -g default-gateway: Default gateway"
echo " -i if/ip/mask: network interface name / IP address / IP mask"
echo " multiple interface are comma separated"
echo " if interface a bridge, epair created and add to it"
echo " -j: jail name"
echo "Example:"
echo " $0 -c -j customer1 -f /tmp/sshkey.pub -i bridge0,vtnet4.2/10.0.0.1/24 -g 10.0.0.254"
exit 1
}
# Create jail configuration files
# TO DO: All die() call in this function need to do a cleanup
create () {
[ -z "${tenant}" ] && die "BUG: tenant name mandatory"
if [ -f /etc/jail.conf ]; then
grep -q "^${tenant} {" /etc/jail.conf && die "Already existing entry into /etc/jail.conf"
[ -d /etc/jails/${tenant} ] && die "Aleary existing directory /etc/jails/${tenant}"
# Check if interface are already declared
nicslist=$nics
while [ "$nicslist" ] ;do
iter=${nicslist%%,*}
nicname=${iter%%/*}
egrep -q "vnet\.interface.*${nicname}" /etc/jail.conf && die "Interface ${nicname} already declared into /etc/jail"
[ "$nicslist" = "$iter" ] && nicslist='' || nicslist="${nicslist#*,}"
done
fi
# Get last ID
if [ -f /etc/jail.lastid ]; then
. /etc/jail.lastid
id=$(( id + 1))
else
id=1
fi
if ($NANOBSD); then
# In NANOBSD mode, autosave should be enabled if jail is created
# It allow to automatically save customer configuration changes
if ! sysrc -c autosave_enable="YES"; then
echo "Enabling autosave feature"
sysrc autosave_enable="YES" > /dev/null 2>&1
service autosave start > /dev/null 2>&1 || echo "Warning: Can't start autosave"
fi
fi
jailroot="${jailbase}/${tenant}"
jailcfg="/etc/jails/${tenant}"
# Generate pre-fstab for jail
{
for i in etc var; do
echo "tmpfs ${jailroot}/$i tmpfs rw,size=16000000 0 0"
#echo "/conf/base/$i ${jailroot}/conf/base/$i nullfs ro 0 0"
done
for i in root bin sbin lib libexec usr; do
echo "/$i ${jailroot}/$i nullfs ro 0 0"
done
echo "/conf/base ${jailroot}/conf/base nullfs ro 0 0"
echo "/etc/jails/${tenant} ${jailroot}/cfg nullfs rw,noatime 0 0"
} > /etc/fstab.${tenant}
# Start to populate internal jail RC configuration file
mkdir -p ${jailcfg}
# Cleanup jail sysctl.conf
echo "#Nothing here" > ${jailcfg}/sysctl.conf
if [ -n "$authorizedkeys" ]; then
mkdir ${jailcfg}/dot.ssh.root || die "Can't create ${jailcfg}/dot.ssh.root"
cp $authorizedkeys ${jailcfg}/dot.ssh.root/authorized_keys || die "Can't copy $authorizedkeys into ${jailcfg}/dot.ssh.root/"
fi
# Start to generate internal jail RC configuration file
cat > ${jailcfg}/rc.conf <<EOF
#Automatically generated ${tenant} RC configuration file
hostname=${tenant}
sshd_enable=YES
gateway_enable=YES
ipv6_gateway_enable=YES
EOF
# Start to generate jail.conf file
cat >> /etc/jail.conf <<EOF
${tenant} {
jid = ${id};
path = "${jailroot}";
# Because we are using jail on nanobsd, the jail directories are volatil (mounted into /var/jails)
# They didn't exist after a reboot, then we need to create jail directories with exec.prestart
# But mount.* instructions are called before exec.prestart, then we need to call mount manually
# into the exec.prestart
#mount.devfs;
#mount.fstab = "/etc/fstab.${tenant}";
#devfs_ruleset = 4;
host.hostname = "${tenant}";
vnet new;
allow.chflags = 1;
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.clean;
exec.consolelog = "/var/log/jail.${tenant}";
exec.poststop = "logger poststop jail ${tenant}";
# Commands to run on host before jail is created
exec.prestart = "logger pre-starting jail ${tenant}";
EOF
# we need to create all mount points
for dir in dev etc var cfg root bin sbin lib libexec usr conf/base; do
echo " exec.prestart += \"mkdir -p ${jailroot}/${dir}\";" >> /etc/jail.conf
done
# And manually use mount
cat >> /etc/jail.conf <<EOF
exec.prestart += "test -L ${jailroot}/tmp || ln -s /var/tmp ${jailroot}/tmp";
exec.prestart += "mount -F /etc/fstab.${tenant} -a";
exec.prestart += "mount -t devfs -o rw,ruleset=4 devfs ${jailroot}/dev";
# Copy reference and backuped files to /etc
exec.prestart += "test -d ${jailroot}/var/cron || cp -a /conf/base/ ${jailroot}";
exec.prestart += "cp -a ${jailcfg}/ ${jailroot}/etc/";
# Prevent diskless
exec.prestart += "test -f ${jailroot}/etc/diskless && rm ${jailroot}/etc/diskless || true";
EOF
# Parse nics variable (can be in form bridge0,vtnet4.4/10.0.0.1/24)
# And finish to populate jail RC and jail configuration file
while [ "$nics" ] ;do
ip=""
prefixlen=""
iter=${nics%%,*}
# Extract nicname/ip/prefixlen
nicname=${iter%%/*}
if echo $iter | grep -q '/'; then
ip=$(echo $iter | cut -d '/' -f 2)
prefixlen=$(echo $iter | cut -d '/' -f 3)
fi
# Check if this interface exist on the system
ifconfig -l | grep -q ${nicname} || echo "WARNING: interface ${nicname} not found"
# rc conf need to use _ in place of . in nicname
rcnicname=$(echo $nicname | tr '.' '_')
# Generate RC config line depending AF
if [ -n "$ip" ] || [ -n "$prefixlen" ]; then
echo $ip | grep -q ':' && value="_ipv6=\"inet6 $ip prefixlen $prefixlen\""
echo $ip | grep -q '\.' && value="=\"inet $ip/$prefixlen\""
else
value="=\"up\""
fi
# If it's a bridge, need to create epair (a added to bridge, b bound to jail)
# But multiple bridge can be added, then epair needs to use bridge id too
if echo $nicname | grep -q bridge; then
bid=${nicname#bridge}
rcnicname=epair${id}${bid}b
# Convert id into hexa
host=""
host=$(sysctl -n kern.hostid)
if [ ${host} -eq 0 ]; then
host=$(od -txC -A n -N2 /dev/urandom | tr -s ' ')
else
host=$(echo $host | od -txC -A n -N2 | tr -s ' ')
fi
hostfirst=$(echo ${host} | cut -d ' ' -f 1)
[ -z "${hostfirst}" ] && echo "WARNING bad MAC addresse generated: hostfirst is empty"
hostsecond=$(echo ${host} | cut -d ' ' -f 2)
[ -z "${hostsecond}" ] && echo "WARNING bad MAC addresse generated: hostsecond is empty"
#need to convert jail id and bid into hexa
if [ "$id" -gt 255 ]; then
cent=$(printf '%x\n' $((id / 255)))
unit=$(printf '%02x\n' $((id % 255)))
else
cent="0"
unit=$(printf '%02x\n' ${id})
fi
if [ "$bid" -lt 256 ]; then
bridgemac=$(printf '%02x\n' ${bid})
else
die "[BUG] bridge id bigged than 255, code not implemented"
fi
cat >> /etc/jail.conf <<EOF
vnet.interface += "epair${id}${bid}b";
exec.prestart += "ifconfig epair${id}${bid} create";
# fix bug that can create conflict on same LAN for epair
# To do: if bridge id bigger than 255
exec.prestart += "ifconfig epair${id}${bid}a ether 02:${bridgemac}:${hostfirst}:${hostsecond}:a${cent}:${unit}";
exec.prestart += "ifconfig epair${id}${bid}b ether 02:${bridgemac}:${hostfirst}:${hostsecond}:b${cent}:${unit}";
exec.prestart += "ifconfig epair${id}${bid}a up";
exec.prestart += "ifconfig ${nicname} addm epair${id}${bid}a up";
exec.poststop += "ifconfig ${nicname} deletem epair${id}${bid}a";
exec.poststop += "ifconfig epair${id}${bid}a destroy";
EOF
else
echo " vnet.interface += \"${nicname}\";" >> /etc/jail.conf
echo " exec.poststop += \"ifconfig ${nicname} -vnet ${id}\";" >> /etc/jail.conf
fi
echo ifconfig_${rcnicname}${value} >> ${jailcfg}/rc.conf
[ "$nics" = "$iter" ] && nics='' || nics="${nics#*,}"
done
if [ -n "$defaultgw" ]; then
if echo $defaultgw | grep -q ':'; then
echo "ipv6_defaultrouter=\"${defaultgw}\"" >> ${jailcfg}/rc.conf
else
echo "defaultrouter=\"${defaultgw}\"" >> ${jailcfg}/rc.conf
fi
fi
# Close jail configuration file
cat >> /etc/jail.conf <<EOF
exec.prestart += "logger jail ${tenant} pre-started";
exec.poststop += "umount ${jailroot}/dev";
exec.poststop += "umount -a -F /etc/fstab.${tenant}";
exec.poststop += "jail -r ${id}";
exec.poststop += "logger jail ${tenant} post-stopped";
}
EOF
{
echo "#!/bin/sh"
echo "id=$id"
} > /etc/jail.lastid
# Now enable this jail
sysrc -q jail_enable="YES" > /dev/null 2>&1
sysrc -q jail_parallel_start="YES" > /dev/null 2>&1
sysrc -q jail_list+=${tenant} > /dev/null 2>&1
echo "Jail is created, you can start it now (service jail start ${tenant})"
exit 0
}
# Delete jail
delete () {
[ -f /etc/jail.conf ] || die "ERROR: No jail configured"
grep -q "^${tenant} {" /etc/jail.conf || die "ERROR: No jail named $tenant configured"
jls -j ${tenant} > /dev/null 2>&1 && die "Jail is running: Stop it before delete it (service jail stop ${tenant})"
mount | grep -q "/var/jails/${tenant}/" && die "There are still some mount points regarding ${tenant}"
[ -f /etc/fstab.${tenant} ] && rm /etc/fstab.${tenant}
for i in etc var; do
[ -d /$i/jails/${tenant} ] && rm -rf /$i/jails/${tenant}
done
sed -i "" -e "/^$tenant {/,/^}/d" /etc/jail.conf
# Removing this jail from the autostart
sysrc -q jail_list-=${tenant} > /dev/null 2>&1
# If it's the last, disable jail and remove jail.conf
echo "Jail ${tenant} is deleted."
if ! grep -q '{' /etc/jail.conf; then
rm /etc/jail.conf || die "ERROR during deleting /etc/jail.conf"
rm /etc/jail.lastid || die "ERROR during deleting /etc/jail.lastid"
sysrc -q jail_enable="NO" > /dev/null 2>&1
fi
exit 0
}
### Main function ###
# User input check
if [ $# -le 0 ] ; then
echo "$0: Extraneous arguments supplied"
usage
fi
args=$(getopt cdf:g:hi:j: $*)
set -- $args
for i; do
case "$i" in
-c)
($delete) && die "delete and create are mutualy exclusive"
create=true
delete=false
shift
;;
-d)
($create) && die "delete and create are mutualy exclusive"
create=false
delete=true
shift
;;
-f)
[ -f $2 ] || die "Can't found file $2"
authorizedkeys=$2
shift
shift
;;
-g)
defaultgw=$2
shift
shift
;;
-h)
shift
usage
;;
-i)
nics=$2
shift
shift
;;
-j)
tenant=$2
shift
shift
;;
--)
shift
break
esac
done
[ -z "$tenant" ] && die "jail name mandatory"
($create) && create
($delete) && delete