From ca5d760255ebe0ea7a4999540b13cd5d59be6552 Mon Sep 17 00:00:00 2001 From: Yevhenii Shcherbina Date: Mon, 2 Feb 2026 17:26:19 +0000 Subject: [PATCH] feat: implement dummy DNS server inside NS --- .gitignore | 3 +- Makefile | 9 ++ cli/cli.go | 14 +-- config/config.go | 6 +- dnsdummy/server.go | 95 ++++++++++++++++++++ go.mod | 5 ++ go.sum | 8 ++ nsjail_manager/child.go | 11 ++- nsjail_manager/nsjail/command_runner.go | 9 +- nsjail_manager/nsjail/dummy_dns.go | 48 ++++++++++ nsjail_manager/nsjail/jail.go | 34 +++---- nsjail_manager/nsjail/local_stub_resolver.go | 50 ----------- nsjail_manager/parent.go | 12 +-- 13 files changed, 215 insertions(+), 89 deletions(-) create mode 100644 dnsdummy/server.go create mode 100644 nsjail_manager/nsjail/dummy_dns.go delete mode 100644 nsjail_manager/nsjail/local_stub_resolver.go diff --git a/.gitignore b/.gitignore index 3b66b277..981de7ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store .idea -./boundary # Compiled binary from makefile +# Compiled binary from makefile +/boundary diff --git a/Makefile b/Makefile index 3037181c..200a97e6 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ # Variables BINARY_NAME := boundary BUILD_DIR := build +INSTALL_PATH ?= /usr/local/bin VERSION := $(shell git describe --tags --exact-match 2>/dev/null || echo "dev-$(shell git rev-parse --short HEAD)") LDFLAGS := -s -w -X main.version=$(VERSION) @@ -18,6 +19,13 @@ build: go build -ldflags="$(LDFLAGS)" -o $(BINARY_NAME) ./cmd/boundary @echo "✓ Built $(BINARY_NAME)" +# Install binary to $(INSTALL_PATH) (default: /usr/local/bin). +.PHONY: install +install: build + @echo "Installing $(BINARY_NAME) to $(INSTALL_PATH)..." + sudo install -m 755 $(BINARY_NAME) $(INSTALL_PATH)/$(BINARY_NAME) + @echo "✓ Installed $(INSTALL_PATH)/$(BINARY_NAME)" + # Build for all supported platforms .PHONY: build-all build-all: @@ -150,6 +158,7 @@ help: @echo "Available targets:" @echo " build Build for current platform" @echo " build-all Build for all supported platforms" + @echo " install Install binary to $(INSTALL_PATH) (may need sudo)" @echo " deps Download and verify dependencies" @echo " test Run tests" @echo " test-coverage Run tests with coverage report" diff --git a/cli/cli.go b/cli/cli.go index 5e577df8..a6f94cdd 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -120,13 +120,6 @@ func BaseCommand(version string) *serpent.Command { Value: &cliConfig.PprofPort, YAML: "pprof_port", }, - { - Flag: "configure-dns-for-local-stub-resolver", - Env: "BOUNDARY_CONFIGURE_DNS_FOR_LOCAL_STUB_RESOLVER", - Description: "Configure DNS for local stub resolver (e.g., systemd-resolved). Only needed when /etc/resolv.conf contains nameserver 127.0.0.53.", - Value: &cliConfig.ConfigureDNSForLocalStubResolver, - YAML: "configure_dns_for_local_stub_resolver", - }, { Flag: "jail-type", Env: "BOUNDARY_JAIL_TYPE", @@ -135,6 +128,13 @@ func BaseCommand(version string) *serpent.Command { Value: &cliConfig.JailType, YAML: "jail_type", }, + { + Flag: "use-real-dns", + Env: "BOUNDARY_USE_REAL_DNS", + Description: "Use real DNS in the jail instead of the dummy DNS (allows DNS exfiltration). Default: false.", + Value: &cliConfig.UseRealDNS, + YAML: "use_real_dns", + }, { Flag: "disable-audit-logs", Env: "DISABLE_AUDIT_LOGS", diff --git a/config/config.go b/config/config.go index edfd929c..5d9feec3 100644 --- a/config/config.go +++ b/config/config.go @@ -64,8 +64,8 @@ type CliConfig struct { ProxyPort serpent.Int64 `yaml:"proxy_port"` PprofEnabled serpent.Bool `yaml:"pprof_enabled"` PprofPort serpent.Int64 `yaml:"pprof_port"` - ConfigureDNSForLocalStubResolver serpent.Bool `yaml:"configure_dns_for_local_stub_resolver"` JailType serpent.String `yaml:"jail_type"` + UseRealDNS serpent.Bool `yaml:"use_real_dns"` DisableAuditLogs serpent.Bool `yaml:"disable_audit_logs"` LogProxySocketPath serpent.String `yaml:"log_proxy_socket_path"` } @@ -77,8 +77,8 @@ type AppConfig struct { ProxyPort int64 PprofEnabled bool PprofPort int64 - ConfigureDNSForLocalStubResolver bool JailType JailType + UseRealDNS bool TargetCMD []string UserInfo *UserInfo DisableAuditLogs bool @@ -107,8 +107,8 @@ func NewAppConfigFromCliConfig(cfg CliConfig, targetCMD []string) (AppConfig, er ProxyPort: cfg.ProxyPort.Value(), PprofEnabled: cfg.PprofEnabled.Value(), PprofPort: cfg.PprofPort.Value(), - ConfigureDNSForLocalStubResolver: cfg.ConfigureDNSForLocalStubResolver.Value(), JailType: jailType, + UseRealDNS: cfg.UseRealDNS.Value(), TargetCMD: targetCMD, UserInfo: userInfo, DisableAuditLogs: cfg.DisableAuditLogs.Value(), diff --git a/dnsdummy/server.go b/dnsdummy/server.go new file mode 100644 index 00000000..c2beb017 --- /dev/null +++ b/dnsdummy/server.go @@ -0,0 +1,95 @@ +package dnsdummy + +import ( + "log/slog" + + "github.com/miekg/dns" +) + +// DummyA is the IPv4 address returned for every A record query (arbitrary non-loopback). +const DummyA = "6.6.6.6" + +// DummyAAAA is the IPv6 address returned for every AAAA record query (documentation prefix). +const DummyAAAA = "2001:db8::1" + +// Server is a minimal DNS server that responds to every query with a dummy A record. +// Used inside the network namespace to prevent DNS exfiltration. +type Server struct { + udp *dns.Server + tcp *dns.Server + logger *slog.Logger +} + +// NewServer creates a dummy DNS server that listens on addr (e.g. "127.0.0.1:53"). +func NewServer(addr string, logger *slog.Logger) *Server { + handler := dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) { + m := new(dns.Msg) + m.SetReply(r) + m.Authoritative = true + + for _, q := range r.Question { + switch q.Qtype { + case dns.TypeA: + rr, err := dns.NewRR(q.Name + " 1 IN A " + DummyA) + if err != nil { + continue + } + m.Answer = append(m.Answer, rr) + case dns.TypeAAAA: + rr, err := dns.NewRR(q.Name + " 1 IN AAAA " + DummyAAAA) + if err != nil { + continue + } + m.Answer = append(m.Answer, rr) + default: + m.Rcode = dns.RcodeSuccess + } + } + + if err := w.WriteMsg(m); err != nil { + logger.Debug("dummy DNS: failed to write response", "error", err) + } + }) + + udp := &dns.Server{ + Addr: addr, + Net: "udp", + Handler: handler, + } + tcp := &dns.Server{ + Addr: addr, + Net: "tcp", + Handler: handler, + } + + return &Server{udp: udp, tcp: tcp, logger: logger} +} + +// ListenAndServe starts the UDP and TCP servers in goroutines and returns immediately. +// Logger must be non-nil; server errors are logged via s.logger. +func (s *Server) ListenAndServe() { + go func() { + if err := s.tcp.ListenAndServe(); err != nil { + s.logger.Error("dummy DNS TCP server failed", "error", err) + } + }() + go func() { + if err := s.udp.ListenAndServe(); err != nil { + s.logger.Error("dummy DNS UDP server failed", "error", err) + } + }() +} + +// Shutdown stops the servers. +func (s *Server) Shutdown() { + if err := s.udp.Shutdown(); err != nil { + s.logger.Error("dummy DNS UDP server shutdown failed", "error", err) + } + if err := s.tcp.Shutdown(); err != nil { + s.logger.Error("dummy DNS TCP server shutdown failed", "error", err) + } +} + +// DefaultDummyDNSPort is the port the dummy DNS server listens on (high port to avoid CAP_NET_BIND_SERVICE). +// Traffic to port 53 is DNAT'd to this port in the namespace. +const DefaultDummyDNSPort = "5353" diff --git a/go.mod b/go.mod index 12fb1c46..a5eee901 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/miekg/dns v1.1.72 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/pion/transport/v2 v2.2.10 // indirect @@ -34,7 +35,11 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sync v0.19.0 // indirect golang.org/x/term v0.38.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gopkg.in/yaml.v3 v3.0.1 // indirect kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 // indirect diff --git a/go.sum b/go.sum index 6f14ff79..1b28fbe2 100644 --- a/go.sum +++ b/go.sum @@ -71,6 +71,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -150,6 +152,8 @@ golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2 golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -164,6 +168,8 @@ golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwE golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -198,6 +204,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= diff --git a/nsjail_manager/child.go b/nsjail_manager/child.go index 824816f1..9c5d4d1e 100644 --- a/nsjail_manager/child.go +++ b/nsjail_manager/child.go @@ -66,12 +66,15 @@ func RunChild(logger *slog.Logger, targetCMD []string) error { } logger.Info("child networking is successfully configured") - if os.Getenv("CONFIGURE_DNS_FOR_LOCAL_STUB_RESOLVER") == "true" { - err = nsjail.ConfigureDNSForLocalStubResolver() + if os.Getenv("USE_REAL_DNS") == "true" { + logger.Info("using real DNS in namespace (--use-real-dns)") + } else { + // Run dummy DNS server in namespace and redirect all DNS to it to prevent DNS exfiltration + err = nsjail.StartDummyDNSAndRedirect(logger) if err != nil { - return fmt.Errorf("failed to configure DNS in namespace: %v", err) + return fmt.Errorf("failed to start dummy DNS in namespace: %v", err) } - logger.Info("DNS in namespace is configured successfully") + logger.Info("dummy DNS server started in namespace") } // Program to run diff --git a/nsjail_manager/nsjail/command_runner.go b/nsjail_manager/nsjail/command_runner.go index cb03b15c..f5c65c38 100644 --- a/nsjail_manager/nsjail/command_runner.go +++ b/nsjail_manager/nsjail/command_runner.go @@ -14,6 +14,7 @@ type command struct { ambientCaps []uintptr // If ignoreErr isn't empty and this specific error occurs, suppress it (don’t log it, don’t return it). + // If ignoreErr is "*" - ignore any error; ignoreErr string } @@ -40,7 +41,13 @@ func newCommandWithIgnoreErr( } func (cmd *command) isIgnorableError(err string) bool { - return cmd.ignoreErr != "" && strings.Contains(err, cmd.ignoreErr) + if cmd.ignoreErr == "" { + return false + } + if cmd.ignoreErr == "*" { + return true + } + return strings.Contains(err, cmd.ignoreErr) } type commandRunner struct { diff --git a/nsjail_manager/nsjail/dummy_dns.go b/nsjail_manager/nsjail/dummy_dns.go new file mode 100644 index 00000000..042d270b --- /dev/null +++ b/nsjail_manager/nsjail/dummy_dns.go @@ -0,0 +1,48 @@ +package nsjail + +import ( + "log/slog" + "os/exec" + + "github.com/coder/boundary/dnsdummy" + "golang.org/x/sys/unix" +) + +// StartDummyDNSAndRedirect starts a dummy DNS server in-process (goroutine) listening on +// 127.0.0.1:5353 and redirects all DNS traffic (UDP/TCP port 53) in the namespace to it +// via iptables. This prevents DNS exfiltration: all DNS queries get a dummy response (6.6.6.6). +// Must be called from inside the network namespace. +func StartDummyDNSAndRedirect(logger *slog.Logger) error { + addr := "127.0.0.1:" + dnsdummy.DefaultDummyDNSPort + server := dnsdummy.NewServer(addr, logger) + server.ListenAndServe() + logger.Debug("dummy DNS server started in-process", "addr", addr) + + // Redirect all DNS (UDP and TCP port 53) to 127.0.0.1:5353 + runner := newCommandRunner([]*command{ + // Allow loopback-destined traffic to pass through NAT so DNAT to 127.0.0.1 works. + // Best-effort: in some environments (e.g. Sysbox/Docker) this command may not work, + // but DNS setup should work anyway. + newCommandWithIgnoreErr( + "Allow loopback-destined traffic for dummy DNS (route_localnet)", + exec.Command("sysctl", "-w", "net.ipv4.conf.all.route_localnet=1"), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + "*", + ), + newCommand( + "Redirect UDP DNS to dummy server", + exec.Command("iptables", "-t", "nat", "-A", "OUTPUT", "-p", "udp", "--dport", "53", "-j", "DNAT", "--to-destination", addr), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + ), + newCommand( + "Redirect TCP DNS to dummy server", + exec.Command("iptables", "-t", "nat", "-A", "OUTPUT", "-p", "tcp", "--dport", "53", "-j", "DNAT", "--to-destination", addr), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + ), + }) + if err := runner.run(); err != nil { + return err + } + + return nil +} diff --git a/nsjail_manager/nsjail/jail.go b/nsjail_manager/nsjail/jail.go index ca1c96cf..3e0f9a7c 100644 --- a/nsjail_manager/nsjail/jail.go +++ b/nsjail_manager/nsjail/jail.go @@ -18,12 +18,12 @@ type Jailer interface { } type Config struct { - Logger *slog.Logger - HttpProxyPort int - HomeDir string - ConfigDir string - CACertPath string - ConfigureDNSForLocalStubResolver bool + Logger *slog.Logger + HttpProxyPort int + HomeDir string + ConfigDir string + CACertPath string + UseRealDNS bool } // LinuxJail implements Jailer using Linux network namespaces @@ -31,19 +31,19 @@ type LinuxJail struct { logger *slog.Logger vethHostName string // Host-side veth interface name for iptables rules vethJailName string // Jail-side veth interface name for iptables rules - httpProxyPort int - configDir string - caCertPath string - configureDNSForLocalStubResolver bool + httpProxyPort int + configDir string + caCertPath string + useRealDNS bool } func NewLinuxJail(config Config) (*LinuxJail, error) { return &LinuxJail{ - logger: config.Logger, - httpProxyPort: config.HttpProxyPort, - configDir: config.ConfigDir, - caCertPath: config.CACertPath, - configureDNSForLocalStubResolver: config.ConfigureDNSForLocalStubResolver, + logger: config.Logger, + httpProxyPort: config.HttpProxyPort, + configDir: config.ConfigDir, + caCertPath: config.CACertPath, + useRealDNS: config.UseRealDNS, }, nil } @@ -71,8 +71,8 @@ func (l *LinuxJail) Command(command []string) *exec.Cmd { cmd.Env = getEnvsForTargetProcess(l.configDir, l.caCertPath) cmd.Env = append(cmd.Env, "CHILD=true") cmd.Env = append(cmd.Env, fmt.Sprintf("VETH_JAIL_NAME=%v", l.vethJailName)) - if l.configureDNSForLocalStubResolver { - cmd.Env = append(cmd.Env, "CONFIGURE_DNS_FOR_LOCAL_STUB_RESOLVER=true") + if l.useRealDNS { + cmd.Env = append(cmd.Env, "USE_REAL_DNS=true") } cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout diff --git a/nsjail_manager/nsjail/local_stub_resolver.go b/nsjail_manager/nsjail/local_stub_resolver.go deleted file mode 100644 index 5983d863..00000000 --- a/nsjail_manager/nsjail/local_stub_resolver.go +++ /dev/null @@ -1,50 +0,0 @@ -package nsjail - -import ( - "os/exec" - - "golang.org/x/sys/unix" -) - -// ConfigureDNSForLocalStubResolver configures DNS redirection from the network namespace -// to the host's local stub resolver. This function should only be called when the host -// runs a local stub resolver such as systemd-resolved, and /etc/resolv.conf contains -// "nameserver 127.0.0.53" (listening on localhost). It redirects DNS requests from the -// namespace to the host by setting up iptables NAT rules. Additionally, /etc/systemd/resolved.conf -// should be configured with DNSStubListener=yes and DNSStubListenerExtra=192.168.100.1:53 -// to listen on the additional server address. -// NOTE: it's called inside network namespace. -func ConfigureDNSForLocalStubResolver() error { - runner := newCommandRunner([]*command{ - // Redirect all DNS queries inside the namespace to the host DNS listener. - // Needed because systemd-resolved listens on a host-side IP, not inside the namespace. - newCommand( - "Redirect DNS queries (DNAT 53 → host DNS)", - exec.Command("iptables", "-t", "nat", "-A", "OUTPUT", "-p", "udp", "--dport", "53", "-j", "DNAT", "--to-destination", "192.168.100.1:53"), - []uintptr{uintptr(unix.CAP_NET_ADMIN)}, - ), - // Rewrite the SOURCE IP of redirected DNS packets. - // Required because DNS queries originating as 127.0.0.1 inside the namespace - // must not leave the namespace with a loopback source (kernel drops them). - // SNAT ensures packets arrive at systemd-resolved with a valid, routable source. - newCommand( - "Fix DNS source IP (SNAT 127.0.0.x → 192.168.100.2)", - exec.Command("iptables", "-t", "nat", "-A", "POSTROUTING", "-p", "udp", "--dport", "53", "-d", "192.168.100.1", "-j", "SNAT", "--to-source", "192.168.100.2"), - []uintptr{uintptr(unix.CAP_NET_ADMIN)}, - ), - // Allow packets destined for 127.0.0.0/8 to go through routing and NAT. - // Without this, DNS queries to 127.0.0.53 never hit iptables OUTPUT - // and cannot be redirected to the host. - newCommand( - "Allow loopback-destined traffic to pass through NAT (route_localnet)", - // TODO(yevhenii): consider replacing with specific interfaces instead of all - exec.Command("sysctl", "-w", "net.ipv4.conf.all.route_localnet=1"), - []uintptr{uintptr(unix.CAP_NET_ADMIN)}, - ), - }) - if err := runner.run(); err != nil { - return err - } - - return nil -} diff --git a/nsjail_manager/parent.go b/nsjail_manager/parent.go index 343cf9db..1cf254ef 100644 --- a/nsjail_manager/parent.go +++ b/nsjail_manager/parent.go @@ -53,12 +53,12 @@ func RunParent(ctx context.Context, logger *slog.Logger, config config.AppConfig // Create jailer with cert path from TLS setup jailer, err := nsjail.NewLinuxJail(nsjail.Config{ - Logger: logger, - HttpProxyPort: int(config.ProxyPort), - HomeDir: config.UserInfo.HomeDir, - ConfigDir: config.UserInfo.ConfigDir, - CACertPath: config.UserInfo.CACertPath(), - ConfigureDNSForLocalStubResolver: config.ConfigureDNSForLocalStubResolver, + Logger: logger, + HttpProxyPort: int(config.ProxyPort), + HomeDir: config.UserInfo.HomeDir, + ConfigDir: config.UserInfo.ConfigDir, + CACertPath: config.UserInfo.CACertPath(), + UseRealDNS: config.UseRealDNS, }) if err != nil { return fmt.Errorf("failed to create jailer: %v", err)