Skip to content

Instantly share code, notes, and snippets.

@deriamis
Last active November 18, 2016 05:09
Show Gist options
  • Save deriamis/6388502d07968342bb83 to your computer and use it in GitHub Desktop.
Save deriamis/6388502d07968342bb83 to your computer and use it in GitHub Desktop.
Install an ACS look-alike using freely available software!
#!/bin/bash
# This script assumes a pre-installed Debian testing release, or at least one that
# has systemd 228 or newer installed. There should be no customizations to the server.
# We make some assumptions about static IP addresses and such. Make sure you read over
# this script to ensure it sets up something that will work on your network.
# An NTP server is set up with some good defaults for operating as a time source.
# The TACACS+ and RADIUS daemons are left unconfigured, as every network is different.
# You can log into a virtual machine directly with "machinectl shell root@$MACHINE_NAME /bin/bash"
# Virtual machines installed:
# * ntpd : NTP service VM
# * tacacs : TACACS+ service VM
# * radius : RADIUS service VM
# ********* NOTE ***********
# This installer changes the network configuration and must be run in screen!
# ********* NOTE ***********
######### Edit the following for your own configuration ###########
EMAIL="root@localhost"
MACHINES_DIR="/var/lib/machines"
BASE_PKGS=( curl wget net-tools iproute2 iputils-ping dbus locales locales-all )
ROOT_PKGS=( schroot binutils debootstrap procps psmisc sysstat ngrep iotop iftop tcpdump vim-nox systemd-container iptables-persistent dbus )
DEB_MIRROR="http://mirrors.kernel.org/debian/"
BRIDGE_IFS=( eth0 )
BRIDGE_NAME="br0"
BRIDGE_IP="10.0.1.1"
BRIDGE_NETWORK="10.0.1.0"
BRIDGE_BCAST="10.0.1.255"
BRIDGE_MASK="255.255.255.0"
BRIDGE_MASK_BITS="24"
EXTERNAL_IP_SRC="dhcp"
EXTERNAL_IP="192.168.1.101"
EXTERNAL_NETWORK="192.168.1.0"
EXTERNAL_MASK="255.255.255.0"
EXTERNAL_MASK_BITS="24"
EXTERNAL_GATEWAY="192.168.1.1"
EXTERNAL_DNS="8.8.8.8"
NTP_LEAP_SECONDS_URL="ftp://time.nist.gov/pub/leap-seconds.list"
NTP_SERVERS=(
"server tick.uh.edu iburst minpoll 6"
"server time.nist.gov iburst minpoll 8"
"server utcnist2.colorado.edu iburst minpoll 4"
"server t1.timegps.net iburst minpoll 6"
"server time-a.timefreq.bldrdoc.gov iburst minpoll 8"
)
####### Do not edit below! #########
store_iptables_rules() {
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
}
container_state() {
local MACHINE_NAME=$1
if [[ -z $MACHINE_NAME ]]; then
echo "No container given!" >&2
exit 1
fi
systemctl -q is-active "systemd-nspawn@${MACHINE_NAME}.service"
RET=$?
case $RET in
0) echo "started" && return 0 ;;
3) echo "stopped" && return 0 ;;
*) echo "unknown" && return 1 ;;
esac
}
stop_container() {
local MACHINE_NAME=$1
[[ -z $MACHINE_NAME ]] && return 1
if [[ $(container_state "${MACHINE_NAME}") == started ]]; then
if ! systemctl stop "systemd-nspawn@${MACHINE_NAME}.service"; then
echo "Could not stop container ${MACHINE_NAME}" >&2
exit 1
fi
while [[ $(container_state "${MACHINE_NAME}") != stopped ]]; do :; done
elif [[ $(container_state "${MACHINE_NAME}") != stopped ]]; then
echo "Unknown error stopping container ${MACHINE_NAME}" >&2
exit 1
fi
}
start_container() {
local MACHINE_NAME=$1
[[ -z $MACHINE_NAME ]] && return 1
if [[ $(container_state "${MACHINE_NAME}") == stopped ]]; then
if ! systemctl start "systemd-nspawn@${MACHINE_NAME}.service"; then
echo "Could not start container ${MACHINE_NAME}" >&2
exit 1
fi
while ! systemd-run --machine "${MACHINE_NAME}" /usr/bin/env >/dev/null 2>&1; do :; done
elif [[ $(container_state "${MACHINE_NAME}") != started ]]; then
echo "Unknown error starting container ${MACHINE_NAME}" >&2
exit 1
fi
}
run_cmd_in() {
local MACHINE_NAME=$1; shift
local EXEC_CMD
[[ -z $MACHINE_NAME ]] && return 1
if [[ $MACHINE_NAME == / || $MACHINE_NAME == root ]]; then
EXEC_CMD=$1; shift
else
EXEC_CMD=( systemd-run --quiet -t --machine "${MACHINE_NAME}" )
start_container "${MACHINE_NAME}"
fi
"${EXEC_CMD[@]}" "$@"
}
run_cmd_chroot() {
local MACHINE_NAME=$1; shift
local EXEC_CMD
[[ -z $MACHINE_NAME ]] && return 1
if [[ $MACHINE_NAME == / || $MACHINE_NAME == root ]]; then
EXEC_CMD=$1; shift
else
EXEC_CMD=( schroot -c "${MACHINE_NAME}" -- )
fi
LANG=C "${EXEC_CMD[@]}" "$@"
}
deb() {
local MACHINE_NAME=$1; shift
local APT_BIN
local MACHINE_ROOT
[[ -z $MACHINE_NAME ]] && return 1
export DEBIAN_FRONTEND=noninteractive
if [[ $MACHINE_NAME == / || $MACHINE_NAME == root ]]; then
MACHINE_ROOT=""
else
MACHINE_ROOT="${MACHINES_DIR}/${MACHINE_NAME}"
fi
if [[ -x ${MACHINE_ROOT}/usr/bin/aptitude ]]; then
APT_BIN=( "/usr/bin/aptitude" )
else
APT_BIN=( "/usr/bin/apt-get" )
fi
run_cmd_chroot "${MACHINE_NAME}" "${APT_BIN[@]}" -y -q -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" "$@"
export -n DEBIAN_FRONTEND
unset DEBIAN_FRONTEND
}
mk_template() {
local TEMPLATE_NAME=$1; shift
local EXTRA_PKGS=( "$@" )
local TEMPLATE_ROOT="${MACHINES_DIR}/${TEMPLATE_NAME}"
[[ -z ${TEMPLATE_NAME} ]] && return 1
if [[ ! ${TEMPLATE_NAME} =~ ^[A-Za-z0-9_-]+$ ]]; then
echo "Invalid template name ${TEMPLATE_NAME}" >&2
exit 1
fi
if [[ -d ${TEMPLATE_ROOT} ]]; then
echo "Directory ${TEMPLATE_ROOT} already exists!" >&2
exit 1
fi
mkdir -p "${TEMPLATE_ROOT}"
IFS=','
local INSTALL_PKGS="${BASE_PKGS[*]}"
(( ${#EXTRA_PKGS[@]} != 0 )) && INSTALL_PKGS+=",${EXTRA_PKGS[*]}"
unset IFS
debootstrap --arch='amd64' --include="${INSTALL_PKGS}" testing "${MACHINES_DIR}/${TEMPLATE_NAME}" "${DEB_MIRROR}"
chroot "${TEMPLATE_ROOT}" systemctl enable dbus.service
ln -s /dev/null "${TEMPLATE_ROOT}/etc/systemd/network/80-container-host0.network"
cat >"${TEMPLATE_ROOT}/usr/sbin/policy-rc.d" <<'EOF'
#!/bin/sh
exit 101
EOF
chmod a+x "${TEMPLATE_ROOT}/usr/sbin/policy-rc.d"
chroot "${TEMPLATE_ROOT}" dpkg-divert --divert /usr/bin/ischroot.debianutils --rename /usr/bin/ischroot
ln -s /bin/true "${TEMPLATE_ROOT}/usr/bin/ischroot"
> "${TEMPLATE_ROOT}/etc/network/interfaces"
chroot "${TEMPLATE_ROOT}" systemctl enable systemd-networkd.service
chroot "${TEMPLATE_ROOT}" systemctl enable systemd-resolved.service
rm -f "${TEMPLATE_ROOT}/etc/securetty"
chroot "${TEMPLATE_ROOT}" systemctl enable dbus.service
sed -i 's/^#\s*\(en_US.UTF-8 UTF-8\)$/\1/' "${TEMPLATE_ROOT}/etc/locale.gen"
chroot "${TEMPLATE_ROOT}" update-locale LANG=en_US.UTF8
tar \
--exclude='./etc/machine-id' \
--exclude='./var/lock/*' \
--exclude='./var/run/*' \
--exclude='./var/lib/dbus/*' \
--exclude='./var/cache/apt/*' \
-C "${TEMPLATE_ROOT}" \
-czvpf "${MACHINES_DIR}/${TEMPLATE_NAME}.tar.gz" ./
rm -rfv "${MACHINES_DIR}/${TEMPLATE_NAME}"
mkdir -p /etc/systemd/system/systemd-nspawn@.service.d/
cat >/etc/systemd/system/systemd-nspawn@.service.d/override.conf <<EOF
[Service]
ExecStart=
ExecStart=/usr/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-bridge=${BRIDGE_NAME} --machine=%I
EOF
systemctl daemon-reload
}
mk_container() {
local MACHINE_NAME=$1
local TEMPLATE_NAME=$2
local MACHINE_IP=$3
local MACHINE_ROOT="${MACHINES_DIR}/${MACHINE_NAME}"
local TEMPLATE_FILE="${MACHINES_DIR}/${TEMPLATE_NAME}.tar.gz"
shift 3
local EXTRA_PKGS=( "$@" )
[[ -z ${MACHINE_NAME} ]] && return 1
if [[ ${MACHINE_NAME} == root || ! ${MACHINE_NAME} =~ ^[A-Za-z0-9_-]+$ ]]; then
echo "Invalid container name ${MACHINE_NAME}" >&2
exit 1
fi
if [[ -d ${MACHINE_ROOT} ]]; then
echo "Machine ${MACHINE_NAME} already exists!" >&2
exit 1
fi
if [[ ! -f ${TEMPLATE_FILE} ]]; then
echo "Template file ${TEMPLATE_FILE} does not exist!" >&2
exit 1
fi
mkdir -p "${MACHINE_ROOT}"
tar -C "${MACHINE_ROOT}" -zxvpf "${TEMPLATE_FILE}"
deb "${MACHINE_NAME}" update
systemd-machine-id-setup --root "${MACHINE_ROOT}"
cat >"/etc/schroot/chroot.d/${MACHINE_NAME}.conf" <<EOF
[${MACHINE_NAME}]
description=Machine '${MACHINE_NAME}' root directory
type=directory
directory=${MACHINE_ROOT}
EOF
cat >"${MACHINE_ROOT}/etc/systemd/network/host0.network" <<EOF
[Match]
Name=host0
[Network]
DNS=${EXTERNAL_DNS}
Address=${MACHINE_IP}/${BRIDGE_MASK_BITS}
Gateway=${BRIDGE_IP}
EOF
cat >"${MACHINE_ROOT}/etc/machine-info" <<'EOF'
CHASSIS=container
DEPLOYMENT=production
EOF
cat >>"${MACHINE_ROOT}/etc/hosts" <<EOF
${MACHINE_IP} ${MACHINE_NAME}
EOF
echo "${MACHINE_NAME}" > "${MACHINE_ROOT}/etc/hosts"
iptables -t nat -A POSTROUTING -o br0 -s ${MACHINE_IP} -j MASQUERADE
store_iptables_rules
systemctl enable systemd-nspawn@${MACHINE_NAME}.service
start_container "${MACHINE_NAME}"
}
ntpd_setup() {
local MACHINE_NAME=$1
local EMAIL=$2
local MACHINE_ROOT
local MACHINE_IP
[[ -z $MACHINE_NAME ]] && return 1
if [[ $MACHINE_NAME == / ]]; then
MACHINE_ROOT=""
else
MACHINE_ROOT="${MACHINES_DIR}/${MACHINE_NAME}"
fi
local NTP_CONF_FILE="${MACHINE_ROOT}/etc/ntp.conf"
local NTP_CRON_FILE="${MACHINE_ROOT}/etc/cron.d/ntp"
deb "${MACHINE_NAME}" update
deb "${MACHINE_NAME}" install ntp
run_cmd_chroot "${MACHINE_NAME}" /usr/sbin/groupadd -g 117 -r ntp
run_cmd_chroot "${MACHINE_NAME}" /usr/sbin/useradd -d /home/ntp -M -g ntp -r -u 111 -s /bin/false ntp
> "${NTP_CONF_FILE}"
for line in "${NTP_SERVERS[@]}"; do
echo "$line" >> "${NTP_CONF_FILE}"
done
cat >>"${NTP_CONF_FILE}" <<EOF
driftfile /var/lib/ntp/drift
leapfile /var/lib/ntp/leap-seconds
keysdir /etc/ntp
keys /etc/ntp/ntp.keys
includefile /etc/ntp/pw
statsdir /var/log/ntpstats
trustedkey 1 15
requestkey 1
controlkey 1
logconfig =syncevents +peerevents +sysevents +allclock
discard average 9 minimum 5
restrict default kod limited nomodify notrap nopeer noquery
restrict -6 default kod limited nomodify notrap nopeer noquery
restrict 127.0.0.1
restrict -6 ::1
EOF
cat >"${MACHINE_ROOT}/usr/local/bin/ntp_genkeys" <<'EOF'
#!/bin/bash
cd /etc/ntp
if [[ $1 == initial ]]; then
echo "crypto pw $(tr -dc '!@$%^&*(){}[]:;,.<>/?\~+=_A-Z-a-z-0-9-' < /dev/urandom | head -c32)" > /etc/ntp/pw
chmod 640 /etc/ntp/pw
ntp-keygen -b 2047 -c RSA-SHA1 -q $(awk '/crypto pw/{print $3}' /etc/ntp/pw)
ntp-keygen -b 2048 -c RSA-SHA1 -H -T -I -p $(awk '/crypto pw/{print $3}' /etc/ntp/pw)
elif [[ $1 == host ]]; then
ntp-keygen -b 2048 -c RSA-SHA1 -T -q $(awk '/crypto pw/{print $3}' /etc/ntp/pw)
elif [[ $1 == client ]]; then
ntp-keygen -b 2047 -c RSA-SHA1 -q $(awk '/crypto pw/{print $3}' /etc/ntp/pw)
else
echo "Unknown option: $1" >&2
exit 1
fi
EOF
chmod +x "${MACHINE_ROOT}/usr/local/bin/ntp_genkeys"
local DAY=$(( $(date +%d) - 1)); (( DAY > 28 )) && DAY=28
local MONTH=$(date +%m)
cat >"${NTP_CRON_FILE}" <<EOF
SHELL=/bin/bash
MAILTO=${EMAIL}
00 22 28 * * ntp sleep \$[(RANDOM/368)+10]m ; /usr/bin/wget -O /var/lib/ntp/leap-seconds "${NTP_LEAP_SECONDS_URL}" ; /bin/systemctl restart ntpd.service
00 03 ${DAY} * * root /usr/local/bin/ntp_genkeys host
EOF
chmod +x "${NTP_CRON_FILE}"
echo "broadcast ${BRIDGE_BCAST} autokey" >> "${NTP_CONF_FILE}"
echo "restrict ${BRIDGE_NETWORK} mask ${BRIDGE_MASK} nomodify notrap nopeer" >> "${NTP_CONF_FILE}"
echo "restrict ${EXTERNAL_NETWORK} mask ${EXTERNAL_MASK} nomodify notrap nopeer" >> "${NTP_CONF_FILE}"
run_cmd_chroot "${MACHINE_NAME}" /usr/bin/wget -O /var/lib/ntp/leap-seconds "${NTP_LEAP_SECONDS_URL}"
run_cmd_chroot "${MACHINE_NAME}" /bin/chown ntp.ntp /var/lib/ntp/leap-seconds
run_cmd_chroot "${MACHINE_NAME}" /usr/local/bin/ntp_genkeys initial
MACHINE_IP=$(grep -oP '(?<=^Address=)[^/]+' "${MACHINE_ROOT}/etc/systemd/network/host0.network")
iptables -t nat -A PREROUTING -p tcp --dport 123 -j DNAT --to-destination "${MACHINE_IP}:123"
iptables -A FORWARD -i "${BRIDGE_NAME}" -p tcp --dport 123 -j ACCEPT
store_iptables_rules
systemctl -M "${MACHINE_NAME}" enable ntp.service
systemctl -M "${MACHINE_NAME}" start ntp.service
}
tacacs_setup() {
local MACHINE_NAME=$1
local EMAIL=$2
local MACHINE_ROOT
local MACHINE_IP
[[ -z $MACHINE_NAME ]] && return 1
if [[ $MACHINE_NAME == / ]]; then
MACHINE_ROOT=""
else
MACHINE_ROOT="${MACHINES_DIR}/${MACHINE_NAME}"
fi
deb "${MACHINE_NAME}" update
deb "${MACHINE_NAME}" install tacacs+
MACHINE_IP=$(grep -oP '(?<=^Address=)[^/]+' "${MACHINE_ROOT}/etc/systemd/network/host0.network")
iptables -t nat -A PREROUTING -p tcp --dport 49 -j DNAT --to-destination "${MACHINE_IP}:49"
iptables -A FORWARD -i "${BRIDGE_NAME}" -p tcp --dport 49 -j ACCEPT
store_iptables_rules
cat >"${MACHINE_ROOT}/etc/systemd/system/tacacs.service" <<'EOF'
[Unit]
Descrption=TACACS+ Authentication Daemon
[Service]
ExecStart=/usr/sbin/tac_plus -C /etc/tacacs+/tac_plus.conf -s -d 64
KillMode=process
Restart=on-failure
RestartSec=60s
TimeoutSec=60s
[Install]
WantedBy=multi-user.target
EOF
systemctl -M "${MACHINE_NAME}" daemon-reload
systemctl -M "${MACHINE_NAME}" enable tacacs.service
systemctl -M "${MACHINE_NAME}" start tacacs.service
}
radius_setup() {
local MACHINE_NAME=$1
local EMAIL=$2
local MACHINE_ROOT
local MACHINE_IP
[[ -z $MACHINE_NAME ]] && return 1
if [[ $MACHINE_NAME == / ]]; then
MACHINE_ROOT=""
else
MACHINE_ROOT="${MACHINES_DIR}/${MACHINE_NAME}"
fi
deb "${MACHINE_NAME}" update
deb "${MACHINE_NAME}" install freeradius
MACHINE_IP=$(grep -oP '(?<=^Address=)[^/]+' "${MACHINE_ROOT}/etc/systemd/network/host0.network")
iptables -t nat -A PREROUTING -p udp --dport 1645 -j DNAT --to-destination "${MACHINE_IP}:1645"
iptables -t nat -A PREROUTING -p udp --dport 1646 -j DNAT --to-destination "${MACHINE_IP}:1646"
iptables -A FORWARD -i "${BRIDGE_NAME}" -p udp --dport 1645 -j ACCEPT
iptables -A FORWARD -i "${BRIDGE_NAME}" -p udp --dport 1646 -j ACCEPT
store_iptables_rules
cat >"/etc/systemd/system/freeradius.service" <<'EOF'
[Unit]
Descrption=FreeRADIUS RADIUS Authentication Daemon
[Service]
ExecStart=/usr/sbin/freeradius -f -d /etc/freeradius
KillMode=process
Restart=on-failure
RestartSec=60s
TimeoutSec=60s
[Install]
WantedBy=multi-user.target
EOF
systemctl -M "${MACHINE_NAME}" daemon-reload
systemctl -M "${MACHINE_NAME}" enable freeradius.service
systemctl -M "${MACHINE_NAME}" start freeradius.service
}
mk_net_bridge() {
(( ${#BRIDGE_IFS[*]} == 0 )) && return 1
[[ ${BRIDGE_NAME} ]] || return 1
[[ ${EXTERNAL_IP_SRC} != dhcp && ${EXTERNAL_IP_SRC} != static ]] && return 1
local interface
cp /etc/network/interfaces{,.pre-${BRIDGE_NAME}}
>/etc/network/interfaces
cat >/etc/systemd/network/"bridge-${BRIDGE_NAME}.netdev" <<'EOF'
[NetDev]
Name=br0
Kind=bridge
EOF
for interface in "${BRIDGE_IFS[@]}"; do
cat >/etc/systemd/network/"${interface}.network" <<EOF
[Match]
Name=${interface}
[Network]
Bridge=br0
EOF
done
if [[ ${EXTERNAL_IP_SRC} == dhcp ]]; then
cat >/etc/systemd/network/"bridge-${BRIDGE_NAME}.network" <<EOF
[Match]
Name=br0
[Address]
Address=${BRIDGE_IP}/${BRIDGE_MASK_BITS}
[Network]
DHCP=ipv4
EOF
elif [[ ${EXTERNAL_IP_SRC} == static ]]; then
cat >/etc/systemd/network/"bridge-${BRIDGE_NAME}.network" <<EOF
[Match]
Name=br0
[Network]
Gateway=${EXTERNAL_GATEWAY}
DNS=${EXTERNAL_DNS}
[Address]
Address=${BRIDGE_IP}/${BRIDGE_MASK_BITS}
[Address]
Address=${EXTERNAL_IP}/${EXTERNAL_MASK_BITS}
EOF
fi
mv /etc/resolv.conf{,.pre-${BRIDGE_NAME}}
ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
systemctl enable systemd-networkd.service
systemctl enable systemd-resolved.service
echo -e "\nNetwork interfaces coming down for reconfiguration."
echo -e "You will need to reconnect your SSH session!\n"
sleep 5
ip address flush dev eth0
systemctl restart networking.service
systemctl start systemd-networkd.service
systemctl start systemd-resolved.service
# Restrict bridge traffic through the firewall
iptables -A FORWARD -i "${BRIDGE_NAME}" -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -o "${BRIDGE_NAME}" -j ACCEPT
iptables -P FORWARD DROP
store_iptables_rules
}
initial_setup() {
# Install packages
deb 'root' update
deb 'root' install aptitude
deb 'root' install ssh-server
deb 'root' install "${ROOT_PKGS[@]}"
# Allow IP forwarding
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p
# Create an extremely basic firewall
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -m state --state INVALID -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -P INPUT DROP
store_iptables_rules
# Make sure the virtual machines we create will start on boot
systemctl enable machines.target
# systemd v228 (still) doesn't do veth networking right
mk_net_bridge
}
syslog_setup() {
iptables -A INPUT -p tcp --dport 514 -j ACCEPT
store_iptables_rules
cat >/etc/rsyslog.d/remote.conf <<EOF
module(load="imtcp")
input(type="imtcp" port="514")
\$AllowedSender TCP, 127.0.0.1, ${BRIDGE_NETWORK}/${BRIDGE_MASK_BITS}, ${EXTERNAL_NETWORK}/${EXTERNAL_MASK_BITS}
EOF
systemctl restart rsyslog.service
}
container_setup() {
# Create our initial template
mk_template debian_testing
# Create and set up the NTP server
mk_container ntpd debian_testing 10.0.1.123
ntpd_setup ntpd "${EMAIL}"
# Create and set up the TACACS+ server
mk_container tacacs debian_testing 10.0.1.49
tacacs_setup tacacs
# Create and set up the RADIUS server
mk_container radius debian_testing 10.0.1.164
radius_setup radius
}
main() {
# Get the server ready to make containers
initial_setup
# Configure remote syslog receive
syslog_setup
# Make the necessary containers
container_setup
# Finish the firewall setup
iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset
iptables -A FORWARD -i "${BRIDGE_NAME}" -p tcp -j REJECT --reject-with tcp-reset
}
if [[ -z $STY ]]; then
echo -e "\nYou must run this script from inside a screen environment.\n" >&2
exit 1
fi
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment