diff --git a/.github/workflows/test-pr-arm64.yaml b/.github/workflows/test-pr-arm64.yaml index e5855ced2..4e48da3e3 100644 --- a/.github/workflows/test-pr-arm64.yaml +++ b/.github/workflows/test-pr-arm64.yaml @@ -75,5 +75,14 @@ jobs: - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli + - name: "Exclude iptables-isolation scenarios from docker-in-docker" + if: matrix.features == 'docker-in-docker' + run: | + sudo apt-get update && sudo apt-get install -y jq + sed 's://.*$::' test/docker-in-docker/scenarios.json \ + | jq 'del(.docker_without_iptables, .docker_without_iptables_ubuntu)' \ + > test/docker-in-docker/scenarios.json.tmp + mv test/docker-in-docker/scenarios.json.tmp test/docker-in-docker/scenarios.json + - name: "Testing '${{ matrix.features }}' scenarios" run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated . diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index e00c50876..9a7edf13d 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -92,5 +92,42 @@ jobs: - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli + - name: "Exclude iptables-isolation scenarios from docker-in-docker" + if: matrix.features == 'docker-in-docker' + run: | + sudo apt-get update && sudo apt-get install -y jq + sed 's://.*$::' test/docker-in-docker/scenarios.json \ + | jq 'del(.docker_without_iptables, .docker_without_iptables_ubuntu)' \ + > test/docker-in-docker/scenarios.json.tmp + mv test/docker-in-docker/scenarios.json.tmp test/docker-in-docker/scenarios.json + - name: "Testing '${{ matrix.features }}' scenarios" run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated . + + iptables-isolation: + needs: [detect-changes] + if: contains(fromJSON(needs.detect-changes.outputs.features), 'docker-in-docker') + runs-on: ubuntu-latest + continue-on-error: true + strategy: + fail-fast: false + matrix: + scenario: + - docker_without_iptables + - docker_without_iptables_ubuntu + steps: + - uses: actions/checkout@v6 + + - name: "Install latest devcontainer CLI" + run: npm install -g @devcontainers/cli + + - name: "Isolate scenario '${{ matrix.scenario }}'" + run: | + sudo apt-get update && sudo apt-get install -y jq + sed 's://.*$::' test/docker-in-docker/scenarios.json \ + | jq '{ "${{ matrix.scenario }}": .["${{ matrix.scenario }}"] }' \ + > test/docker-in-docker/scenarios.json.tmp + mv test/docker-in-docker/scenarios.json.tmp test/docker-in-docker/scenarios.json + + - name: "Testing docker-in-docker scenario '${{ matrix.scenario }}'" + run: devcontainer features test --features docker-in-docker --filter ${{ matrix.scenario }} --skip-autogenerated . diff --git a/src/docker-in-docker/devcontainer-feature.json b/src/docker-in-docker/devcontainer-feature.json index 0af78923e..0c5259ac2 100644 --- a/src/docker-in-docker/devcontainer-feature.json +++ b/src/docker-in-docker/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "docker-in-docker", - "version": "3.0.1", + "version": "4.0.0", "name": "Docker (Docker-in-Docker)", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-in-docker", "description": "Create child containers *inside* a container, independent from the host's docker instance. Installs Docker extension in the container along with needed CLIs.", diff --git a/src/docker-in-docker/install.sh b/src/docker-in-docker/install.sh index e9740efc5..543e90055 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -314,25 +314,28 @@ if [ "${ADJUSTED_ID}" = "debian" ] && command -v update-ca-certificates > /dev/n fi # Swap to legacy iptables for compatibility (Debian only) -if [ "${ADJUSTED_ID}" = "debian" ]; then +#if [ "${ADJUSTED_ID}" = "debian" ]; then # On distros where legacy iptables is no longer kernel-supported (e.g. Ubuntu 26.04 / resolute), # prefer iptables-nft. Otherwise prefer legacy for backward compatibility. - use_nft=false - case "${VERSION_CODENAME}" in - resolute) use_nft=true ;; - esac - - if [ "${use_nft}" = "true" ] && type iptables-nft > /dev/null 2>&1; then - update-alternatives --set iptables /usr/sbin/iptables-nft || true - update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true - elif type iptables-legacy > /dev/null 2>&1; then - update-alternatives --set iptables /usr/sbin/iptables-legacy || true - update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true - elif type iptables-nft > /dev/null 2>&1; then - update-alternatives --set iptables /usr/sbin/iptables-nft || true - update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true - fi -fi +# use_nft=false +# case "${VERSION_CODENAME}" in +# resolute) use_nft=true ;; +# esac + +# if [ "${use_nft}" = "true" ] && type iptables-nft > /dev/null 2>&1; then +# echo "(*) Setting iptables alternatives to nft for better compatibility with newer kernels" +# update-alternatives --set iptables /usr/sbin/iptables-nft || true +# update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true +# elif type iptables-legacy > /dev/null 2>&1 && iptables-legacy -L > /dev/null 2>&1; then +# echo "(*) Setting iptables alternatives to legacy for better compatibility with Docker and older kernels" +# update-alternatives --set iptables /usr/sbin/iptables-legacy || true +# update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true +# elif type iptables-nft > /dev/null 2>&1; then +# echo "(*) Setting iptables alternatives to nft for better compatibility with newer kernels for non resolute" +# update-alternatives --set iptables /usr/sbin/iptables-nft || true +# update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true +# fi +#fi # Set up the necessary repositories if [ "${USE_MOBY}" = "true" ]; then @@ -970,6 +973,27 @@ DOCKER_DEFAULT_ADDRESS_POOL=${DOCKER_DEFAULT_ADDRESS_POOL} DOCKER_DEFAULT_IP6_TABLES=${DOCKER_DEFAULT_IP6_TABLES} EOF +# On Debian-based images, re-assert the iptables alternative at container start. +if [ "${ADJUSTED_ID}" = "debian" ]; then + tee -a /usr/local/share/docker-init.sh > /dev/null \ +<< 'EOF' +# Prefer legacy only when the ip_tables kernel module is actually present. +# (Do NOT call `iptables-legacy -L/-nL` to test this — it auto-modprobes ip_tables +# and would defeat hosts/scenarios where the module is intentionally absent.) +if type iptables-legacy > /dev/null 2>&1 \ + && { grep -qE '^(ip_tables)\b' /proc/modules \ + || [ -d /sys/module/ip_tables ]; } \ + && update-alternatives --list iptables 2>/dev/null | grep -q '/usr/sbin/iptables-legacy'; then + update-alternatives --set iptables /usr/sbin/iptables-legacy || true + update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true +elif type iptables-nft > /dev/null 2>&1 \ + && update-alternatives --list iptables 2>/dev/null | grep -q '/usr/sbin/iptables-nft'; then + update-alternatives --set iptables /usr/sbin/iptables-nft || true + update-alternatives --set ip6tables /usr/sbin/ip6tables-nft || true +fi +EOF +fi + tee -a /usr/local/share/docker-init.sh > /dev/null \ << 'EOF' dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAULT_ADDRESS_POOL=${DOCKER_DEFAULT_ADDRESS_POOL} DOCKER_DEFAULT_IP6_TABLES=${DOCKER_DEFAULT_IP6_TABLES} $(cat << 'INNEREOF' diff --git a/test/docker-in-docker/docker_with_iptables.sh b/test/docker-in-docker/docker_with_iptables.sh new file mode 100644 index 000000000..e29e10146 --- /dev/null +++ b/test/docker-in-docker/docker_with_iptables.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Feature specific tests +check "iptables works" sudo iptables -L +check "iptables uses legacy" bash -c "iptables --version | grep legacy" + +check "version" docker --version +check "docker-ps" bash -c "docker ps" +check "log-exists" bash -c "ls /tmp/dockerd.log" +check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'" +check "log-contents" bash -c "cat /tmp/dockerd.log | grep 'API listen on /var/run/docker.sock'" + +# Report result +reportResults + diff --git a/test/docker-in-docker/docker_with_iptables_ubuntu.sh b/test/docker-in-docker/docker_with_iptables_ubuntu.sh new file mode 100644 index 000000000..e29e10146 --- /dev/null +++ b/test/docker-in-docker/docker_with_iptables_ubuntu.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Feature specific tests +check "iptables works" sudo iptables -L +check "iptables uses legacy" bash -c "iptables --version | grep legacy" + +check "version" docker --version +check "docker-ps" bash -c "docker ps" +check "log-exists" bash -c "ls /tmp/dockerd.log" +check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'" +check "log-contents" bash -c "cat /tmp/dockerd.log | grep 'API listen on /var/run/docker.sock'" + +# Report result +reportResults + diff --git a/test/docker-in-docker/docker_without_iptables.sh b/test/docker-in-docker/docker_without_iptables.sh new file mode 100644 index 000000000..6ecaddc8d --- /dev/null +++ b/test/docker-in-docker/docker_without_iptables.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Feature specific tests +check "docker-ps" bash -c "docker ps" +# Fail loudly if dockerd never finished initializing, printing the real error +check "dockerd-started-successfully" bash -c ' + if ! grep -q "Daemon has completed initialization" /tmp/dockerd.log; then + echo "❌ Docker daemon failed to start. Last errors from /tmp/dockerd.log:" + echo "----- dockerd.log (tail) -----" + tail -n 100 /tmp/dockerd.log + echo "----- error/fatal lines -----" + grep -iE "error|fatal|failed|panic" /tmp/dockerd.log || true + exit 1 + fi +' +check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'" + +check "iptables works" sudo iptables -L +check "iptables uses nf_tables" bash -c "iptables --version | grep nf_tables" + +check "version" docker --version +check "docker-ps" bash -c "docker ps" +check "log-exists" bash -c "ls /tmp/dockerd.log" +check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'" +check "log-contents" bash -c "cat /tmp/dockerd.log | grep 'API listen on /var/run/docker.sock'" + +# Report result +reportResults + diff --git a/test/docker-in-docker/docker_without_iptables_ubuntu.sh b/test/docker-in-docker/docker_without_iptables_ubuntu.sh new file mode 100644 index 000000000..6d2dab04c --- /dev/null +++ b/test/docker-in-docker/docker_without_iptables_ubuntu.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Feature specific tests +check "iptables works" sudo iptables -L +check "iptables uses nf_tables" bash -c "iptables --version | grep nf_tables" + +check "version" docker --version +check "docker-ps" bash -c "docker ps" +check "log-exists" bash -c "ls /tmp/dockerd.log" +check "log-for-completion" bash -c "cat /tmp/dockerd.log | grep 'Daemon has completed initialization'" +check "log-contents" bash -c "cat /tmp/dockerd.log | grep 'API listen on /var/run/docker.sock'" + +# Report result +reportResults + diff --git a/test/docker-in-docker/scenarios.json b/test/docker-in-docker/scenarios.json index 2f9df3958..497708f26 100644 --- a/test/docker-in-docker/scenarios.json +++ b/test/docker-in-docker/scenarios.json @@ -1,4 +1,40 @@ { + "docker_without_iptables": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "docker-in-docker": { + "moby": "false" + } + }, + "initializeCommand": "sudo modprobe --remove --remove-holders --wait 1000 ip_tables" + }, + "docker_with_iptables": { + "image": "mcr.microsoft.com/devcontainers/base:debian", + "features": { + "docker-in-docker": { + "moby": "false" + } + }, + "initializeCommand": "sudo modprobe ip_tables" + }, + "docker_without_iptables_ubuntu": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "docker-in-docker": { + "moby": "false" + } + }, + "initializeCommand": "sudo modprobe --remove --remove-holders --wait 1000 ip_tables" + }, + "docker_with_iptables_ubuntu": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "docker-in-docker": { + "moby": "false" + } + }, + "initializeCommand": "sudo modprobe ip_tables" + }, "overlayfs_containerd_root": { "image": "mcr.microsoft.com/devcontainers/base:noble", "features": {