From 011f352577bd00cd3aaeb7994a73997a6878af5b Mon Sep 17 00:00:00 2001 From: Matt Heon Date: Fri, 5 Apr 2024 10:58:32 -0400 Subject: Internal networks cannot make external DNS requests Internal networks cannot connect to the internet thanks to routing, but they can connect to Aardvark, which will happily forward their DNS requests to the internet. This could theoretically be used to build a data-exfiltration sidechannel. Fix this by identifying internal networks with a filename suffix (using a character disallowed in actual network names to ensure we don't conflict with another network) and explicitly setting their DNS servers to an empty list (and refusing to set per-container DNS at all). We could actually error on finding DNS servers in an internal network, but silently ignoring prevents possible compatibility issues with Netavark. Signed-off-by: Matt Heon --- test/100-basic-name-resolution.bats | 50 +++++++++++++++++++++++++++++++++++++ test/helpers.bash | 18 +++++++------ 2 files changed, 61 insertions(+), 7 deletions(-) (limited to 'test') diff --git a/test/100-basic-name-resolution.bats b/test/100-basic-name-resolution.bats index f0f1bd7..e178717 100644 --- a/test/100-basic-name-resolution.bats +++ b/test/100-basic-name-resolution.bats @@ -165,3 +165,53 @@ load helpers # contain unexpected warning. assert "$output" !~ "WARNING: recursion requested but not available" } + +# Internal network, meaning no DNS servers. +# Hence all external requests must fail. +@test "basic container - internal network has no DNS" { + setup_slirp4netns + + subnet_a=$(random_subnet) + create_config network_name="podman1" internal=true container_id=$(random_string 64) container_name="aone" subnet="$subnet_a" custom_dns_server='"1.1.1.1","8.8.8.8"' aliases='"a1", "1a"' + config_a1=$config + # Network name is still recorded as podman1 + ip_a1=$(echo "$config_a1" | jq -r .networks.podman1.static_ips[0]) + gw=$(echo "$config_a1" | jq -r .network_info.podman1.subnets[0].gateway) + create_container "$config_a1" + a1_pid=$CONTAINER_NS_PID + run_in_container_netns "$a1_pid" "dig" "+short" "aone" "@$gw" + assert "$ip_a1" + # Set recursion bit is already set if requested so output must not + # contain unexpected warning. + assert "$output" !~ "WARNING: recursion requested but not available" + + # Internal network means no DNS server means this should hard-fail + expected_rc=1 run_in_container_netns "$a1_pid" "host" "-t" "ns" "google.com" "$gw" + assert "$output" =~ "Host google.com not found" + assert "$output" =~ "NXDOMAIN" +} + +# Internal network, but this time with IPv6. Same result as above expected. +@test "basic container - internal network has no DNS - ipv6" { + setup_slirp4netns + + subnet_a=$(random_subnet 6) + # Cloudflare and Google public anycast DNS v6 nameservers + create_config network_name="podman1" internal=true container_id=$(random_string 64) container_name="aone" subnet="$subnet_a" custom_dns_server='"2606:4700:4700::1111","2001:4860:4860::8888"' aliases='"a1", "1a"' + config_a1=$config + # Network name is still recorded as podman1 + ip_a1=$(echo "$config_a1" | jq -r .networks.podman1.static_ips[0]) + gw=$(echo "$config_a1" | jq -r .network_info.podman1.subnets[0].gateway) + create_container "$config_a1" + a1_pid=$CONTAINER_NS_PID + run_in_container_netns "$a1_pid" "dig" "+short" "aone" "@$gw" "AAAA" + assert "$ip_a1" + # Set recursion bit is already set if requested so output must not + # contain unexpected warning. + assert "$output" !~ "WARNING: recursion requested but not available" + + # Internal network means no DNS server means this should hard-fail + expected_rc=1 run_in_container_netns "$a1_pid" "host" "-t" "ns" "google.com" "$gw" + assert "$output" =~ "Host google.com not found" + assert "$output" =~ "NXDOMAIN" +} diff --git a/test/helpers.bash b/test/helpers.bash index 947e563..9537af8 100644 --- a/test/helpers.bash +++ b/test/helpers.bash @@ -366,6 +366,7 @@ function run_in_host_netns() { # subnet=$subnet specifies the network subnet # custom_dns_serve=$custom_dns_server # aliases=$aliases comma seperated container aliases for dns resolution. +# internal={true,false} default is false function create_config() { local network_name="" local container_id="" @@ -373,6 +374,7 @@ function create_config() { local subnet="" local custom_dns_server local aliases="" + local internal=false # parse arguments while [[ "$#" -gt 0 ]]; do @@ -396,6 +398,9 @@ function create_config() { aliases) aliases="$value" ;; + internal) + internal="$value" + ;; *) die "unknown argument for '$arg' create_config" ;; esac shift @@ -407,7 +412,7 @@ function create_config() { subnets="{\"subnet\":\"$subnet\",\"gateway\":\"$container_gw\"}" create_network "$network_name" "$container_ip" "eth0" "$aliases" - create_network_infos "$network_name" $(random_string 64) "$subnets" + create_network_infos "$network_name" $(random_string 64) "$subnets" "$internal" read -r -d '\0' config <