feat: add dependency status to --version and document AppArmor userns fix
Some checks failed
Build and test / Build (push) Successful in 11s
Build and test / Lint (push) Failing after 1m24s
Build and test / Test (Linux) (push) Failing after 40s
Build and test / Test (macOS) (push) Has been cancelled

Show installed dependencies, security features, and transparent proxy
availability when running --version. Detect AppArmor
unprivileged_userns restriction on Ubuntu 24.04+ and suggest the fix.
Document the RTM_NEWADDR issue in experience.md.
This commit is contained in:
2026-02-11 19:31:24 -06:00
parent 70d0685c97
commit b55b3364af
4 changed files with 152 additions and 0 deletions

View File

@@ -125,6 +125,7 @@ func runCommand(cmd *cobra.Command, args []string) error {
fmt.Printf(" Version: %s\n", version)
fmt.Printf(" Built: %s\n", buildTime)
fmt.Printf(" Commit: %s\n", gitCommit)
sandbox.PrintDependencyStatus()
return nil
}

View File

@@ -67,3 +67,29 @@ Lessons learned and issues encountered during development.
**Problem:** gost's SOCKS5 server always selects authentication method 0x02 (username/password), even when no real credentials are needed. Clients that only offer method 0x00 (no auth) get rejected.
**Fix:** Always include credentials in the proxy URL (e.g., `proxy:proxy@`). In tun2socks proxy URL construction, include `userinfo` so tun2socks offers both auth methods during SOCKS5 negotiation.
---
## Network namespaces fail on Ubuntu 24.04 (`RTM_NEWADDR: Operation not permitted`)
**Problem:** On Ubuntu 24.04 (tested in a KVM guest with bridged virtio/virbr0), `--version` reports `bwrap(no-netns)` and transparent proxy is unavailable. `kernel.unprivileged_userns_clone=1` is set, bwrap and socat are installed, but `bwrap --unshare-net` fails with:
```
bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted
```
**Cause:** Ubuntu 24.04 introduced `kernel.apparmor_restrict_unprivileged_userns` (default: 1). This strips capabilities like `CAP_NET_ADMIN` from processes inside unprivileged user namespaces, even without a bwrap-specific AppArmor profile. Bubblewrap creates the network namespace successfully but cannot configure the loopback interface (adding 127.0.0.1 via netlink RTM_NEWADDR requires `CAP_NET_ADMIN`). Not a hypervisor issue — happens on bare metal Ubuntu 24.04 too.
**Diagnosis:**
```bash
sysctl kernel.apparmor_restrict_unprivileged_userns # likely returns 1
bwrap --unshare-net --ro-bind / / -- /bin/true # reproduces the error
```
**Fix:** Disable the restriction (requires root on the guest):
```bash
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
# Persist across reboots:
echo 'kernel.apparmor_restrict_unprivileged_userns=0' | sudo tee /etc/sysctl.d/99-greywall-userns.conf
```
**Alternative:** Accept the limitation — greywall still works for filesystem sandboxing, seccomp, and Landlock. Network access is blocked outright rather than redirected through a proxy.

View File

