Sanitize lxc-download script with shellcheck

Fix potential CVE when using spaces in LXC names

Signed-off-by: William Gathoye <william@gathoye.be>
This commit is contained in:
William Gathoye 2017-05-03 11:59:18 +02:00 committed by Serge Hallyn
parent ed38feee45
commit 3f7be9d079

View File

@ -67,14 +67,14 @@ export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin
# Some useful functions # Some useful functions
cleanup() { cleanup() {
if [ -d "$DOWNLOAD_TEMP" ]; then if [ -d "${DOWNLOAD_TEMP}" ]; then
rm -Rf $DOWNLOAD_TEMP rm -Rf "${DOWNLOAD_TEMP}"
fi fi
} }
wget_wrapper() { wget_wrapper() {
for i in $(seq 3); do for i in $(seq 3); do
if wget $@; then if wget "$@"; then
return 0 return 0
fi fi
done done
@ -83,26 +83,26 @@ wget_wrapper() {
} }
download_file() { download_file() {
if ! wget_wrapper -T 30 -q https://${DOWNLOAD_SERVER}/$1 -O $2 >/dev/null 2>&1; then if ! wget_wrapper -T 30 -q "https://${DOWNLOAD_SERVER}/$1" -O "$2" >/dev/null 2>&1; then
if ! wget_wrapper -T 30 -q http://${DOWNLOAD_SERVER}/$1 -O $2 >/dev/null 2>&1; then if ! wget_wrapper -T 30 -q "http://${DOWNLOAD_SERVER}/$1" -O "$2" >/dev/null 2>&1; then
if [ "$3" = "noexit" ]; then if [ "$3" = "noexit" ]; then
return 1 return 1
else else
echo "ERROR: Failed to download http://${DOWNLOAD_SERVER}/$1" 1>&2 echo "ERROR: Failed to download http://${DOWNLOAD_SERVER}/$1" 1>&2
exit 1 exit 1
fi fi
elif [ "$DOWNLOAD_SHOW_HTTP_WARNING" = "true" ]; then elif [ "${DOWNLOAD_SHOW_HTTP_WARNING}" = "true" ]; then
DOWNLOAD_SHOW_HTTP_WARNING="false" DOWNLOAD_SHOW_HTTP_WARNING="false"
echo "WARNING: Failed to download the file over HTTPs." 1>&2 echo "WARNING: Failed to download the file over HTTPs." 1>&2
echo -n " The file was instead download over HTTP. " 1>&2 echo " The file was instead download over HTTP. " 1>&2
echo "A server replay attack may be possible!" 1>&2 echo "A server replay attack may be possible!" 1>&2
fi fi
fi fi
} }
download_sig() { download_sig() {
if ! download_file $1 $2 noexit; then if ! download_file "$1" "$2" noexit; then
if [ "$DOWNLOAD_VALIDATE" = "true" ]; then if [ "${DOWNLOAD_VALIDATE}" = "true" ]; then
if [ "$3" = "normal" ]; then if [ "$3" = "normal" ]; then
echo "ERROR: Failed to download http://${DOWNLOAD_SERVER}/$1" 1>&2 echo "ERROR: Failed to download http://${DOWNLOAD_SERVER}/$1" 1>&2
exit 1 exit 1
@ -116,30 +116,31 @@ download_sig() {
} }
gpg_setup() { gpg_setup() {
if [ "$DOWNLOAD_VALIDATE" = "false" ]; then if [ "${DOWNLOAD_VALIDATE}" = "false" ]; then
return return
fi fi
if [ "$DOWNLOAD_READY_GPG" = "true" ]; then if [ "${DOWNLOAD_READY_GPG}" = "true" ]; then
return return
fi fi
echo "Setting up the GPG keyring" echo "Setting up the GPG keyring"
mkdir -p "$DOWNLOAD_TEMP/gpg" mkdir -p "${DOWNLOAD_TEMP}/gpg"
chmod 700 "$DOWNLOAD_TEMP/gpg" chmod 700 "${DOWNLOAD_TEMP}/gpg"
export GNUPGHOME="$DOWNLOAD_TEMP/gpg" export GNUPGHOME="${DOWNLOAD_TEMP}/gpg"
success= success=
for i in $(seq 3); do for i in $(seq 3); do
if gpg --keyserver $DOWNLOAD_KEYSERVER \ if gpg --keyserver "${DOWNLOAD_KEYSERVER}" \
--recv-keys ${DOWNLOAD_KEYID} >/dev/null 2>&1; then --recv-keys "${DOWNLOAD_KEYID}" >/dev/null 2>&1; then
success=1 success=1
break break
fi fi
break
done done
if [ -z "$success" ]; then if [ -z "${success}" ]; then
echo "ERROR: Unable to fetch GPG key from keyserver." echo "ERROR: Unable to fetch GPG key from keyserver."
exit 1 exit 1
fi fi
@ -148,15 +149,15 @@ gpg_setup() {
} }
gpg_validate() { gpg_validate() {
if [ "$DOWNLOAD_VALIDATE" = "false" ]; then if [ "${DOWNLOAD_VALIDATE}" = "false" ]; then
if [ "$DOWNLOAD_SHOW_GPG_WARNING" = "true" ]; then if [ "${DOWNLOAD_SHOW_GPG_WARNING}" = "true" ]; then
echo "WARNING: Running without gpg validation!" 1>&2 echo "WARNING: Running without gpg validation!" 1>&2
fi fi
DOWNLOAD_SHOW_GPG_WARNING="false" DOWNLOAD_SHOW_GPG_WARNING="false"
return 0 return 0
fi fi
if ! gpg --verify $1 >/dev/zero 2>&1; then if ! gpg --verify "$1" >/dev/null 2>&1; then
echo "ERROR: Invalid signature for $1" 1>&2 echo "ERROR: Invalid signature for $1" 1>&2
exit 1 exit 1
fi fi
@ -164,10 +165,16 @@ gpg_validate() {
in_userns() { in_userns() {
[ -e /proc/self/uid_map ] || { echo no; return; } [ -e /proc/self/uid_map ] || { echo no; return; }
while read line; do while read -r line; do
fields=$(echo $line | awk '{ print $1 " " $2 " " $3 }') fields="$(echo "$line" | awk '{ print $1 " " $2 " " $3 }')"
[ "$fields" = "0 0 4294967295" ] && { echo no; return; } || true if [ "${fields}" = "0 0 4294967295" ]; then
echo $fields | grep -q " 0 1$" && { echo userns-root; return; } || true echo no;
return;
fi
if echo "${fields}" | grep -q " 0 1$"; then
echo userns-root;
return;
fi
done < /proc/self/uid_map done < /proc/self/uid_map
[ "$(cat /proc/self/uid_map)" = "$(cat /proc/1/uid_map)" ] && \ [ "$(cat /proc/self/uid_map)" = "$(cat /proc/1/uid_map)" ] && \
@ -180,11 +187,11 @@ relevant_file() {
if [ -e "${FILE_PATH}-${DOWNLOAD_MODE}" ]; then if [ -e "${FILE_PATH}-${DOWNLOAD_MODE}" ]; then
FILE_PATH="${FILE_PATH}-${DOWNLOAD_MODE}" FILE_PATH="${FILE_PATH}-${DOWNLOAD_MODE}"
fi fi
if [ -e "$FILE_PATH.${DOWNLOAD_COMPAT_LEVEL}" ]; then if [ -e "${FILE_PATH}.${DOWNLOAD_COMPAT_LEVEL}" ]; then
FILE_PATH="${FILE_PATH}.${DOWNLOAD_COMPAT_LEVEL}" FILE_PATH="${FILE_PATH}.${DOWNLOAD_COMPAT_LEVEL}"
fi fi
echo $FILE_PATH echo "${FILE_PATH}"
} }
usage() { usage() {
@ -224,11 +231,9 @@ EOF
return 0 return 0
} }
options=$(getopt -o d:r:a:hl -l dist:,release:,arch:,help,list,variant:,\ if ! options=$(getopt -o d:r:a:hl -l dist:,release:,arch:,help,list,variant:,\
server:,keyid:,keyserver:,no-validate,flush-cache,force-cache,name:,path:,\ server:,keyid:,keyserver:,no-validate,flush-cache,force-cache,name:,path:,\
rootfs:,mapped-uid:,mapped-gid: -- "$@") rootfs:,mapped-uid:,mapped-gid: -- "$@"); then
if [ $? -ne 0 ]; then
usage usage
exit 1 exit 1
fi fi
@ -238,36 +243,36 @@ while :; do
case "$1" in case "$1" in
-h|--help) usage && exit 1;; -h|--help) usage && exit 1;;
-l|--list) DOWNLOAD_LIST_IMAGES="true"; shift 1;; -l|--list) DOWNLOAD_LIST_IMAGES="true"; shift 1;;
-d|--dist) DOWNLOAD_DIST=$2; shift 2;; -d|--dist) DOWNLOAD_DIST="$2"; shift 2;;
-r|--release) DOWNLOAD_RELEASE=$2; shift 2;; -r|--release) DOWNLOAD_RELEASE="$2"; shift 2;;
-a|--arch) DOWNLOAD_ARCH=$2; shift 2;; -a|--arch) DOWNLOAD_ARCH="$2"; shift 2;;
--variant) DOWNLOAD_VARIANT=$2; shift 2;; --variant) DOWNLOAD_VARIANT="$2"; shift 2;;
--server) DOWNLOAD_SERVER=$2; shift 2;; --server) DOWNLOAD_SERVER="$2"; shift 2;;
--keyid) DOWNLOAD_KEYID=$2; shift 2;; --keyid) DOWNLOAD_KEYID="$2"; shift 2;;
--keyserver) DOWNLOAD_KEYSERVER=$2; shift 2;; --keyserver) DOWNLOAD_KEYSERVER="$2"; shift 2;;
--no-validate) DOWNLOAD_VALIDATE="false"; shift 1;; --no-validate) DOWNLOAD_VALIDATE="false"; shift 1;;
--flush-cache) DOWNLOAD_FLUSH_CACHE="true"; shift 1;; --flush-cache) DOWNLOAD_FLUSH_CACHE="true"; shift 1;;
--force-cache) DOWNLOAD_FORCE_CACHE="true"; shift 1;; --force-cache) DOWNLOAD_FORCE_CACHE="true"; shift 1;;
--name) LXC_NAME=$2; shift 2;; --name) LXC_NAME="$2"; shift 2;;
--path) LXC_PATH=$2; shift 2;; --path) LXC_PATH="$2"; shift 2;;
--rootfs) LXC_ROOTFS=$2; shift 2;; --rootfs) LXC_ROOTFS="$2"; shift 2;;
--mapped-uid) LXC_MAPPED_UID=$2; shift 2;; --mapped-uid) LXC_MAPPED_UID="$2"; shift 2;;
--mapped-gid) LXC_MAPPED_GID=$2; shift 2;; --mapped-gid) LXC_MAPPED_GID="$2"; shift 2;;
*) break;; *) break;;
esac esac
done done
# Check for required binaries # Check for required binaries
for bin in tar xz wget; do for bin in tar xz wget; do
if ! type $bin >/dev/null 2>&1; then if ! command -V "${bin}" >/dev/null 2>&1; then
echo "ERROR: Missing required tool: $bin" 1>&2 echo "ERROR: Missing required tool: ${bin}" 1>&2
exit 1 exit 1
fi fi
done done
# Check for GPG # Check for GPG
if [ "$DOWNLOAD_VALIDATE" = "true" ]; then if [ "${DOWNLOAD_VALIDATE}" = "true" ]; then
if ! type gpg >/dev/null 2>&1; then if ! command -V gpg >/dev/null 2>&1; then
echo "ERROR: Missing recommended tool: gpg" 1>&2 echo "ERROR: Missing recommended tool: gpg" 1>&2
echo "You can workaround this by using --no-validate." 1>&2 echo "You can workaround this by using --no-validate." 1>&2
exit 1 exit 1
@ -275,18 +280,18 @@ if [ "$DOWNLOAD_VALIDATE" = "true" ]; then
fi fi
# Check that we have all variables we need # Check that we have all variables we need
if [ -z "$LXC_NAME" ] || [ -z "$LXC_PATH" ] || [ -z "$LXC_ROOTFS" ]; then if [ -z "${LXC_NAME}" ] || [ -z "${LXC_PATH}" ] || [ -z "${LXC_ROOTFS}" ]; then
if [ "$DOWNLOAD_LIST_IMAGES" != "true" ]; then if [ "${DOWNLOAD_LIST_IMAGES}" != "true" ]; then
echo "ERROR: Not running through LXC." 1>&2 echo "ERROR: Not running through LXC." 1>&2
exit 1 exit 1
fi fi
fi fi
USERNS=$(in_userns) USERNS="$(in_userns)"
if [ "$USERNS" != "no" ]; then if [ "${USERNS}" != "no" ]; then
if [ "$USERNS" = "yes" ]; then if [ "${USERNS}" = "yes" ]; then
if [ -z "$LXC_MAPPED_UID" ] || [ "$LXC_MAPPED_UID" = "-1" ]; then if [ -z "${LXC_MAPPED_UID}" ] || [ "${LXC_MAPPED_UID}" = "-1" ]; then
echo "ERROR: In a user namespace without a map." 1>&2 echo "ERROR: In a user namespace without a map." 1>&2
exit 1 exit 1
fi fi
@ -298,112 +303,108 @@ if [ "$USERNS" != "no" ]; then
fi fi
fi fi
if [ -z "$DOWNLOAD_DIST" ] || [ -z "$DOWNLOAD_RELEASE" ] || \ if [ -z "${DOWNLOAD_DIST}" ] || [ -z "${DOWNLOAD_RELEASE}" ] || \
[ -z "$DOWNLOAD_ARCH" ]; then [ -z "${DOWNLOAD_ARCH}" ]; then
DOWNLOAD_INTERACTIVE="true" DOWNLOAD_INTERACTIVE="true"
fi fi
# Trap all exit signals # Trap all exit signals
trap cleanup EXIT HUP INT TERM trap cleanup EXIT HUP INT TERM
if ! type mktemp >/dev/null 2>&1; then if ! command -V mktemp >/dev/null 2>&1; then
DOWNLOAD_TEMP=/tmp/lxc-download.$$ DOWNLOAD_TEMP=/tmp/lxc-download.$$
mkdir -p $DOWNLOAD_TEMP mkdir -p "${DOWNLOAD_TEMP}"
else else
DOWNLOAD_TEMP=$(mktemp -d) DOWNLOAD_TEMP=$(mktemp -d)
fi fi
# Simply list images # Simply list images
if [ "$DOWNLOAD_LIST_IMAGES" = "true" ] || \ if [ "${DOWNLOAD_LIST_IMAGES}" = "true" ] || \
[ "$DOWNLOAD_INTERACTIVE" = "true" ]; then [ "${DOWNLOAD_INTERACTIVE}" = "true" ]; then
# Initialize GPG # Initialize GPG
gpg_setup gpg_setup
# Grab the index # Grab the index
DOWNLOAD_INDEX_PATH=/meta/1.0/index-${DOWNLOAD_MODE} DOWNLOAD_INDEX_PATH="/meta/1.0/index-${DOWNLOAD_MODE}"
echo "Downloading the image index" echo "Downloading the image index"
if ! download_file ${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL} \ if ! download_file "${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}" \
${DOWNLOAD_TEMP}/index noexit || "${DOWNLOAD_TEMP}/index" noexit ||
! download_sig ${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}.asc \ ! download_sig "${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}.asc" \
${DOWNLOAD_TEMP}/index.asc noexit; then "${DOWNLOAD_TEMP}/index.asc" noexit; then
download_file ${DOWNLOAD_INDEX_PATH} ${DOWNLOAD_TEMP}/index normal download_file "${DOWNLOAD_INDEX_PATH}" "${DOWNLOAD_TEMP}/index" normal
download_sig ${DOWNLOAD_INDEX_PATH}.asc \ download_sig "${DOWNLOAD_INDEX_PATH}.asc" \
${DOWNLOAD_TEMP}/index.asc normal "${DOWNLOAD_TEMP}/index.asc" normal
fi fi
gpg_validate ${DOWNLOAD_TEMP}/index.asc gpg_validate "${DOWNLOAD_TEMP}/index.asc"
# Parse it # Parse it
echo "" echo ""
echo "---" echo "---"
printf "DIST\tRELEASE\tARCH\tVARIANT\tBUILD\n" printf "DIST\tRELEASE\tARCH\tVARIANT\tBUILD\n"
echo "---" echo "---"
while read line; do while IFS=';' read -r f1 f2 f3 f4 f5 f6; do
# Basic CSV parser
OLD_IFS=$IFS
IFS=";"
set -- $line
IFS=$OLD_IFS
[ -n "$DOWNLOAD_DIST" ] && [ "$1" != "$DOWNLOAD_DIST" ] && continue [ -n "${DOWNLOAD_DIST}" ] && [ "$f1" != "${DOWNLOAD_DIST}" ] && continue
[ -n "$DOWNLOAD_RELEASE" ] && [ "$2" != "$DOWNLOAD_RELEASE" ] && continue [ -n "${DOWNLOAD_RELEASE}" ] && [ "$f2" != "${DOWNLOAD_RELEASE}" ] && continue
[ -n "$DOWNLOAD_ARCH" ] && [ "$3" != "$DOWNLOAD_ARCH" ] && continue [ -n "${DOWNLOAD_ARCH}" ] && [ "$f3" != "${DOWNLOAD_ARCH}" ] && continue
[ -n "$DOWNLOAD_VARIANT" ] && [ "$4" != "$DOWNLOAD_VARIANT" ] && continue [ -n "${DOWNLOAD_VARIANT}" ] && [ "$f4" != "${DOWNLOAD_VARIANT}" ] && continue
[ -z "$5" ] || [ -z "$6" ] && continue [ -z "${f5}" ] || [ -z "${f6}" ] && continue
printf "$1\t$2\t$3\t$4\t$5\n" printf "%s\t%s\t%s\t%s\t%s\n" "${f1}" "${f2}" "${f3}" "${f4}" "${f5}"
done < ${DOWNLOAD_TEMP}/index unset f1 f2 f3 f4 f5 f6
done < "${DOWNLOAD_TEMP}/index"
echo "---" echo "---"
if [ "$DOWNLOAD_LIST_IMAGES" = "true" ]; then if [ "${DOWNLOAD_LIST_IMAGES}" = "true" ]; then
exit 1 exit 1
fi fi
# Interactive mode # Interactive mode
echo "" echo ""
if [ -z "$DOWNLOAD_DIST" ]; then if [ -z "${DOWNLOAD_DIST}" ]; then
echo -n "Distribution: " echo "Distribution: "
read DOWNLOAD_DIST read -r DOWNLOAD_DIST
fi fi
if [ -z "$DOWNLOAD_RELEASE" ]; then if [ -z "${DOWNLOAD_RELEASE}" ]; then
echo -n "Release: " echo "Release: "
read DOWNLOAD_RELEASE read -r DOWNLOAD_RELEASE
fi fi
if [ -z "$DOWNLOAD_ARCH" ]; then if [ -z "${DOWNLOAD_ARCH}" ]; then
echo -n "Architecture: " echo "Architecture: "
read DOWNLOAD_ARCH read -r DOWNLOAD_ARCH
fi fi
echo "" echo ""
fi fi
# Setup the cache # Setup the cache
if [ "$DOWNLOAD_TARGET" = "system" ]; then if [ "${DOWNLOAD_TARGET}" = "system" ]; then
LXC_CACHE_BASE="$LOCALSTATEDIR/cache/lxc/" LXC_CACHE_BASE="${LOCALSTATEDIR}/cache/lxc/"
else else
LXC_CACHE_BASE="$HOME/.cache/lxc/" LXC_CACHE_BASE="${HOME}/.cache/lxc/"
fi fi
# Allow the setting of the LXC_CACHE_PATH with the usage of environment variables. # Allow the setting of the LXC_CACHE_PATH with the usage of environment variables.
LXC_CACHE_PATH=${LXC_CACHE_PATH:-"$LXC_CACHE_BASE"} LXC_CACHE_PATH="${LXC_CACHE_PATH:-"${LXC_CACHE_BASE}"}"
LXC_CACHE_PATH=$LXC_CACHE_PATH/download/$DOWNLOAD_DIST LXC_CACHE_PATH="${LXC_CACHE_PATH}/download/${DOWNLOAD_DIST}"
LXC_CACHE_PATH="$LXC_CACHE_PATH/$DOWNLOAD_RELEASE/$DOWNLOAD_ARCH/" LXC_CACHE_PATH="${LXC_CACHE_PATH}/${DOWNLOAD_RELEASE}/${DOWNLOAD_ARCH}/"
LXC_CACHE_PATH="$LXC_CACHE_PATH/$DOWNLOAD_VARIANT" LXC_CACHE_PATH="${LXC_CACHE_PATH}/${DOWNLOAD_VARIANT}"
if [ -d "$LXC_CACHE_PATH" ]; then if [ -d "${LXC_CACHE_PATH}" ]; then
if [ "$DOWNLOAD_FLUSH_CACHE" = "true" ]; then if [ "${DOWNLOAD_FLUSH_CACHE}" = "true" ]; then
echo "Flushing the cache..." echo "Flushing the cache..."
rm -Rf $LXC_CACHE_PATH rm -Rf "${LXC_CACHE_PATH}"
elif [ "$DOWNLOAD_FORCE_CACHE" = "true" ]; then elif [ "${DOWNLOAD_FORCE_CACHE}" = "true" ]; then
DOWNLOAD_USE_CACHE="true" DOWNLOAD_USE_CACHE="true"
else else
DOWNLOAD_USE_CACHE="true" DOWNLOAD_USE_CACHE="true"
if [ -e "$(relevant_file expiry)" ]; then if [ -e "$(relevant_file expiry)" ]; then
if [ "$(cat $(relevant_file expiry))" -lt $(date +%s) ]; then if [ "$(cat "$(relevant_file expiry)")" -lt "$(date +%s)" ]; then
echo "The cached copy has expired, re-downloading..." echo "The cached copy has expired, re-downloading..."
DOWNLOAD_USE_CACHE="false" DOWNLOAD_USE_CACHE="false"
fi fi
@ -412,88 +413,88 @@ if [ -d "$LXC_CACHE_PATH" ]; then
fi fi
# Download what's needed # Download what's needed
if [ "$DOWNLOAD_USE_CACHE" = "false" ]; then if [ "${DOWNLOAD_USE_CACHE}" = "false" ]; then
# Initialize GPG # Initialize GPG
gpg_setup gpg_setup
# Grab the index # Grab the index
DOWNLOAD_INDEX_PATH=/meta/1.0/index-${DOWNLOAD_MODE} DOWNLOAD_INDEX_PATH="/meta/1.0/index-${DOWNLOAD_MODE}"
echo "Downloading the image index" echo "Downloading the image index"
if ! download_file ${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL} \ if ! download_file "${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}" \
${DOWNLOAD_TEMP}/index noexit || "${DOWNLOAD_TEMP}/index" noexit ||
! download_sig ${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}.asc \ ! download_sig "${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}.asc" \
${DOWNLOAD_TEMP}/index.asc noexit; then "${DOWNLOAD_TEMP}/index.asc" noexit; then
download_file ${DOWNLOAD_INDEX_PATH} ${DOWNLOAD_TEMP}/index normal download_file "${DOWNLOAD_INDEX_PATH}" "${DOWNLOAD_TEMP}/index" normal
download_sig ${DOWNLOAD_INDEX_PATH}.asc \ download_sig "${DOWNLOAD_INDEX_PATH}.asc" \
${DOWNLOAD_TEMP}/index.asc normal "${DOWNLOAD_TEMP}/index.asc" normal
fi fi
gpg_validate ${DOWNLOAD_TEMP}/index.asc gpg_validate "${DOWNLOAD_TEMP}/index.asc"
# Parse it # Parse it
while read line; do while IFS=';' read -r f1 f2 f3 f4 f5 f6; do
# Basic CSV parser
OLD_IFS=$IFS
IFS=";"
set -- $line
IFS=$OLD_IFS
if [ "$1" != "$DOWNLOAD_DIST" ] || \ if [ "${f1}" != "${DOWNLOAD_DIST}" ] || \
[ "$2" != "$DOWNLOAD_RELEASE" ] || \ [ "${f2}" != "${DOWNLOAD_RELEASE}" ] || \
[ "$3" != "$DOWNLOAD_ARCH" ] || \ [ "${f3}" != "${DOWNLOAD_ARCH}" ] || \
[ "$4" != "$DOWNLOAD_VARIANT" ] || \ [ "${f4}" != "${DOWNLOAD_VARIANT}" ] || \
[ -z "$6" ]; then [ -z "${f6}" ]; then
continue continue
fi fi
DOWNLOAD_BUILD=$5 DOWNLOAD_BUILD="${f5}"
DOWNLOAD_URL=$6 DOWNLOAD_URL="${f6}"
break
done < ${DOWNLOAD_TEMP}/index
if [ -z "$DOWNLOAD_URL" ]; then unset f1 f2 f3 f4 f5 f6
break
done < "${DOWNLOAD_TEMP}/index"
if [ -z "${DOWNLOAD_URL}" ]; then
echo "ERROR: Couldn't find a matching image." 1>&1 echo "ERROR: Couldn't find a matching image." 1>&1
exit 1 exit 1
fi fi
if [ -d "$LXC_CACHE_PATH" ] && [ -f "$LXC_CACHE_PATH/build_id" ] && \ if [ -d "${LXC_CACHE_PATH}" ] && [ -f "${LXC_CACHE_PATH}/build_id" ] && \
[ "$(cat $LXC_CACHE_PATH/build_id)" = "$DOWNLOAD_BUILD" ]; then [ "$(cat "${LXC_CACHE_PATH}/build_id")" = "${DOWNLOAD_BUILD}" ]; then
echo "The cache is already up to date." echo "The cache is already up to date."
echo "Using image from local cache" echo "Using image from local cache"
else else
# Download the actual files # Download the actual files
echo "Downloading the rootfs" echo "Downloading the rootfs"
download_file $DOWNLOAD_URL/rootfs.tar.xz \ download_file "${DOWNLOAD_URL}/rootfs.tar.xz" \
${DOWNLOAD_TEMP}/rootfs.tar.xz normal "${DOWNLOAD_TEMP}/rootfs.tar.xz" normal
download_sig $DOWNLOAD_URL/rootfs.tar.xz.asc \ download_sig "${DOWNLOAD_URL}/rootfs.tar.xz.asc" \
${DOWNLOAD_TEMP}/rootfs.tar.xz.asc normal "${DOWNLOAD_TEMP}/rootfs.tar.xz.asc" normal
gpg_validate ${DOWNLOAD_TEMP}/rootfs.tar.xz.asc gpg_validate "${DOWNLOAD_TEMP}/rootfs.tar.xz.asc"
echo "Downloading the metadata" echo "Downloading the metadata"
download_file $DOWNLOAD_URL/meta.tar.xz \ download_file "${DOWNLOAD_URL}/meta.tar.xz" \
${DOWNLOAD_TEMP}/meta.tar.xz normal "${DOWNLOAD_TEMP}/meta.tar.xz" normal
download_sig $DOWNLOAD_URL/meta.tar.xz.asc \ download_sig "$DOWNLOAD_URL/meta.tar.xz.asc" \
${DOWNLOAD_TEMP}/meta.tar.xz.asc normal "${DOWNLOAD_TEMP}/meta.tar.xz.asc" normal
gpg_validate ${DOWNLOAD_TEMP}/meta.tar.xz.asc gpg_validate "${DOWNLOAD_TEMP}/meta.tar.xz.asc"
if [ -d $LXC_CACHE_PATH ]; then if [ -d "${LXC_CACHE_PATH}" ]; then
rm -Rf $LXC_CACHE_PATH rm -Rf "${LXC_CACHE_PATH}"
fi fi
mkdir -p $LXC_CACHE_PATH mkdir -p "${LXC_CACHE_PATH}"
mv ${DOWNLOAD_TEMP}/rootfs.tar.xz $LXC_CACHE_PATH mv "${DOWNLOAD_TEMP}/rootfs.tar.xz" "${LXC_CACHE_PATH}"
if ! tar Jxf ${DOWNLOAD_TEMP}/meta.tar.xz -C $LXC_CACHE_PATH; then if ! tar Jxf "${DOWNLOAD_TEMP}/meta.tar.xz" -C "${LXC_CACHE_PATH}"; then
echo "ERROR: Invalid rootfs tarball." 2>&1 echo "ERROR: Invalid rootfs tarball." 2>&1
exit 1 exit 1
fi fi
echo $DOWNLOAD_BUILD > $LXC_CACHE_PATH/build_id echo "${DOWNLOAD_BUILD}" > "${LXC_CACHE_PATH}/build_id"
if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then if [ -n "${LXC_MAPPED_UID}" ] && [ "${LXC_MAPPED_UID}" != "-1" ]; then
chown -R $LXC_MAPPED_UID $LXC_CACHE_BASE >/dev/null 2>&1 || true # As the script is run in strict mode (set -eu), all commands
# exiting with non 0 would make the script stop.
# || true or || : (more portable) prevents that.
chown -R "${LXC_MAPPED_UID}" "${LXC_CACHE_BASE}" >/dev/null 2>&1 || :
fi fi
if [ -n "$LXC_MAPPED_GID" ] && [ "$LXC_MAPPED_GID" != "-1" ]; then if [ -n "${LXC_MAPPED_GID}" ] && [ "${LXC_MAPPED_GID}" != "-1" ]; then
chgrp -R $LXC_MAPPED_GID $LXC_CACHE_BASE >/dev/null 2>&1 || true chgrp -R "${LXC_MAPPED_GID}" "${LXC_CACHE_BASE}" >/dev/null 2>&1 || :
fi fi
echo "The image cache is now ready" echo "The image cache is now ready"
fi fi
@ -507,94 +508,103 @@ echo "Unpacking the rootfs"
EXCLUDES="" EXCLUDES=""
excludelist=$(relevant_file excludes) excludelist=$(relevant_file excludes)
if [ -f "${excludelist}" ]; then if [ -f "${excludelist}" ]; then
while read line; do while read -r line; do
EXCLUDES="$EXCLUDES --exclude=$line" EXCLUDES="${EXCLUDES} --exclude=${line}"
done < $excludelist done < "${excludelist}"
fi fi
# Do not surround ${EXCLUDES} by quotes. This does not work. The solution could
# use array but this is not POSIX compliant. The only POSIX compliant solution
# is to use a function wrapper, but the latter can't be used here as the args
# are dynamic. We thus need to ignore the warning brought by shellcheck.
# shellcheck disable=SC2086
tar --anchored ${EXCLUDES} --numeric-owner -xpJf \ tar --anchored ${EXCLUDES} --numeric-owner -xpJf \
${LXC_CACHE_PATH}/rootfs.tar.xz -C ${LXC_ROOTFS} "${LXC_CACHE_PATH}/rootfs.tar.xz" -C "${LXC_ROOTFS}"
mkdir -p ${LXC_ROOTFS}/dev/pts/ mkdir -p "${LXC_ROOTFS}/dev/pts/"
# Setup the configuration # Setup the configuration
configfile=$(relevant_file config) configfile="$(relevant_file config)"
fstab=$(relevant_file fstab) fstab="$(relevant_file fstab)"
if [ ! -e $configfile ]; then if [ ! -e "${configfile}" ]; then
echo "ERROR: meta tarball is missing the configuration file" 1>&2 echo "ERROR: meta tarball is missing the configuration file" 1>&2
exit 1 exit 1
fi fi
## Extract all the network config entries ## Extract all the network config entries
sed -i -e "/lxc.net.0/{w ${LXC_PATH}/config-network" -e "d}" \ sed -i -e "/lxc.net.0/{w ${LXC_PATH}/config-network" -e "d}" \
${LXC_PATH}/config "${LXC_PATH}/config"
## Extract any other config entry ## Extract any other config entry
sed -i -e "/lxc./{w ${LXC_PATH}/config-auto" -e "d}" ${LXC_PATH}/config sed -i -e "/lxc./{w ${LXC_PATH}/config-auto" -e "d}" "${LXC_PATH}/config"
## Append the defaults ## Append the defaults
echo "" >> ${LXC_PATH}/config echo "" >> "${LXC_PATH}/config"
echo "# Distribution configuration" >> ${LXC_PATH}/config echo "# Distribution configuration" >> "${LXC_PATH}/config"
cat $configfile >> ${LXC_PATH}/config cat "$configfile" >> "${LXC_PATH}/config"
## Add the container-specific config ## Add the container-specific config
echo "" >> ${LXC_PATH}/config echo "" >> "${LXC_PATH}/config"
echo "# Container specific configuration" >> ${LXC_PATH}/config echo "# Container specific configuration" >> "${LXC_PATH}/config"
if [ -e "${LXC_PATH}/config-auto" ]; then if [ -e "${LXC_PATH}/config-auto" ]; then
cat ${LXC_PATH}/config-auto >> ${LXC_PATH}/config cat "${LXC_PATH}/config-auto" >> "${LXC_PATH}/config"
rm ${LXC_PATH}/config-auto rm "${LXC_PATH}/config-auto"
fi fi
if [ -e "$fstab" ]; then if [ -e "${fstab}" ]; then
echo "lxc.mount.fstab = ${LXC_PATH}/fstab" >> ${LXC_PATH}/config echo "lxc.mount.fstab = ${LXC_PATH}/fstab" >> "${LXC_PATH}/config"
fi fi
echo "lxc.uts.name = ${LXC_NAME}" >> ${LXC_PATH}/config echo "lxc.uts.name = ${LXC_NAME}" >> "${LXC_PATH}/config"
## Re-add the previously removed network config ## Re-add the previously removed network config
if [ -e "${LXC_PATH}/config-network" ]; then if [ -e "${LXC_PATH}/config-network" ]; then
echo "" >> ${LXC_PATH}/config echo "" >> "${LXC_PATH}/config"
echo "# Network configuration" >> ${LXC_PATH}/config echo "# Network configuration" >> "${LXC_PATH}/config"
cat ${LXC_PATH}/config-network >> ${LXC_PATH}/config cat "${LXC_PATH}/config-network" >> "${LXC_PATH}/config"
rm ${LXC_PATH}/config-network rm "${LXC_PATH}/config-network"
fi fi
TEMPLATE_FILES="${LXC_PATH}/config" TEMPLATE_FILES="${LXC_PATH}/config"
# Setup the fstab # Setup the fstab
if [ -e $fstab ]; then if [ -e "${fstab}" ]; then
cp ${fstab} ${LXC_PATH}/fstab cp "${fstab}" "${LXC_PATH}/fstab"
TEMPLATE_FILES="$TEMPLATE_FILES ${LXC_PATH}/fstab" TEMPLATE_FILES="${TEMPLATE_FILES};${LXC_PATH}/fstab"
fi fi
# Look for extra templates # Look for extra templates
if [ -e "$(relevant_file templates)" ]; then if [ -e "$(relevant_file templates)" ]; then
while read line; do while read -r line; do
fullpath=${LXC_ROOTFS}/$line fullpath="${LXC_ROOTFS}/${line}"
[ ! -e "$fullpath" ] && continue [ ! -e "${fullpath}" ] && continue
TEMPLATE_FILES="$TEMPLATE_FILES $fullpath" TEMPLATE_FILES="${TEMPLATE_FILES};${fullpath}"
done < $(relevant_file templates) done < "$(relevant_file templates)"
fi fi
# Replace variables in all templates # Replace variables in all templates
for file in $TEMPLATE_FILES; do for file in $TEMPLATE_FILES; do
[ ! -f "$file" ] && continue OLD_IFS=${IFS}
IFS=";"
for file in ${TEMPLATE_FILES}; do
[ ! -f "${file}" ] && continue
sed -i "s#LXC_NAME#$LXC_NAME#g" $file sed -i "s#LXC_NAME#${LXC_NAME}#g" "${file}"
sed -i "s#LXC_PATH#$LXC_PATH#g" $file sed -i "s#LXC_PATH#${LXC_PATH}#g" "${file}"
sed -i "s#LXC_ROOTFS#$LXC_ROOTFS#g" $file sed -i "s#LXC_ROOTFS#${LXC_ROOTFS}#g" "${file}"
sed -i "s#LXC_TEMPLATE_CONFIG#$LXC_TEMPLATE_CONFIG#g" $file sed -i "s#LXC_TEMPLATE_CONFIG#${LXC_TEMPLATE_CONFIG}#g" "${file}"
sed -i "s#LXC_HOOK_DIR#$LXC_HOOK_DIR#g" $file sed -i "s#LXC_HOOK_DIR#${LXC_HOOK_DIR}#g" "${file}"
done done
IFS=${OLD_IFS}
# prevent mingetty from calling vhangup(2) since it fails with userns on CentOS / Oracle # prevent mingetty from calling vhangup(2) since it fails with userns on CentOS / Oracle
if [ -f ${LXC_ROOTFS}/etc/init/tty.conf ]; then if [ -f "${LXC_ROOTFS}/etc/init/tty.conf" ]; then
sed -i 's|mingetty|mingetty --nohangup|' ${LXC_ROOTFS}/etc/init/tty.conf sed -i 's|mingetty|mingetty --nohangup|' "${LXC_ROOTFS}/etc/init/tty.conf"
fi fi
if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then if [ -n "${LXC_MAPPED_UID}" ] && [ "${LXC_MAPPED_UID}" != "-1" ]; then
chown $LXC_MAPPED_UID $LXC_PATH/config $LXC_PATH/fstab >/dev/null 2>&1 || true chown "${LXC_MAPPED_UID}" "${LXC_PATH}/config" "${LXC_PATH}/fstab" >/dev/null 2>&1 || :
fi fi
if [ -n "$LXC_MAPPED_GID" ] && [ "$LXC_MAPPED_GID" != "-1" ]; then if [ -n "${LXC_MAPPED_GID}" ] && [ "${LXC_MAPPED_GID}" != "-1" ]; then
chgrp $LXC_MAPPED_GID $LXC_PATH/config $LXC_PATH/fstab >/dev/null 2>&1 || true chgrp "${LXC_MAPPED_GID}" "${LXC_PATH}/config" "${LXC_PATH}/fstab" >/dev/null 2>&1 || :
fi fi
if [ -e "$(relevant_file create-message)" ]; then if [ -e "$(relevant_file create-message)" ]; then