mirror_lxc/templates/lxc-download.in
Stéphane Graber 5852026304
lxc-download: Rely on HTTPS only
GPG has been a major source of issues over the years with various
attacks on the key network as well as client side issues making it hard
to retrieve our keys.

Back when we introduced the image server, SSL certificates were still
expensive and annoying to setup, so not something we'd have expected
potential mirrors to setup for us. They were also issued for multiple
years, making a compromise of such a certificate quite problematic.

But things have changed since, we now have completely free, very easily
deployable SSL certificates everywhere with the majority of those being
shortlived and with good reporting of issued certificates.

With that, we can now deprecate the GPG validation, disable the fallback
to non-HTTPS download and rely on our indices being accurate because
they've been downloaded from a server with a valid certificate.

This puts LXC more in line with what LXD has done since the beginning
and should offer a more reliable user experience.

Signed-off-by: Stéphane Graber <stgraber@ubuntu.com>
2022-01-17 21:15:53 -05:00

499 lines
14 KiB
Bash
Executable File

#!/bin/sh
# Client script for LXC container images.
#
# Copyright © 2014 Stéphane Graber <stgraber@ubuntu.com>
#
# 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
set -eu
LOCALSTATEDIR=@LOCALSTATEDIR@
LXC_HOOK_DIR=@LXCHOOKDIR@
LXC_TEMPLATE_CONFIG=@LXCTEMPLATECONFIG@
# Defaults
DOWNLOAD_ARCH=
DOWNLOAD_BUILD=
DOWNLOAD_COMPAT_LEVEL=7
DOWNLOAD_DIST=
DOWNLOAD_FLUSH_CACHE="false"
DOWNLOAD_FORCE_CACHE="false"
DOWNLOAD_INTERACTIVE="false"
DOWNLOAD_LIST_IMAGES="false"
DOWNLOAD_MODE="system"
DOWNLOAD_RELEASE=
DOWNLOAD_SERVER="images.linuxcontainers.org"
DOWNLOAD_TARGET="system"
DOWNLOAD_URL=
DOWNLOAD_USE_CACHE="false"
DOWNLOAD_VARIANT="default"
DOWNLOAD_TEMP=
LXC_MAPPED_GID=
LXC_MAPPED_UID=
LXC_NAME=
LXC_PATH=
LXC_ROOTFS=
# Make sure the usual locations are in PATH
export PATH="$PATH:/usr/sbin:/usr/bin:/sbin:/bin"
# Some useful functions
cleanup() {
if [ -d "${DOWNLOAD_TEMP}" ]; then
rm -Rf "${DOWNLOAD_TEMP}"
fi
}
wget_wrapper() {
for _ in $(seq 3); do
if wget "$@"; then
return 0
fi
done
return 1
}
download_file() {
if ! wget_wrapper --user-agent="lxc/@PACKAGE_VERSION@ compat:${DOWNLOAD_COMPAT_LEVEL}" -T 30 -q "https://${DOWNLOAD_SERVER}/$1" -O "$2" >/dev/null 2>&1; then
if [ "$3" = "noexit" ]; then
return 1
else
echo "ERROR: Failed to download https://${DOWNLOAD_SERVER}/$1" 1>&2
exit 1
fi
fi
}
in_userns() {
[ -e /proc/self/uid_map ] || { echo no; return; }
while read -r line; do
fields="$(echo "$line" | awk '{ print $1 " " $2 " " $3 }')"
if [ "${fields}" = "0 0 4294967295" ]; then
echo no;
return;
fi
if echo "${fields}" | grep -q " 0 1$"; then
echo userns-root;
return;
fi
done < /proc/self/uid_map
if [ -e /proc/1/uid_map ]; then
if [ "$(cat /proc/self/uid_map)" = "$(cat /proc/1/uid_map)" ]; then
echo userns-root
return
fi
fi
echo yes
}
relevant_file() {
FILE_PATH="${LXC_CACHE_PATH}/$1"
if [ -e "${FILE_PATH}-${DOWNLOAD_MODE}" ]; then
FILE_PATH="${FILE_PATH}-${DOWNLOAD_MODE}"
fi
if [ -e "${FILE_PATH}.${DOWNLOAD_COMPAT_LEVEL}" ]; then
FILE_PATH="${FILE_PATH}.${DOWNLOAD_COMPAT_LEVEL}"
fi
echo "${FILE_PATH}"
}
usage() {
cat <<EOF
LXC container image downloader
Special arguments:
[ -h | --help ]: Print this help message and exit
[ -l | --list ]: List all available images and exit
Required arguments:
[ -d | --dist <distribution> ]: The name of the distribution
[ -r | --release <release> ]: Release name/version
[ -a | --arch <architecture> ]: Architecture of the container
Optional arguments:
[ --variant <variant> ]: Variant of the image (default: "default")
[ --server <server> ]: Image server (default: "images.linuxcontainers.org")
[ --flush-cache ]: Flush the local copy (if present)
[ --force-cache ]: Force the use of the local copy even if expired
LXC internal arguments (do not pass manually!):
[ --name <name> ]: The container name
[ --path <path> ]: The path to the container
[ --rootfs <rootfs> ]: The path to the container's rootfs
[ --mapped-uid <map> ]: A uid map (user namespaces)
[ --mapped-gid <map> ]: A gid map (user namespaces)
EOF
return 0
}
if ! options=$(getopt -o d:r:a:hl -l dist:,release:,arch:,help,list,variant:,\
server:,flush-cache,force-cache,name:,path:,\
rootfs:,mapped-uid:,mapped-gid: -- "$@"); then
usage
exit 1
fi
eval set -- "$options"
while :; do
case "$1" in
-h|--help) usage && exit 1;;
-l|--list) DOWNLOAD_LIST_IMAGES="true"; shift 1;;
-d|--dist) DOWNLOAD_DIST="$2"; shift 2;;
-r|--release) DOWNLOAD_RELEASE="$2"; shift 2;;
-a|--arch) DOWNLOAD_ARCH="$2"; shift 2;;
--variant) DOWNLOAD_VARIANT="$2"; shift 2;;
--server) DOWNLOAD_SERVER="$2"; shift 2;;
--flush-cache) DOWNLOAD_FLUSH_CACHE="true"; shift 1;;
--force-cache) DOWNLOAD_FORCE_CACHE="true"; shift 1;;
--name) LXC_NAME="$2"; shift 2;;
--path) LXC_PATH="$2"; shift 2;;
--rootfs) LXC_ROOTFS="$2"; shift 2;;
--mapped-uid) LXC_MAPPED_UID="$2"; shift 2;;
--mapped-gid) LXC_MAPPED_GID="$2"; shift 2;;
*) break;;
esac
done
# Check for required binaries
for bin in tar xz wget; do
if ! command -V "${bin}" >/dev/null 2>&1; then
echo "ERROR: Missing required tool: ${bin}" 1>&2
exit 1
fi
done
# Check that we have all variables we need
if [ -z "${LXC_NAME}" ] || [ -z "${LXC_PATH}" ] || [ -z "${LXC_ROOTFS}" ]; then
if [ "${DOWNLOAD_LIST_IMAGES}" != "true" ]; then
echo "ERROR: Please pass the name, path, and rootfs for the container" 1>&2
exit 1
fi
fi
USERNS="$(in_userns)"
if [ "${USERNS}" != "no" ]; then
if [ "${USERNS}" = "yes" ]; then
if [ -z "${LXC_MAPPED_UID}" ] || [ "${LXC_MAPPED_UID}" = "-1" ]; then
echo "ERROR: In a user namespace without a map" 1>&2
exit 1
fi
DOWNLOAD_MODE="user"
DOWNLOAD_TARGET="user"
else
DOWNLOAD_MODE="user"
DOWNLOAD_TARGET="system"
fi
fi
if [ -z "${DOWNLOAD_DIST}" ] || [ -z "${DOWNLOAD_RELEASE}" ] || [ -z "${DOWNLOAD_ARCH}" ]; then
DOWNLOAD_INTERACTIVE="true"
fi
# Trap all exit signals
trap cleanup EXIT HUP INT TERM
# /tmp may be mounted in tmpfs or noexec
if mountpoint -q /tmp; then
DOWNLOAD_TEMP="${LXC_PATH}"
fi
if ! command -V mktemp >/dev/null 2>&1; then
DOWNLOAD_TEMP="${DOWNLOAD_TEMP}/tmp/lxc-download.$$"
elif [ -n "${DOWNLOAD_TEMP}" ]; then
mkdir -p "${DOWNLOAD_TEMP}"
DOWNLOAD_TEMP="$(mktemp -p "${DOWNLOAD_TEMP}" -d)"
else
DOWNLOAD_TEMP="${DOWNLOAD_TEMP}$(mktemp -d)"
fi
# Simply list images
if [ "${DOWNLOAD_LIST_IMAGES}" = "true" ] || [ "${DOWNLOAD_INTERACTIVE}" = "true" ]; then
# Grab the index
DOWNLOAD_INDEX_PATH="/meta/1.0/index-${DOWNLOAD_MODE}"
echo "Downloading the image index"
if ! download_file "${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}" "${DOWNLOAD_TEMP}/index" noexit; then
download_file "${DOWNLOAD_INDEX_PATH}" "${DOWNLOAD_TEMP}/index" normal
fi
# Parse it
echo ""
echo "---"
printf "DIST\tRELEASE\tARCH\tVARIANT\tBUILD\n"
echo "---"
while IFS=';' read -r f1 f2 f3 f4 f5 f6; do
[ -n "${DOWNLOAD_DIST}" ] && [ "$f1" != "${DOWNLOAD_DIST}" ] && continue
[ -n "${DOWNLOAD_RELEASE}" ] && [ "$f2" != "${DOWNLOAD_RELEASE}" ] && continue
[ -n "${DOWNLOAD_ARCH}" ] && [ "$f3" != "${DOWNLOAD_ARCH}" ] && continue
[ -n "${DOWNLOAD_VARIANT}" ] && [ "$f4" != "${DOWNLOAD_VARIANT}" ] && continue
[ -z "${f5}" ] || [ -z "${f6}" ] && continue
printf "%s\t%s\t%s\t%s\t%s\n" "${f1}" "${f2}" "${f3}" "${f4}" "${f5}"
unset f1 f2 f3 f4 f5 f6
done < "${DOWNLOAD_TEMP}/index"
echo "---"
if [ "${DOWNLOAD_LIST_IMAGES}" = "true" ]; then
exit 1
fi
# Interactive mode
echo ""
if [ -z "${DOWNLOAD_DIST}" ]; then
echo "Distribution: "
read -r DOWNLOAD_DIST
fi
if [ -z "${DOWNLOAD_RELEASE}" ]; then
echo "Release: "
read -r DOWNLOAD_RELEASE
fi
if [ -z "${DOWNLOAD_ARCH}" ]; then
echo "Architecture: "
read -r DOWNLOAD_ARCH
fi
echo ""
fi
# Setup the cache
if [ "${DOWNLOAD_TARGET}" = "system" ]; then
LXC_CACHE_BASE="${LOCALSTATEDIR}/cache/lxc/"
else
LXC_CACHE_BASE="${HOME}/.cache/lxc/"
fi
# 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}/download/${DOWNLOAD_DIST}"
LXC_CACHE_PATH="${LXC_CACHE_PATH}/${DOWNLOAD_RELEASE}/${DOWNLOAD_ARCH}/"
LXC_CACHE_PATH="${LXC_CACHE_PATH}/${DOWNLOAD_VARIANT}"
if [ -d "${LXC_CACHE_PATH}" ]; then
if [ "${DOWNLOAD_FLUSH_CACHE}" = "true" ]; then
echo "Flushing the cache..."
rm -Rf "${LXC_CACHE_PATH}"
elif [ "${DOWNLOAD_FORCE_CACHE}" = "true" ]; then
DOWNLOAD_USE_CACHE="true"
else
DOWNLOAD_USE_CACHE="true"
if [ -e "$(relevant_file expiry)" ]; then
if [ "$(cat "$(relevant_file expiry)")" -lt "$(date +%s)" ]; then
echo "The cached copy has expired, re-downloading..."
DOWNLOAD_USE_CACHE="false"
fi
fi
fi
fi
# Download what's needed
if [ "${DOWNLOAD_USE_CACHE}" = "false" ]; then
# Grab the index
DOWNLOAD_INDEX_PATH="/meta/1.0/index-${DOWNLOAD_MODE}"
echo "Downloading the image index"
if ! download_file "${DOWNLOAD_INDEX_PATH}.${DOWNLOAD_COMPAT_LEVEL}" "${DOWNLOAD_TEMP}/index" noexit; then
download_file "${DOWNLOAD_INDEX_PATH}" "${DOWNLOAD_TEMP}/index" normal
fi
# Parse it
while IFS=';' read -r f1 f2 f3 f4 f5 f6; do
if [ "${f1}" != "${DOWNLOAD_DIST}" ] || \
[ "${f2}" != "${DOWNLOAD_RELEASE}" ] || \
[ "${f3}" != "${DOWNLOAD_ARCH}" ] || \
[ "${f4}" != "${DOWNLOAD_VARIANT}" ] || \
[ -z "${f6}" ]; then
continue
fi
DOWNLOAD_BUILD="${f5}"
DOWNLOAD_URL="${f6}"
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
exit 1
fi
if [ -d "${LXC_CACHE_PATH}" ] && [ -f "${LXC_CACHE_PATH}/build_id" ] && \
[ "$(cat "${LXC_CACHE_PATH}/build_id")" = "${DOWNLOAD_BUILD}" ]; then
echo "The cache is already up to date"
echo "Using image from local cache"
else
# Download the actual files
echo "Downloading the rootfs"
download_file "${DOWNLOAD_URL}/rootfs.tar.xz" "${DOWNLOAD_TEMP}/rootfs.tar.xz" normal
echo "Downloading the metadata"
download_file "${DOWNLOAD_URL}/meta.tar.xz" "${DOWNLOAD_TEMP}/meta.tar.xz" normal
if [ -d "${LXC_CACHE_PATH}" ]; then
rm -Rf "${LXC_CACHE_PATH}"
fi
mkdir -p "${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
echo "ERROR: Invalid rootfs tarball." 2>&1
exit 1
fi
echo "${DOWNLOAD_BUILD}" > "${LXC_CACHE_PATH}/build_id"
if [ -n "${LXC_MAPPED_UID}" ] && [ "${LXC_MAPPED_UID}" != "-1" ]; then
# 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
if [ -n "${LXC_MAPPED_GID}" ] && [ "${LXC_MAPPED_GID}" != "-1" ]; then
chgrp -R "${LXC_MAPPED_GID}" "${LXC_CACHE_BASE}" >/dev/null 2>&1 || :
fi
echo "The image cache is now ready"
fi
else
echo "Using image from local cache"
fi
# Unpack the rootfs
echo "Unpacking the rootfs"
EXCLUDES=""
excludelist=$(relevant_file excludes)
if [ -f "${excludelist}" ]; then
while read -r line; do
EXCLUDES="${EXCLUDES} --exclude=${line}"
done < "${excludelist}"
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 "${LXC_CACHE_PATH}/rootfs.tar.xz" -C "${LXC_ROOTFS}"
mkdir -p "${LXC_ROOTFS}/dev/pts/"
# Setup the configuration
configfile="$(relevant_file config)"
fstab="$(relevant_file fstab)"
if [ ! -e "${configfile}" ]; then
echo "ERROR: meta tarball is missing the configuration file" 1>&2
exit 1
fi
## Extract all the network config entries
sed -i -e "/lxc.net.0/{w ${LXC_PATH}/config-network" -e "d}" "${LXC_PATH}/config"
## Extract any other config entry
sed -i -e "/lxc./{w ${LXC_PATH}/config-auto" -e "d}" "${LXC_PATH}/config"
## Append the defaults
{
echo ""
echo "# Distribution configuration"
cat "$configfile"
} >> "${LXC_PATH}/config"
## Add the container-specific config
{
echo ""
echo "# Container specific configuration"
if [ -e "${LXC_PATH}/config-auto" ]; then
cat "${LXC_PATH}/config-auto"
rm "${LXC_PATH}/config-auto"
fi
if [ -e "${fstab}" ]; then
echo "lxc.mount.fstab = ${LXC_PATH}/fstab"
fi
echo "lxc.uts.name = ${LXC_NAME}"
} >> "${LXC_PATH}/config"
## Re-add the previously removed network config
if [ -e "${LXC_PATH}/config-network" ]; then
{
echo ""
echo "# Network configuration"
cat "${LXC_PATH}/config-network"
rm "${LXC_PATH}/config-network"
} >> "${LXC_PATH}/config"
fi
TEMPLATE_FILES="${LXC_PATH}/config"
# Setup the fstab
if [ -e "${fstab}" ]; then
cp "${fstab}" "${LXC_PATH}/fstab"
TEMPLATE_FILES="${TEMPLATE_FILES};${LXC_PATH}/fstab"
fi
# Look for extra templates
if [ -e "$(relevant_file templates)" ]; then
while read -r line; do
fullpath="${LXC_ROOTFS}/${line}"
[ ! -e "${fullpath}" ] && continue
TEMPLATE_FILES="${TEMPLATE_FILES};${fullpath}"
done < "$(relevant_file templates)"
fi
# Replace variables in all templates
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_PATH#${LXC_PATH}#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_HOOK_DIR#${LXC_HOOK_DIR}#g" "${file}"
done
IFS=${OLD_IFS}
# prevent mingetty from calling vhangup(2) since it fails with userns on CentOS / Oracle
if [ -f "${LXC_ROOTFS}/etc/init/tty.conf" ]; then
sed -i 's|mingetty|mingetty --nohangup|' "${LXC_ROOTFS}/etc/init/tty.conf"
fi
if [ -n "${LXC_MAPPED_UID}" ] && [ "${LXC_MAPPED_UID}" != "-1" ]; then
chown "${LXC_MAPPED_UID}" "${LXC_PATH}/config" "${LXC_PATH}/fstab" >/dev/null 2>&1 || :
fi
if [ -n "${LXC_MAPPED_GID}" ] && [ "${LXC_MAPPED_GID}" != "-1" ]; then
chgrp "${LXC_MAPPED_GID}" "${LXC_PATH}/config" "${LXC_PATH}/fstab" >/dev/null 2>&1 || :
fi
if [ -e "$(relevant_file create-message)" ]; then
echo ""
echo "---"
cat "$(relevant_file create-message)"
fi
exit 0