Skip to content

Configure upstream DNS servers and implement split DNS with dnsmasq#60

Open
adamw wants to merge 6 commits into
masterfrom
gh-57-intranet-dns
Open

Configure upstream DNS servers and implement split DNS with dnsmasq#60
adamw wants to merge 6 commits into
masterfrom
gh-57-intranet-dns

Conversation

@adamw
Copy link
Copy Markdown
Member

@adamw adamw commented May 7, 2026

Closes #57

This pull request implements robust, defense-in-depth improvements to DNS handling and configuration for sandcat's dev-container environment. The changes introduce user-configurable upstream DNS servers, ensure reliable and secure DNS resolution for agent containers, and close a potential DNS exfiltration channel. The implementation includes new configuration options, container orchestration updates, and supporting scripts.

DNS configuration and security enhancements:

  • Added support for user-, project-, and project-local configuration of upstream DNS servers via the new dns_servers setting, with merging and precedence logic documented and implemented. Validation ensures only numeric IP addresses are accepted, and changes are applied via a sidecar config file consumed by wg-client. [1] [2] [3] [4] [5] [6] [7] [8]
  • Introduced atomic writing of sidecar files (e.g., dns.conf and sandcat.env) to prevent race conditions between containers.

Container orchestration and DNS routing:

  • Updated Docker Compose and Dockerfiles to:
    • Add dnsmasq as a local DNS forwarder in the wg-client container, providing split-DNS: sibling container names resolve locally, all other queries go through the WireGuard tunnel.
    • Mount a shared wg-runtime volume to publish resolv.conf from wg-client, which agent containers copy on startup to ensure DNS queries are routed securely. [1] [2] [3] [4]
    • Set the embedded Docker DNS resolver (127.0.0.11) to forward unknown search-domain queries to an unroutable sink (192.0.2.1), preventing DNS exfiltration bypasses.

Reliability and health checks:

  • Added a new dnsmasq-ready script and updated health checks to ensure dnsmasq is running before marking the wg-client as healthy, preventing silent DNS failures. [1] [2]

Documentation and review process:

  • Updated documentation to describe new DNS configuration options, container-to-container DNS behavior, and security rationale.
  • Added a security reviewer system prompt outlining critical boundaries and attack surface concerns for future reviews.

adamw and others added 3 commits May 7, 2026 10:00
Run dnsmasq inside wg-client and split DNS by search domain: queries under
the compose project's network go to Docker's embedded resolver at 127.0.0.11
(sibling-container resolution), everything else is forwarded through wg0 to
the upstream from dns.conf or the hardcoded defaults.

Sibling containers share wg-client's network namespace via network_mode but
each have their own /etc/resolv.conf in their own mount namespace, so
wg-client publishes its resolv.conf onto a new wg-runtime volume and
app-init.sh copies it into /etc/resolv.conf on startup. wg-client itself
gets a 192.0.2.1 dns sink to close the bypass where Docker's embedded
resolver would otherwise forward unknown search-domain queries to the
host's upstream DNS, invisible to mitmproxy.

iptables kill-switch is installed before dnsmasq starts so there is no
window where the forwarder can leak out via eth0. A shared dnsmasq-ready
helper backs both the in-script wait loop and the compose healthcheck.

Closes #57

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"""
tmp_path = path + ".tmp"
with open(tmp_path, "w") as f:
f.write(body)
adamw and others added 3 commits May 8, 2026 10:47
Two resilience fixes for the split-DNS path:

1. Sibling containers bind to wg-client's network namespace at create-time
   via `network_mode: service:wg-client`, so a wg-client restart would
   strand them on a destroyed namespace. Replace `exec sleep infinity`
   with a foreground supervisor that respawns dnsmasq locally if it dies,
   keeping the namespace alive. `restart: unless-stopped` remains as a
   backstop for SIGKILL/oom of the supervisor itself.

2. The mitmproxy healthcheck only required wireguard.conf and the CA
   cert, so wg-client could (in principle) read an absent dns.conf and
   silently fall back to defaults before the addon had written it. The
   addon now always writes dns.conf (empty when no overrides) and the
   healthcheck gates on its presence. wg-client already treats empty as
   "use defaults".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dns_servers setting and container-to-container DNS belong with the
other settings/network docs in the top-level README, not duplicated under
cli/. Adds a brief merge-rule entry for dns_servers and a new DNS
resolution section between Network access rules and Secret substitution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to resolve intranet domain names

2 participants