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:
@@ -10,7 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
@@ -305,8 +305,8 @@ func BenchmarkOverhead(b *testing.B) {
|
||||
|
||||
func skipBenchIfSandboxed(b *testing.B) {
|
||||
b.Helper()
|
||||
if os.Getenv("FENCE_SANDBOX") == "1" {
|
||||
b.Skip("already running inside Fence sandbox")
|
||||
if os.Getenv("GREYWALL_SANDBOX") == "1" {
|
||||
b.Skip("already running inside Greywall sandbox")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
)
|
||||
|
||||
// CommandBlockedError is returned when a command is blocked by policy.
|
||||
|
||||
@@ -3,7 +3,7 @@ package sandbox
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
)
|
||||
|
||||
func TestCheckCommand_BasicDeny(t *testing.T) {
|
||||
|
||||
@@ -39,14 +39,14 @@ func GetDefaultWritePaths() []string {
|
||||
"/dev/tty",
|
||||
"/dev/dtracehelper",
|
||||
"/dev/autofs_nowait",
|
||||
"/tmp/fence",
|
||||
"/private/tmp/fence",
|
||||
"/tmp/greywall",
|
||||
"/private/tmp/greywall",
|
||||
}
|
||||
|
||||
if home != "" {
|
||||
paths = append(paths,
|
||||
filepath.Join(home, ".npm/_logs"),
|
||||
filepath.Join(home, ".fence/debug"),
|
||||
filepath.Join(home, ".greywall/debug"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestGetDefaultWritePaths(t *testing.T) {
|
||||
t.Error("GetDefaultWritePaths() returned empty slice")
|
||||
}
|
||||
|
||||
essentialPaths := []string{"/dev/stdout", "/dev/stderr", "/dev/null", "/tmp/fence"}
|
||||
essentialPaths := []string{"/dev/stdout", "/dev/stderr", "/dev/null", "/tmp/greywall"}
|
||||
for _, essential := range essentialPaths {
|
||||
found := slices.Contains(paths, essential)
|
||||
if !found {
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
|
||||
// skipIfLandlockNotUsable skips tests that require the Landlock wrapper.
|
||||
// The Landlock wrapper re-executes the binary with --landlock-apply, which only
|
||||
// the fence CLI understands. Test binaries (e.g., sandbox.test) don't have this
|
||||
// handler, so Landlock tests must be skipped when not running as the fence CLI.
|
||||
// the greywall CLI understands. Test binaries (e.g., sandbox.test) don't have this
|
||||
// handler, so Landlock tests must be skipped when not running as the greywall CLI.
|
||||
// TODO: consider removing tests that call this function, for now can keep them
|
||||
// as documentation.
|
||||
func skipIfLandlockNotUsable(t *testing.T) {
|
||||
@@ -28,8 +28,8 @@ func skipIfLandlockNotUsable(t *testing.T) {
|
||||
t.Skip("skipping: Landlock not available on this kernel")
|
||||
}
|
||||
exePath, _ := os.Executable()
|
||||
if !strings.Contains(filepath.Base(exePath), "fence") {
|
||||
t.Skip("skipping: Landlock wrapper requires fence CLI (test binary cannot use --landlock-apply)")
|
||||
if !strings.Contains(filepath.Base(exePath), "greywall") {
|
||||
t.Skip("skipping: Landlock wrapper requires greywall CLI (test binary cannot use --landlock-apply)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestLinux_LandlockBlocksWriteOutsideWorkspace(t *testing.T) {
|
||||
skipIfLandlockNotUsable(t)
|
||||
|
||||
workspace := createTempWorkspace(t)
|
||||
outsideFile := "/tmp/fence-test-outside-" + filepath.Base(workspace) + ".txt"
|
||||
outsideFile := "/tmp/greywall-test-outside-" + filepath.Base(workspace) + ".txt"
|
||||
defer func() { _ = os.Remove(outsideFile) }()
|
||||
|
||||
cfg := testConfigWithWorkspace(workspace)
|
||||
@@ -198,24 +198,24 @@ func TestLinux_LandlockBlocksWriteSystemFiles(t *testing.T) {
|
||||
cfg := testConfigWithWorkspace(workspace)
|
||||
|
||||
// Attempting to write to /etc should fail
|
||||
result := runUnderSandbox(t, cfg, "touch /etc/fence-test-file", workspace)
|
||||
result := runUnderSandbox(t, cfg, "touch /etc/greywall-test-file", workspace)
|
||||
|
||||
assertBlocked(t, result)
|
||||
assertFileNotExists(t, "/etc/fence-test-file")
|
||||
assertFileNotExists(t, "/etc/greywall-test-file")
|
||||
}
|
||||
|
||||
// TestLinux_LandlockAllowsTmpFence verifies /tmp/fence is writable.
|
||||
func TestLinux_LandlockAllowsTmpFence(t *testing.T) {
|
||||
// TestLinux_LandlockAllowsTmpGreywall verifies /tmp/greywall is writable.
|
||||
func TestLinux_LandlockAllowsTmpGreywall(t *testing.T) {
|
||||
skipIfAlreadySandboxed(t)
|
||||
skipIfLandlockNotUsable(t)
|
||||
|
||||
workspace := createTempWorkspace(t)
|
||||
cfg := testConfigWithWorkspace(workspace)
|
||||
|
||||
// Ensure /tmp/fence exists
|
||||
_ = os.MkdirAll("/tmp/fence", 0o750)
|
||||
// Ensure /tmp/greywall exists
|
||||
_ = os.MkdirAll("/tmp/greywall", 0o750)
|
||||
|
||||
testFile := "/tmp/fence/test-file-" + filepath.Base(workspace)
|
||||
testFile := "/tmp/greywall/test-file-" + filepath.Base(workspace)
|
||||
defer func() { _ = os.Remove(testFile) }()
|
||||
|
||||
result := runUnderSandbox(t, cfg, "echo 'test' > "+testFile, workspace)
|
||||
@@ -351,8 +351,8 @@ func TestLinux_TransparentProxyRoutesThroughSocks(t *testing.T) {
|
||||
cfg.Filesystem.AllowWrite = []string{workspace}
|
||||
|
||||
// This test requires actual network and a running SOCKS5 proxy
|
||||
if os.Getenv("FENCE_TEST_NETWORK") != "1" {
|
||||
t.Skip("skipping: set FENCE_TEST_NETWORK=1 to run network tests (requires SOCKS5 proxy on localhost:1080)")
|
||||
if os.Getenv("GREYWALL_TEST_NETWORK") != "1" {
|
||||
t.Skip("skipping: set GREYWALL_TEST_NETWORK=1 to run network tests (requires SOCKS5 proxy on localhost:1080)")
|
||||
}
|
||||
|
||||
result := runUnderSandboxWithTimeout(t, cfg, "curl -s --connect-timeout 5 --max-time 10 https://httpbin.org/get", workspace, 15*time.Second)
|
||||
@@ -455,10 +455,10 @@ func TestLinux_SymlinkEscapeBlocked(t *testing.T) {
|
||||
_ = os.Symlink("/etc", symlinkPath)
|
||||
|
||||
// Try to write through the symlink
|
||||
result := runUnderSandbox(t, cfg, "echo 'test' > "+symlinkPath+"/fence-test", workspace)
|
||||
result := runUnderSandbox(t, cfg, "echo 'test' > "+symlinkPath+"/greywall-test", workspace)
|
||||
|
||||
assertBlocked(t, result)
|
||||
assertFileNotExists(t, "/etc/fence-test")
|
||||
assertFileNotExists(t, "/etc/greywall-test")
|
||||
}
|
||||
|
||||
// TestLinux_PathTraversalBlocked verifies path traversal attacks are prevented.
|
||||
@@ -470,10 +470,10 @@ func TestLinux_PathTraversalBlocked(t *testing.T) {
|
||||
cfg := testConfigWithWorkspace(workspace)
|
||||
|
||||
// Try to escape using ../../../
|
||||
result := runUnderSandbox(t, cfg, "touch ../../../../tmp/fence-escape-test", workspace)
|
||||
result := runUnderSandbox(t, cfg, "touch ../../../../tmp/greywall-escape-test", workspace)
|
||||
|
||||
assertBlocked(t, result)
|
||||
assertFileNotExists(t, "/tmp/fence-escape-test")
|
||||
assertFileNotExists(t, "/tmp/greywall-escape-test")
|
||||
}
|
||||
|
||||
// TestLinux_DeviceAccessBlocked verifies device files cannot be accessed.
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestMacOS_SeatbeltBlocksWriteOutsideWorkspace(t *testing.T) {
|
||||
skipIfAlreadySandboxed(t)
|
||||
|
||||
workspace := createTempWorkspace(t)
|
||||
outsideFile := "/tmp/fence-test-outside-" + filepath.Base(workspace) + ".txt"
|
||||
outsideFile := "/tmp/greywall-test-outside-" + filepath.Base(workspace) + ".txt"
|
||||
defer func() { _ = os.Remove(outsideFile) }()
|
||||
|
||||
cfg := testConfigWithWorkspace(workspace)
|
||||
@@ -138,23 +138,23 @@ func TestMacOS_SeatbeltBlocksWriteSystemFiles(t *testing.T) {
|
||||
cfg := testConfigWithWorkspace(workspace)
|
||||
|
||||
// Attempting to write to /etc should fail
|
||||
result := runUnderSandbox(t, cfg, "touch /etc/fence-test-file", workspace)
|
||||
result := runUnderSandbox(t, cfg, "touch /etc/greywall-test-file", workspace)
|
||||
|
||||
assertBlocked(t, result)
|
||||
assertFileNotExists(t, "/etc/fence-test-file")
|
||||
assertFileNotExists(t, "/etc/greywall-test-file")
|
||||
}
|
||||
|
||||
// TestMacOS_SeatbeltAllowsTmpFence verifies /tmp/fence is writable.
|
||||
func TestMacOS_SeatbeltAllowsTmpFence(t *testing.T) {
|
||||
// TestMacOS_SeatbeltAllowsTmpGreywall verifies /tmp/greywall is writable.
|
||||
func TestMacOS_SeatbeltAllowsTmpGreywall(t *testing.T) {
|
||||
skipIfAlreadySandboxed(t)
|
||||
|
||||
workspace := createTempWorkspace(t)
|
||||
cfg := testConfigWithWorkspace(workspace)
|
||||
|
||||
// Ensure /tmp/fence exists
|
||||
_ = os.MkdirAll("/tmp/fence", 0o750)
|
||||
// Ensure /tmp/greywall exists
|
||||
_ = os.MkdirAll("/tmp/greywall", 0o750)
|
||||
|
||||
testFile := "/tmp/fence/test-file-" + filepath.Base(workspace)
|
||||
testFile := "/tmp/greywall/test-file-" + filepath.Base(workspace)
|
||||
defer func() { _ = os.Remove(testFile) }()
|
||||
|
||||
result := runUnderSandbox(t, cfg, "echo 'test' > "+testFile, workspace)
|
||||
@@ -221,8 +221,8 @@ func TestMacOS_ProxyAllowsTrafficViaProxy(t *testing.T) {
|
||||
cfg.Filesystem.AllowWrite = []string{workspace}
|
||||
|
||||
// This test requires actual network and a running SOCKS5 proxy
|
||||
if os.Getenv("FENCE_TEST_NETWORK") != "1" {
|
||||
t.Skip("skipping: set FENCE_TEST_NETWORK=1 to run network tests (requires SOCKS5 proxy on localhost:1080)")
|
||||
if os.Getenv("GREYWALL_TEST_NETWORK") != "1" {
|
||||
t.Skip("skipping: set GREYWALL_TEST_NETWORK=1 to run network tests (requires SOCKS5 proxy on localhost:1080)")
|
||||
}
|
||||
|
||||
result := runUnderSandboxWithTimeout(t, cfg, "curl -s --connect-timeout 5 --max-time 10 https://httpbin.org/get", workspace, 15*time.Second)
|
||||
@@ -290,10 +290,10 @@ func TestMacOS_SymlinkEscapeBlocked(t *testing.T) {
|
||||
}
|
||||
|
||||
// Try to write through the symlink
|
||||
result := runUnderSandbox(t, cfg, "echo 'test' > "+symlinkPath+"/fence-test", workspace)
|
||||
result := runUnderSandbox(t, cfg, "echo 'test' > "+symlinkPath+"/greywall-test", workspace)
|
||||
|
||||
assertBlocked(t, result)
|
||||
assertFileNotExists(t, "/etc/fence-test")
|
||||
assertFileNotExists(t, "/etc/greywall-test")
|
||||
}
|
||||
|
||||
// TestMacOS_PathTraversalBlocked verifies path traversal attacks are prevented.
|
||||
@@ -303,10 +303,10 @@ func TestMacOS_PathTraversalBlocked(t *testing.T) {
|
||||
workspace := createTempWorkspace(t)
|
||||
cfg := testConfigWithWorkspace(workspace)
|
||||
|
||||
result := runUnderSandbox(t, cfg, "touch ../../../../tmp/fence-escape-test", workspace)
|
||||
result := runUnderSandbox(t, cfg, "touch ../../../../tmp/greywall-escape-test", workspace)
|
||||
|
||||
assertBlocked(t, result)
|
||||
assertFileNotExists(t, "/tmp/fence-escape-test")
|
||||
assertFileNotExists(t, "/tmp/greywall-escape-test")
|
||||
}
|
||||
|
||||
// TestMacOS_DeviceAccessBlocked verifies device files cannot be written.
|
||||
@@ -332,7 +332,7 @@ func TestMacOS_DeviceAccessBlocked(t *testing.T) {
|
||||
// ============================================================================
|
||||
|
||||
// TestMacOS_ReadOnlyPolicy verifies that files outside the allowed write paths cannot be written.
|
||||
// Note: Fence always adds some default writable paths (/tmp/fence, /dev/null, etc.)
|
||||
// Note: Greywall always adds some default writable paths (/tmp/greywall, /dev/null, etc.)
|
||||
// so "read-only" here means "outside the workspace".
|
||||
func TestMacOS_ReadOnlyPolicy(t *testing.T) {
|
||||
skipIfAlreadySandboxed(t)
|
||||
@@ -353,7 +353,7 @@ func TestMacOS_ReadOnlyPolicy(t *testing.T) {
|
||||
assertAllowed(t, result)
|
||||
|
||||
// Writing outside workspace should fail
|
||||
outsidePath := "/tmp/fence-test-readonly-" + filepath.Base(workspace) + ".txt"
|
||||
outsidePath := "/tmp/greywall-test-readonly-" + filepath.Base(workspace) + ".txt"
|
||||
defer func() { _ = os.Remove(outsidePath) }()
|
||||
result = runUnderSandbox(t, cfg, "echo 'outside' > "+outsidePath, workspace)
|
||||
assertBlocked(t, result)
|
||||
@@ -373,7 +373,7 @@ func TestMacOS_WorkspaceWritePolicy(t *testing.T) {
|
||||
assertFileExists(t, filepath.Join(workspace, "test.txt"))
|
||||
|
||||
// Writing outside workspace should fail
|
||||
outsideFile := "/tmp/fence-test-outside.txt"
|
||||
outsideFile := "/tmp/greywall-test-outside.txt"
|
||||
defer func() { _ = os.Remove(outsideFile) }()
|
||||
result = runUnderSandbox(t, cfg, "echo 'test' > "+outsideFile, workspace)
|
||||
assertBlocked(t, result)
|
||||
@@ -399,7 +399,7 @@ func TestMacOS_MultipleWritableRoots(t *testing.T) {
|
||||
assertAllowed(t, result)
|
||||
|
||||
// Writing outside both should fail
|
||||
outsideFile := "/tmp/fence-test-outside-multi.txt"
|
||||
outsideFile := "/tmp/greywall-test-outside-multi.txt"
|
||||
defer func() { _ = os.Remove(outsideFile) }()
|
||||
result = runUnderSandbox(t, cfg, "echo 'test' > "+outsideFile, workspace1)
|
||||
assertBlocked(t, result)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
@@ -44,8 +44,8 @@ func (r *SandboxTestResult) Failed() bool {
|
||||
// skipIfAlreadySandboxed skips the test if running inside a sandbox.
|
||||
func skipIfAlreadySandboxed(t *testing.T) {
|
||||
t.Helper()
|
||||
if os.Getenv("FENCE_SANDBOX") == "1" {
|
||||
t.Skip("skipping: already running inside Fence sandbox")
|
||||
if os.Getenv("GREYWALL_SANDBOX") == "1" {
|
||||
t.Skip("skipping: already running inside Greywall sandbox")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ func testConfigWithProxy(proxyURL string) *config.Config {
|
||||
// Sandbox Execution Helpers
|
||||
// ============================================================================
|
||||
|
||||
// runUnderSandbox executes a command under the fence sandbox.
|
||||
// runUnderSandbox executes a command under the greywall sandbox.
|
||||
// This uses the sandbox Manager directly for integration testing.
|
||||
func runUnderSandbox(t *testing.T, cfg *config.Config, command string, workDir string) *SandboxTestResult {
|
||||
t.Helper()
|
||||
@@ -281,7 +281,7 @@ func executeShellCommandWithTimeout(t *testing.T, command string, workDir string
|
||||
// createTempWorkspace creates a temporary directory for testing.
|
||||
func createTempWorkspace(t *testing.T) string {
|
||||
t.Helper()
|
||||
dir, err := os.MkdirTemp("", "fence-test-*")
|
||||
dir, err := os.MkdirTemp("", "greywall-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp workspace: %v", err)
|
||||
}
|
||||
@@ -492,10 +492,10 @@ func TestIntegration_EnvWorks(t *testing.T) {
|
||||
workspace := createTempWorkspace(t)
|
||||
cfg := testConfigWithWorkspace(workspace)
|
||||
|
||||
result := runUnderSandbox(t, cfg, "env | grep FENCE", workspace)
|
||||
result := runUnderSandbox(t, cfg, "env | grep GREYWALL", workspace)
|
||||
|
||||
assertAllowed(t, result)
|
||||
assertContains(t, result.Stdout, "FENCE_SANDBOX=1")
|
||||
assertContains(t, result.Stdout, "GREYWALL_SANDBOX=1")
|
||||
}
|
||||
|
||||
func TestExecuteShellCommandBwrapError(t *testing.T) {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ func (m *EBPFMonitor) Start() error {
|
||||
features := DetectLinuxFeatures()
|
||||
if !features.HasEBPF {
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:ebpf] eBPF monitoring not available (need CAP_BPF or root)\n")
|
||||
fmt.Fprintf(os.Stderr, "[greywall:ebpf] eBPF monitoring not available (need CAP_BPF or root)\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -51,14 +51,14 @@ func (m *EBPFMonitor) Start() error {
|
||||
// Try multiple eBPF tracing approaches
|
||||
if err := m.tryBpftrace(ctx); err != nil {
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:ebpf] bpftrace not available: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:ebpf] bpftrace not available: %v\n", err)
|
||||
}
|
||||
// Fall back to other methods
|
||||
go m.traceWithPerfEvents()
|
||||
}
|
||||
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:ebpf] Started eBPF monitoring for PID %d\n", m.pid)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:ebpf] Started eBPF monitoring for PID %d\n", m.pid)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -101,7 +101,7 @@ func (m *EBPFMonitor) tryBpftrace(ctx context.Context) error {
|
||||
script := m.generateBpftraceScript()
|
||||
|
||||
// Write script to temp file
|
||||
tmpFile, err := os.CreateTemp("", "fence-ebpf-*.bt")
|
||||
tmpFile, err := os.CreateTemp("", "greywall-ebpf-*.bt")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temp file: %w", err)
|
||||
}
|
||||
@@ -136,7 +136,7 @@ func (m *EBPFMonitor) tryBpftrace(ctx context.Context) error {
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:ebpf:trace] %s\n", line)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:ebpf:trace] %s\n", line)
|
||||
}
|
||||
if violation := m.parseBpftraceOutput(line); violation != "" {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", violation)
|
||||
@@ -150,7 +150,7 @@ func (m *EBPFMonitor) tryBpftrace(ctx context.Context) error {
|
||||
scanner := bufio.NewScanner(stderr)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
fmt.Fprintf(os.Stderr, "[fence:ebpf:err] %s\n", line)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:ebpf:err] %s\n", line)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -173,7 +173,7 @@ func (m *EBPFMonitor) generateBpftraceScript() string {
|
||||
script := fmt.Sprintf(`
|
||||
BEGIN
|
||||
{
|
||||
printf("fence:ebpf monitoring started for sandbox PID %%d (filtering pid >= %%d)\n", %d, %d);
|
||||
printf("greywall:ebpf monitoring started for sandbox PID %%d (filtering pid >= %%d)\n", %d, %d);
|
||||
}
|
||||
|
||||
// Monitor filesystem errors (EPERM=-1, EACCES=-13, EROFS=-30)
|
||||
@@ -227,7 +227,7 @@ func (m *EBPFMonitor) parseBpftraceOutput(line string) string {
|
||||
errorName := getErrnoName(ret)
|
||||
timestamp := time.Now().Format("15:04:05")
|
||||
|
||||
return fmt.Sprintf("[fence:ebpf] %s ✗ %s: %s (%s, pid=%d)",
|
||||
return fmt.Sprintf("[greywall:ebpf] %s ✗ %s: %s (%s, pid=%d)",
|
||||
timestamp, syscall, errorName, comm, pid)
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ func (m *EBPFMonitor) traceWithPerfEvents() {
|
||||
tracePipe := "/sys/kernel/debug/tracing/trace_pipe"
|
||||
if _, err := os.Stat(tracePipe); err != nil {
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:ebpf] trace_pipe not available\n")
|
||||
fmt.Fprintf(os.Stderr, "[greywall:ebpf] trace_pipe not available\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -247,7 +247,7 @@ func (m *EBPFMonitor) traceWithPerfEvents() {
|
||||
f, err := os.Open(tracePipe)
|
||||
if err != nil {
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:ebpf] Failed to open trace_pipe: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:ebpf] Failed to open trace_pipe: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -317,10 +317,10 @@ func (v *ViolationEvent) FormatViolation() string {
|
||||
errName := getErrnoName(-v.Errno)
|
||||
|
||||
if v.Path != "" {
|
||||
return fmt.Sprintf("[fence:ebpf] %s ✗ %s: %s (%s, %s:%d)",
|
||||
return fmt.Sprintf("[greywall:ebpf] %s ✗ %s: %s (%s, %s:%d)",
|
||||
timestamp, v.Operation, v.Path, errName, v.Comm, v.PID)
|
||||
}
|
||||
return fmt.Sprintf("[fence:ebpf] %s ✗ %s: %s (%s:%d)",
|
||||
return fmt.Sprintf("[greywall:ebpf] %s ✗ %s: %s (%s:%d)",
|
||||
timestamp, v.Operation, errName, v.Comm, v.PID)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
@@ -22,7 +22,7 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
||||
features := DetectLinuxFeatures()
|
||||
if !features.CanUseLandlock() {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Not available (kernel %d.%d < 5.13), skipping\n",
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Not available (kernel %d.%d < 5.13), skipping\n",
|
||||
features.KernelMajor, features.KernelMinor)
|
||||
}
|
||||
return nil // Graceful fallback - Landlock not available
|
||||
@@ -31,7 +31,7 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
||||
ruleset, err := NewLandlockRuleset(debug)
|
||||
if err != nil {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Failed to create ruleset: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Failed to create ruleset: %v\n", err)
|
||||
}
|
||||
return nil // Graceful fallback
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
||||
|
||||
if err := ruleset.Initialize(); err != nil {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Failed to initialize: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Failed to initialize: %v\n", err)
|
||||
}
|
||||
return nil // Graceful fallback
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
||||
if err := ruleset.AllowRead(p); err != nil && debug {
|
||||
// Ignore errors for paths that don't exist
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Warning: failed to add read path %s: %v\n", p, err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Warning: failed to add read path %s: %v\n", p, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,40 +77,40 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
||||
if target, err := filepath.EvalSymlinks("/etc/resolv.conf"); err == nil && target != "/etc/resolv.conf" {
|
||||
targetDir := filepath.Dir(target)
|
||||
if err := ruleset.AllowRead(targetDir); err != nil && debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Warning: failed to add resolv.conf target dir %s: %v\n", targetDir, err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Warning: failed to add resolv.conf target dir %s: %v\n", targetDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Current working directory - read access (may be upgraded to write below)
|
||||
if cwd != "" {
|
||||
if err := ruleset.AllowRead(cwd); err != nil && debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Warning: failed to add cwd read path: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Warning: failed to add cwd read path: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Home directory - read access
|
||||
if home, err := os.UserHomeDir(); err == nil {
|
||||
if err := ruleset.AllowRead(home); err != nil && debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Warning: failed to add home read path: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Warning: failed to add home read path: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// /tmp - allow read+write (many programs need this)
|
||||
if err := ruleset.AllowReadWrite("/tmp"); err != nil && debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Warning: failed to add /tmp write path: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Warning: failed to add /tmp write path: %v\n", err)
|
||||
}
|
||||
|
||||
// /dev needs read+write for /dev/null, /dev/zero, /dev/tty, etc.
|
||||
// Landlock doesn't support rules on device files directly, so we allow the whole /dev
|
||||
if err := ruleset.AllowReadWrite("/dev"); err != nil && debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Warning: failed to add /dev write path: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Warning: failed to add /dev write path: %v\n", err)
|
||||
}
|
||||
|
||||
// Socket paths for proxy communication
|
||||
for _, p := range socketPaths {
|
||||
dir := filepath.Dir(p)
|
||||
if err := ruleset.AllowReadWrite(dir); err != nil && debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Warning: failed to add socket path %s: %v\n", dir, err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Warning: failed to add socket path %s: %v\n", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
||||
expandedPaths := ExpandGlobPatterns(cfg.Filesystem.AllowWrite)
|
||||
for _, p := range expandedPaths {
|
||||
if err := ruleset.AllowReadWrite(p); err != nil && debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Warning: failed to add write path %s: %v\n", p, err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Warning: failed to add write path %s: %v\n", p, err)
|
||||
}
|
||||
}
|
||||
// Also add non-glob paths directly
|
||||
@@ -127,7 +127,7 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
||||
if !ContainsGlobChars(p) {
|
||||
normalized := NormalizePath(p)
|
||||
if err := ruleset.AllowReadWrite(normalized); err != nil && debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Warning: failed to add write path %s: %v\n", normalized, err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Warning: failed to add write path %s: %v\n", normalized, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,13 +136,13 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
||||
// Apply the ruleset
|
||||
if err := ruleset.Apply(); err != nil {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Failed to apply: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Failed to apply: %v\n", err)
|
||||
}
|
||||
return nil // Graceful fallback
|
||||
}
|
||||
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Applied restrictions (ABI v%d)\n", features.LandlockABI)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Applied restrictions (ABI v%d)\n", features.LandlockABI)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -212,7 +212,7 @@ func (l *LandlockRuleset) Initialize() error {
|
||||
l.initialized = true
|
||||
|
||||
if l.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Created ruleset (ABI v%d, fd=%d)\n", l.abiVersion, l.rulesetFd)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Created ruleset (ABI v%d, fd=%d)\n", l.abiVersion, l.rulesetFd)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -318,7 +318,7 @@ func (l *LandlockRuleset) addPathRule(path string, access uint64) error {
|
||||
// Check if path exists
|
||||
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||
if l.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Skipping non-existent path: %s\n", absPath)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Skipping non-existent path: %s\n", absPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -327,7 +327,7 @@ func (l *LandlockRuleset) addPathRule(path string, access uint64) error {
|
||||
fd, err := unix.Open(absPath, unix.O_PATH|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
if l.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Failed to open path %s: %v\n", absPath, err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Failed to open path %s: %v\n", absPath, err)
|
||||
}
|
||||
return nil // Don't fail on paths we can't access
|
||||
}
|
||||
@@ -337,7 +337,7 @@ func (l *LandlockRuleset) addPathRule(path string, access uint64) error {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstat(fd, &stat); err != nil {
|
||||
if l.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Failed to fstat path %s: %v\n", absPath, err)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Failed to fstat path %s: %v\n", absPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -370,7 +370,7 @@ func (l *LandlockRuleset) addPathRule(path string, access uint64) error {
|
||||
|
||||
if access == 0 {
|
||||
if l.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Skipping %s: no applicable access rights\n", absPath)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Skipping %s: no applicable access rights\n", absPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -391,7 +391,7 @@ func (l *LandlockRuleset) addPathRule(path string, access uint64) error {
|
||||
}
|
||||
|
||||
if l.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Added rule: %s (access=0x%x)\n", absPath, access)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Added rule: %s (access=0x%x)\n", absPath, access)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -420,7 +420,7 @@ func (l *LandlockRuleset) Apply() error {
|
||||
}
|
||||
|
||||
if l.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:landlock] Ruleset applied to process\n")
|
||||
fmt.Fprintf(os.Stderr, "[greywall:landlock] Ruleset applied to process\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
package sandbox
|
||||
|
||||
import "github.com/Use-Tusk/fence/internal/config"
|
||||
import "gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
|
||||
// ApplyLandlockFromConfig is a no-op on non-Linux platforms.
|
||||
func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []string, debug bool) error {
|
||||
|
||||
@@ -60,12 +60,12 @@ func (s *SeccompFilter) GenerateBPFFilter() (string, error) {
|
||||
}
|
||||
|
||||
// Create a temporary directory for the filter
|
||||
tmpDir := filepath.Join(os.TempDir(), "fence-seccomp")
|
||||
tmpDir := filepath.Join(os.TempDir(), "greywall-seccomp")
|
||||
if err := os.MkdirAll(tmpDir, 0o700); err != nil {
|
||||
return "", fmt.Errorf("failed to create seccomp dir: %w", err)
|
||||
}
|
||||
|
||||
filterPath := filepath.Join(tmpDir, fmt.Sprintf("fence-seccomp-%d.bpf", os.Getpid()))
|
||||
filterPath := filepath.Join(tmpDir, fmt.Sprintf("greywall-seccomp-%d.bpf", os.Getpid()))
|
||||
|
||||
// Generate the filter using the seccomp library or raw BPF
|
||||
// For now, we'll use bwrap's built-in seccomp support via --seccomp
|
||||
@@ -77,7 +77,7 @@ func (s *SeccompFilter) GenerateBPFFilter() (string, error) {
|
||||
}
|
||||
|
||||
if s.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence:seccomp] Generated BPF filter at %s\n", filterPath)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:seccomp] Generated BPF filter at %s\n", filterPath)
|
||||
}
|
||||
|
||||
return filterPath, nil
|
||||
|
||||
@@ -5,7 +5,7 @@ package sandbox
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
)
|
||||
|
||||
// ProxyBridge is a stub for non-Linux platforms.
|
||||
|
||||
@@ -3,7 +3,7 @@ package sandbox
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
)
|
||||
|
||||
// TestLinux_NoProxyBlocksNetwork verifies that when no ProxyURL is set,
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
)
|
||||
|
||||
// sessionSuffix is a unique identifier for this process session.
|
||||
@@ -609,10 +609,10 @@ func WrapCommandMacOS(cfg *config.Config, command string, exposedPorts []int, de
|
||||
}
|
||||
|
||||
if debug && len(exposedPorts) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "[fence:macos] Enabling local binding for exposed ports: %v\n", exposedPorts)
|
||||
fmt.Fprintf(os.Stderr, "[greywall:macos] Enabling local binding for exposed ports: %v\n", exposedPorts)
|
||||
}
|
||||
if debug && allowLocalBinding && !allowLocalOutbound {
|
||||
fmt.Fprintf(os.Stderr, "[fence:macos] Blocking localhost outbound (AllowLocalOutbound=false)\n")
|
||||
fmt.Fprintf(os.Stderr, "[greywall:macos] Blocking localhost outbound (AllowLocalOutbound=false)\n")
|
||||
}
|
||||
|
||||
profile := GenerateSandboxProfile(params)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
)
|
||||
|
||||
// TestMacOS_NetworkRestrictionWithProxy verifies that when a proxy URL is set,
|
||||
@@ -274,14 +274,14 @@ func TestExpandMacOSTmpPaths(t *testing.T) {
|
||||
want: []string{".", "~/.cache"},
|
||||
},
|
||||
{
|
||||
name: "mirrors /tmp/fence to /private/tmp/fence",
|
||||
input: []string{".", "/tmp/fence"},
|
||||
want: []string{".", "/tmp/fence", "/private/tmp/fence"},
|
||||
name: "mirrors /tmp/greywall to /private/tmp/greywall",
|
||||
input: []string{".", "/tmp/greywall"},
|
||||
want: []string{".", "/tmp/greywall", "/private/tmp/greywall"},
|
||||
},
|
||||
{
|
||||
name: "mirrors /private/tmp/fence to /tmp/fence",
|
||||
input: []string{".", "/private/tmp/fence"},
|
||||
want: []string{".", "/private/tmp/fence", "/tmp/fence"},
|
||||
name: "mirrors /private/tmp/greywall to /tmp/greywall",
|
||||
input: []string{".", "/private/tmp/greywall"},
|
||||
want: []string{".", "/private/tmp/greywall", "/tmp/greywall"},
|
||||
},
|
||||
{
|
||||
name: "mirrors nested subdirectory",
|
||||
@@ -290,8 +290,8 @@ func TestExpandMacOSTmpPaths(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no duplicate when mirror already present",
|
||||
input: []string{".", "/tmp/fence", "/private/tmp/fence"},
|
||||
want: []string{".", "/tmp/fence", "/private/tmp/fence"},
|
||||
input: []string{".", "/tmp/greywall", "/private/tmp/greywall"},
|
||||
want: []string{".", "/tmp/greywall", "/private/tmp/greywall"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/config"
|
||||
"github.com/Use-Tusk/fence/internal/platform"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/platform"
|
||||
)
|
||||
|
||||
// Manager handles sandbox initialization and command wrapping.
|
||||
@@ -153,6 +153,6 @@ func (m *Manager) Cleanup() {
|
||||
|
||||
func (m *Manager) logDebug(format string, args ...interface{}) {
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "[fence] "+format+"\n", args...)
|
||||
fmt.Fprintf(os.Stderr, "[greywall] "+format+"\n", args...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Use-Tusk/fence/internal/platform"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/platform"
|
||||
)
|
||||
|
||||
// LogMonitor monitors sandbox violations via macOS log stream.
|
||||
@@ -138,9 +138,9 @@ func parseViolation(line string) string {
|
||||
timestamp := time.Now().Format("15:04:05")
|
||||
|
||||
if details != "" {
|
||||
return fmt.Sprintf("[fence:logstream] %s ✗ %s %s (%s:%s)", timestamp, operation, details, process, pid)
|
||||
return fmt.Sprintf("[greywall:logstream] %s ✗ %s %s (%s:%s)", timestamp, operation, details, process, pid)
|
||||
}
|
||||
return fmt.Sprintf("[fence:logstream] %s ✗ %s (%s:%s)", timestamp, operation, process, pid)
|
||||
return fmt.Sprintf("[greywall:logstream] %s ✗ %s (%s:%s)", timestamp, operation, process, pid)
|
||||
}
|
||||
|
||||
// shouldShowViolation returns true if this violation type should be displayed.
|
||||
|
||||
@@ -32,7 +32,7 @@ func extractTun2Socks() (string, error) {
|
||||
return "", fmt.Errorf("tun2socks: embedded binary not found for %s: %w", arch, err)
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "fence-tun2socks-*")
|
||||
tmpFile, err := os.CreateTemp("", "greywall-tun2socks-*")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("tun2socks: failed to create temp file: %w", err)
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ func NormalizePath(pathPattern string) string {
|
||||
// Used on macOS where transparent proxying is not available.
|
||||
func GenerateProxyEnvVars(proxyURL string) []string {
|
||||
envVars := []string{
|
||||
"FENCE_SANDBOX=1",
|
||||
"TMPDIR=/tmp/fence",
|
||||
"GREYWALL_SANDBOX=1",
|
||||
"TMPDIR=/tmp/greywall",
|
||||
}
|
||||
|
||||
if proxyURL == "" {
|
||||
|
||||
@@ -134,8 +134,8 @@ func TestGenerateProxyEnvVars(t *testing.T) {
|
||||
name: "no proxy",
|
||||
proxyURL: "",
|
||||
wantEnvs: []string{
|
||||
"FENCE_SANDBOX=1",
|
||||
"TMPDIR=/tmp/fence",
|
||||
"GREYWALL_SANDBOX=1",
|
||||
"TMPDIR=/tmp/greywall",
|
||||
},
|
||||
dontWant: []string{
|
||||
"HTTP_PROXY=",
|
||||
@@ -147,7 +147,7 @@ func TestGenerateProxyEnvVars(t *testing.T) {
|
||||
name: "socks5 proxy",
|
||||
proxyURL: "socks5://localhost:1080",
|
||||
wantEnvs: []string{
|
||||
"FENCE_SANDBOX=1",
|
||||
"GREYWALL_SANDBOX=1",
|
||||
"ALL_PROXY=socks5://localhost:1080",
|
||||
"all_proxy=socks5://localhost:1080",
|
||||
"HTTP_PROXY=socks5://localhost:1080",
|
||||
@@ -162,7 +162,7 @@ func TestGenerateProxyEnvVars(t *testing.T) {
|
||||
name: "socks5h proxy",
|
||||
proxyURL: "socks5h://proxy.example.com:1080",
|
||||
wantEnvs: []string{
|
||||
"FENCE_SANDBOX=1",
|
||||
"GREYWALL_SANDBOX=1",
|
||||
"ALL_PROXY=socks5h://proxy.example.com:1080",
|
||||
"HTTP_PROXY=socks5h://proxy.example.com:1080",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user