Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions boundary.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func (b *Boundary) Command(command []string) *exec.Cmd {
return b.jailer.Command(command)
}

func (b *Boundary) ConfigureChildProcess(pid int) error {
return b.jailer.ConfigureChildProcess(pid)
}

func (b *Boundary) Close() error {
// Stop proxy server
if b.proxyServer != nil {
Expand Down
29 changes: 21 additions & 8 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,21 +188,34 @@ func Run(ctx context.Context, config Config, args []string) error {
}
}()

var pid int
// Execute command in boundary
cmd := boundaryInstance.Command(args)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin

logger.Debug("Executing command in boundary", "command", strings.Join(args, " "))
err = cmd.Start()
if err != nil {
logger.Error("Command execution failed", "error", err)
return err
}

pid = cmd.Process.Pid
go func() {
defer cancel()
cmd := boundaryInstance.Command(args)
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()
if err != nil {
if err = cmd.Wait(); err != nil {
logger.Error("Command execution failed", "error", err)
return
}
}()

err = boundaryInstance.ConfigureChildProcess(pid)
if err != nil {
return err
}

// Wait for signal or context cancellation
select {
case sig := <-sigChan:
Expand Down
1 change: 1 addition & 0 deletions jail/jail.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Jailer interface {
Start() error
Command(command []string) *exec.Cmd
Close() error
ConfigureChildProcess(pid int) error
}

type Config struct {
Expand Down
149 changes: 64 additions & 85 deletions jail/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
"log/slog"
"os"
"os/exec"
"syscall"
"time"
)

// 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
commandEnv []string
httpProxyPort int
Expand All @@ -28,7 +28,6 @@ type LinuxJail struct {
func NewLinuxJail(config Config) (*LinuxJail, error) {
return &LinuxJail{
logger: config.Logger,
namespace: newNamespaceName(),
httpProxyPort: config.HttpProxyPort,
configDir: config.ConfigDir,
caCertPath: config.CACertPath,
Expand All @@ -46,21 +45,19 @@ func (l *LinuxJail) Start() error {
e := getEnvs(l.configDir, l.caCertPath)
l.commandEnv = mergeEnvs(e, map[string]string{})

return nil
}

func (l *LinuxJail) ConfigureChildProcess(pid int) error {
// 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)
}
//err := l.setupDNS()
//if err != nil {
// return fmt.Errorf("failed to setup DNS: %v", err)
//}

// Setup networking within namespace
err = l.setupNetworking()
err := l.setupNetworking(pid)
if err != nil {
return fmt.Errorf("failed to setup networking: %v", err)
}
Expand All @@ -76,12 +73,18 @@ func (l *LinuxJail) Start() error {

// Command returns an exec.Cmd configured to run within the network namespace
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...)
l.logger.Debug("Creating command with namespace")

cmd := exec.Command(command[0], command[1:]...)
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},
},
}
cmd.Env = l.commandEnv

return cmd
Expand All @@ -106,34 +109,20 @@ func (l *LinuxJail) Close() error {
}

// Clean up namespace-specific DNS config directory
netnsEtc := fmt.Sprintf("/etc/netns/%s", l.namespace)
err = os.RemoveAll(netnsEtc)
if err != nil {
l.logger.Warn("Failed to remove namespace DNS config", "dir", netnsEtc, "error", err)
// Continue with other cleanup
}

// Remove network namespace
err = l.removeNamespace()
if err != nil {
return fmt.Errorf("failed to remove namespace: %v", err)
}
//netnsEtc := fmt.Sprintf("/etc/netns/%s", l.namespace)
//err = os.RemoveAll(netnsEtc)
//if err != nil {
// l.logger.Warn("Failed to remove namespace DNS config", "dir", netnsEtc, "error", err)
// // Continue with other cleanup
//}

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 {
func (l *LinuxJail) setupNetworking(pidInt int) error {
PID := fmt.Sprintf("%v", pidInt)

// 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
Expand All @@ -148,18 +137,18 @@ func (l *LinuxJail) setupNetworking() error {
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)},
{"move veth to namespace", exec.Command("ip", "link", "set", vethNetJail, "netns", PID)},
{"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")},
{"configure namespace veth", exec.Command("nsenter", "-t", PID, "-n", "--", "ip", "addr", "add", "192.168.100.2/24", "dev", vethNetJail)},
{"bring up namespace veth", exec.Command("nsenter", "-t", PID, "-n", "--", "ip", "link", "set", vethNetJail, "up")},
{"bring up loopback", exec.Command("nsenter", "-t", PID, "-n", "--", "ip", "link", "set", "lo", "up")},
{"set default route in namespace", exec.Command("nsenter", "-t", PID, "-n", "--", "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)
if output, err := command.command.CombinedOutput(); err != nil {
return fmt.Errorf("failed to %s: %v, output: %s, args: %v", command.description, err, output, command.command.Args)
}
}

Expand All @@ -169,32 +158,32 @@ func (l *LinuxJail) setupNetworking() error {
// 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
}
//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 {
Expand Down Expand Up @@ -277,13 +266,3 @@ func (l *LinuxJail) cleanupNetworking() error {

return nil
}

// removeNamespace removes the network namespace
func (l *LinuxJail) removeNamespace() error {
cmd := exec.Command("ip", "netns", "del", l.namespace)
err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to remove namespace: %v", err)
}
return nil
}
4 changes: 4 additions & 0 deletions jail/macos.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ func (n *MacOSJail) Start() error {
return nil
}

func (l *MacOSJail) ConfigureChildProcess(pid int) error {
return nil
}

// Command runs the command with the network boundary group membership
func (n *MacOSJail) Command(command []string) *exec.Cmd {
n.logger.Debug("Command called", "command", command)
Expand Down
4 changes: 4 additions & 0 deletions jail/unprivileged.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func (u *Unprivileged) Start() error {
return nil
}

func (l *Unprivileged) ConfigureChildProcess(pid int) error {
return nil
}

func (u *Unprivileged) Command(command []string) *exec.Cmd {
u.logger.Debug("Creating unprivileged command", "command", command)

Expand Down
Loading