From c696b952693fa6174068e2bd26459a206308c95f Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Mon, 11 May 2020 22:12:50 +0900 Subject: [PATCH] add dockerd-rootless-setuptool.sh Usage: `dockerd-rootless-setuptool.sh install --force` . See `--help` for further information. Co-authored-by: Tianon Gravi Signed-off-by: Akihiro Suda --- Dockerfile | 1 + contrib/dockerd-rootless-setuptool.sh | 439 ++++++++++++++++++++++++++ hack/make/.binary-setup | 1 + hack/make/binary-daemon | 2 +- hack/make/install-binary | 1 + 5 files changed, 443 insertions(+), 1 deletion(-) create mode 100755 contrib/dockerd-rootless-setuptool.sh diff --git a/Dockerfile b/Dockerfile index 357a9be251..3fefa62c64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -234,6 +234,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \ PREFIX=/build /tmp/install/install.sh rootlesskit COPY ./contrib/dockerd-rootless.sh /build +COPY ./contrib/dockerd-rootless-setuptool.sh /build FROM djs55/vpnkit:${VPNKIT_VERSION} AS vpnkit diff --git a/contrib/dockerd-rootless-setuptool.sh b/contrib/dockerd-rootless-setuptool.sh new file mode 100755 index 0000000000..80c868ea5f --- /dev/null +++ b/contrib/dockerd-rootless-setuptool.sh @@ -0,0 +1,439 @@ +#!/bin/sh +# dockerd-rootless-setuptool.sh: setup tool for dockerd-rootless.sh +# Needs to be executed as a non-root user. +# +# Typical usage: dockerd-rootless-setuptool.sh install --force +# +# Documentation: https://docs.docker.com/engine/security/rootless/ +set -eu + +# utility functions +INFO() { + /bin/echo -e "\e[104m\e[97m[INFO]\e[49m\e[39m $@" +} + +WARNING() { + /bin/echo >&2 -e "\e[101m\e[97m[WARNING]\e[49m\e[39m $@" +} + +ERROR() { + /bin/echo >&2 -e "\e[101m\e[97m[ERROR]\e[49m\e[39m $@" +} + +# constants +DOCKERD_ROOTLESS_SH="dockerd-rootless.sh" +SYSTEMD_UNIT="docker.service" + +# CLI opt: --force +OPT_FORCE="" +# CLI opt: --skip-iptables +OPT_SKIP_IPTABLES="" + +# global vars +ARG0="$0" +DOCKERD_ROOTLESS_SH_FLAGS="" +BIN="" +SYSTEMD="" +CFG_DIR="" +XDG_RUNTIME_DIR_CREATED="" + +# run checks and also initialize global vars +init() { + # OS verification: Linux only + case "$(uname)" in + Linux) ;; + + *) + ERROR "Rootless Docker cannot be installed on $(uname)" + exit 1 + ;; + esac + + # User verification: deny running as root + if [ "$(id -u)" = "0" ]; then + ERROR "Refusing to install rootless Docker as the root user" + exit 1 + fi + + # set BIN + if ! BIN="$(command -v "$DOCKERD_ROOTLESS_SH" 2> /dev/null)"; then + ERROR "$DOCKERD_ROOTLESS_SH needs to be present under \$PATH" + exit 1 + fi + BIN=$(dirname "$BIN") + + # set SYSTEMD + if systemctl --user show-environment > /dev/null 2>&1; then + SYSTEMD=1 + fi + + # HOME verification + if [ -z "${HOME:-}" ] || [ ! -d "$HOME" ]; then + ERROR "HOME needs to be set" + exit 1 + fi + if [ ! -w "$HOME" ]; then + ERROR "HOME needs to be writable" + exit 1 + fi + + # set CFG_DIR + CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}" + + # Existing rootful docker verification + if [ -w /var/run/docker.sock ] && [ -z "$OPT_FORCE" ]; then + ERROR "Aborting because rootful Docker (/var/run/docker.sock) is running and accessible. Set --force to ignore." + exit 1 + fi + + # Validate XDG_RUNTIME_DIR and set XDG_RUNTIME_DIR_CREATED + if [ -z "${XDG_RUNTIME_DIR:-}" ] || [ ! -w "$XDG_RUNTIME_DIR" ]; then + if [ -n "$SYSTEMD" ]; then + ERROR "Aborting because systemd was detected but XDG_RUNTIME_DIR (\"$XDG_RUNTIME_DIR\") is not set, does not exist, or is not writable" + ERROR "Hint: this could happen if you changed users with 'su' or 'sudo'. To work around this:" + ERROR "- try again by first running with root privileges 'loginctl enable-linger ' where is the unprivileged user and export XDG_RUNTIME_DIR to the value of RuntimePath as shown by 'loginctl show-user '" + ERROR "- or simply log back in as the desired unprivileged user (ssh works for remote machines, machinectl shell works for local machines)" + exit 1 + fi + export XDG_RUNTIME_DIR="/tmp/docker-$(id -u)" + mkdir -p "$XDG_RUNTIME_DIR" + XDG_RUNTIME_DIR_CREATED=1 + fi + + instructions="" + # instruction: uidmap dependency check + if ! command -v newuidmap > /dev/null 2>&1; then + if command -v apt-get > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install newuidmap & newgidmap binaries + apt-get install -y uidmap + EOI + ) + elif command -v dnf > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install newuidmap & newgidmap binaries + dnf install -y shadow-utils + EOI + ) + elif command -v yum > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install newuidmap & newgidmap binaries + yum install -y shadow-utils + EOI + ) + else + ERROR "newuidmap binary not found. Please install with a package manager." + exit 1 + fi + fi + + # instruction: iptables dependency check + faced_iptables_error="" + if ! command -v iptables > /dev/null 2>&1 && [ ! -f /sbin/iptables ] && [ ! -f /usr/sbin/iptables ]; then + faced_iptables_error=1 + if [ -z "$OPT_SKIP_IPTABLES" ]; then + if command -v apt-get > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install iptables + apt-get install -y iptables + EOI + ) + elif command -v dnf > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install iptables + dnf install -y iptables + EOI + ) + elif command -v yum > /dev/null 2>&1; then + instructions=$( + cat <<- EOI + ${instructions} + # Install iptables + yum install -y iptables + EOI + ) + else + ERROR "iptables binary not found. Please install with a package manager." + exit 1 + fi + fi + fi + + # instruction: ip_tables module dependency check + if ! grep -q ip_tables /proc/modules 2> /dev/null && ! grep -q ip_tables /lib/modules/$(uname -r)/modules.builtin 2> /dev/null; then + faced_iptables_error=1 + if [ -z "$OPT_SKIP_IPTABLES" ]; then + instructions=$( + cat <<- EOI + ${instructions} + # Load ip_tables module + modprobe ip_tables + EOI + ) + fi + fi + + # set DOCKERD_ROOTLESS_SH_FLAGS + if [ -n "$faced_iptables_error" ] && [ -n "$OPT_SKIP_IPTABLES" ]; then + DOCKERD_ROOTLESS_SH_FLAGS="${DOCKERD_ROOTLESS_SH_FLAGS} --iptables=false" + fi + + # instruction: Debian and Arch require setting unprivileged_userns_clone + if [ -f /proc/sys/kernel/unprivileged_userns_clone ]; then + if [ "1" != "$(cat /proc/sys/kernel/unprivileged_userns_clone)" ]; then + instructions=$( + cat <<- EOI + ${instructions} + # Set kernel.unprivileged_userns_clone + cat < /etc/sysctl.d/50-rootless.conf + kernel.unprivileged_userns_clone = 1 + EOT + sysctl --system + EOI + ) + fi + fi + + # instruction: RHEL/CentOS 7 requires setting max_user_namespaces + if [ -f /proc/sys/user/max_user_namespaces ]; then + if [ "0" = "$(cat /proc/sys/user/max_user_namespaces)" ]; then + instructions=$( + cat <<- EOI + ${instructions} + # Set user.max_user_namespaces + cat < /etc/sysctl.d/51-rootless.conf + user.max_user_namespaces = 28633 + EOT + sysctl --system + EOI + ) + fi + fi + + # instructions: validate subuid/subgid files for current user + if ! grep -q "^$(id -un):\|^$(id -u):" /etc/subuid 2> /dev/null; then + instructions=$( + cat <<- EOI + ${instructions} + # Add subuid entry for $(id -un) + echo "$(id -un):100000:65536" >> /etc/subuid + EOI + ) + fi + if ! grep -q "^$(id -un):\|^$(id -u):" /etc/subgid 2> /dev/null; then + instructions=$( + cat <<- EOI + ${instructions} + # Add subgid entry for $(id -un) + echo "$(id -un):100000:65536" >> /etc/subgid + EOI + ) + fi + + # fail with instructions if requirements are not satisfied. + if [ -n "$instructions" ]; then + ERROR "Missing system requirements. Run the following commands to" + ERROR "install the requirements and run this tool again." + if [ -n "$faced_iptables_error" ] && [ -z "$OPT_SKIP_IPTABLES" ]; then + ERROR "Alternatively iptables checks can be disabled with --skip-iptables ." + fi + echo + echo "########## BEGIN ##########" + echo "sudo sh -eux < "${unit_file}" + [Unit] + Description=Docker Application Container Engine (Rootless) + Documentation=https://docs.docker.com/engine/security/rootless/ + + [Service] + Environment=PATH=$BIN:/sbin:/usr/sbin:$PATH + ExecStart=$BIN/dockerd-rootless.sh $DOCKERD_ROOTLESS_SH_FLAGS + ExecReload=/bin/kill -s HUP \$MAINPID + TimeoutSec=0 + RestartSec=2 + Restart=always + StartLimitBurst=3 + StartLimitInterval=60s + LimitNOFILE=infinity + LimitNPROC=infinity + LimitCORE=infinity + TasksMax=infinity + Delegate=yes + Type=simple + + [Install] + WantedBy=default.target + EOT + systemctl --user daemon-reload + fi + if ! systemctl --user --no-pager status "${SYSTEMD_UNIT}" > /dev/null 2>&1; then + INFO "starting systemd service ${SYSTEMD_UNIT}" + ( + set -x + systemctl --user start "${SYSTEMD_UNIT}" + sleep 3 + ) + fi + ( + set -x + systemctl --user --no-pager --full status "${SYSTEMD_UNIT}" + DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock" $BIN/docker version + systemctl --user enable "${SYSTEMD_UNIT}" + ) + INFO "Installed ${SYSTEMD_UNIT} successfully." + INFO "To control ${SYSTEMD_UNIT}, run: \`systemctl --user (start|stop|restart) ${SYSTEMD_UNIT}\`" + INFO "To run ${SYSTEMD_UNIT} on system startup, run: \`sudo loginctl enable-linger $(id -un)\`" + echo +} + +# install (non-systemd) +install_nonsystemd() { + INFO "systemd not detected, ${DOCKERD_ROOTLESS_SH} needs to be started manually:" + echo + echo "PATH=$BIN:/sbin:/usr/sbin:\$PATH ${DOCKERD_ROOTLESS_SH} ${DOCKERD_ROOTLESS_SH_FLAGS}" + echo +} + +# CLI subcommand: "install" +cmd_entrypoint_install() { + # requirements are already checked in init() + if [ -z "$SYSTEMD" ]; then + install_nonsystemd + else + install_systemd + fi + + INFO "Make sure the following environment variables are set (or add them to ~/.bashrc):" + echo + if [ -n "$XDG_RUNTIME_DIR_CREATED" ]; then + echo "export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR}" + fi + echo "export PATH=${BIN}:\$PATH" + echo "export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/docker.sock" + echo + +} + +# CLI subcommand: "uninstall" +cmd_entrypoint_uninstall() { + # requirements are already checked in init() + if [ -z "$SYSTEMD" ]; then + INFO "systemd not detected, ${DOCKERD_ROOTLESS_SH} needs to be stopped manually:" + else + unit_file="${CFG_DIR}/systemd/user/${SYSTEMD_UNIT}" + ( + set -x + systemctl --user stop "${SYSTEMD_UNIT}" + ) || : + ( + set -x + systemctl --user disable "${SYSTEMD_UNIT}" + ) || : + rm -f "${unit_file}" + INFO "Uninstalled ${SYSTEMD_UNIT}" + fi + + INFO "This uninstallation tool does NOT remove Docker binaries and data." + INFO "To remove data, run: \`$BIN/rootlesskit rm -rf $HOME/.local/share/docker\`" +} + +# text for --help +usage() { + echo "Usage: ${ARG0} [OPTIONS] COMMAND" + echo + echo "A setup tool for Rootless Docker (${DOCKERD_ROOTLESS_SH})." + echo + echo "Documentation: https://docs.docker.com/engine/security/rootless/" + echo + echo "Options:" + echo " -f, --force Ignore rootful Docker (/var/run/docker.sock)" + echo " --skip-iptables Ignore missing iptables" + echo + echo "Commands:" + echo " check Check prerequisites" + echo " install Install systemd unit (if systemd is available) and show how to manage the service" + echo " uninstall Uninstall systemd unit" +} + +# parse CLI args +if ! args="$(getopt -o hf --long help,force,skip-iptables -n "$ARG0" -- "$@")"; then + usage + exit 1 +fi +eval set -- "$args" +while [ "$#" -gt 0 ]; do + arg="$1" + shift + case "$arg" in + -h | --help) + usage + exit 0 + ;; + -f | --force) + OPT_FORCE=1 + ;; + --skip-iptables) + OPT_SKIP_IPTABLES=1 + ;; + --) + break + ;; + *) + # XXX this means we missed something in our "getopt" arguments above! + ERROR "Scripting error, unknown argument '$arg' when parsing script arguments." + exit 1 + ;; + esac +done + +command="${1:-}" +if [ -z "$command" ]; then + ERROR "No command was specified. Run with --help to see the usage. Maybe you want to run \`$ARG0 install\`?" + exit 1 +fi + +if ! command -v "cmd_entrypoint_${command}" > /dev/null 2>&1; then + ERROR "Unknown command: ${command}. Run with --help to see the usage." + exit 1 +fi + +# main +init +"cmd_entrypoint_${command}" diff --git a/hack/make/.binary-setup b/hack/make/.binary-setup index 7a4eb8bc12..44d42fa759 100644 --- a/hack/make/.binary-setup +++ b/hack/make/.binary-setup @@ -11,3 +11,4 @@ DOCKER_ROOTLESSKIT_BINARY_NAME='rootlesskit' DOCKER_ROOTLESSKIT_DOCKER_PROXY_BINARY_NAME='rootlesskit-docker-proxy' DOCKER_VPNKIT_BINARY_NAME='vpnkit' DOCKER_DAEMON_ROOTLESS_SH_BINARY_NAME='dockerd-rootless.sh' +DOCKER_DAEMON_ROOTLESS_SETUPTOOL_SH_BINARY_NAME='dockerd-rootless-setuptool.sh' diff --git a/hack/make/binary-daemon b/hack/make/binary-daemon index 3836efd836..6498b8e3d6 100644 --- a/hack/make/binary-daemon +++ b/hack/make/binary-daemon @@ -14,7 +14,7 @@ copy_binaries() { return fi echo "Copying nested executables into $dir" - for file in containerd containerd-shim containerd-shim-runc-v2 ctr runc docker-init docker-proxy rootlesskit rootlesskit-docker-proxy dockerd-rootless.sh; do + for file in containerd containerd-shim containerd-shim-runc-v2 ctr runc docker-init docker-proxy rootlesskit rootlesskit-docker-proxy dockerd-rootless.sh dockerd-rootless-setuptool.sh; do cp -f "$(command -v "$file")" "$dir/" if [ "$hash" = "hash" ]; then hash_files "$dir/$file" diff --git a/hack/make/install-binary b/hack/make/install-binary index 39c826dd43..1e1b107df1 100644 --- a/hack/make/install-binary +++ b/hack/make/install-binary @@ -29,6 +29,7 @@ install_binary() { install_binary "${DEST}/${DOCKER_ROOTLESSKIT_BINARY_NAME}" install_binary "${DEST}/${DOCKER_ROOTLESSKIT_DOCKER_PROXY_BINARY_NAME}" install_binary "${DEST}/${DOCKER_DAEMON_ROOTLESS_SH_BINARY_NAME}" + install_binary "${DEST}/${DOCKER_DAEMON_ROOTLESS_SETUPTOOL_SH_BINARY_NAME}" if [ -f "${DEST}/${DOCKER_VPNKIT_BINARY_NAME}" ]; then install_binary "${DEST}/${DOCKER_VPNKIT_BINARY_NAME}" fi