rename Fence to Greywall as GreyHaven sandboxing component

Rebrand the project from Fence to Greywall, the sandboxing layer of the
GreyHaven platform. This updates:

- Go module path to gitea.app.monadical.io/monadical/greywall
- Binary name, CLI help text, and all usage examples
- Config paths (~/.config/greywall/greywall.json), env vars (GREYWALL_*)
- Log prefixes ([greywall:*]), temp file prefixes (greywall-*)
- All documentation, scripts, CI workflows, and example files
- README rewritten with GreyHaven branding and Fence attribution

Directory/file renames: cmd/fence → cmd/greywall, pkg/fence → pkg/greywall,
docs/why-fence.md → docs/why-greywall.md, example JSON files, and banner.
This commit is contained in:
2026-02-10 16:00:24 -06:00
parent 481616455a
commit da3a2ac3a4
68 changed files with 586 additions and 586 deletions

View File

@@ -15,7 +15,7 @@ import (
"syscall"
"time"
"github.com/Use-Tusk/fence/internal/config"
"gitea.app.monadical.io/monadical/greywall/internal/config"
)
// ProxyBridge bridges sandbox to an external SOCKS5 proxy via Unix socket.
@@ -53,7 +53,7 @@ func NewDnsBridge(dnsAddr string, debug bool) (*DnsBridge, error) {
socketID := hex.EncodeToString(id)
tmpDir := os.TempDir()
socketPath := filepath.Join(tmpDir, fmt.Sprintf("fence-dns-%s.sock", socketID))
socketPath := filepath.Join(tmpDir, fmt.Sprintf("greywall-dns-%s.sock", socketID))
bridge := &DnsBridge{
SocketPath: socketPath,
@@ -68,7 +68,7 @@ func NewDnsBridge(dnsAddr string, debug bool) (*DnsBridge, error) {
}
bridge.process = exec.Command("socat", socatArgs...) //nolint:gosec // args constructed from trusted input
if debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Starting DNS bridge: socat %s\n", strings.Join(socatArgs, " "))
fmt.Fprintf(os.Stderr, "[greywall:linux] Starting DNS bridge: socat %s\n", strings.Join(socatArgs, " "))
}
if err := bridge.process.Start(); err != nil {
return nil, fmt.Errorf("failed to start DNS bridge: %w", err)
@@ -78,7 +78,7 @@ func NewDnsBridge(dnsAddr string, debug bool) (*DnsBridge, error) {
for range 50 {
if fileExists(socketPath) {
if debug {
fmt.Fprintf(os.Stderr, "[fence:linux] DNS bridge ready (%s -> %s)\n", socketPath, dnsAddr)
fmt.Fprintf(os.Stderr, "[greywall:linux] DNS bridge ready (%s -> %s)\n", socketPath, dnsAddr)
}
return bridge, nil
}
@@ -98,7 +98,7 @@ func (b *DnsBridge) Cleanup() {
_ = os.Remove(b.SocketPath)
if b.debug {
fmt.Fprintf(os.Stderr, "[fence:linux] DNS bridge cleaned up\n")
fmt.Fprintf(os.Stderr, "[greywall:linux] DNS bridge cleaned up\n")
}
}
@@ -143,7 +143,7 @@ func NewProxyBridge(proxyURL string, debug bool) (*ProxyBridge, error) {
socketID := hex.EncodeToString(id)
tmpDir := os.TempDir()
socketPath := filepath.Join(tmpDir, fmt.Sprintf("fence-proxy-%s.sock", socketID))
socketPath := filepath.Join(tmpDir, fmt.Sprintf("greywall-proxy-%s.sock", socketID))
bridge := &ProxyBridge{
SocketPath: socketPath,
@@ -166,7 +166,7 @@ func NewProxyBridge(proxyURL string, debug bool) (*ProxyBridge, error) {
}
bridge.process = exec.Command("socat", socatArgs...) //nolint:gosec // args constructed from trusted input
if debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Starting proxy bridge: socat %s\n", strings.Join(socatArgs, " "))
fmt.Fprintf(os.Stderr, "[greywall:linux] Starting proxy bridge: socat %s\n", strings.Join(socatArgs, " "))
}
if err := bridge.process.Start(); err != nil {
return nil, fmt.Errorf("failed to start proxy bridge: %w", err)
@@ -176,7 +176,7 @@ func NewProxyBridge(proxyURL string, debug bool) (*ProxyBridge, error) {
for range 50 {
if fileExists(socketPath) {
if debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Proxy bridge ready (%s)\n", socketPath)
fmt.Fprintf(os.Stderr, "[greywall:linux] Proxy bridge ready (%s)\n", socketPath)
}
return bridge, nil
}
@@ -196,7 +196,7 @@ func (b *ProxyBridge) Cleanup() {
_ = os.Remove(b.SocketPath)
if b.debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Proxy bridge cleaned up\n")
fmt.Fprintf(os.Stderr, "[greywall:linux] Proxy bridge cleaned up\n")
}
}
@@ -239,7 +239,7 @@ func NewReverseBridge(ports []int, debug bool) (*ReverseBridge, error) {
}
for _, port := range ports {
socketPath := filepath.Join(tmpDir, fmt.Sprintf("fence-rev-%d-%s.sock", port, socketID))
socketPath := filepath.Join(tmpDir, fmt.Sprintf("greywall-rev-%d-%s.sock", port, socketID))
bridge.SocketPaths = append(bridge.SocketPaths, socketPath)
// Start reverse bridge: TCP listen on host port -> Unix socket
@@ -251,7 +251,7 @@ func NewReverseBridge(ports []int, debug bool) (*ReverseBridge, error) {
}
proc := exec.Command("socat", args...) //nolint:gosec // args constructed from trusted input
if debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Starting reverse bridge for port %d: socat %s\n", port, strings.Join(args, " "))
fmt.Fprintf(os.Stderr, "[greywall:linux] Starting reverse bridge for port %d: socat %s\n", port, strings.Join(args, " "))
}
if err := proc.Start(); err != nil {
bridge.Cleanup()
@@ -261,7 +261,7 @@ func NewReverseBridge(ports []int, debug bool) (*ReverseBridge, error) {
}
if debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Reverse bridges ready for ports: %v\n", ports)
fmt.Fprintf(os.Stderr, "[greywall:linux] Reverse bridges ready for ports: %v\n", ports)
}
return bridge, nil
@@ -282,7 +282,7 @@ func (b *ReverseBridge) Cleanup() {
}
if b.debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Reverse bridges cleaned up\n")
fmt.Fprintf(os.Stderr, "[greywall:linux] Reverse bridges cleaned up\n")
}
}
@@ -412,7 +412,7 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
features := DetectLinuxFeatures()
if opts.Debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Available features: %s\n", features.Summary())
fmt.Fprintf(os.Stderr, "[greywall:linux] Available features: %s\n", features.Summary())
}
// Build bwrap args with filesystem restrictions
@@ -427,7 +427,7 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
if features.CanUnshareNet {
bwrapArgs = append(bwrapArgs, "--unshare-net") // Network namespace isolation
} else if opts.Debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Skipping --unshare-net (network namespace unavailable in this environment)\n")
fmt.Fprintf(os.Stderr, "[greywall:linux] Skipping --unshare-net (network namespace unavailable in this environment)\n")
}
bwrapArgs = append(bwrapArgs, "--unshare-pid") // PID namespace isolation
@@ -439,12 +439,12 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
filterPath, err := filter.GenerateBPFFilter()
if err != nil {
if opts.Debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Seccomp filter generation failed: %v\n", err)
fmt.Fprintf(os.Stderr, "[greywall:linux] Seccomp filter generation failed: %v\n", err)
}
} else {
seccompFilterPath = filterPath
if opts.Debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Seccomp filter enabled (blocking %d dangerous syscalls)\n", len(DangerousSyscalls))
fmt.Fprintf(os.Stderr, "[greywall:linux] Seccomp filter enabled (blocking %d dangerous syscalls)\n", len(DangerousSyscalls))
}
// Add seccomp filter via fd 3 (will be set up via shell redirection)
bwrapArgs = append(bwrapArgs, "--seccomp", "3")
@@ -457,7 +457,7 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
// In defaultDenyRead mode, we only bind essential system paths read-only
// and user-specified allowRead paths. Everything else is inaccessible.
if opts.Debug {
fmt.Fprintf(os.Stderr, "[fence:linux] DefaultDenyRead mode enabled - binding only essential system paths\n")
fmt.Fprintf(os.Stderr, "[greywall:linux] DefaultDenyRead mode enabled - binding only essential system paths\n")
}
// Bind essential system paths read-only
@@ -555,7 +555,7 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
bwrapArgs = append(bwrapArgs, "--ro-bind", target, target)
}
if opts.Debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Resolved /etc/resolv.conf symlink -> %s (cross-mount)\n", target)
fmt.Fprintf(os.Stderr, "[greywall:linux] Resolved /etc/resolv.conf symlink -> %s (cross-mount)\n", target)
}
}
}
@@ -683,7 +683,7 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
bwrapArgs = append(bwrapArgs, "--cap-add", "CAP_NET_ADMIN")
bwrapArgs = append(bwrapArgs, "--cap-add", "CAP_NET_BIND_SERVICE")
// Bind the tun2socks binary into the sandbox (read-only)
bwrapArgs = append(bwrapArgs, "--ro-bind", tun2socksPath, "/tmp/fence-tun2socks")
bwrapArgs = append(bwrapArgs, "--ro-bind", tun2socksPath, "/tmp/greywall-tun2socks")
}
// Bind DNS bridge socket if available
@@ -697,7 +697,7 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
// Inside the sandbox, a socat relay on UDP :53 converts queries to the
// DNS bridge (Unix socket -> host DNS server) or to TCP through the tunnel.
if dnsBridge != nil || (tun2socksPath != "" && features.CanUseTransparentProxy()) {
tmpResolv, err := os.CreateTemp("", "fence-resolv-*.conf")
tmpResolv, err := os.CreateTemp("", "greywall-resolv-*.conf")
if err == nil {
_, _ = tmpResolv.WriteString("nameserver 127.0.0.1\n")
tmpResolv.Close()
@@ -705,9 +705,9 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
bwrapArgs = append(bwrapArgs, "--ro-bind", dnsRelayResolvConf, "/etc/resolv.conf")
if opts.Debug {
if dnsBridge != nil {
fmt.Fprintf(os.Stderr, "[fence:linux] DNS: overriding resolv.conf -> 127.0.0.1 (bridge to %s)\n", dnsBridge.DnsAddr)
fmt.Fprintf(os.Stderr, "[greywall:linux] DNS: overriding resolv.conf -> 127.0.0.1 (bridge to %s)\n", dnsBridge.DnsAddr)
} else {
fmt.Fprintf(os.Stderr, "[fence:linux] DNS: overriding resolv.conf -> 127.0.0.1 (TCP relay through tunnel)\n")
fmt.Fprintf(os.Stderr, "[greywall:linux] DNS: overriding resolv.conf -> 127.0.0.1 (TCP relay through tunnel)\n")
}
}
}
@@ -721,21 +721,21 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
bwrapArgs = append(bwrapArgs, "--bind", tmpDir, tmpDir)
}
// Get fence executable path for Landlock wrapper
fenceExePath, _ := os.Executable()
// Get greywall executable path for Landlock wrapper
greywallExePath, _ := os.Executable()
// Skip Landlock wrapper if executable is in /tmp (test binaries are built there)
// The wrapper won't work because --tmpfs /tmp hides the test binary
executableInTmp := strings.HasPrefix(fenceExePath, "/tmp/")
// Skip Landlock wrapper if fence is being used as a library (executable is not fence)
// The wrapper re-executes the binary with --landlock-apply, which only fence understands
executableIsFence := strings.Contains(filepath.Base(fenceExePath), "fence")
useLandlockWrapper := opts.UseLandlock && features.CanUseLandlock() && fenceExePath != "" && !executableInTmp && executableIsFence
executableInTmp := strings.HasPrefix(greywallExePath, "/tmp/")
// Skip Landlock wrapper if greywall is being used as a library (executable is not greywall)
// The wrapper re-executes the binary with --landlock-apply, which only greywall understands
executableIsGreywall := strings.Contains(filepath.Base(greywallExePath), "greywall")
useLandlockWrapper := opts.UseLandlock && features.CanUseLandlock() && greywallExePath != "" && !executableInTmp && executableIsGreywall
if opts.Debug && executableInTmp {
fmt.Fprintf(os.Stderr, "[fence:linux] Skipping Landlock wrapper (executable in /tmp, likely a test)\n")
fmt.Fprintf(os.Stderr, "[greywall:linux] Skipping Landlock wrapper (executable in /tmp, likely a test)\n")
}
if opts.Debug && !executableIsFence {
fmt.Fprintf(os.Stderr, "[fence:linux] Skipping Landlock wrapper (running as library, not fence CLI)\n")
if opts.Debug && !executableIsGreywall {
fmt.Fprintf(os.Stderr, "[greywall:linux] Skipping Landlock wrapper (running as library, not greywall CLI)\n")
}
bwrapArgs = append(bwrapArgs, "--", shellPath, "-c")
@@ -743,7 +743,7 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
// Build the inner command that sets up tun2socks and runs the user command
var innerScript strings.Builder
innerScript.WriteString("export FENCE_SANDBOX=1\n")
innerScript.WriteString("export GREYWALL_SANDBOX=1\n")
if proxyBridge != nil && tun2socksPath != "" && features.CanUseTransparentProxy() {
// Build the tun2socks proxy URL with credentials if available
@@ -773,7 +773,7 @@ socat TCP-LISTEN:${PROXY_PORT},fork,reuseaddr,bind=127.0.0.1 UNIX-CONNECT:%s >/d
BRIDGE_PID=$!
# Start tun2socks (transparent proxy via gvisor netstack)
/tmp/fence-tun2socks -device tun0 -proxy %s >/dev/null 2>&1 &
/tmp/greywall-tun2socks -device tun0 -proxy %s >/dev/null 2>&1 &
TUN2SOCKS_PID=$!
`, proxyBridge.SocketPath, tun2socksProxyURL))
@@ -853,13 +853,13 @@ sleep 0.3
if cfg != nil {
configJSON, err := json.Marshal(cfg)
if err == nil {
innerScript.WriteString(fmt.Sprintf("export FENCE_CONFIG_JSON=%s\n", ShellQuoteSingle(string(configJSON))))
innerScript.WriteString(fmt.Sprintf("export GREYWALL_CONFIG_JSON=%s\n", ShellQuoteSingle(string(configJSON))))
}
}
// Build wrapper command with proper quoting
// Use bash -c to preserve shell semantics (e.g., "echo hi && ls")
wrapperArgs := []string{fenceExePath, "--landlock-apply"}
wrapperArgs := []string{greywallExePath, "--landlock-apply"}
if opts.Debug {
wrapperArgs = append(wrapperArgs, "--debug")
}
@@ -897,7 +897,7 @@ sleep 0.3
if reverseBridge != nil && len(reverseBridge.Ports) > 0 {
featureList = append(featureList, fmt.Sprintf("inbound:%v", reverseBridge.Ports))
}
fmt.Fprintf(os.Stderr, "[fence:linux] Sandbox: %s\n", strings.Join(featureList, ", "))
fmt.Fprintf(os.Stderr, "[greywall:linux] Sandbox: %s\n", strings.Join(featureList, ", "))
}
// Build the final command
@@ -926,7 +926,7 @@ func StartLinuxMonitor(pid int, opts LinuxSandboxOptions) (*LinuxMonitors, error
// or SECCOMP_RET_KILL (logs but kills process) or SECCOMP_RET_USER_NOTIF (complex).
// For now, we rely on the eBPF monitor to detect syscall failures.
if opts.Debug && opts.Monitor && features.SeccompLogLevel >= 1 {
fmt.Fprintf(os.Stderr, "[fence:linux] Note: seccomp violations are blocked but not logged (SECCOMP_RET_ERRNO is silent)\n")
fmt.Fprintf(os.Stderr, "[greywall:linux] Note: seccomp violations are blocked but not logged (SECCOMP_RET_ERRNO is silent)\n")
}
// Start eBPF monitor if available and requested
@@ -935,17 +935,17 @@ func StartLinuxMonitor(pid int, opts LinuxSandboxOptions) (*LinuxMonitors, error
ebpfMon := NewEBPFMonitor(pid, opts.Debug)
if err := ebpfMon.Start(); err != nil {
if opts.Debug {
fmt.Fprintf(os.Stderr, "[fence:linux] Failed to start eBPF monitor: %v\n", err)
fmt.Fprintf(os.Stderr, "[greywall:linux] Failed to start eBPF monitor: %v\n", err)
}
} else {
monitors.EBPFMonitor = ebpfMon
if opts.Debug {
fmt.Fprintf(os.Stderr, "[fence:linux] eBPF monitor started for PID %d\n", pid)
fmt.Fprintf(os.Stderr, "[greywall:linux] eBPF monitor started for PID %d\n", pid)
}
}
} else if opts.Monitor && opts.Debug {
if !features.HasEBPF {
fmt.Fprintf(os.Stderr, "[fence:linux] eBPF monitoring not available (need CAP_BPF or root)\n")
fmt.Fprintf(os.Stderr, "[greywall:linux] eBPF monitoring not available (need CAP_BPF or root)\n")
}
}