diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22fb82a..1931848 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,6 +73,36 @@ jobs: - name: Download and verify dependencies run: make deps + # Before (default): + # - /etc/resolv.conf -> /run/systemd/resolve/stub-resolv.conf + # - stub-resolv.conf points to 127.0.0.53 (systemd-resolved stub listener) + # - systemd-resolved forwards to the real upstream file: + # /run/systemd/resolve/resolv.conf + # Flow: /etc/resolv.conf -> stub-resolv.conf (127.0.0.53) -> systemd-resolved -> /run/systemd/resolve/resolv.conf + # + # After (bind-mount): + # - /etc/resolv.conf is bind-mounted to /run/systemd/resolve/resolv.conf + # - processes read upstream nameservers directly from /run/systemd/resolve/resolv.conf + # Flow: /etc/resolv.conf -> /run/systemd/resolve/resolv.conf + # + # This makes processes talk directly to the upstream DNS servers and + # bypasses the systemd-resolved *stub listener* (127.0.0.53). + # + # Important nuance: systemd-resolved itself is NOT stopped; it still runs and updates + # /run/systemd/resolve/resolv.conf. Because this is a bind (not a copy), updates to the + # upstream list are visible. Trade-off: we lose the stub’s features (caching, + # split-DNS/VPN per-interface behavior, DNSSEC/DoT/DoH mediation, mDNS/LLMNR). + # + # Reason: network namespaces have their own network stack (interfaces, routes and loopback). + # A process inside a network namespace resolves 127.0.0.53 against that namespace’s loopback, not the host’s, + # and systemd-resolved usually listens on the host loopback. As a result the stub at 127.0.0.53 is often + # unreachable from an isolated namespace and DNS lookups fail. + # Bind-mounting /run/systemd/resolve/resolv.conf over /etc/resolv.conf forces processes to use the upstream + # nameservers directly, avoiding that failure. + - name: Change DNS configuration + if: runner.os == 'Linux' + run: sudo mount --bind /run/systemd/resolve/resolv.conf /etc/resolv.conf + - name: Run unit tests run: make unit-test diff --git a/Makefile b/Makefile index efc16de..e2c2beb 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ e2e-test: echo "E2E tests require Linux platform. Current platform: $$(uname)"; \ exit 1; \ fi - sudo $(shell which go) test -v -race ./e2e_tests + sudo $(shell which go) test -v -race ./e2e_tests -count=1 @echo "✓ E2E tests passed!" # Run tests with coverage (needs sudo for E2E tests) diff --git a/boundary.go b/boundary.go index d3e98a6..eee6d57 100644 --- a/boundary.go +++ b/boundary.go @@ -56,7 +56,7 @@ func New(ctx context.Context, config Config) (*Boundary, error) { func (b *Boundary) Start() error { // Start the jailer (network isolation) - err := b.jailer.Start() + err := b.jailer.ConfigureBeforeCommandExecution() if err != nil { return fmt.Errorf("failed to start jailer: %v", err) } @@ -78,6 +78,14 @@ func (b *Boundary) Command(command []string) *exec.Cmd { return b.jailer.Command(command) } +func (b *Boundary) ConfigureAfterCommandExecution(processPID int) error { + return b.jailer.ConfigureAfterCommandExecution(processPID) +} + +func (b *Boundary) GetNetworkConfiguration() jail.NetworkConfiguration { + return b.jailer.GetNetworkConfiguration() +} + func (b *Boundary) Close() error { // Stop proxy server if b.proxyServer != nil { diff --git a/cli/cli.go b/cli/cli.go index dabaa46..04a6b4e 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -3,8 +3,10 @@ package cli import ( "context" "fmt" + "log" "log/slog" "os" + "os/exec" "os/signal" "path/filepath" "strings" @@ -94,6 +96,35 @@ func BaseCommand() *serpent.Command { // Run executes the boundary command with the given configuration and arguments func Run(ctx context.Context, config Config, args []string) error { + isChild := os.Getenv("CHILD") == "true" + if isChild { + log.Printf("CHILD process is started") + vethNetJail := os.Getenv("VETH_JAIL_NAME") + + err := jail.SetupChildNetworking(vethNetJail) + if err != nil { + return fmt.Errorf("failed to run SetupChildNetworking: %v", err) + } + log.Printf("child networking is configured") + + // Program to run + bin := args[0] + args = args[1:] + + cmd := exec.Command(bin, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + log.Printf("failed to run %s: %v, output: %s", bin, err, "output") + return err + } + log.Printf("successfully run %s: %s", bin, "output") + + return nil + } + ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -191,15 +222,28 @@ func Run(ctx context.Context, config Config, args []string) error { // Execute command in boundary go func() { defer cancel() - cmd := boundaryInstance.Command(args) + cmd := boundaryInstance.Command(os.Args) + cmd.Env = append(cmd.Env, "CHILD=true") + cmd.Env = append(cmd.Env, fmt.Sprintf("VETH_JAIL_NAME=%v", boundaryInstance.GetNetworkConfiguration().VethJailName)) cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin - logger.Debug("Executing command in boundary", "command", strings.Join(args, " ")) - err := cmd.Run() + logger.Debug("Executing command in boundary", "command", strings.Join(os.Args, " ")) + err := cmd.Start() + if err != nil { + logger.Error("Command execution failed(Start)", "error", err) + } + + err = boundaryInstance.ConfigureAfterCommandExecution(cmd.Process.Pid) + if err != nil { + logger.Error("configuration failed", "error", err) + } + + logger.Debug("waiting on a child process to finish") + err = cmd.Wait() if err != nil { - logger.Error("Command execution failed", "error", err) + logger.Error("Command execution failed(Wait)", "error", err) } }() diff --git a/e2e_tests/boundary_integration_test.go b/e2e_tests/boundary_integration_test.go index 84ff284..7efe682 100644 --- a/e2e_tests/boundary_integration_test.go +++ b/e2e_tests/boundary_integration_test.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "testing" "time" @@ -37,29 +38,15 @@ func findProjectRoot(t *testing.T) string { } } -// getNamespaceName gets the single network namespace name -// Fails if there are 0 or multiple namespaces -func getNamespaceName(t *testing.T) string { - cmd := exec.Command("ip", "netns", "list") +func getChildProcessPID(t *testing.T) int { + cmd := exec.Command("pgrep", "-f", "boundary-test", "-n") output, err := cmd.Output() - require.NoError(t, err, "Failed to list network namespaces") - - lines := strings.Split(string(output), "\n") - var namespaces []string - - for _, line := range lines { - line = strings.TrimSpace(line) - if line != "" { - // Extract namespace name (first field) - parts := strings.Fields(line) - if len(parts) > 0 { - namespaces = append(namespaces, parts[0]) - } - } - } + require.NoError(t, err) - require.Len(t, namespaces, 1, "Expected exactly one network namespace, found %d: %v", len(namespaces), namespaces) - return namespaces[0] + pidStr := strings.TrimSpace(string(output)) + pid, err := strconv.Atoi(pidStr) + require.NoError(t, err) + return pid } func TestBoundaryIntegration(t *testing.T) { @@ -73,7 +60,7 @@ func TestBoundaryIntegration(t *testing.T) { require.NoError(t, err, "Failed to build boundary binary") // Create context for boundary process - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 1500*time.Second) defer cancel() // Start boundary process with sudo @@ -81,10 +68,11 @@ func TestBoundaryIntegration(t *testing.T) { "--allow", "dev.coder.com", "--allow", "jsonplaceholder.typicode.com", "--log-level", "debug", - "--", "bash", "-c", "sleep 10 && echo 'Test completed'") + //"--", "/bin/bash") + "--", "/bin/bash", "-c", "/usr/bin/sleep 20 && /usr/bin/echo 'Test completed'") - // Suppress output to prevent terminal corruption - boundaryCmd.Stdout = os.Stdout // Let it go to /dev/null + boundaryCmd.Stdin = os.Stdin + boundaryCmd.Stdout = os.Stdout boundaryCmd.Stderr = os.Stderr // Start the process @@ -95,12 +83,18 @@ func TestBoundaryIntegration(t *testing.T) { time.Sleep(2 * time.Second) // Get the namespace name that boundary created - namespaceName := getNamespaceName(t) + //namespaceName := getNamespaceName(t) + + pidInt := getChildProcessPID(t) + pid := fmt.Sprintf("%v", pidInt) + + fmt.Printf("pidInt: %v\n", pidInt) + //time.Sleep(200 * time.Second) // Test HTTP request through boundary (from inside the jail) t.Run("HTTPRequestThroughBoundary", func(t *testing.T) { // Run curl directly in the namespace using ip netns exec - curlCmd := exec.Command("sudo", "ip", "netns", "exec", namespaceName, + curlCmd := exec.Command("sudo", "nsenter", "-t", pid, "-n", "--", "curl", "http://jsonplaceholder.typicode.com/todos/1") // Capture stderr separately @@ -128,7 +122,7 @@ func TestBoundaryIntegration(t *testing.T) { certPath := fmt.Sprintf("%v/ca-cert.pem", configDir) // Run curl directly in the namespace using ip netns exec - curlCmd := exec.Command("sudo", "ip", "netns", "exec", namespaceName, + curlCmd := exec.Command("sudo", "sudo", "nsenter", "-t", pid, "-n", "--", "env", fmt.Sprintf("SSL_CERT_FILE=%v", certPath), "curl", "-s", "https://dev.coder.com/api/v2") // Capture stderr separately @@ -149,7 +143,7 @@ func TestBoundaryIntegration(t *testing.T) { // Test blocked domain (from inside the jail) t.Run("BlockedDomainTest", func(t *testing.T) { // Run curl directly in the namespace using ip netns exec - curlCmd := exec.Command("sudo", "ip", "netns", "exec", namespaceName, + curlCmd := exec.Command("sudo", "sudo", "nsenter", "-t", pid, "-n", "--", "curl", "-s", "http://example.com") // Capture stderr separately diff --git a/jail/jail.go b/jail/jail.go index 2c2f217..8de8232 100644 --- a/jail/jail.go +++ b/jail/jail.go @@ -8,9 +8,15 @@ import ( ) type Jailer interface { - Start() error + ConfigureBeforeCommandExecution() error Command(command []string) *exec.Cmd + ConfigureAfterCommandExecution(processPID int) error Close() error + GetNetworkConfiguration() NetworkConfiguration +} + +type NetworkConfiguration struct { + VethJailName string } type Config struct { diff --git a/jail/linux.go b/jail/linux.go index a8c3a5f..08c0285 100644 --- a/jail/linux.go +++ b/jail/linux.go @@ -7,14 +7,18 @@ import ( "log/slog" "os" "os/exec" + "syscall" "time" + + "golang.org/x/sys/unix" ) // LinuxJail implements Jailer using Linux network namespaces type LinuxJail struct { logger *slog.Logger namespace string - vethHost string // Host-side veth interface name for iptables rules + vethHostName string // Host-side veth interface name for iptables rules + vethJailName string // Jail-side veth interface name for iptables rules commandEnv []string httpProxyPort int configDir string @@ -39,36 +43,20 @@ func NewLinuxJail(config Config) (*LinuxJail, error) { }, nil } -// Start creates network namespace and configures iptables rules -func (l *LinuxJail) Start() error { +// ConfigureBeforeCommandExecution prepares the jail environment before the target +// process is launched. It sets environment variables, creates the veth pair, and +// installs iptables rules on the host. At this stage, the target PID and its netns +// are not yet known. +func (l *LinuxJail) ConfigureBeforeCommandExecution() error { l.logger.Debug("Setup called") - e := getEnvs(l.configDir, l.caCertPath) - l.commandEnv = mergeEnvs(e, map[string]string{}) - - // Setup DNS configuration BEFORE creating namespace - // This ensures the namespace-specific resolv.conf is available when namespace is created - err := l.setupDNS() - if err != nil { - return fmt.Errorf("failed to setup DNS: %v", err) - } - - // Create namespace - err = l.createNamespace() - if err != nil { - return fmt.Errorf("failed to create namespace: %v", err) - } + l.commandEnv = getEnvs(l.configDir, l.caCertPath) - // Setup networking within namespace - err = l.setupNetworking() - if err != nil { - return fmt.Errorf("failed to setup networking: %v", err) + if err := l.configureHostNetworkBeforeCmdExec(); err != nil { + return err } - - // Setup iptables rules on host - err = l.setupIptables() - if err != nil { - return fmt.Errorf("failed to setup iptables: %v", err) + if err := l.configureIptables(); err != nil { + return fmt.Errorf("failed to configure iptables: %v", err) } return nil @@ -78,15 +66,41 @@ func (l *LinuxJail) Start() error { func (l *LinuxJail) Command(command []string) *exec.Cmd { l.logger.Debug("Creating command with namespace", "namespace", l.namespace) - cmdArgs := []string{"netns", "exec", l.namespace} - cmdArgs = append(cmdArgs, command...) - - cmd := exec.Command("ip", cmdArgs...) + cmd := exec.Command(command[0], command[1:]...) cmd.Env = l.commandEnv + cmd.Env = append(cmd.Env, "CHILD=true") + + cmd.SysProcAttr = &syscall.SysProcAttr{ + Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNET, + UidMappings: []syscall.SysProcIDMap{ + {ContainerID: 0, HostID: os.Getuid(), Size: 1}, + }, + GidMappings: []syscall.SysProcIDMap{ + {ContainerID: 0, HostID: os.Getgid(), Size: 1}, + }, + } return cmd } +// ConfigureAfterCommandExecution finalizes setup once the target process starts. +// With the child PID known, it moves the jail-side veth into the child’s network +// namespace. +func (l *LinuxJail) ConfigureAfterCommandExecution(pidInt int) error { + err := l.configureHostNetworkAfterCmdExec(pidInt) + if err != nil { + return fmt.Errorf("failed to configure parent networking: %v", err) + } + + return nil +} + +func (l *LinuxJail) GetNetworkConfiguration() NetworkConfiguration { + return NetworkConfiguration{ + VethJailName: l.vethJailName, + } +} + // Close removes the network namespace and iptables rules func (l *LinuxJail) Close() error { l.logger.Debug("Close called") @@ -122,88 +136,17 @@ func (l *LinuxJail) Close() error { return nil } -// createNamespace creates a new network namespace -func (l *LinuxJail) createNamespace() error { - cmd := exec.Command("ip", "netns", "add", l.namespace) - err := cmd.Run() - if err != nil { - return fmt.Errorf("failed to create namespace: %v", err) - } - return nil -} - -// setupNetworking configures networking within the namespace -func (l *LinuxJail) setupNetworking() error { - // Create veth pair with short names (Linux interface names limited to 15 chars) - // Generate unique ID to avoid conflicts - uniqueID := fmt.Sprintf("%d", time.Now().UnixNano()%10000000) // 7 digits max - vethHost := fmt.Sprintf("veth_h_%s", uniqueID) // veth_h_1234567 = 14 chars - vethNetJail := fmt.Sprintf("veth_n_%s", uniqueID) // veth_n_1234567 = 14 chars - - // Store veth interface name for iptables rules - l.vethHost = vethHost - - setupCmds := []struct { - description string - command *exec.Cmd - }{ - {"create veth pair", exec.Command("ip", "link", "add", vethHost, "type", "veth", "peer", "name", vethNetJail)}, - {"move veth to namespace", exec.Command("ip", "link", "set", vethNetJail, "netns", l.namespace)}, - {"configure host veth", exec.Command("ip", "addr", "add", "192.168.100.1/24", "dev", vethHost)}, - {"bring up host veth", exec.Command("ip", "link", "set", vethHost, "up")}, - {"configure namespace veth", exec.Command("ip", "netns", "exec", l.namespace, "ip", "addr", "add", "192.168.100.2/24", "dev", vethNetJail)}, - {"bring up namespace veth", exec.Command("ip", "netns", "exec", l.namespace, "ip", "link", "set", vethNetJail, "up")}, - {"bring up loopback", exec.Command("ip", "netns", "exec", l.namespace, "ip", "link", "set", "lo", "up")}, - {"set default route in namespace", exec.Command("ip", "netns", "exec", l.namespace, "ip", "route", "add", "default", "via", "192.168.100.1")}, - } - - for _, command := range setupCmds { - if err := command.command.Run(); err != nil { - return fmt.Errorf("failed to %s: %v", command.description, err) - } - } - - return nil -} - -// setupDNS configures DNS resolution for the namespace -// This ensures reliable DNS resolution by using public DNS servers -// instead of relying on the host's potentially complex DNS configuration -func (l *LinuxJail) setupDNS() error { - // Always create namespace-specific resolv.conf with reliable public DNS servers - // This avoids issues with systemd-resolved, Docker DNS, and other complex setups - netnsEtc := fmt.Sprintf("/etc/netns/%s", l.namespace) - err := os.MkdirAll(netnsEtc, 0755) - if err != nil { - return fmt.Errorf("failed to create /etc/netns directory: %v", err) - } - - // Write custom resolv.conf with multiple reliable public DNS servers - resolvConfPath := fmt.Sprintf("%s/resolv.conf", netnsEtc) - dnsConfig := `# Custom DNS for network namespace -nameserver 8.8.8.8 -nameserver 8.8.4.4 -nameserver 1.1.1.1 -nameserver 9.9.9.9 -options timeout:2 attempts:2 -` - err = os.WriteFile(resolvConfPath, []byte(dnsConfig), 0644) - if err != nil { - return fmt.Errorf("failed to write namespace-specific resolv.conf: %v", err) - } - - l.logger.Debug("DNS setup completed") - return nil -} - // setupIptables configures iptables rules for comprehensive TCP traffic interception -func (l *LinuxJail) setupIptables() error { +func (l *LinuxJail) configureIptables() error { // Enable IP forwarding cmd := exec.Command("sysctl", "-w", "net.ipv4.ip_forward=1") _ = cmd.Run() // Ignore error // NAT rules for outgoing traffic (MASQUERADE for return traffic) cmd = exec.Command("iptables", "-t", "nat", "-A", "POSTROUTING", "-s", "192.168.100.0/24", "-j", "MASQUERADE") + cmd.SysProcAttr = &syscall.SysProcAttr{ + AmbientCaps: []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + } err := cmd.Run() if err != nil { return fmt.Errorf("failed to add NAT rule: %v", err) @@ -211,7 +154,10 @@ func (l *LinuxJail) setupIptables() error { // COMPREHENSIVE APPROACH: Route ALL TCP traffic to HTTP proxy // The HTTP proxy will intelligently handle both HTTP and TLS traffic - cmd = exec.Command("iptables", "-t", "nat", "-A", "PREROUTING", "-i", l.vethHost, "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", l.httpProxyPort)) + cmd = exec.Command("iptables", "-t", "nat", "-A", "PREROUTING", "-i", l.vethHostName, "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", l.httpProxyPort)) + cmd.SysProcAttr = &syscall.SysProcAttr{ + AmbientCaps: []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + } err = cmd.Run() if err != nil { return fmt.Errorf("failed to add comprehensive TCP redirect rule: %v", err) @@ -219,25 +165,31 @@ func (l *LinuxJail) setupIptables() error { // TODO: clean up this rules cmd = exec.Command("iptables", "-A", "FORWARD", "-s", "192.168.100.0/24", "-j", "ACCEPT") + cmd.SysProcAttr = &syscall.SysProcAttr{ + AmbientCaps: []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + } err = cmd.Run() if err != nil { - return err + return fmt.Errorf("forward -s error: %v", err) } cmd = exec.Command("iptables", "-A", "FORWARD", "-d", "192.168.100.0/24", "-j", "ACCEPT") + cmd.SysProcAttr = &syscall.SysProcAttr{ + AmbientCaps: []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + } err = cmd.Run() if err != nil { - return err + return fmt.Errorf("forward -r error: %v", err) } - l.logger.Debug("Comprehensive TCP boundarying enabled", "interface", l.vethHost, "proxy_port", l.httpProxyPort) + l.logger.Debug("Comprehensive TCP boundarying enabled", "interface", l.vethHostName, "proxy_port", l.httpProxyPort) return nil } // cleanupIptables removes iptables rules func (l *LinuxJail) cleanupIptables() error { // Remove comprehensive TCP redirect rule - cmd := exec.Command("iptables", "-t", "nat", "-D", "PREROUTING", "-i", l.vethHost, "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", l.httpProxyPort)) + cmd := exec.Command("iptables", "-t", "nat", "-D", "PREROUTING", "-i", l.vethHostName, "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", l.httpProxyPort)) err := cmd.Run() if err != nil { l.logger.Error("Failed to remove TCP redirect rule", "error", err) @@ -287,3 +239,126 @@ func (l *LinuxJail) removeNamespace() error { } return nil } + +type command struct { + description string + cmd *exec.Cmd + ambientCaps []uintptr +} + +type commandRunner struct { + commands []*command +} + +func newCommandRunner(commands []*command) *commandRunner { + return &commandRunner{ + commands: commands, + } +} + +func (r *commandRunner) run() error { + for _, command := range r.commands { + command.cmd.SysProcAttr = &syscall.SysProcAttr{ + AmbientCaps: command.ambientCaps, + } + + output, err := command.cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to %s: %v, output: %s", command.description, err, output) + } + } + + return nil +} + +// configureHostNetworkBeforeCmdExec prepares host-side networking before the target +// process is started. At this point the target process is not running, so its PID and network +// namespace ID are not yet known. +func (l *LinuxJail) configureHostNetworkBeforeCmdExec() error { + // Create veth pair with short names (Linux interface names limited to 15 chars) + // Generate unique ID to avoid conflicts + uniqueID := fmt.Sprintf("%d", time.Now().UnixNano()%10000000) // 7 digits max + vethHostName := fmt.Sprintf("veth_h_%s", uniqueID) // veth_h_1234567 = 14 chars + vethJailName := fmt.Sprintf("veth_n_%s", uniqueID) // veth_n_1234567 = 14 chars + + // Store veth interface name for iptables rules + l.vethHostName = vethHostName + l.vethJailName = vethJailName + + runner := newCommandRunner([]*command{ + { + "create veth pair", + exec.Command("ip", "link", "add", vethHostName, "type", "veth", "peer", "name", vethJailName), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + }, + { + "configure host veth", + exec.Command("ip", "addr", "add", "192.168.100.1/24", "dev", vethHostName), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + }, + { + "bring up host veth", + exec.Command("ip", "link", "set", vethHostName, "up"), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + }, + }) + if err := runner.run(); err != nil { + return err + } + + return nil +} + +// configureHostNetworkAfterCmdExec finalizes host-side networking after the target +// process has started. It moves the jail-side veth into the target process's network +// namespace using the provided PID. This requires the process to be running so +// its PID (and thus its netns) are available. +func (l *LinuxJail) configureHostNetworkAfterCmdExec(pidInt int) error { + PID := fmt.Sprintf("%v", pidInt) + + runner := newCommandRunner([]*command{ + { + "move veth to namespace", + exec.Command("ip", "link", "set", l.vethJailName, "netns", PID), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + }, + }) + if err := runner.run(); err != nil { + return err + } + + return nil +} + +// SetupChildNetworking configures networking within the target process's network +// namespace. This runs inside the child process after it has been +// created and moved to its own network namespace. +func SetupChildNetworking(vethNetJail string) error { + runner := newCommandRunner([]*command{ + { + "configure namespace veth", + exec.Command("ip", "addr", "add", "192.168.100.2/24", "dev", vethNetJail), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + }, + { + "bring up namespace veth", + exec.Command("ip", "link", "set", vethNetJail, "up"), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + }, + { + "bring up loopback", + exec.Command("ip", "link", "set", "lo", "up"), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + }, + { + "set default route in namespace", + exec.Command("ip", "route", "add", "default", "via", "192.168.100.1"), + []uintptr{uintptr(unix.CAP_NET_ADMIN)}, + }, + }) + if err := runner.run(); err != nil { + return err + } + + return nil +} diff --git a/jail/macos.go b/jail/macos.go index f10c273..d108bce 100644 --- a/jail/macos.go +++ b/jail/macos.go @@ -54,8 +54,12 @@ func NewMacOSJail(config Config) (*MacOSJail, error) { }, nil } +func SetupChildNetworking(vethNetJail string) error { + return nil +} + // Setup creates the network boundary group and configures PF rules -func (n *MacOSJail) Start() error { +func (n *MacOSJail) ConfigureBeforeCommandExecution() error { n.logger.Debug("Setup called") // Create or get network boundary group @@ -340,3 +344,11 @@ func (n *MacOSJail) cleanupTempFiles() { } } } + +func (u *MacOSJail) ConfigureAfterCommandExecution(processPID int) error { + return nil +} + +func (l *MacOSJail) GetNetworkConfiguration() NetworkConfiguration { + return NetworkConfiguration{} +} diff --git a/jail/unprivileged.go b/jail/unprivileged.go index 516ac9c..456b1a1 100644 --- a/jail/unprivileged.go +++ b/jail/unprivileged.go @@ -31,7 +31,7 @@ func NewUnprivileged(config Config) (*Unprivileged, error) { }, nil } -func (u *Unprivileged) Start() error { +func (u *Unprivileged) ConfigureBeforeCommandExecution() error { u.logger.Debug("Starting in unprivileged mode") e := getEnvs(u.configDir, u.caCertPath) p := fmt.Sprintf("http://localhost:%d", u.httpProxyPort) @@ -60,3 +60,11 @@ func (u *Unprivileged) Close() error { u.logger.Debug("Closing unprivileged jail") return nil } + +func (u *Unprivileged) ConfigureAfterCommandExecution(processPID int) error { + return nil +} + +func (l *Unprivileged) GetNetworkConfiguration() NetworkConfiguration { + return NetworkConfiguration{} +}