@@ -276,6 +276,107 @@ func (f *LinuxFeatures) MinimumViable() bool {
return f.HasBwrap && f.HasSocat
}
// PrintDependencyStatus prints dependency status with install suggestions for Linux.
func PrintDependencyStatus() {
features := DetectLinuxFeatures()
fmt.Printf("\n Platform: linux (kernel %d.%d)\n", features.KernelMajor, features.KernelMinor)
fmt.Printf("\n Dependencies (required):\n")
allGood := true
if features.HasBwrap {
fmt.Printf(" ✓ bubblewrap (bwrap)\n")
} else {
fmt.Printf(" ✗ bubblewrap (bwrap) — REQUIRED\n")
allGood = false
}
if features.HasSocat {
fmt.Printf(" ✓ socat\n")
} else {
fmt.Printf(" ✗ socat — REQUIRED\n")
allGood = false
}
if !allGood {
fmt.Printf("\n Install missing dependencies:\n")
fmt.Printf(" %s\n", suggestInstallCmd(features))
}
fmt.Printf("\n Security features: %s\n", features.Summary())
if features.CanUseTransparentProxy() {
fmt.Printf(" Transparent proxy: available\n")
} else {
parts := []string{}
if !features.HasIpCommand {
parts = append(parts, "iproute2")
}
if !features.HasDevNetTun {
parts = append(parts, "/dev/net/tun")
}
if !features.CanUnshareNet {
parts = append(parts, "network namespace")
}
if len(parts) > 0 {
fmt.Printf(" Transparent proxy: unavailable (missing: %s)\n", strings.Join(parts, ", "))
} else {
fmt.Printf(" Transparent proxy: unavailable\n")
}
if !features.CanUnshareNet && features.HasBwrap {
if val := readSysctl("kernel/apparmor_restrict_unprivileged_userns"); val == "1" {
fmt.Printf("\n Note: AppArmor is restricting unprivileged user namespaces.\n")
fmt.Printf(" This prevents bwrap --unshare-net (needed for transparent proxy).\n")
fmt.Printf(" To fix: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0\n")
fmt.Printf(" Persist: echo 'kernel.apparmor_restrict_unprivileged_userns=0' | sudo tee /etc/sysctl.d/99-greywall-userns.conf\n")
}
}
}
if allGood {
fmt.Printf("\n Status: ready\n")
} else {
fmt.Printf("\n Status: missing required dependencies\n")
}
}
func suggestInstallCmd(features *LinuxFeatures) string {
var missing []string
if !features.HasBwrap {
missing = append(missing, "bubblewrap")
}
if !features.HasSocat {
missing = append(missing, "socat")
}
pkgs := strings.Join(missing, " ")
switch {
case commandExists("apt-get"):
return fmt.Sprintf("sudo apt install %s", pkgs)
case commandExists("dnf"):
return fmt.Sprintf("sudo dnf install %s", pkgs)
case commandExists("yum"):
return fmt.Sprintf("sudo yum install %s", pkgs)
case commandExists("pacman"):
return fmt.Sprintf("sudo pacman -S %s", pkgs)
case commandExists("apk"):
return fmt.Sprintf("sudo apk add %s", pkgs)
case commandExists("zypper"):
return fmt.Sprintf("sudo zypper install %s", pkgs)
default:
return fmt.Sprintf("install %s using your package manager", pkgs)
}
}
func readSysctl(name string) string {
data, err := os.ReadFile("/proc/sys/" + name)
if err != nil {
return ""
}
return strings.TrimSpace(string(data))
}
func commandExists(name string) bool {
_, err := exec.LookPath(name)
return err == nil

View File

@@ -2,6 +2,12 @@
package sandbox
import (
"fmt"
"os/exec"
"runtime"
)
// LinuxFeatures describes available Linux sandboxing features.
// This is a stub for non-Linux platforms.
type LinuxFeatures struct {
@@ -51,3 +57,21 @@ func (f *LinuxFeatures) CanUseTransparentProxy() bool {
func (f *LinuxFeatures) MinimumViable() bool {
return false
}
// PrintDependencyStatus prints dependency status for non-Linux platforms.
func PrintDependencyStatus() {
if runtime.GOOS == "darwin" {
fmt.Printf("\n Platform: macOS\n")
fmt.Printf("\n Dependencies (required):\n")
if _, err := exec.LookPath("sandbox-exec"); err == nil {
fmt.Printf(" ✓ sandbox-exec (Seatbelt)\n")
fmt.Printf("\n Status: ready\n")
} else {
fmt.Printf(" ✗ sandbox-exec — REQUIRED (should be built-in on macOS)\n")
fmt.Printf("\n Status: missing required dependencies\n")
}
} else {
fmt.Printf("\n Platform: %s (unsupported)\n", runtime.GOOS)
fmt.Printf("\n Status: this platform is not supported\n")
}
}