diff --git a/src/docker-outside-of-docker/install.sh b/src/docker-outside-of-docker/install.sh index b883227c0..e28f4b7ed 100755 --- a/src/docker-outside-of-docker/install.sh +++ b/src/docker-outside-of-docker/install.sh @@ -466,8 +466,6 @@ if [ "${ENABLE_NONROOT_DOCKER}" = "false" ] || [ "${USERNAME}" = "root" ]; then exit 0 fi -DOCKER_GID="$(grep -oP '^docker:x:\K[^:]+' /etc/group)" - # If enabling non-root access and specified user is found, setup socat and add script chown -h "${USERNAME}":root "${TARGET_SOCKET}" check_packages socat @@ -508,8 +506,11 @@ log "Ensuring ${USERNAME} has access to ${SOURCE_SOCKET} via ${TARGET_SOCKET}" # fall back on using socat to forward the docker socket to another unix socket so # that we can set permissions on it without affecting the host. if [ "${ENABLE_NONROOT_DOCKER}" = "true" ] && [ "${SOURCE_SOCKET}" != "${TARGET_SOCKET}" ] && [ "${USERNAME}" != "root" ] && [ "${USERNAME}" != "0" ]; then + DOCKER_GID="\$(getent group docker | cut -d: -f3)" SOCKET_GID=\$(stat -c '%g' ${SOURCE_SOCKET}) - if [ "\${SOCKET_GID}" != "0" ] && [ "\${SOCKET_GID}" != "${DOCKER_GID}" ] && ! grep -E ".+:x:\${SOCKET_GID}" /etc/group; then + if [ "\${SOCKET_GID}" = "\${DOCKER_GID}" ] && [ -L "${TARGET_SOCKET}" ] && [ "${TARGET_SOCKET}" -ef "${SOURCE_SOCKET}" ]; then + log "Docker group matches socket GID \${SOCKET_GID}, nothing to do" + elif [ "\${SOCKET_GID}" != "0" ] && ! getent group "\${SOCKET_GID}" > /dev/null; then sudoIf groupmod --gid "\${SOCKET_GID}" docker else # Enable proxy if not already running diff --git a/test/docker-outside-of-docker/docker_init_gid.sh b/test/docker-outside-of-docker/docker_init_gid.sh new file mode 100755 index 000000000..749230945 --- /dev/null +++ b/test/docker-outside-of-docker/docker_init_gid.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +set -e + +source dev-container-features-test-lib + +test_gid_case() ( + set -e + + local scenario="$1" + export GID_TEST_DIR + GID_TEST_DIR="$(mktemp -d)" + trap 'rm -rf "${GID_TEST_DIR}"' EXIT + + echo 2000 > "${GID_TEST_DIR}/docker-gid" + echo 100 > "${GID_TEST_DIR}/socket-gid" + touch "${GID_TEST_DIR}/source.sock" "${GID_TEST_DIR}/groups" + ln -s "${GID_TEST_DIR}/source.sock" "${GID_TEST_DIR}/target.sock" + + sed -e "s|/var/run/docker-host.sock|${GID_TEST_DIR}/source.sock|g" \ + -e "s|/var/run/docker.sock|${GID_TEST_DIR}/target.sock|g" \ + -e "s|/tmp/vscr-docker-from-docker|${GID_TEST_DIR}/proxy|g" \ + /usr/local/share/docker-init.sh > "${GID_TEST_DIR}/docker-init.sh" + + getent() { + if [ "$1" != group ]; then + return 1 + fi + if [ "$2" = docker ] || [ "$2" = "$(cat "${GID_TEST_DIR}/docker-gid")" ]; then + echo "docker:x:$(cat "${GID_TEST_DIR}/docker-gid"):vscode" + else + awk -F: -v gid="$2" '$3 == gid { print; found = 1 } END { exit !found }' "${GID_TEST_DIR}/groups" + fi + } + + stat() { + [ "$1" = -c ] && [ "$2" = '%g' ] && [ "$3" = "${GID_TEST_DIR}/source.sock" ] + cat "${GID_TEST_DIR}/socket-gid" + } + + groupmod() { + [ "$1" = --gid ] && [ "$3" = docker ] + echo "$2" > "${GID_TEST_DIR}/docker-gid" + echo "$*" > "${GID_TEST_DIR}/groupmod" + } + + id() { + [ "$1" = -u ] + echo 0 + } + + socat() { + return 0 + } + + export -f getent stat groupmod id socat + + case "${scenario}" in + matching|stale-target) + echo 100 > "${GID_TEST_DIR}/docker-gid" + if [ "${scenario}" = stale-target ]; then + rm "${GID_TEST_DIR}/target.sock" + touch "${GID_TEST_DIR}/target.sock" + fi + ;; + prefix) + echo 'other:x:1000:' > "${GID_TEST_DIR}/groups" + ;; + collision) + echo 'other:x:100:' > "${GID_TEST_DIR}/groups" + ;; + *) return 1 ;; + esac + + bash "${GID_TEST_DIR}/docker-init.sh" true || return 1 + + case "${scenario}" in + matching) + [ -L "${GID_TEST_DIR}/target.sock" ] && + [ "$(readlink "${GID_TEST_DIR}/target.sock")" = "${GID_TEST_DIR}/source.sock" ] && + [ ! -e "${GID_TEST_DIR}/proxy.pid" ] && + [ ! -e "${GID_TEST_DIR}/groupmod" ] && + grep -q 'Docker group matches socket GID 100, nothing to do' "${GID_TEST_DIR}/proxy.log" + ;; + prefix) + [ "$(cat "${GID_TEST_DIR}/docker-gid")" = 100 ] && + grep -qx -- '--gid 100 docker' "${GID_TEST_DIR}/groupmod" && + [ -L "${GID_TEST_DIR}/target.sock" ] && + [ ! -e "${GID_TEST_DIR}/proxy.pid" ] + ;; + collision|stale-target) + [ ! -L "${GID_TEST_DIR}/target.sock" ] && + [ -s "${GID_TEST_DIR}/proxy.pid" ] && + [ ! -e "${GID_TEST_DIR}/groupmod" ] && + grep -q 'Enabling socket proxy.' "${GID_TEST_DIR}/proxy.log" + ;; + esac +) + +check "runtime-matching-gid-preserves-symlink-without-proxy" test_gid_case matching +check "prefix-only-gid-match-updates-docker-group-without-proxy" test_gid_case prefix +check "exact-gid-collision-enables-proxy" test_gid_case collision +check "matching-gid-with-stale-target-enables-proxy" test_gid_case stale-target + +reportResults diff --git a/test/docker-outside-of-docker/scenarios.json b/test/docker-outside-of-docker/scenarios.json index 166cb1b03..43ddd1015 100644 --- a/test/docker-outside-of-docker/scenarios.json +++ b/test/docker-outside-of-docker/scenarios.json @@ -242,7 +242,14 @@ } }, "containerUser": "vscode" + }, + "docker_init_gid": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "docker-outside-of-docker": { + "moby": true + } + }, + "containerUser": "vscode" } } - - \ No newline at end of file