Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/docker-outside-of-docker/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{

"id": "docker-outside-of-docker",
"version": "1.10.2",
"version": "1.10.3",
"name": "Docker (docker-outside-of-docker)",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-outside-of-docker",
"description": "Re-use the host docker socket, adding the Docker CLI to a container. Feature invokes a script to enable using a forwarded Docker socket within a container to run Docker commands.",
Expand Down
7 changes: 4 additions & 3 deletions src/docker-outside-of-docker/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Comment on lines +509 to +513
sudoIf groupmod --gid "\${SOCKET_GID}" docker
else
# Enable proxy if not already running
Expand Down
105 changes: 105 additions & 0 deletions test/docker-outside-of-docker/docker_init_gid.sh
Original file line number Diff line number Diff line change
@@ -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
11 changes: 9 additions & 2 deletions test/docker-outside-of-docker/scenarios.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}