Remove the built-in HTTP/SOCKS5 proxy servers and domain allowlist/denylist system. Instead, use tun2socks with a TUN device inside the network namespace to transparently route all TCP/UDP traffic through an external SOCKS5 proxy. This enables truly transparent proxying where any binary (Go, static, etc.) has its traffic routed through the proxy without needing to respect HTTP_PROXY/ALL_PROXY environment variables. The external proxy handles its own filtering. Key changes: - NetworkConfig: remove AllowedDomains/DeniedDomains/proxy ports, add ProxyURL - Delete internal/proxy/, internal/templates/, internal/importer/ - Embed tun2socks binary (downloaded at build time via Makefile) - Replace LinuxBridge with ProxyBridge (single Unix socket to external proxy) - Inner script sets up TUN device + tun2socks inside network namespace - Falls back to env-var proxying when TUN is unavailable - macOS: best-effort env-var proxying to external SOCKS5 proxy - CLI: remove --template/import, add --proxy flag - Feature detection: add ip/tun/tun2socks status to --linux-features
142 lines
3.8 KiB
Go
142 lines
3.8 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/Use-Tusk/fence/internal/config"
|
|
"github.com/Use-Tusk/fence/internal/platform"
|
|
)
|
|
|
|
// Manager handles sandbox initialization and command wrapping.
|
|
type Manager struct {
|
|
config *config.Config
|
|
proxyBridge *ProxyBridge
|
|
reverseBridge *ReverseBridge
|
|
tun2socksPath string // path to extracted tun2socks binary on host
|
|
exposedPorts []int
|
|
debug bool
|
|
monitor bool
|
|
initialized bool
|
|
}
|
|
|
|
// NewManager creates a new sandbox manager.
|
|
func NewManager(cfg *config.Config, debug, monitor bool) *Manager {
|
|
return &Manager{
|
|
config: cfg,
|
|
debug: debug,
|
|
monitor: monitor,
|
|
}
|
|
}
|
|
|
|
// SetExposedPorts sets the ports to expose for inbound connections.
|
|
func (m *Manager) SetExposedPorts(ports []int) {
|
|
m.exposedPorts = ports
|
|
}
|
|
|
|
// Initialize sets up the sandbox infrastructure.
|
|
func (m *Manager) Initialize() error {
|
|
if m.initialized {
|
|
return nil
|
|
}
|
|
|
|
if !platform.IsSupported() {
|
|
return fmt.Errorf("sandbox is not supported on platform: %s", platform.Detect())
|
|
}
|
|
|
|
// On Linux, set up proxy bridge and tun2socks if proxy is configured
|
|
if platform.Detect() == platform.Linux {
|
|
if m.config.Network.ProxyURL != "" {
|
|
// Extract embedded tun2socks binary
|
|
tun2socksPath, err := extractTun2Socks()
|
|
if err != nil {
|
|
m.logDebug("Failed to extract tun2socks: %v (will fall back to env-var proxying)", err)
|
|
} else {
|
|
m.tun2socksPath = tun2socksPath
|
|
}
|
|
|
|
// Create proxy bridge (socat: Unix socket -> external SOCKS5 proxy)
|
|
bridge, err := NewProxyBridge(m.config.Network.ProxyURL, m.debug)
|
|
if err != nil {
|
|
if m.tun2socksPath != "" {
|
|
os.Remove(m.tun2socksPath)
|
|
}
|
|
return fmt.Errorf("failed to initialize proxy bridge: %w", err)
|
|
}
|
|
m.proxyBridge = bridge
|
|
}
|
|
|
|
// Set up reverse bridge for exposed ports (inbound connections)
|
|
// Only needed when network namespace is available - otherwise they share the network
|
|
features := DetectLinuxFeatures()
|
|
if len(m.exposedPorts) > 0 && features.CanUnshareNet {
|
|
reverseBridge, err := NewReverseBridge(m.exposedPorts, m.debug)
|
|
if err != nil {
|
|
if m.proxyBridge != nil {
|
|
m.proxyBridge.Cleanup()
|
|
}
|
|
if m.tun2socksPath != "" {
|
|
os.Remove(m.tun2socksPath)
|
|
}
|
|
return fmt.Errorf("failed to initialize reverse bridge: %w", err)
|
|
}
|
|
m.reverseBridge = reverseBridge
|
|
} else if len(m.exposedPorts) > 0 && m.debug {
|
|
m.logDebug("Skipping reverse bridge (no network namespace, ports accessible directly)")
|
|
}
|
|
}
|
|
|
|
m.initialized = true
|
|
if m.config.Network.ProxyURL != "" {
|
|
m.logDebug("Sandbox manager initialized (proxy: %s)", m.config.Network.ProxyURL)
|
|
} else {
|
|
m.logDebug("Sandbox manager initialized (no proxy, network blocked)")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WrapCommand wraps a command with sandbox restrictions.
|
|
// Returns an error if the command is blocked by policy.
|
|
func (m *Manager) WrapCommand(command string) (string, error) {
|
|
if !m.initialized {
|
|
if err := m.Initialize(); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
// Check if command is blocked by policy
|
|
if err := CheckCommand(command, m.config); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
plat := platform.Detect()
|
|
switch plat {
|
|
case platform.MacOS:
|
|
return WrapCommandMacOS(m.config, command, m.exposedPorts, m.debug)
|
|
case platform.Linux:
|
|
return WrapCommandLinux(m.config, command, m.proxyBridge, m.reverseBridge, m.tun2socksPath, m.debug)
|
|
default:
|
|
return "", fmt.Errorf("unsupported platform: %s", plat)
|
|
}
|
|
}
|
|
|
|
// Cleanup stops the proxies and cleans up resources.
|
|
func (m *Manager) Cleanup() {
|
|
if m.reverseBridge != nil {
|
|
m.reverseBridge.Cleanup()
|
|
}
|
|
if m.proxyBridge != nil {
|
|
m.proxyBridge.Cleanup()
|
|
}
|
|
if m.tun2socksPath != "" {
|
|
os.Remove(m.tun2socksPath)
|
|
}
|
|
m.logDebug("Sandbox manager cleaned up")
|
|
}
|
|
|
|
func (m *Manager) logDebug(format string, args ...interface{}) {
|
|
if m.debug {
|
|
fmt.Fprintf(os.Stderr, "[fence] "+format+"\n", args...)
|
|
}
|
|
}
|