feat: add dependency status to --version and document AppArmor userns fix
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:
@@ -125,6 +125,7 @@ func runCommand(cmd *cobra.Command, args []string) error {
|
|||||||
fmt.Printf(" Version: %s\n", version)
|
fmt.Printf(" Version: %s\n", version)
|
||||||
fmt.Printf(" Built: %s\n", buildTime)
|
fmt.Printf(" Built: %s\n", buildTime)
|
||||||
fmt.Printf(" Commit: %s\n", gitCommit)
|
fmt.Printf(" Commit: %s\n", gitCommit)
|
||||||
|
sandbox.PrintDependencyStatus()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
**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.
|
**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.
|
||||||
|
|||||||
@@ -276,6 +276,107 @@ func (f *LinuxFeatures) MinimumViable() bool {
|
|||||||
return f.HasBwrap && f.HasSocat
|
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 {
|
func commandExists(name string) bool {
|
||||||
_, err := exec.LookPath(name)
|
_, err := exec.LookPath(name)
|
||||||
return err == nil
|
return err == nil
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
package sandbox
|
package sandbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
// LinuxFeatures describes available Linux sandboxing features.
|
// LinuxFeatures describes available Linux sandboxing features.
|
||||||
// This is a stub for non-Linux platforms.
|
// This is a stub for non-Linux platforms.
|
||||||
type LinuxFeatures struct {
|
type LinuxFeatures struct {
|
||||||
@@ -51,3 +57,21 @@ func (f *LinuxFeatures) CanUseTransparentProxy() bool {
|
|||||||
func (f *LinuxFeatures) MinimumViable() bool {
|
func (f *LinuxFeatures) MinimumViable() bool {
|
||||||
return false
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user