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:
6
.github/workflows/benchmark.yml
vendored
6
.github/workflows/benchmark.yml
vendored
@@ -19,7 +19,7 @@ on:
|
|||||||
# paths:
|
# paths:
|
||||||
# - "internal/sandbox/**"
|
# - "internal/sandbox/**"
|
||||||
# - "internal/proxy/**"
|
# - "internal/proxy/**"
|
||||||
# - "cmd/fence/**"
|
# - "cmd/greywall/**"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
- name: Install benchstat
|
- name: Install benchstat
|
||||||
run: go install golang.org/x/perf/cmd/benchstat@latest
|
run: go install golang.org/x/perf/cmd/benchstat@latest
|
||||||
|
|
||||||
- name: Build fence
|
- name: Build greywall
|
||||||
run: make build-ci
|
run: make build-ci
|
||||||
|
|
||||||
- name: Run Go microbenchmarks
|
- name: Run Go microbenchmarks
|
||||||
@@ -146,7 +146,7 @@ jobs:
|
|||||||
- name: Install benchstat
|
- name: Install benchstat
|
||||||
run: go install golang.org/x/perf/cmd/benchstat@latest
|
run: go install golang.org/x/perf/cmd/benchstat@latest
|
||||||
|
|
||||||
- name: Build fence
|
- name: Build greywall
|
||||||
run: make build-ci
|
run: make build-ci
|
||||||
|
|
||||||
- name: Run Go microbenchmarks
|
- name: Run Go microbenchmarks
|
||||||
|
|||||||
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -115,7 +115,7 @@ jobs:
|
|||||||
run: make build-ci
|
run: make build-ci
|
||||||
|
|
||||||
- name: Run smoke tests
|
- name: Run smoke tests
|
||||||
run: FENCE_TEST_NETWORK=1 ./scripts/smoke_test.sh ./fence
|
run: GREYWALL_TEST_NETWORK=1 ./scripts/smoke_test.sh ./greywall
|
||||||
|
|
||||||
test-macos:
|
test-macos:
|
||||||
name: Test (macOS)
|
name: Test (macOS)
|
||||||
@@ -160,4 +160,4 @@ jobs:
|
|||||||
run: make build-ci
|
run: make build-ci
|
||||||
|
|
||||||
- name: Run smoke tests
|
- name: Run smoke tests
|
||||||
run: FENCE_TEST_NETWORK=1 ./scripts/smoke_test.sh ./fence
|
run: GREYWALL_TEST_NETWORK=1 ./scripts/smoke_test.sh ./greywall
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -75,7 +75,7 @@ jobs:
|
|||||||
{
|
{
|
||||||
"version": "${{ github.ref_name }}",
|
"version": "${{ github.ref_name }}",
|
||||||
"published_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
"published_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||||
"url": "https://github.com/Use-Tusk/fence/releases/tag/${{ github.ref_name }}"
|
"url": "https://gitea.app.monadical.io/monadical/greywall/releases/tag/${{ github.ref_name }}"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,7 +1,7 @@
|
|||||||
# Binary (only at root, not cmd/fence or pkg/fence)
|
# Binary (only at root, not cmd/greywall or pkg/greywall)
|
||||||
/fence
|
/greywall
|
||||||
/fence_*
|
/greywall_*
|
||||||
/fence-*
|
/greywall-*
|
||||||
|
|
||||||
# Tar archives
|
# Tar archives
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ linters-settings:
|
|||||||
sections:
|
sections:
|
||||||
- standard
|
- standard
|
||||||
- default
|
- default
|
||||||
- prefix(github.com/Use-Tusk/fence)
|
- prefix(gitea.app.monadical.io/monadical/greywall)
|
||||||
gofmt:
|
gofmt:
|
||||||
simplify: true
|
simplify: true
|
||||||
goimports:
|
goimports:
|
||||||
local-prefixes: github.com/Use-Tusk/fence
|
local-prefixes: gitea.app.monadical.io/monadical/greywall
|
||||||
gocritic:
|
gocritic:
|
||||||
disabled-checks:
|
disabled-checks:
|
||||||
- singleCaseSwitch
|
- singleCaseSwitch
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ builds:
|
|||||||
- -X main.version={{.Version}}
|
- -X main.version={{.Version}}
|
||||||
- -X main.buildTime={{.Date}}
|
- -X main.buildTime={{.Date}}
|
||||||
- -X main.gitCommit={{.Commit}}
|
- -X main.gitCommit={{.Commit}}
|
||||||
binary: fence
|
binary: greywall
|
||||||
main: ./cmd/fence
|
main: ./cmd/greywall
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- formats: [tar.gz]
|
- formats: [tar.gz]
|
||||||
@@ -77,7 +77,7 @@ changelog:
|
|||||||
|
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: Use-Tusk
|
owner: monadical
|
||||||
name: fence
|
name: greywall
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
Fence restricts network, filesystem, and command access for arbitrary commands. It works by:
|
Greywall restricts network, filesystem, and command access for arbitrary commands. It works by:
|
||||||
|
|
||||||
1. **Blocking commands** via configurable deny/allow lists before execution
|
1. **Blocking commands** via configurable deny/allow lists before execution
|
||||||
2. **Intercepting network traffic** via HTTP/SOCKS5 proxies that filter by domain
|
2. **Intercepting network traffic** via HTTP/SOCKS5 proxies that filter by domain
|
||||||
@@ -9,7 +9,7 @@ Fence restricts network, filesystem, and command access for arbitrary commands.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
subgraph Fence
|
subgraph Greywall
|
||||||
Config["Config<br/>(JSON)"]
|
Config["Config<br/>(JSON)"]
|
||||||
Manager
|
Manager
|
||||||
CmdCheck["Command<br/>Blocking"]
|
CmdCheck["Command<br/>Blocking"]
|
||||||
@@ -30,8 +30,8 @@ flowchart TB
|
|||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```text
|
```text
|
||||||
fence/
|
greywall/
|
||||||
├── cmd/fence/ # CLI entry point
|
├── cmd/greywall/ # CLI entry point
|
||||||
│ └── main.go # Includes --landlock-apply wrapper mode
|
│ └── main.go # Includes --landlock-apply wrapper mode
|
||||||
├── internal/ # Private implementation
|
├── internal/ # Private implementation
|
||||||
│ ├── config/ # Configuration loading/validation
|
│ ├── config/ # Configuration loading/validation
|
||||||
@@ -52,8 +52,8 @@ fence/
|
|||||||
│ ├── dangerous.go # Protected file/directory lists
|
│ ├── dangerous.go # Protected file/directory lists
|
||||||
│ ├── shell.go # Shell quoting utilities
|
│ ├── shell.go # Shell quoting utilities
|
||||||
│ └── utils.go # Path normalization
|
│ └── utils.go # Path normalization
|
||||||
└── pkg/fence/ # Public Go API
|
└── pkg/greywall/ # Public Go API
|
||||||
└── fence.go
|
└── greywall.go
|
||||||
```
|
```
|
||||||
|
|
||||||
## Core Components
|
## Core Components
|
||||||
@@ -71,7 +71,7 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Loads from XDG config dir (`~/.config/fence/fence.json` or `~/Library/Application Support/fence/fence.json`) or custom path
|
- Loads from XDG config dir (`~/.config/greywall/greywall.json` or `~/Library/Application Support/greywall/greywall.json`) or custom path
|
||||||
- Falls back to restrictive defaults (block all network, default command deny list)
|
- Falls back to restrictive defaults (block all network, default command deny list)
|
||||||
- Validates paths and normalizes them
|
- Validates paths and normalizes them
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ flowchart TB
|
|||||||
SOCKS["SOCKS Proxy<br/>:random"]
|
SOCKS["SOCKS Proxy<br/>:random"]
|
||||||
HSOCAT["socat<br/>(HTTP bridge)"]
|
HSOCAT["socat<br/>(HTTP bridge)"]
|
||||||
SSOCAT["socat<br/>(SOCKS bridge)"]
|
SSOCAT["socat<br/>(SOCKS bridge)"]
|
||||||
USOCK["Unix Sockets<br/>/tmp/fence-*.sock"]
|
USOCK["Unix Sockets<br/>/tmp/greywall-*.sock"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Sandbox ["Sandbox (bwrap --unshare-net)"]
|
subgraph Sandbox ["Sandbox (bwrap --unshare-net)"]
|
||||||
@@ -221,7 +221,7 @@ flowchart TB
|
|||||||
|
|
||||||
subgraph Host
|
subgraph Host
|
||||||
HSOCAT["socat<br/>TCP-LISTEN:8888"]
|
HSOCAT["socat<br/>TCP-LISTEN:8888"]
|
||||||
USOCK["Unix Socket<br/>/tmp/fence-rev-8888-*.sock"]
|
USOCK["Unix Socket<br/>/tmp/greywall-rev-8888-*.sock"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Sandbox
|
subgraph Sandbox
|
||||||
@@ -286,7 +286,7 @@ flowchart TD
|
|||||||
|
|
||||||
### Linux Security Layers
|
### Linux Security Layers
|
||||||
|
|
||||||
On Linux, fence uses multiple security layers with graceful fallback:
|
On Linux, greywall uses multiple security layers with graceful fallback:
|
||||||
|
|
||||||
1. bubblewrap (core isolation via Linux namespaces)
|
1. bubblewrap (core isolation via Linux namespaces)
|
||||||
2. seccomp (syscall filtering)
|
2. seccomp (syscall filtering)
|
||||||
@@ -306,15 +306,15 @@ The `-m` (monitor) flag enables real-time visibility into blocked operations. Th
|
|||||||
|
|
||||||
| Prefix | Source | Description |
|
| Prefix | Source | Description |
|
||||||
|--------|--------|-------------|
|
|--------|--------|-------------|
|
||||||
| `[fence:http]` | Both | HTTP/HTTPS proxy (blocked requests only in monitor mode) |
|
| `[greywall:http]` | Both | HTTP/HTTPS proxy (blocked requests only in monitor mode) |
|
||||||
| `[fence:socks]` | Both | SOCKS5 proxy (blocked requests only in monitor mode) |
|
| `[greywall:socks]` | Both | SOCKS5 proxy (blocked requests only in monitor mode) |
|
||||||
| `[fence:logstream]` | macOS only | Kernel-level sandbox violations from `log stream` |
|
| `[greywall:logstream]` | macOS only | Kernel-level sandbox violations from `log stream` |
|
||||||
| `[fence:ebpf]` | Linux only | Filesystem/syscall failures (requires CAP_BPF or root) |
|
| `[greywall:ebpf]` | Linux only | Filesystem/syscall failures (requires CAP_BPF or root) |
|
||||||
| `[fence:filter]` | Both | Domain filter rule matches (debug mode only) |
|
| `[greywall:filter]` | Both | Domain filter rule matches (debug mode only) |
|
||||||
|
|
||||||
### macOS Log Stream
|
### macOS Log Stream
|
||||||
|
|
||||||
On macOS, fence spawns `log stream` with a predicate to capture sandbox violations:
|
On macOS, greywall spawns `log stream` with a predicate to capture sandbox violations:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
log stream --predicate 'eventMessage ENDSWITH "_SBX"' --style compact
|
log stream --predicate 'eventMessage ENDSWITH "_SBX"' --style compact
|
||||||
@@ -344,4 +344,4 @@ Filtered out (too noisy):
|
|||||||
|
|
||||||
## Security Model
|
## Security Model
|
||||||
|
|
||||||
See [`docs/security-model.md`](docs/security-model.md) for Fence's threat model, guarantees, and limitations.
|
See [`docs/security-model.md`](docs/security-model.md) for Greywall's threat model, guarantees, and limitations.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Thanks for helping improve `fence`!
|
Thanks for helping improve `greywall`!
|
||||||
|
|
||||||
If you have any questions, feel free to open an issue.
|
If you have any questions, feel free to open an issue.
|
||||||
|
|
||||||
@@ -12,11 +12,11 @@ If you have any questions, feel free to open an issue.
|
|||||||
- Clone and prepare:
|
- Clone and prepare:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/Use-Tusk/fence
|
git clone https://gitea.app.monadical.io/monadical/greywall
|
||||||
cd fence
|
cd greywall
|
||||||
make setup # Install deps and lint tools
|
make setup # Install deps and lint tools
|
||||||
make build # Build the binary
|
make build # Build the binary
|
||||||
./fence --help
|
./greywall --help
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dev workflow
|
## Dev workflow
|
||||||
@@ -25,7 +25,7 @@ Common targets:
|
|||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `make build` | Build the binary (`./fence`) |
|
| `make build` | Build the binary (`./greywall`) |
|
||||||
| `make run` | Build and run |
|
| `make run` | Build and run |
|
||||||
| `make test` | Run tests |
|
| `make test` | Run tests |
|
||||||
| `make test-ci` | Run tests with coverage |
|
| `make test-ci` | Run tests with coverage |
|
||||||
@@ -63,14 +63,14 @@ make test-ci
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Test blocked network request
|
# Test blocked network request
|
||||||
./fence curl https://example.com
|
./greywall curl https://example.com
|
||||||
|
|
||||||
# Test with allowed domain
|
# Test with allowed domain
|
||||||
echo '{"network":{"allowedDomains":["example.com"]}}' > /tmp/test.json
|
echo '{"network":{"allowedDomains":["example.com"]}}' > /tmp/test.json
|
||||||
./fence -s /tmp/test.json curl https://example.com
|
./greywall -s /tmp/test.json curl https://example.com
|
||||||
|
|
||||||
# Test monitor mode
|
# Test monitor mode
|
||||||
./fence -m -c "touch /etc/test"
|
./greywall -m -c "touch /etc/test"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing on Linux
|
### Testing on Linux
|
||||||
@@ -82,7 +82,7 @@ Requires `bubblewrap` and `socat`:
|
|||||||
sudo apt install bubblewrap socat
|
sudo apt install bubblewrap socat
|
||||||
|
|
||||||
# Test in Colima or VM
|
# Test in Colima or VM
|
||||||
./fence curl https://example.com
|
./greywall curl https://example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|||||||
10
Makefile
10
Makefile
@@ -3,7 +3,7 @@ GOBUILD=$(GOCMD) build
|
|||||||
GOCLEAN=$(GOCMD) clean
|
GOCLEAN=$(GOCMD) clean
|
||||||
GOTEST=$(GOCMD) test
|
GOTEST=$(GOCMD) test
|
||||||
GOMOD=$(GOCMD) mod
|
GOMOD=$(GOCMD) mod
|
||||||
BINARY_NAME=fence
|
BINARY_NAME=greywall
|
||||||
BINARY_UNIX=$(BINARY_NAME)_unix
|
BINARY_UNIX=$(BINARY_NAME)_unix
|
||||||
TUN2SOCKS_VERSION=v2.5.2
|
TUN2SOCKS_VERSION=v2.5.2
|
||||||
TUN2SOCKS_BIN_DIR=internal/sandbox/bin
|
TUN2SOCKS_BIN_DIR=internal/sandbox/bin
|
||||||
@@ -29,14 +29,14 @@ download-tun2socks:
|
|||||||
|
|
||||||
build: download-tun2socks
|
build: download-tun2socks
|
||||||
@echo "Building $(BINARY_NAME)..."
|
@echo "Building $(BINARY_NAME)..."
|
||||||
$(GOBUILD) -o $(BINARY_NAME) -v ./cmd/fence
|
$(GOBUILD) -o $(BINARY_NAME) -v ./cmd/greywall
|
||||||
|
|
||||||
build-ci: download-tun2socks
|
build-ci: download-tun2socks
|
||||||
@echo "CI: Building $(BINARY_NAME) with version info..."
|
@echo "CI: Building $(BINARY_NAME) with version info..."
|
||||||
$(eval VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev"))
|
$(eval VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev"))
|
||||||
$(eval BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ'))
|
$(eval BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ'))
|
||||||
$(eval GIT_COMMIT := $(shell git rev-parse HEAD 2>/dev/null || echo "unknown"))
|
$(eval GIT_COMMIT := $(shell git rev-parse HEAD 2>/dev/null || echo "unknown"))
|
||||||
$(GOBUILD) -ldflags "-s -w -X main.version=$(VERSION) -X main.buildTime=$(BUILD_TIME) -X main.gitCommit=$(GIT_COMMIT)" -o $(BINARY_NAME) -v ./cmd/fence
|
$(GOBUILD) -ldflags "-s -w -X main.version=$(VERSION) -X main.buildTime=$(BUILD_TIME) -X main.gitCommit=$(GIT_COMMIT)" -o $(BINARY_NAME) -v ./cmd/greywall
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@echo "Running tests..."
|
@echo "Running tests..."
|
||||||
@@ -61,11 +61,11 @@ deps:
|
|||||||
|
|
||||||
build-linux: download-tun2socks
|
build-linux: download-tun2socks
|
||||||
@echo "Building for Linux..."
|
@echo "Building for Linux..."
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v ./cmd/fence
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v ./cmd/greywall
|
||||||
|
|
||||||
build-darwin:
|
build-darwin:
|
||||||
@echo "Building for macOS..."
|
@echo "Building for macOS..."
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(BINARY_NAME)_darwin -v ./cmd/fence
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(BINARY_NAME)_darwin -v ./cmd/greywall
|
||||||
|
|
||||||
install-lint-tools:
|
install-lint-tools:
|
||||||
@echo "Installing linting tools..."
|
@echo "Installing linting tools..."
|
||||||
|
|||||||
50
README.md
50
README.md
@@ -1,32 +1,30 @@
|
|||||||

|

|
||||||
|
|
||||||
<div align="center">
|
# Greywall
|
||||||
|
|
||||||

|
**The sandboxing layer of the GreyHaven platform.**
|
||||||
|
|
||||||
</div>
|
Greywall wraps commands in a sandbox that blocks network access by default and restricts filesystem operations. It is the core sandboxing component of the GreyHaven platform, providing defense-in-depth for running untrusted code.
|
||||||
|
|
||||||
Fence wraps commands in a sandbox that blocks network access by default and restricts filesystem operations based on configurable rules. It's most useful for running semi-trusted code (package installs, build scripts, CI jobs, unfamiliar repos) with controlled side effects, and it can also complement AI coding agents as defense-in-depth.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Block all network access (default)
|
# Block all network access (default)
|
||||||
fence curl https://example.com # → 403 Forbidden
|
greywall curl https://example.com # → 403 Forbidden
|
||||||
|
|
||||||
# Allow specific domains
|
# Allow specific domains
|
||||||
fence -t code npm install # → uses 'code' template with npm/pypi/etc allowed
|
greywall -t code npm install # → uses 'code' template with npm/pypi/etc allowed
|
||||||
|
|
||||||
# Block dangerous commands
|
# Block dangerous commands
|
||||||
fence -c "rm -rf /" # → blocked by command deny rules
|
greywall -c "rm -rf /" # → blocked by command deny rules
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also think of Fence as a permission manager for your CLI agents. **Fence works with popular coding agents like Claude Code, Codex, Gemini CLI, Cursor Agent, OpenCode, Factory (Droid) CLI, etc.** See [agents.md](./docs/agents.md) for more details.
|
Greywall also works as a permission manager for CLI agents. **Greywall works with popular coding agents like Claude Code, Codex, Gemini CLI, Cursor Agent, OpenCode, Factory (Droid) CLI, etc.** See [agents.md](./docs/agents.md) for more details.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
**macOS / Linux:**
|
**macOS / Linux:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://raw.githubusercontent.com/Use-Tusk/fence/main/install.sh | sh
|
curl -fsSL https://gitea.app.monadical.io/monadical/greywall/raw/branch/main/install.sh | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -35,15 +33,15 @@ curl -fsSL https://raw.githubusercontent.com/Use-Tusk/fence/main/install.sh | sh
|
|||||||
**Go install:**
|
**Go install:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go install github.com/Use-Tusk/fence/cmd/fence@latest
|
go install gitea.app.monadical.io/monadical/greywall/cmd/greywall@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
**Build from source:**
|
**Build from source:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/Use-Tusk/fence
|
git clone https://gitea.app.monadical.io/monadical/greywall
|
||||||
cd fence
|
cd greywall
|
||||||
go build -o fence ./cmd/fence
|
go build -o greywall ./cmd/greywall
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@@ -60,27 +58,27 @@ go build -o fence ./cmd/fence
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run command with all network blocked (no domains allowed by default)
|
# Run command with all network blocked (no domains allowed by default)
|
||||||
fence curl https://example.com
|
greywall curl https://example.com
|
||||||
|
|
||||||
# Run with shell expansion
|
# Run with shell expansion
|
||||||
fence -c "echo hello && ls"
|
greywall -c "echo hello && ls"
|
||||||
|
|
||||||
# Enable debug logging
|
# Enable debug logging
|
||||||
fence -d curl https://example.com
|
greywall -d curl https://example.com
|
||||||
|
|
||||||
# Use a template
|
# Use a template
|
||||||
fence -t code -- claude # Runs Claude Code using `code` template config
|
greywall -t code -- claude # Runs Claude Code using `code` template config
|
||||||
|
|
||||||
# Monitor mode (shows violations)
|
# Monitor mode (shows violations)
|
||||||
fence -m npm install
|
greywall -m npm install
|
||||||
|
|
||||||
# Show all commands and options
|
# Show all commands and options
|
||||||
fence --help
|
greywall --help
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
Fence reads from `~/.config/fence/fence.json` by default (or `~/Library/Application Support/fence/fence.json` on macOS).
|
Greywall reads from `~/.config/greywall/greywall.json` by default (or `~/Library/Application Support/greywall/greywall.json` on macOS).
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -91,12 +89,12 @@ Fence reads from `~/.config/fence/fence.json` by default (or `~/Library/Applicat
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `fence --settings ./custom.json` to specify a different config.
|
Use `greywall --settings ./custom.json` to specify a different config.
|
||||||
|
|
||||||
### Import from Claude Code
|
### Import from Claude Code
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence import --claude --save
|
greywall import --claude --save
|
||||||
```
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -109,7 +107,7 @@ fence import --claude --save
|
|||||||
- **Violation monitoring** - Real-time logging of blocked requests (`-m`)
|
- **Violation monitoring** - Real-time logging of blocked requests (`-m`)
|
||||||
- **Cross-platform** - macOS (sandbox-exec) + Linux (bubblewrap)
|
- **Cross-platform** - macOS (sandbox-exec) + Linux (bubblewrap)
|
||||||
|
|
||||||
Fence can be used as a Go package or CLI tool.
|
Greywall can be used as a Go package or CLI tool.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@@ -123,4 +121,6 @@ Fence can be used as a Go package or CLI tool.
|
|||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
|
Greywall is based on [Fence](https://github.com/Use-Tusk/fence) by Use-Tusk.
|
||||||
|
|
||||||
Inspired by Anthropic's [sandbox-runtime](https://github.com/anthropic-experimental/sandbox-runtime).
|
Inspired by Anthropic's [sandbox-runtime](https://github.com/anthropic-experimental/sandbox-runtime).
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 407 KiB After Width: | Height: | Size: 407 KiB |
@@ -1,4 +1,4 @@
|
|||||||
// Package main implements the fence CLI.
|
// Package main implements the greywall CLI.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,9 +10,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/Use-Tusk/fence/internal/config"
|
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||||
"github.com/Use-Tusk/fence/internal/platform"
|
"gitea.app.monadical.io/monadical/greywall/internal/platform"
|
||||||
"github.com/Use-Tusk/fence/internal/sandbox"
|
"gitea.app.monadical.io/monadical/greywall/internal/sandbox"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,29 +45,29 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootCmd := &cobra.Command{
|
rootCmd := &cobra.Command{
|
||||||
Use: "fence [flags] -- [command...]",
|
Use: "greywall [flags] -- [command...]",
|
||||||
Short: "Run commands in a sandbox with network and filesystem restrictions",
|
Short: "Run commands in a sandbox with network and filesystem restrictions",
|
||||||
Long: `fence is a command-line tool that runs commands in a sandboxed environment
|
Long: `greywall is a command-line tool that runs commands in a sandboxed environment
|
||||||
with network and filesystem restrictions.
|
with network and filesystem restrictions.
|
||||||
|
|
||||||
By default, all network access is blocked. Use --proxy to route traffic through
|
By default, all network access is blocked. Use --proxy to route traffic through
|
||||||
an external SOCKS5 proxy, or configure a proxy URL in your settings file at
|
an external SOCKS5 proxy, or configure a proxy URL in your settings file at
|
||||||
~/.config/fence/fence.json (or ~/Library/Application Support/fence/fence.json on macOS).
|
~/.config/greywall/greywall.json (or ~/Library/Application Support/greywall/greywall.json on macOS).
|
||||||
|
|
||||||
On Linux, fence uses tun2socks for truly transparent proxying: all TCP/UDP traffic
|
On Linux, greywall uses tun2socks for truly transparent proxying: all TCP/UDP traffic
|
||||||
from any binary is captured at the kernel level via a TUN device and forwarded
|
from any binary is captured at the kernel level via a TUN device and forwarded
|
||||||
through the external SOCKS5 proxy. No application awareness needed.
|
through the external SOCKS5 proxy. No application awareness needed.
|
||||||
|
|
||||||
On macOS, fence uses environment variables (best-effort) to direct traffic
|
On macOS, greywall uses environment variables (best-effort) to direct traffic
|
||||||
to the proxy.
|
to the proxy.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
fence -- curl https://example.com # Blocked (no proxy)
|
greywall -- curl https://example.com # Blocked (no proxy)
|
||||||
fence --proxy socks5://localhost:1080 -- curl https://example.com # Via proxy
|
greywall --proxy socks5://localhost:1080 -- curl https://example.com # Via proxy
|
||||||
fence -- curl -s https://example.com # Use -- to separate flags
|
greywall -- curl -s https://example.com # Use -- to separate flags
|
||||||
fence -c "echo hello && ls" # Run with shell expansion
|
greywall -c "echo hello && ls" # Run with shell expansion
|
||||||
fence --settings config.json npm install
|
greywall --settings config.json npm install
|
||||||
fence -p 3000 -c "npm run dev" # Expose port 3000
|
greywall -p 3000 -c "npm run dev" # Expose port 3000
|
||||||
|
|
||||||
Configuration file format:
|
Configuration file format:
|
||||||
{
|
{
|
||||||
@@ -112,7 +112,7 @@ Configuration file format:
|
|||||||
|
|
||||||
func runCommand(cmd *cobra.Command, args []string) error {
|
func runCommand(cmd *cobra.Command, args []string) error {
|
||||||
if showVersion {
|
if showVersion {
|
||||||
fmt.Printf("fence - lightweight, container-free sandbox for running untrusted commands\n")
|
fmt.Printf("greywall - lightweight, container-free sandbox for running untrusted commands\n")
|
||||||
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)
|
||||||
@@ -135,7 +135,7 @@ func runCommand(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
fmt.Fprintf(os.Stderr, "[fence] Command: %s\n", command)
|
fmt.Fprintf(os.Stderr, "[greywall] Command: %s\n", command)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ports []int
|
var ports []int
|
||||||
@@ -148,7 +148,7 @@ func runCommand(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if debug && len(ports) > 0 {
|
if debug && len(ports) > 0 {
|
||||||
fmt.Fprintf(os.Stderr, "[fence] Exposing ports: %v\n", ports)
|
fmt.Fprintf(os.Stderr, "[greywall] Exposing ports: %v\n", ports)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load config: settings file > default path > default config
|
// Load config: settings file > default path > default config
|
||||||
@@ -169,7 +169,7 @@ func runCommand(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
if debug {
|
if debug {
|
||||||
fmt.Fprintf(os.Stderr, "[fence] No config found at %s, using default (block all network)\n", configPath)
|
fmt.Fprintf(os.Stderr, "[greywall] No config found at %s, using default (block all network)\n", configPath)
|
||||||
}
|
}
|
||||||
cfg = config.Default()
|
cfg = config.Default()
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ func runCommand(cmd *cobra.Command, args []string) error {
|
|||||||
logMonitor = sandbox.NewLogMonitor(sandbox.GetSessionSuffix())
|
logMonitor = sandbox.NewLogMonitor(sandbox.GetSessionSuffix())
|
||||||
if logMonitor != nil {
|
if logMonitor != nil {
|
||||||
if err := logMonitor.Start(); err != nil {
|
if err := logMonitor.Start(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "[fence] Warning: failed to start log monitor: %v\n", err)
|
fmt.Fprintf(os.Stderr, "[greywall] Warning: failed to start log monitor: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
defer logMonitor.Stop()
|
defer logMonitor.Stop()
|
||||||
}
|
}
|
||||||
@@ -209,13 +209,13 @@ func runCommand(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
fmt.Fprintf(os.Stderr, "[fence] Sandboxed command: %s\n", sandboxedCommand)
|
fmt.Fprintf(os.Stderr, "[greywall] Sandboxed command: %s\n", sandboxedCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
hardenedEnv := sandbox.GetHardenedEnv()
|
hardenedEnv := sandbox.GetHardenedEnv()
|
||||||
if debug {
|
if debug {
|
||||||
if stripped := sandbox.GetStrippedEnvVars(os.Environ()); len(stripped) > 0 {
|
if stripped := sandbox.GetStrippedEnvVars(os.Environ()); len(stripped) > 0 {
|
||||||
fmt.Fprintf(os.Stderr, "[fence] Stripped dangerous env vars: %v\n", stripped)
|
fmt.Fprintf(os.Stderr, "[greywall] Stripped dangerous env vars: %v\n", stripped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,24 +280,24 @@ func newCompletionCmd(rootCmd *cobra.Command) *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "completion [bash|zsh|fish|powershell]",
|
Use: "completion [bash|zsh|fish|powershell]",
|
||||||
Short: "Generate shell completion scripts",
|
Short: "Generate shell completion scripts",
|
||||||
Long: `Generate shell completion scripts for fence.
|
Long: `Generate shell completion scripts for greywall.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
# Bash (load in current session)
|
# Bash (load in current session)
|
||||||
source <(fence completion bash)
|
source <(greywall completion bash)
|
||||||
|
|
||||||
# Zsh (load in current session)
|
# Zsh (load in current session)
|
||||||
source <(fence completion zsh)
|
source <(greywall completion zsh)
|
||||||
|
|
||||||
# Fish (load in current session)
|
# Fish (load in current session)
|
||||||
fence completion fish | source
|
greywall completion fish | source
|
||||||
|
|
||||||
# PowerShell (load in current session)
|
# PowerShell (load in current session)
|
||||||
fence completion powershell | Out-String | Invoke-Expression
|
greywall completion powershell | Out-String | Invoke-Expression
|
||||||
|
|
||||||
To persist completions, redirect output to the appropriate completions
|
To persist completions, redirect output to the appropriate completions
|
||||||
directory for your shell (e.g., /etc/bash_completion.d/ for bash,
|
directory for your shell (e.g., /etc/bash_completion.d/ for bash,
|
||||||
${fpath[1]}/_fence for zsh, ~/.config/fish/completions/fence.fish for fish).
|
${fpath[1]}/_greywall for zsh, ~/.config/fish/completions/greywall.fish for fish).
|
||||||
`,
|
`,
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||||
@@ -322,11 +322,11 @@ ${fpath[1]}/_fence for zsh, ~/.config/fish/completions/fence.fish for fish).
|
|||||||
|
|
||||||
// runLandlockWrapper runs in "wrapper mode" inside the sandbox.
|
// runLandlockWrapper runs in "wrapper mode" inside the sandbox.
|
||||||
// It applies Landlock restrictions and then execs the user command.
|
// It applies Landlock restrictions and then execs the user command.
|
||||||
// Usage: fence --landlock-apply [--debug] -- <command...>
|
// Usage: greywall --landlock-apply [--debug] -- <command...>
|
||||||
// Config is passed via FENCE_CONFIG_JSON environment variable.
|
// Config is passed via GREYWALL_CONFIG_JSON environment variable.
|
||||||
func runLandlockWrapper() {
|
func runLandlockWrapper() {
|
||||||
// Parse arguments: --landlock-apply [--debug] -- <command...>
|
// Parse arguments: --landlock-apply [--debug] -- <command...>
|
||||||
args := os.Args[2:] // Skip "fence" and "--landlock-apply"
|
args := os.Args[2:] // Skip "greywall" and "--landlock-apply"
|
||||||
|
|
||||||
var debugMode bool
|
var debugMode bool
|
||||||
var cmdStart int
|
var cmdStart int
|
||||||
@@ -347,25 +347,25 @@ func runLandlockWrapper() {
|
|||||||
|
|
||||||
parseCommand:
|
parseCommand:
|
||||||
if cmdStart >= len(args) {
|
if cmdStart >= len(args) {
|
||||||
fmt.Fprintf(os.Stderr, "[fence:landlock-wrapper] Error: no command specified\n")
|
fmt.Fprintf(os.Stderr, "[greywall:landlock-wrapper] Error: no command specified\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
command := args[cmdStart:]
|
command := args[cmdStart:]
|
||||||
|
|
||||||
if debugMode {
|
if debugMode {
|
||||||
fmt.Fprintf(os.Stderr, "[fence:landlock-wrapper] Applying Landlock restrictions\n")
|
fmt.Fprintf(os.Stderr, "[greywall:landlock-wrapper] Applying Landlock restrictions\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only apply Landlock on Linux
|
// Only apply Landlock on Linux
|
||||||
if platform.Detect() == platform.Linux {
|
if platform.Detect() == platform.Linux {
|
||||||
// Load config from environment variable (passed by parent fence process)
|
// Load config from environment variable (passed by parent greywall process)
|
||||||
var cfg *config.Config
|
var cfg *config.Config
|
||||||
if configJSON := os.Getenv("FENCE_CONFIG_JSON"); configJSON != "" {
|
if configJSON := os.Getenv("GREYWALL_CONFIG_JSON"); configJSON != "" {
|
||||||
cfg = &config.Config{}
|
cfg = &config.Config{}
|
||||||
if err := json.Unmarshal([]byte(configJSON), cfg); err != nil {
|
if err := json.Unmarshal([]byte(configJSON), cfg); err != nil {
|
||||||
if debugMode {
|
if debugMode {
|
||||||
fmt.Fprintf(os.Stderr, "[fence:landlock-wrapper] Warning: failed to parse config: %v\n", err)
|
fmt.Fprintf(os.Stderr, "[greywall:landlock-wrapper] Warning: failed to parse config: %v\n", err)
|
||||||
}
|
}
|
||||||
cfg = nil
|
cfg = nil
|
||||||
}
|
}
|
||||||
@@ -381,23 +381,23 @@ parseCommand:
|
|||||||
err := sandbox.ApplyLandlockFromConfig(cfg, cwd, nil, debugMode)
|
err := sandbox.ApplyLandlockFromConfig(cfg, cwd, nil, debugMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if debugMode {
|
if debugMode {
|
||||||
fmt.Fprintf(os.Stderr, "[fence:landlock-wrapper] Warning: Landlock not applied: %v\n", err)
|
fmt.Fprintf(os.Stderr, "[greywall:landlock-wrapper] Warning: Landlock not applied: %v\n", err)
|
||||||
}
|
}
|
||||||
// Continue without Landlock - bwrap still provides isolation
|
// Continue without Landlock - bwrap still provides isolation
|
||||||
} else if debugMode {
|
} else if debugMode {
|
||||||
fmt.Fprintf(os.Stderr, "[fence:landlock-wrapper] Landlock restrictions applied\n")
|
fmt.Fprintf(os.Stderr, "[greywall:landlock-wrapper] Landlock restrictions applied\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the executable
|
// Find the executable
|
||||||
execPath, err := exec.LookPath(command[0])
|
execPath, err := exec.LookPath(command[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "[fence:landlock-wrapper] Error: command not found: %s\n", command[0])
|
fmt.Fprintf(os.Stderr, "[greywall:landlock-wrapper] Error: command not found: %s\n", command[0])
|
||||||
os.Exit(127)
|
os.Exit(127)
|
||||||
}
|
}
|
||||||
|
|
||||||
if debugMode {
|
if debugMode {
|
||||||
fmt.Fprintf(os.Stderr, "[fence:landlock-wrapper] Exec: %s %v\n", execPath, command[1:])
|
fmt.Fprintf(os.Stderr, "[greywall:landlock-wrapper] Exec: %s %v\n", execPath, command[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize environment (strips LD_PRELOAD, etc.)
|
// Sanitize environment (strips LD_PRELOAD, etc.)
|
||||||
@@ -406,7 +406,7 @@ parseCommand:
|
|||||||
// Exec the command (replaces this process)
|
// Exec the command (replaces this process)
|
||||||
err = syscall.Exec(execPath, command, hardenedEnv) //nolint:gosec
|
err = syscall.Exec(execPath, command, hardenedEnv) //nolint:gosec
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "[fence:landlock-wrapper] Exec failed: %v\n", err)
|
fmt.Fprintf(os.Stderr, "[greywall:landlock-wrapper] Exec failed: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
# Fence Documentation
|
# Greywall Documentation
|
||||||
|
|
||||||
Fence is a sandboxing tool that restricts network and filesystem access for arbitrary commands. It's most useful for running semi-trusted code (package installs, build scripts, CI jobs, unfamiliar repos) with controlled side effects.
|
Greywall is a sandboxing tool that restricts network and filesystem access for arbitrary commands. It's most useful for running semi-trusted code (package installs, build scripts, CI jobs, unfamiliar repos) with controlled side effects.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
- [Quickstart](quickstart.md) - Install fence and run your first sandboxed command in 5 minutes
|
- [Quickstart](quickstart.md) - Install greywall and run your first sandboxed command in 5 minutes
|
||||||
- [Why Fence](why-fence.md) - What problem it solves (and what it doesn't)
|
- [Why Greywall](why-greywall.md) - What problem it solves (and what it doesn't)
|
||||||
|
|
||||||
## Guides
|
## Guides
|
||||||
|
|
||||||
- [Concepts](concepts.md) - Mental model: OS sandbox + local proxies + config
|
- [Concepts](concepts.md) - Mental model: OS sandbox + local proxies + config
|
||||||
- [Troubleshooting](troubleshooting.md) - Common failure modes and fixes
|
- [Troubleshooting](troubleshooting.md) - Common failure modes and fixes
|
||||||
- [Using Fence with AI agents](agents.md) - Defense-in-depth and policy standardization
|
- [Using Greywall with AI agents](agents.md) - Defense-in-depth and policy standardization
|
||||||
- [Recipes](recipes/README.md) - Common workflows (npm/pip/git/CI)
|
- [Recipes](recipes/README.md) - Common workflows (npm/pip/git/CI)
|
||||||
- [Templates](./templates.md) - Copy/paste templates you can start from
|
- [Templates](./templates.md) - Copy/paste templates you can start from
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
- [README](../README.md) - CLI usage
|
- [README](../README.md) - CLI usage
|
||||||
- [Library Usage (Go)](library.md) - Using Fence as a Go package
|
- [Library Usage (Go)](library.md) - Using Greywall as a Go package
|
||||||
- [Configuration](./configuration.md) - How to configure Fence
|
- [Configuration](./configuration.md) - How to configure Greywall
|
||||||
- [Architecture](../ARCHITECTURE.md) - How fence works under the hood
|
- [Architecture](../ARCHITECTURE.md) - How greywall works under the hood
|
||||||
- [Security model](security-model.md) - Threat model, guarantees, and limitations
|
- [Security model](security-model.md) - Threat model, guarantees, and limitations
|
||||||
- [Linux security features](linux-security-features.md) - Landlock, seccomp, eBPF details and fallback behavior
|
- [Linux security features](linux-security-features.md) - Landlock, seccomp, eBPF details and fallback behavior
|
||||||
- [Testing](testing.md) - How to run tests and write new ones
|
- [Testing](testing.md) - How to run tests and write new ones
|
||||||
@@ -36,20 +36,20 @@ See [`examples/`](../examples/README.md) for runnable demos.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Block all network (default)
|
# Block all network (default)
|
||||||
fence <command>
|
greywall <command>
|
||||||
|
|
||||||
# Use custom config
|
# Use custom config
|
||||||
fence --settings ./fence.json <command>
|
greywall --settings ./greywall.json <command>
|
||||||
|
|
||||||
# Debug mode (verbose output)
|
# Debug mode (verbose output)
|
||||||
fence -d <command>
|
greywall -d <command>
|
||||||
|
|
||||||
# Monitor mode (show blocked requests)
|
# Monitor mode (show blocked requests)
|
||||||
fence -m <command>
|
greywall -m <command>
|
||||||
|
|
||||||
# Expose port for servers
|
# Expose port for servers
|
||||||
fence -p 3000 <command>
|
greywall -p 3000 <command>
|
||||||
|
|
||||||
# Run shell command
|
# Run shell command
|
||||||
fence -c "echo hello && ls"
|
greywall -c "echo hello && ls"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Using Fence with AI Agents
|
# Using Greywall with AI Agents
|
||||||
|
|
||||||
Many popular coding agents already include sandboxing. Fence can still be useful when you want a tool-agnostic policy layer that works the same way across:
|
Many popular coding agents already include sandboxing. Greywall can still be useful when you want a tool-agnostic policy layer that works the same way across:
|
||||||
|
|
||||||
- local developer machines
|
- local developer machines
|
||||||
- CI jobs
|
- CI jobs
|
||||||
@@ -15,7 +15,7 @@ Treat an agent as "semi-trusted automation":
|
|||||||
- Allowlist only the network destinations you actually need
|
- Allowlist only the network destinations you actually need
|
||||||
- Use `-m` (monitor mode) to audit blocked attempts and tighten policy
|
- Use `-m` (monitor mode) to audit blocked attempts and tighten policy
|
||||||
|
|
||||||
Fence can also reduce the risk of running agents with fewer interactive permission prompts (e.g. "skip permissions"), as long as your Fence config tightly scopes writes and outbound destinations. It's defense-in-depth, not a substitute for the agent's own safeguards.
|
Greywall can also reduce the risk of running agents with fewer interactive permission prompts (e.g. "skip permissions"), as long as your Greywall config tightly scopes writes and outbound destinations. It's defense-in-depth, not a substitute for the agent's own safeguards.
|
||||||
|
|
||||||
## Example: API-only agent
|
## Example: API-only agent
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ Fence can also reduce the risk of running agents with fewer interactive permissi
|
|||||||
Run:
|
Run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence --settings ./fence.json <agent-command>
|
greywall --settings ./greywall.json <agent-command>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Popular CLI coding agents
|
## Popular CLI coding agents
|
||||||
@@ -43,7 +43,7 @@ We provide these template for guardrailing CLI coding agents:
|
|||||||
- [`code`](/internal/templates/code.json) - Strict deny-by-default network filtering via proxy. Works with agents that respect `HTTP_PROXY`. Blocks cloud metadata APIs, protects secrets, restricts dangerous commands.
|
- [`code`](/internal/templates/code.json) - Strict deny-by-default network filtering via proxy. Works with agents that respect `HTTP_PROXY`. Blocks cloud metadata APIs, protects secrets, restricts dangerous commands.
|
||||||
- [`code-relaxed`](/internal/templates/code-relaxed.json) - Allows direct network connections for agents that ignore `HTTP_PROXY`. Same filesystem/command protections as `code`, but `deniedDomains` only enforced for proxy-respecting apps.
|
- [`code-relaxed`](/internal/templates/code-relaxed.json) - Allows direct network connections for agents that ignore `HTTP_PROXY`. Same filesystem/command protections as `code`, but `deniedDomains` only enforced for proxy-respecting apps.
|
||||||
|
|
||||||
You can use it like `fence -t code -- claude`.
|
You can use it like `greywall -t code -- claude`.
|
||||||
|
|
||||||
| Agent | Works with template | Notes |
|
| Agent | Works with template | Notes |
|
||||||
|-------|--------| ----- |
|
|-------|--------| ----- |
|
||||||
@@ -60,7 +60,7 @@ Note: On Linux, if OpenCode or Gemini CLI is installed via Linuxbrew, Landlock c
|
|||||||
|
|
||||||
## Protecting your environment
|
## Protecting your environment
|
||||||
|
|
||||||
Fence includes additional "dangerous file protection" (writes blocked regardless of config) to reduce persistence and environment-tampering vectors like:
|
Greywall includes additional "dangerous file protection" (writes blocked regardless of config) to reduce persistence and environment-tampering vectors like:
|
||||||
|
|
||||||
- `.git/hooks/*`
|
- `.git/hooks/*`
|
||||||
- shell startup files (`.zshrc`, `.bashrc`, etc.)
|
- shell startup files (`.zshrc`, `.bashrc`, etc.)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Benchmarking
|
# Benchmarking
|
||||||
|
|
||||||
This document describes how to run, interpret, and compare sandbox performance benchmarks for Fence.
|
This document describes how to run, interpret, and compare sandbox performance benchmarks for Greywall.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -29,9 +29,9 @@ go test -run=^$ -bench=. -benchmem ./internal/sandbox/...
|
|||||||
|
|
||||||
### Layer 1: CLI Benchmarks (`scripts/benchmark.sh`)
|
### Layer 1: CLI Benchmarks (`scripts/benchmark.sh`)
|
||||||
|
|
||||||
**What it measures**: Real-world agent cost - full `fence` invocation including proxy startup, socat bridges (Linux), and sandbox-exec/bwrap setup.
|
**What it measures**: Real-world agent cost - full `greywall` invocation including proxy startup, socat bridges (Linux), and sandbox-exec/bwrap setup.
|
||||||
|
|
||||||
This is the most realistic benchmark for understanding the cost of running agent commands through Fence.
|
This is the most realistic benchmark for understanding the cost of running agent commands through Greywall.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Full benchmark suite
|
# Full benchmark suite
|
||||||
@@ -51,7 +51,7 @@ This is the most realistic benchmark for understanding the cost of running agent
|
|||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| `-b, --binary PATH` | Path to fence binary (default: ./fence) |
|
| `-b, --binary PATH` | Path to greywall binary (default: ./greywall) |
|
||||||
| `-o, --output DIR` | Output directory (default: ./benchmarks) |
|
| `-o, --output DIR` | Output directory (default: ./benchmarks) |
|
||||||
| `-n, --runs N` | Minimum runs per benchmark (default: 30) |
|
| `-n, --runs N` | Minimum runs per benchmark (default: 30) |
|
||||||
| `-q, --quick` | Quick mode: fewer runs, skip slow benchmarks |
|
| `-q, --quick` | Quick mode: fewer runs, skip slow benchmarks |
|
||||||
@@ -92,13 +92,13 @@ benchstat bench.txt
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Quick syscall cost breakdown
|
# Quick syscall cost breakdown
|
||||||
strace -f -c ./fence -- true
|
strace -f -c ./greywall -- true
|
||||||
|
|
||||||
# Context switches, page faults
|
# Context switches, page faults
|
||||||
perf stat -- ./fence -- true
|
perf stat -- ./greywall -- true
|
||||||
|
|
||||||
# Full profiling (flamegraph-ready)
|
# Full profiling (flamegraph-ready)
|
||||||
perf record -F 99 -g -- ./fence -- git status
|
perf record -F 99 -g -- ./greywall -- git status
|
||||||
perf report
|
perf report
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -106,10 +106,10 @@ perf report
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Time Profiler via Instruments
|
# Time Profiler via Instruments
|
||||||
xcrun xctrace record --template 'Time Profiler' --launch -- ./fence -- true
|
xcrun xctrace record --template 'Time Profiler' --launch -- ./greywall -- true
|
||||||
|
|
||||||
# Quick call-stack snapshot
|
# Quick call-stack snapshot
|
||||||
./fence -- sleep 5 &
|
./greywall -- sleep 5 &
|
||||||
sample $! 5 -file sample.txt
|
sample $! 5 -file sample.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ The overhead factor decreases as the actual workload increases (because sandbox
|
|||||||
|
|
||||||
1. Run benchmarks on each platform independently
|
1. Run benchmarks on each platform independently
|
||||||
2. Compare overhead factors, not absolute times
|
2. Compare overhead factors, not absolute times
|
||||||
3. Use the same fence version and workloads
|
3. Use the same greywall version and workloads
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# On macOS
|
# On macOS
|
||||||
@@ -256,7 +256,7 @@ Linux initialization is ~3,700x slower because it must:
|
|||||||
|
|
||||||
macOS only generates a Seatbelt profile string (very cheap).
|
macOS only generates a Seatbelt profile string (very cheap).
|
||||||
|
|
||||||
### Cold Start Overhead (one `fence` invocation per command)
|
### Cold Start Overhead (one `greywall` invocation per command)
|
||||||
|
|
||||||
| Workload | Linux | macOS |
|
| Workload | Linux | macOS |
|
||||||
|----------|-------|-------|
|
|----------|-------|-------|
|
||||||
@@ -264,7 +264,7 @@ macOS only generates a Seatbelt profile string (very cheap).
|
|||||||
| Python | 124 ms | 33 ms |
|
| Python | 124 ms | 33 ms |
|
||||||
| Git status | 114 ms | 25 ms |
|
| Git status | 114 ms | 25 ms |
|
||||||
|
|
||||||
This is the realistic cost for scripts running `fence -c "command"` repeatedly.
|
This is the realistic cost for scripts running `greywall -c "command"` repeatedly.
|
||||||
|
|
||||||
### Warm Path Overhead (pre-initialized manager)
|
### Warm Path Overhead (pre-initialized manager)
|
||||||
|
|
||||||
@@ -289,9 +289,9 @@ Overhead decreases as the actual workload increases (sandbox setup is fixed cost
|
|||||||
|
|
||||||
## Impact on Agent Usage
|
## Impact on Agent Usage
|
||||||
|
|
||||||
### Long-Running Agents (`fence claude`, `fence codex`)
|
### Long-Running Agents (`greywall claude`, `greywall codex`)
|
||||||
|
|
||||||
For agents that run as a child process under fence:
|
For agents that run as a child process under greywall:
|
||||||
|
|
||||||
| Phase | Cost |
|
| Phase | Cost |
|
||||||
|-------|------|
|
|-------|------|
|
||||||
@@ -306,11 +306,11 @@ Child processes inherit the sandbox - no re-initialization, no WrapCommand overh
|
|||||||
| `git status` | 2.1 ms | 5.9 ms |
|
| `git status` | 2.1 ms | 5.9 ms |
|
||||||
| Python script | 11 ms | 15 ms |
|
| Python script | 11 ms | 15 ms |
|
||||||
|
|
||||||
**Bottom line**: For `fence <agent>` usage, sandbox overhead is a one-time startup cost. Tool calls inside the agent run at native speed.
|
**Bottom line**: For `greywall <agent>` usage, sandbox overhead is a one-time startup cost. Tool calls inside the agent run at native speed.
|
||||||
|
|
||||||
### Per-Command Invocation (`fence -c "command"`)
|
### Per-Command Invocation (`greywall -c "command"`)
|
||||||
|
|
||||||
For scripts or CI running fence per command:
|
For scripts or CI running greywall per command:
|
||||||
|
|
||||||
| Session | Linux Cost | macOS Cost |
|
| Session | Linux Cost | macOS Cost |
|
||||||
|---------|------------|------------|
|
|---------|------------|------------|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
# Concepts
|
# Concepts
|
||||||
|
|
||||||
Fence combines two ideas:
|
Greywall combines two ideas:
|
||||||
|
|
||||||
1. **An OS sandbox** to enforce "no direct network" and restrict filesystem operations.
|
1. **An OS sandbox** to enforce "no direct network" and restrict filesystem operations.
|
||||||
2. **Local filtering proxies** (HTTP + SOCKS5) to selectively allow outbound traffic by domain.
|
2. **Local filtering proxies** (HTTP + SOCKS5) to selectively allow outbound traffic by domain.
|
||||||
|
|
||||||
## Network model
|
## Network model
|
||||||
|
|
||||||
By default, fence blocks all outbound network access.
|
By default, greywall blocks all outbound network access.
|
||||||
|
|
||||||
When you allow domains, fence:
|
When you allow domains, greywall:
|
||||||
|
|
||||||
- Starts local HTTP and SOCKS5 proxies
|
- Starts local HTTP and SOCKS5 proxies
|
||||||
- Sets proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`)
|
- Sets proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`)
|
||||||
@@ -29,13 +29,13 @@ These are separate on purpose. A typical safe default for dev servers is:
|
|||||||
|
|
||||||
## Filesystem model
|
## Filesystem model
|
||||||
|
|
||||||
Fence is designed around "read mostly, write narrowly":
|
Greywall is designed around "read mostly, write narrowly":
|
||||||
|
|
||||||
- **Reads**: allowed by default (you can block specific paths via `denyRead`).
|
- **Reads**: allowed by default (you can block specific paths via `denyRead`).
|
||||||
- **Writes**: denied by default (you must opt-in with `allowWrite`).
|
- **Writes**: denied by default (you must opt-in with `allowWrite`).
|
||||||
- **denyWrite**: overrides `allowWrite` (useful for protecting secrets and dangerous files).
|
- **denyWrite**: overrides `allowWrite` (useful for protecting secrets and dangerous files).
|
||||||
|
|
||||||
Fence also protects some dangerous targets regardless of config (e.g. shell startup files and git hooks). See `ARCHITECTURE.md` for the full list.
|
Greywall also protects some dangerous targets regardless of config (e.g. shell startup files and git hooks). See `ARCHITECTURE.md` for the full list.
|
||||||
|
|
||||||
## Debug vs Monitor mode
|
## Debug vs Monitor mode
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
Fence reads settings from `~/.config/fence/fence.json` by default (or `~/Library/Application Support/fence/fence.json` on macOS). Legacy `~/.fence.json` is also supported. Pass `--settings ./fence.json` to use a custom path. Config files support JSONC.
|
Greywall reads settings from `~/.config/greywall/greywall.json` by default (or `~/Library/Application Support/greywall/greywall.json` on macOS). Legacy `~/.greywall.json` is also supported. Pass `--settings ./greywall.json` to use a custom path. Config files support JSONC.
|
||||||
|
|
||||||
Example config:
|
Example config:
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ You can also extend other config files using absolute or relative paths:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"extends": "/etc/fence/company-base.json",
|
"extends": "/etc/greywall/company-base.json",
|
||||||
"filesystem": {
|
"filesystem": {
|
||||||
"denyRead": ["~/company-secrets/**"]
|
"denyRead": ["~/company-secrets/**"]
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ Example:
|
|||||||
|
|
||||||
### Default Denied Commands
|
### Default Denied Commands
|
||||||
|
|
||||||
When `useDefaults` is `true` (the default), fence blocks these dangerous commands:
|
When `useDefaults` is `true` (the default), greywall blocks these dangerous commands:
|
||||||
|
|
||||||
- System control: `shutdown`, `reboot`, `halt`, `poweroff`, `init 0/6`
|
- System control: `shutdown`, `reboot`, `halt`, `poweroff`, `init 0/6`
|
||||||
- Kernel manipulation: `insmod`, `rmmod`, `modprobe`, `kexec`
|
- Kernel manipulation: `insmod`, `rmmod`, `modprobe`, `kexec`
|
||||||
@@ -155,7 +155,7 @@ To disable defaults: `"useDefaults": false`
|
|||||||
|
|
||||||
### Command Detection
|
### Command Detection
|
||||||
|
|
||||||
Fence detects blocked commands in:
|
Greywall detects blocked commands in:
|
||||||
|
|
||||||
- Direct commands: `git push origin main`
|
- Direct commands: `git push origin main`
|
||||||
- Command chains: `ls && git push` or `ls; git push`
|
- Command chains: `ls && git push` or `ls; git push`
|
||||||
@@ -260,26 +260,26 @@ SSH host patterns support wildcards anywhere:
|
|||||||
|
|
||||||
## Importing from Claude Code
|
## Importing from Claude Code
|
||||||
|
|
||||||
If you've been using Claude Code and have already built up permission rules, you can import them into fence:
|
If you've been using Claude Code and have already built up permission rules, you can import them into greywall:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Preview import (prints JSON to stdout)
|
# Preview import (prints JSON to stdout)
|
||||||
fence import --claude
|
greywall import --claude
|
||||||
|
|
||||||
# Save to the default config path
|
# Save to the default config path
|
||||||
fence import --claude --save
|
greywall import --claude --save
|
||||||
|
|
||||||
# Import from a specific file
|
# Import from a specific file
|
||||||
fence import --claude -f ~/.claude/settings.json --save
|
greywall import --claude -f ~/.claude/settings.json --save
|
||||||
|
|
||||||
# Save to a specific output file
|
# Save to a specific output file
|
||||||
fence import --claude -o ./fence.json
|
greywall import --claude -o ./greywall.json
|
||||||
|
|
||||||
# Import without extending any template (minimal config)
|
# Import without extending any template (minimal config)
|
||||||
fence import --claude --no-extend --save
|
greywall import --claude --no-extend --save
|
||||||
|
|
||||||
# Import and extend a different template
|
# Import and extend a different template
|
||||||
fence import --claude --extend local-dev-server --save
|
greywall import --claude --extend local-dev-server --save
|
||||||
```
|
```
|
||||||
|
|
||||||
### Default Template
|
### Default Template
|
||||||
@@ -294,7 +294,7 @@ Use `--no-extend` if you want a minimal config without these defaults, or `--ext
|
|||||||
|
|
||||||
### Permission Mapping
|
### Permission Mapping
|
||||||
|
|
||||||
| Claude Code | Fence |
|
| Claude Code | Greywall |
|
||||||
|-------------|-------|
|
|-------------|-------|
|
||||||
| `Bash(xyz)` allow | `command.allow: ["xyz"]` |
|
| `Bash(xyz)` allow | `command.allow: ["xyz"]` |
|
||||||
| `Bash(xyz:*)` deny | `command.deny: ["xyz"]` |
|
| `Bash(xyz:*)` deny | `command.deny: ["xyz"]` |
|
||||||
@@ -302,9 +302,9 @@ Use `--no-extend` if you want a minimal config without these defaults, or `--ext
|
|||||||
| `Write(path)` allow | `filesystem.allowWrite: [path]` |
|
| `Write(path)` allow | `filesystem.allowWrite: [path]` |
|
||||||
| `Write(path)` deny | `filesystem.denyWrite: [path]` |
|
| `Write(path)` deny | `filesystem.denyWrite: [path]` |
|
||||||
| `Edit(path)` | Same as `Write(path)` |
|
| `Edit(path)` | Same as `Write(path)` |
|
||||||
| `ask` rules | Converted to deny (fence doesn't support interactive prompts) |
|
| `ask` rules | Converted to deny (greywall doesn't support interactive prompts) |
|
||||||
|
|
||||||
Global tool permissions (e.g., bare `Read`, `Write`, `Grep`) are skipped since fence uses path/command-based rules.
|
Global tool permissions (e.g., bare `Read`, `Write`, `Grep`) are skipped since greywall uses path/command-based rules.
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Library Usage (Go)
|
# Library Usage (Go)
|
||||||
|
|
||||||
Fence can be used as a Go library to sandbox commands programmatically.
|
Greywall can be used as a Go library to sandbox commands programmatically.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get github.com/Use-Tusk/fence
|
go get gitea.app.monadical.io/monadical/greywall
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -17,25 +17,25 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/Use-Tusk/fence/pkg/fence"
|
"gitea.app.monadical.io/monadical/greywall/pkg/greywall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Check platform support
|
// Check platform support
|
||||||
if !fence.IsSupported() {
|
if !greywall.IsSupported() {
|
||||||
fmt.Println("Sandboxing not supported on this platform")
|
fmt.Println("Sandboxing not supported on this platform")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create config
|
// Create config
|
||||||
cfg := &fence.Config{
|
cfg := &greywall.Config{
|
||||||
Network: fence.NetworkConfig{
|
Network: greywall.NetworkConfig{
|
||||||
AllowedDomains: []string{"api.example.com"},
|
AllowedDomains: []string{"api.example.com"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and initialize manager
|
// Create and initialize manager
|
||||||
manager := fence.NewManager(cfg, false, false)
|
manager := greywall.NewManager(cfg, false, false)
|
||||||
defer manager.Cleanup()
|
defer manager.Cleanup()
|
||||||
|
|
||||||
if err := manager.Initialize(); err != nil {
|
if err := manager.Initialize(); err != nil {
|
||||||
@@ -64,7 +64,7 @@ func main() {
|
|||||||
Returns `true` if the current platform supports sandboxing (macOS or Linux).
|
Returns `true` if the current platform supports sandboxing (macOS or Linux).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if !fence.IsSupported() {
|
if !greywall.IsSupported() {
|
||||||
log.Fatal("Platform not supported")
|
log.Fatal("Platform not supported")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -74,7 +74,7 @@ if !fence.IsSupported() {
|
|||||||
Returns a default configuration with all network blocked.
|
Returns a default configuration with all network blocked.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
cfg := fence.DefaultConfig()
|
cfg := greywall.DefaultConfig()
|
||||||
cfg.Network.AllowedDomains = []string{"example.com"}
|
cfg.Network.AllowedDomains = []string{"example.com"}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -83,18 +83,18 @@ cfg.Network.AllowedDomains = []string{"example.com"}
|
|||||||
Loads configuration from a JSON file. Supports JSONC (comments allowed).
|
Loads configuration from a JSON file. Supports JSONC (comments allowed).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
cfg, err := fence.LoadConfig(fence.DefaultConfigPath())
|
cfg, err := greywall.LoadConfig(greywall.DefaultConfigPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = fence.DefaultConfig() // File doesn't exist
|
cfg = greywall.DefaultConfig() // File doesn't exist
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `DefaultConfigPath() string`
|
#### `DefaultConfigPath() string`
|
||||||
|
|
||||||
Returns the default config file path (`~/.config/fence/fence.json` on Linux, `~/Library/Application Support/fence/fence.json` on macOS, with fallback to legacy `~/.fence.json`).
|
Returns the default config file path (`~/.config/greywall/greywall.json` on Linux, `~/Library/Application Support/greywall/greywall.json` on macOS, with fallback to legacy `~/.greywall.json`).
|
||||||
|
|
||||||
#### `NewManager(cfg *Config, debug, monitor bool) *Manager`
|
#### `NewManager(cfg *Config, debug, monitor bool) *Manager`
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ Creates a new sandbox manager.
|
|||||||
Sets up sandbox infrastructure (starts HTTP and SOCKS proxies). Called automatically by `WrapCommand` if not already initialized.
|
Sets up sandbox infrastructure (starts HTTP and SOCKS proxies). Called automatically by `WrapCommand` if not already initialized.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
manager := fence.NewManager(cfg, false, false)
|
manager := greywall.NewManager(cfg, false, false)
|
||||||
defer manager.Cleanup()
|
defer manager.Cleanup()
|
||||||
|
|
||||||
if err := manager.Initialize(); err != nil {
|
if err := manager.Initialize(); err != nil {
|
||||||
@@ -222,8 +222,8 @@ type SSHConfig struct {
|
|||||||
### Allow specific domains
|
### Allow specific domains
|
||||||
|
|
||||||
```go
|
```go
|
||||||
cfg := &fence.Config{
|
cfg := &greywall.Config{
|
||||||
Network: fence.NetworkConfig{
|
Network: greywall.NetworkConfig{
|
||||||
AllowedDomains: []string{
|
AllowedDomains: []string{
|
||||||
"registry.npmjs.org",
|
"registry.npmjs.org",
|
||||||
"*.github.com",
|
"*.github.com",
|
||||||
@@ -236,8 +236,8 @@ cfg := &fence.Config{
|
|||||||
### Restrict filesystem access
|
### Restrict filesystem access
|
||||||
|
|
||||||
```go
|
```go
|
||||||
cfg := &fence.Config{
|
cfg := &greywall.Config{
|
||||||
Filesystem: fence.FilesystemConfig{
|
Filesystem: greywall.FilesystemConfig{
|
||||||
AllowWrite: []string{".", "/tmp"},
|
AllowWrite: []string{".", "/tmp"},
|
||||||
DenyRead: []string{"~/.ssh", "~/.aws"},
|
DenyRead: []string{"~/.ssh", "~/.aws"},
|
||||||
},
|
},
|
||||||
@@ -247,8 +247,8 @@ cfg := &fence.Config{
|
|||||||
### Block dangerous commands
|
### Block dangerous commands
|
||||||
|
|
||||||
```go
|
```go
|
||||||
cfg := &fence.Config{
|
cfg := &greywall.Config{
|
||||||
Command: fence.CommandConfig{
|
Command: greywall.CommandConfig{
|
||||||
Deny: []string{
|
Deny: []string{
|
||||||
"rm -rf /",
|
"rm -rf /",
|
||||||
"git push",
|
"git push",
|
||||||
@@ -261,7 +261,7 @@ cfg := &fence.Config{
|
|||||||
### Expose dev server port
|
### Expose dev server port
|
||||||
|
|
||||||
```go
|
```go
|
||||||
manager := fence.NewManager(cfg, false, false)
|
manager := greywall.NewManager(cfg, false, false)
|
||||||
manager.SetExposedPorts([]int{3000})
|
manager.SetExposedPorts([]int{3000})
|
||||||
defer manager.Cleanup()
|
defer manager.Cleanup()
|
||||||
|
|
||||||
@@ -271,12 +271,12 @@ wrapped, _ := manager.WrapCommand("npm run dev")
|
|||||||
### Load and extend config
|
### Load and extend config
|
||||||
|
|
||||||
```go
|
```go
|
||||||
cfg, err := fence.LoadConfig(fence.DefaultConfigPath())
|
cfg, err := greywall.LoadConfig(greywall.DefaultConfigPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = fence.DefaultConfig()
|
cfg = greywall.DefaultConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add additional restrictions
|
// Add additional restrictions
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Linux Security Features
|
# Linux Security Features
|
||||||
|
|
||||||
Fence uses multiple layers of security on Linux, with graceful fallback when features are unavailable.
|
Greywall uses multiple layers of security on Linux, with graceful fallback when features are unavailable.
|
||||||
|
|
||||||
## Security Layers
|
## Security Layers
|
||||||
|
|
||||||
@@ -13,13 +13,13 @@ Fence uses multiple layers of security on Linux, with graceful fallback when fea
|
|||||||
|
|
||||||
## Feature Detection
|
## Feature Detection
|
||||||
|
|
||||||
Fence automatically detects available features and uses the best available combination.
|
Greywall automatically detects available features and uses the best available combination.
|
||||||
|
|
||||||
To see what features are detected:
|
To see what features are detected:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check what features are available on your system
|
# Check what features are available on your system
|
||||||
fence --linux-features
|
greywall --linux-features
|
||||||
|
|
||||||
# Example output:
|
# Example output:
|
||||||
# Linux Sandbox Features:
|
# Linux Sandbox Features:
|
||||||
@@ -41,7 +41,7 @@ fence --linux-features
|
|||||||
|
|
||||||
Landlock is applied via an **embedded wrapper** approach:
|
Landlock is applied via an **embedded wrapper** approach:
|
||||||
|
|
||||||
1. bwrap spawns `fence --landlock-apply -- <user-command>`
|
1. bwrap spawns `greywall --landlock-apply -- <user-command>`
|
||||||
2. The wrapper applies Landlock kernel restrictions
|
2. The wrapper applies Landlock kernel restrictions
|
||||||
3. The wrapper `exec()`s the user command
|
3. The wrapper `exec()`s the user command
|
||||||
|
|
||||||
@@ -75,25 +75,25 @@ This provides **defense-in-depth**: both bwrap mounts AND Landlock kernel restri
|
|||||||
- **Impact**: `--unshare-net` is skipped; network is not fully isolated
|
- **Impact**: `--unshare-net` is skipped; network is not fully isolated
|
||||||
- **Cause**: Running in Docker, GitHub Actions, or other environments without `CAP_NET_ADMIN`
|
- **Cause**: Running in Docker, GitHub Actions, or other environments without `CAP_NET_ADMIN`
|
||||||
- **Fallback**: Proxy-based filtering still works; filesystem/PID/seccomp isolation still active
|
- **Fallback**: Proxy-based filtering still works; filesystem/PID/seccomp isolation still active
|
||||||
- **Check**: Run `fence --linux-features` and look for "Network namespace (--unshare-net): false"
|
- **Check**: Run `greywall --linux-features` and look for "Network namespace (--unshare-net): false"
|
||||||
- **Workaround**: Run with `sudo`, or in Docker use `--cap-add=NET_ADMIN`
|
- **Workaround**: Run with `sudo`, or in Docker use `--cap-add=NET_ADMIN`
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> This is the most common "reduced isolation" scenario. Fence automatically detects this at startup and adapts. See the troubleshooting guide for more details.
|
> This is the most common "reduced isolation" scenario. Greywall automatically detects this at startup and adapts. See the troubleshooting guide for more details.
|
||||||
|
|
||||||
### When bwrap is not available
|
### When bwrap is not available
|
||||||
|
|
||||||
- **Impact**: Cannot run fence on Linux
|
- **Impact**: Cannot run greywall on Linux
|
||||||
- **Solution**: Install bubblewrap: `apt install bubblewrap` or `dnf install bubblewrap`
|
- **Solution**: Install bubblewrap: `apt install bubblewrap` or `dnf install bubblewrap`
|
||||||
|
|
||||||
### When socat is not available
|
### When socat is not available
|
||||||
|
|
||||||
- **Impact**: Cannot run fence on Linux
|
- **Impact**: Cannot run greywall on Linux
|
||||||
- **Solution**: Install socat: `apt install socat` or `dnf install socat`
|
- **Solution**: Install socat: `apt install socat` or `dnf install socat`
|
||||||
|
|
||||||
## Blocked Syscalls (seccomp)
|
## Blocked Syscalls (seccomp)
|
||||||
|
|
||||||
Fence blocks dangerous syscalls that could be used for sandbox escape or privilege escalation:
|
Greywall blocks dangerous syscalls that could be used for sandbox escape or privilege escalation:
|
||||||
|
|
||||||
| Syscall | Reason |
|
| Syscall | Reason |
|
||||||
|---------|--------|
|
|---------|--------|
|
||||||
@@ -111,13 +111,13 @@ Fence blocks dangerous syscalls that could be used for sandbox escape or privile
|
|||||||
|
|
||||||
## Violation Monitoring
|
## Violation Monitoring
|
||||||
|
|
||||||
On Linux, violation monitoring (`fence -m`) shows:
|
On Linux, violation monitoring (`greywall -m`) shows:
|
||||||
|
|
||||||
| Source | What it shows | Requirements |
|
| Source | What it shows | Requirements |
|
||||||
|--------|---------------|--------------|
|
|--------|---------------|--------------|
|
||||||
| `[fence:http]` | Blocked HTTP/HTTPS requests | None |
|
| `[greywall:http]` | Blocked HTTP/HTTPS requests | None |
|
||||||
| `[fence:socks]` | Blocked SOCKS connections | None |
|
| `[greywall:socks]` | Blocked SOCKS connections | None |
|
||||||
| `[fence:ebpf]` | Blocked filesystem access + syscalls | CAP_BPF or root |
|
| `[greywall:ebpf]` | Blocked filesystem access + syscalls | CAP_BPF or root |
|
||||||
|
|
||||||
**Notes**:
|
**Notes**:
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ On Linux, violation monitoring (`fence -m`) shows:
|
|||||||
|
|
||||||
## Comparison with macOS
|
## Comparison with macOS
|
||||||
|
|
||||||
| Feature | macOS (Seatbelt) | Linux (fence) |
|
| Feature | macOS (Seatbelt) | Linux (greywall) |
|
||||||
|---------|------------------|---------------|
|
|---------|------------------|---------------|
|
||||||
| Filesystem control | Native | bwrap + Landlock |
|
| Filesystem control | Native | bwrap + Landlock |
|
||||||
| Glob patterns | Native regex | Expanded at startup |
|
| Glob patterns | Native regex | Expanded at startup |
|
||||||
@@ -181,12 +181,12 @@ sudo apk add bubblewrap socat
|
|||||||
For full violation visibility without root:
|
For full violation visibility without root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Grant CAP_BPF to the fence binary
|
# Grant CAP_BPF to the greywall binary
|
||||||
sudo setcap cap_bpf+ep /usr/local/bin/fence
|
sudo setcap cap_bpf+ep /usr/local/bin/greywall
|
||||||
```
|
```
|
||||||
|
|
||||||
Or run fence with sudo when monitoring is needed:
|
Or run greywall with sudo when monitoring is needed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo fence -m <command>
|
sudo greywall -m <command>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -5,16 +5,16 @@
|
|||||||
### From Source (recommended for now)
|
### From Source (recommended for now)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/Use-Tusk/fence
|
git clone https://gitea.app.monadical.io/monadical/greywall
|
||||||
cd fence
|
cd greywall
|
||||||
go build -o fence ./cmd/fence
|
go build -o greywall ./cmd/greywall
|
||||||
sudo mv fence /usr/local/bin/
|
sudo mv greywall /usr/local/bin/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Go Install
|
### Using Go Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go install github.com/Use-Tusk/fence/cmd/fence@latest
|
go install gitea.app.monadical.io/monadical/greywall/cmd/greywall@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Linux Dependencies
|
### Linux Dependencies
|
||||||
@@ -32,30 +32,30 @@ sudo dnf install bubblewrap socat
|
|||||||
sudo pacman -S bubblewrap socat
|
sudo pacman -S bubblewrap socat
|
||||||
```
|
```
|
||||||
|
|
||||||
### Do I need sudo to run fence?
|
### Do I need sudo to run greywall?
|
||||||
|
|
||||||
No, for most Linux systems. Fence works without root privileges because:
|
No, for most Linux systems. Greywall works without root privileges because:
|
||||||
|
|
||||||
- Package-manager-installed `bubblewrap` is typically already setuid
|
- Package-manager-installed `bubblewrap` is typically already setuid
|
||||||
- Fence detects available capabilities and adapts automatically
|
- Greywall detects available capabilities and adapts automatically
|
||||||
|
|
||||||
If some features aren't available (like network namespaces in Docker/CI), fence falls back gracefully - you'll still get filesystem isolation, command blocking, and proxy-based network filtering.
|
If some features aren't available (like network namespaces in Docker/CI), greywall falls back gracefully - you'll still get filesystem isolation, command blocking, and proxy-based network filtering.
|
||||||
|
|
||||||
Run `fence --linux-features` to see what's available in your environment.
|
Run `greywall --linux-features` to see what's available in your environment.
|
||||||
|
|
||||||
## Verify Installation
|
## Verify Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence --version
|
greywall --version
|
||||||
```
|
```
|
||||||
|
|
||||||
## Your First Sandboxed Command
|
## Your First Sandboxed Command
|
||||||
|
|
||||||
By default, fence blocks all network access:
|
By default, greywall blocks all network access:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# This will fail - network is blocked
|
# This will fail - network is blocked
|
||||||
fence curl https://example.com
|
greywall curl https://example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see something like:
|
You should see something like:
|
||||||
@@ -66,7 +66,7 @@ curl: (56) CONNECT tunnel failed, response 403
|
|||||||
|
|
||||||
## Allow Specific Domains
|
## Allow Specific Domains
|
||||||
|
|
||||||
Create a config file at `~/.config/fence/fence.json` (or `~/Library/Application Support/fence/fence.json` on macOS):
|
Create a config file at `~/.config/greywall/greywall.json` (or `~/Library/Application Support/greywall/greywall.json` on macOS):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -79,7 +79,7 @@ Create a config file at `~/.config/fence/fence.json` (or `~/Library/Application
|
|||||||
Now try again:
|
Now try again:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence curl https://example.com
|
greywall curl https://example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
This time it succeeds!
|
This time it succeeds!
|
||||||
@@ -89,7 +89,7 @@ This time it succeeds!
|
|||||||
Use `-d` to see what's happening under the hood:
|
Use `-d` to see what's happening under the hood:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence -d curl https://example.com
|
greywall -d curl https://example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
This shows:
|
This shows:
|
||||||
@@ -103,7 +103,7 @@ This shows:
|
|||||||
Use `-m` to see only violations and blocked requests:
|
Use `-m` to see only violations and blocked requests:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence -m npm install
|
greywall -m npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
This is useful for:
|
This is useful for:
|
||||||
@@ -117,7 +117,7 @@ This is useful for:
|
|||||||
Use `-c` to run compound commands:
|
Use `-c` to run compound commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence -c "echo hello && ls -la"
|
greywall -c "echo hello && ls -la"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Expose Ports for Servers
|
## Expose Ports for Servers
|
||||||
@@ -125,14 +125,14 @@ fence -c "echo hello && ls -la"
|
|||||||
If you're running a server that needs to accept connections:
|
If you're running a server that needs to accept connections:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence -p 3000 -c "npm run dev"
|
greywall -p 3000 -c "npm run dev"
|
||||||
```
|
```
|
||||||
|
|
||||||
This allows external connections to port 3000 while keeping outbound network restricted.
|
This allows external connections to port 3000 while keeping outbound network restricted.
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
- Read **[Why Fence](why-fence.md)** to understand when fence is a good fit (and when it isn't).
|
- Read **[Why Greywall](why-greywall.md)** to understand when greywall is a good fit (and when it isn't).
|
||||||
- Learn the mental model in **[Concepts](concepts.md)**.
|
- Learn the mental model in **[Concepts](concepts.md)**.
|
||||||
- Use **[Troubleshooting](troubleshooting.md)** if something is blocked unexpectedly.
|
- Use **[Troubleshooting](troubleshooting.md)** if something is blocked unexpectedly.
|
||||||
- Start from copy/paste configs in **[`docs/templates/`](templates/README.md)**.
|
- Start from copy/paste configs in **[`docs/templates/`](templates/README.md)**.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Goal: make CI steps safer by default: minimal egress and controlled writes.
|
|||||||
Run:
|
Run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence --settings ./fence.json -c "make test"
|
greywall --settings ./greywall.json -c "make test"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Add only what you need
|
## Add only what you need
|
||||||
@@ -26,7 +26,7 @@ fence --settings ./fence.json -c "make test"
|
|||||||
Use monitor mode to discover what a job tries to reach:
|
Use monitor mode to discover what a job tries to reach:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence -m --settings ./fence.json -c "make test"
|
greywall -m --settings ./greywall.json -c "make test"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then allowlist only:
|
Then allowlist only:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Goal: allow fetching code from a limited set of hosts.
|
|||||||
Run:
|
Run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence --settings ./fence.json git clone https://github.com/OWNER/REPO.git
|
greywall --settings ./greywall.json git clone https://github.com/OWNER/REPO.git
|
||||||
```
|
```
|
||||||
|
|
||||||
## SSH clone
|
## SSH clone
|
||||||
@@ -28,5 +28,5 @@ SSH traffic may go through SOCKS5 (`ALL_PROXY`) depending on your git/ssh config
|
|||||||
If it fails, use monitor/debug mode to see what was blocked:
|
If it fails, use monitor/debug mode to see what was blocked:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence -m --settings ./fence.json git clone git@github.com:OWNER/REPO.git
|
greywall -m --settings ./greywall.json git clone git@github.com:OWNER/REPO.git
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Goal: allow npm to fetch packages, but block unexpected egress.
|
|||||||
Run:
|
Run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence --settings ./fence.json npm install
|
greywall --settings ./greywall.json npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Iterate with monitor mode
|
## Iterate with monitor mode
|
||||||
@@ -26,7 +26,7 @@ fence --settings ./fence.json npm install
|
|||||||
If installs fail, run:
|
If installs fail, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence -m --settings ./fence.json npm install
|
greywall -m --settings ./greywall.json npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Then add the minimum extra domains required for your workflow (private registries, GitHub tarballs, etc.).
|
Then add the minimum extra domains required for your workflow (private registries, GitHub tarballs, etc.).
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ Goal: allow Python dependency fetching while keeping egress minimal.
|
|||||||
Run:
|
Run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence --settings ./fence.json pip install -r requirements.txt
|
greywall --settings ./greywall.json pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
For Poetry:
|
For Poetry:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence --settings ./fence.json poetry install
|
greywall --settings ./greywall.json poetry install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Iterate with monitor mode
|
## Iterate with monitor mode
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence -m --settings ./fence.json poetry install
|
greywall -m --settings ./greywall.json poetry install
|
||||||
```
|
```
|
||||||
|
|
||||||
If you use private indexes, add those domains explicitly.
|
If you use private indexes, add those domains explicitly.
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
# Security Model
|
# Security Model
|
||||||
|
|
||||||
Fence is intended as defense-in-depth for running semi-trusted commands with reduced side effects (package installs, build scripts, CI jobs, unfamiliar repos).
|
Greywall is intended as defense-in-depth for running semi-trusted commands with reduced side effects (package installs, build scripts, CI jobs, unfamiliar repos).
|
||||||
|
|
||||||
It is not designed to be a strong isolation boundary against actively malicious code that is attempting to escape.
|
It is not designed to be a strong isolation boundary against actively malicious code that is attempting to escape.
|
||||||
|
|
||||||
## Threat model (what Fence helps with)
|
## Threat model (what Greywall helps with)
|
||||||
|
|
||||||
Fence is useful when you want to reduce risk from:
|
Greywall is useful when you want to reduce risk from:
|
||||||
|
|
||||||
- Supply-chain scripts that unexpectedly call out to the network
|
- Supply-chain scripts that unexpectedly call out to the network
|
||||||
- Tools that write broadly across your filesystem
|
- Tools that write broadly across your filesystem
|
||||||
- Accidental leakage of secrets via "phone home" behavior
|
- Accidental leakage of secrets via "phone home" behavior
|
||||||
- Unfamiliar repos that run surprising commands during install/build/test
|
- Unfamiliar repos that run surprising commands during install/build/test
|
||||||
|
|
||||||
## What Fence enforces
|
## What Greywall enforces
|
||||||
|
|
||||||
### Network
|
### Network
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ Important: domain filtering does not inspect content. If you allow a domain, cod
|
|||||||
|
|
||||||
#### How allowlisting works
|
#### How allowlisting works
|
||||||
|
|
||||||
Fence combines OS-level enforcement with proxy-based allowlisting:
|
Greywall combines OS-level enforcement with proxy-based allowlisting:
|
||||||
|
|
||||||
- The OS sandbox / network namespace is expected to block direct outbound connections.
|
- The OS sandbox / network namespace is expected to block direct outbound connections.
|
||||||
- Domain allowlisting happens via local HTTP/SOCKS proxies and proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`).
|
- Domain allowlisting happens via local HTTP/SOCKS proxies and proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`).
|
||||||
@@ -41,11 +41,11 @@ Localhost is separate from "external domains":
|
|||||||
- **Writes are denied by default**; you must opt in with `allowWrite`.
|
- **Writes are denied by default**; you must opt in with `allowWrite`.
|
||||||
- **denyWrite** can block specific files/patterns even if the parent directory is writable.
|
- **denyWrite** can block specific files/patterns even if the parent directory is writable.
|
||||||
- **denyRead** can block reads from sensitive paths.
|
- **denyRead** can block reads from sensitive paths.
|
||||||
- Fence includes an internal list of always-protected targets (e.g. shell configs, git hooks) to reduce common persistence vectors.
|
- Greywall includes an internal list of always-protected targets (e.g. shell configs, git hooks) to reduce common persistence vectors.
|
||||||
|
|
||||||
### Environment sanitization
|
### Environment sanitization
|
||||||
|
|
||||||
Fence strips dangerous environment variables before passing them to sandboxed commands:
|
Greywall strips dangerous environment variables before passing them to sandboxed commands:
|
||||||
|
|
||||||
- `LD_*` (Linux): `LD_PRELOAD`, `LD_LIBRARY_PATH`, etc.
|
- `LD_*` (Linux): `LD_PRELOAD`, `LD_LIBRARY_PATH`, etc.
|
||||||
- `DYLD_*` (macOS): `DYLD_INSERT_LIBRARIES`, `DYLD_LIBRARY_PATH`, etc.
|
- `DYLD_*` (macOS): `DYLD_INSERT_LIBRARIES`, `DYLD_LIBRARY_PATH`, etc.
|
||||||
@@ -57,11 +57,11 @@ This prevents a library injection attack where a sandboxed process writes a mali
|
|||||||
- `-m/--monitor` helps you discover what a command *tries* to access (blocked only).
|
- `-m/--monitor` helps you discover what a command *tries* to access (blocked only).
|
||||||
- `-d/--debug` shows more detail to understand why something was blocked.
|
- `-d/--debug` shows more detail to understand why something was blocked.
|
||||||
|
|
||||||
## Limitations (what Fence does NOT try to solve)
|
## Limitations (what Greywall does NOT try to solve)
|
||||||
|
|
||||||
- **Hostile code containment**: assume determined attackers may escape via kernel/OS vulnerabilities.
|
- **Hostile code containment**: assume determined attackers may escape via kernel/OS vulnerabilities.
|
||||||
- **Resource limits**: CPU, memory, disk, fork bombs, etc. are out of scope.
|
- **Resource limits**: CPU, memory, disk, fork bombs, etc. are out of scope.
|
||||||
- **Content-based controls**: Fence does not block data exfiltration to *allowed* destinations.
|
- **Content-based controls**: Greywall does not block data exfiltration to *allowed* destinations.
|
||||||
- **Proxy limitations / protocol edge cases**: some programs may not respect proxy environment variables, so they won't get domain allowlisting unless you configure them to use a proxy (e.g. Node.js `http`/`https` without a proxy-aware client).
|
- **Proxy limitations / protocol edge cases**: some programs may not respect proxy environment variables, so they won't get domain allowlisting unless you configure them to use a proxy (e.g. Node.js `http`/`https` without a proxy-aware client).
|
||||||
|
|
||||||
### Practical examples of proxy limitations
|
### Practical examples of proxy limitations
|
||||||
@@ -71,14 +71,14 @@ The proxy approach works well for many tools (curl, wget, git, npm, pip), but no
|
|||||||
- Node.js native `http`/`https` (use a proxy-aware client, e.g. `undici` + `ProxyAgent`)
|
- Node.js native `http`/`https` (use a proxy-aware client, e.g. `undici` + `ProxyAgent`)
|
||||||
- Raw socket connections (custom TCP/UDP protocols)
|
- Raw socket connections (custom TCP/UDP protocols)
|
||||||
|
|
||||||
Fence's OS-level sandbox is still expected to block direct outbound connections; bypassing the proxy should fail rather than silently succeeding.
|
Greywall's OS-level sandbox is still expected to block direct outbound connections; bypassing the proxy should fail rather than silently succeeding.
|
||||||
|
|
||||||
### Domain-based filtering only
|
### Domain-based filtering only
|
||||||
|
|
||||||
Fence does not inspect request content. If you allow a domain, a sandboxed process can still exfiltrate data to that domain.
|
Greywall does not inspect request content. If you allow a domain, a sandboxed process can still exfiltrate data to that domain.
|
||||||
|
|
||||||
### Not a hostile-code containment boundary
|
### Not a hostile-code containment boundary
|
||||||
|
|
||||||
Fence is defense-in-depth for running semi-trusted code, not a strong isolation boundary against malware designed to escape sandboxes.
|
Greywall is defense-in-depth for running semi-trusted code, not a strong isolation boundary against malware designed to escape sandboxes.
|
||||||
|
|
||||||
For implementation details (how proxies/sandboxes/bridges work), see [`ARCHITECTURE.md`](../ARCHITECTURE.md).
|
For implementation details (how proxies/sandboxes/bridges work), see [`ARCHITECTURE.md`](../ARCHITECTURE.md).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Config Templates
|
# Config Templates
|
||||||
|
|
||||||
Fence includes built-in config templates for common use cases. Templates are embedded in the binary, so you can use them directly without copying files.
|
Greywall includes built-in config templates for common use cases. Templates are embedded in the binary, so you can use them directly without copying files.
|
||||||
|
|
||||||
## Using templates
|
## Using templates
|
||||||
|
|
||||||
@@ -8,13 +8,13 @@ Use the `-t` / `--template` flag to apply a template:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Use a built-in template
|
# Use a built-in template
|
||||||
fence -t npm-install npm install
|
greywall -t npm-install npm install
|
||||||
|
|
||||||
# Wraps Claude Code
|
# Wraps Claude Code
|
||||||
fence -t code -- claude
|
greywall -t code -- claude
|
||||||
|
|
||||||
# List available templates
|
# List available templates
|
||||||
fence --list-templates
|
greywall --list-templates
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also copy and customize templates from [`internal/templates/`](/internal/templates/).
|
You can also copy and customize templates from [`internal/templates/`](/internal/templates/).
|
||||||
|
|||||||
@@ -67,9 +67,9 @@ go test -v -count=1 ./internal/sandbox/...
|
|||||||
|
|
||||||
#### Sandboxed Build Environments (Nix, etc.)
|
#### Sandboxed Build Environments (Nix, etc.)
|
||||||
|
|
||||||
If you're packaging fence for a distribution (e.g., Nix, Homebrew, Debian), note that some integration tests will be skipped when running `go test` during the build.
|
If you're packaging greywall for a distribution (e.g., Nix, Homebrew, Debian), note that some integration tests will be skipped when running `go test` during the build.
|
||||||
|
|
||||||
Fence's Landlock integration on Linux uses a wrapper approach: the `fence` binary re-executes itself with `--landlock-apply` inside the sandbox. Test binaries (e.g., `sandbox.test`) don't have this handler, so Landlock-specific tests automatically skip when not running as the `fence` CLI.
|
Greywall's Landlock integration on Linux uses a wrapper approach: the `greywall` binary re-executes itself with `--landlock-apply` inside the sandbox. Test binaries (e.g., `sandbox.test`) don't have this handler, so Landlock-specific tests automatically skip when not running as the `greywall` CLI.
|
||||||
|
|
||||||
Tests that skip include those calling `skipIfLandlockNotUsable()`:
|
Tests that skip include those calling `skipIfLandlockNotUsable()`:
|
||||||
|
|
||||||
@@ -77,25 +77,25 @@ Tests that skip include those calling `skipIfLandlockNotUsable()`:
|
|||||||
- `TestLinux_LandlockProtectsGitHooks`
|
- `TestLinux_LandlockProtectsGitHooks`
|
||||||
- `TestLinux_LandlockProtectsGitConfig`
|
- `TestLinux_LandlockProtectsGitConfig`
|
||||||
- `TestLinux_LandlockProtectsBashrc`
|
- `TestLinux_LandlockProtectsBashrc`
|
||||||
- `TestLinux_LandlockAllowsTmpFence`
|
- `TestLinux_LandlockAllowsTmpGreywall`
|
||||||
- `TestLinux_PathTraversalBlocked`
|
- `TestLinux_PathTraversalBlocked`
|
||||||
- `TestLinux_SeccompBlocksDangerousSyscalls`
|
- `TestLinux_SeccompBlocksDangerousSyscalls`
|
||||||
|
|
||||||
| Test Type | What it tests | Landlock coverage |
|
| Test Type | What it tests | Landlock coverage |
|
||||||
|-----------|---------------|-------------------|
|
|-----------|---------------|-------------------|
|
||||||
| `go test` (integration) | Go APIs, bwrap isolation, command blocking | Skipped (test binary can't use `--landlock-apply`) |
|
| `go test` (integration) | Go APIs, bwrap isolation, command blocking | Skipped (test binary can't use `--landlock-apply`) |
|
||||||
| `smoke_test.sh` | Actual `fence` CLI end-to-end | ✅ Full coverage |
|
| `smoke_test.sh` | Actual `greywall` CLI end-to-end | ✅ Full coverage |
|
||||||
|
|
||||||
For full test coverage including Landlock, run the smoke tests against the built binary (see "Smoke Tests" section below).
|
For full test coverage including Landlock, run the smoke tests against the built binary (see "Smoke Tests" section below).
|
||||||
|
|
||||||
**Nested sandboxing limitations:**
|
**Nested sandboxing limitations:**
|
||||||
|
|
||||||
- **macOS**: Nested Seatbelt sandboxing is not supported. If the build environment already uses `sandbox-exec` (like Nix's Darwin sandbox), fence's tests cannot create another sandbox. The kernel returns `forbidden-sandbox-reinit`. This is a macOS limitation.
|
- **macOS**: Nested Seatbelt sandboxing is not supported. If the build environment already uses `sandbox-exec` (like Nix's Darwin sandbox), greywall's tests cannot create another sandbox. The kernel returns `forbidden-sandbox-reinit`. This is a macOS limitation.
|
||||||
- **Linux**: Tests should work in most build sandboxes, but Landlock tests will skip as explained above. Runtime functionality is unaffected.
|
- **Linux**: Tests should work in most build sandboxes, but Landlock tests will skip as explained above. Runtime functionality is unaffected.
|
||||||
|
|
||||||
### Smoke Tests
|
### Smoke Tests
|
||||||
|
|
||||||
Smoke tests verify the compiled `fence` binary works end-to-end. Unlike integration tests (which test internal Go APIs), smoke tests exercise the CLI interface.
|
Smoke tests verify the compiled `greywall` binary works end-to-end. Unlike integration tests (which test internal Go APIs), smoke tests exercise the CLI interface.
|
||||||
|
|
||||||
**File:** [`scripts/smoke_test.sh`](/scripts/smoke_test.sh)
|
**File:** [`scripts/smoke_test.sh`](/scripts/smoke_test.sh)
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ Smoke tests verify the compiled `fence` binary works end-to-end. Unlike integrat
|
|||||||
- Filesystem restrictions via settings file
|
- Filesystem restrictions via settings file
|
||||||
- Command blocking via settings file
|
- Command blocking via settings file
|
||||||
- Network blocking
|
- Network blocking
|
||||||
- Environment variable injection (FENCE_SANDBOX, HTTP_PROXY)
|
- Environment variable injection (GREYWALL_SANDBOX, HTTP_PROXY)
|
||||||
- Tool compatibility (python3, node, git, rg) - ensure that frequently used tools don't break in sandbox
|
- Tool compatibility (python3, node, git, rg) - ensure that frequently used tools don't break in sandbox
|
||||||
|
|
||||||
**Run:**
|
**Run:**
|
||||||
@@ -115,10 +115,10 @@ Smoke tests verify the compiled `fence` binary works end-to-end. Unlike integrat
|
|||||||
./scripts/smoke_test.sh
|
./scripts/smoke_test.sh
|
||||||
|
|
||||||
# Test specific binary
|
# Test specific binary
|
||||||
./scripts/smoke_test.sh ./path/to/fence
|
./scripts/smoke_test.sh ./path/to/greywall
|
||||||
|
|
||||||
# Enable network tests (requires internet)
|
# Enable network tests (requires internet)
|
||||||
FENCE_TEST_NETWORK=1 ./scripts/smoke_test.sh
|
GREYWALL_TEST_NETWORK=1 ./scripts/smoke_test.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Platform-Specific Behavior
|
## Platform-Specific Behavior
|
||||||
@@ -158,7 +158,7 @@ The `integration_test.go` file provides helpers for writing sandbox tests:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
// Skip helpers
|
// Skip helpers
|
||||||
skipIfAlreadySandboxed(t) // Skip if running inside Fence
|
skipIfAlreadySandboxed(t) // Skip if running inside Greywall
|
||||||
skipIfCommandNotFound(t, "python3") // Skip if command missing
|
skipIfCommandNotFound(t, "python3") // Skip if command missing
|
||||||
|
|
||||||
// Run a command under the sandbox
|
// Run a command under the sandbox
|
||||||
@@ -237,10 +237,10 @@ go test -v -run TestSpecificTest ./internal/sandbox/...
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Replicate what the test does
|
# Replicate what the test does
|
||||||
./fence -c "the-command-that-failed"
|
./greywall -c "the-command-that-failed"
|
||||||
|
|
||||||
# With a settings file
|
# With a settings file
|
||||||
./fence -s /path/to/settings.json -c "command"
|
./greywall -s /path/to/settings.json -c "command"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Check platform capabilities
|
### Check platform capabilities
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
## Nested Sandboxing Not Supported
|
## Nested Sandboxing Not Supported
|
||||||
|
|
||||||
Fence cannot run inside another sandbox that uses the same underlying technology.
|
Greywall cannot run inside another sandbox that uses the same underlying technology.
|
||||||
|
|
||||||
**macOS (Seatbelt)**: If you try to run fence inside an existing `sandbox-exec` sandbox (e.g., Nix's Darwin build sandbox), you'll see:
|
**macOS (Seatbelt)**: If you try to run greywall inside an existing `sandbox-exec` sandbox (e.g., Nix's Darwin build sandbox), you'll see:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Sandbox: sandbox-exec(...) deny(1) forbidden-sandbox-reinit
|
Sandbox: sandbox-exec(...) deny(1) forbidden-sandbox-reinit
|
||||||
@@ -12,11 +12,11 @@ Sandbox: sandbox-exec(...) deny(1) forbidden-sandbox-reinit
|
|||||||
|
|
||||||
This is a macOS kernel limitation - nested Seatbelt sandboxes are not allowed. There is no workaround.
|
This is a macOS kernel limitation - nested Seatbelt sandboxes are not allowed. There is no workaround.
|
||||||
|
|
||||||
**Linux (Landlock)**: Landlock supports stacking (nested restrictions), but fence's test binaries cannot use the Landlock wrapper (see [Testing docs](testing.md#sandboxed-build-environments-nix-etc)).
|
**Linux (Landlock)**: Landlock supports stacking (nested restrictions), but greywall's test binaries cannot use the Landlock wrapper (see [Testing docs](testing.md#sandboxed-build-environments-nix-etc)).
|
||||||
|
|
||||||
## "bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted" (Linux)
|
## "bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted" (Linux)
|
||||||
|
|
||||||
This error occurs when fence tries to create a network namespace but the environment lacks the `CAP_NET_ADMIN` capability. This is common in:
|
This error occurs when greywall tries to create a network namespace but the environment lacks the `CAP_NET_ADMIN` capability. This is common in:
|
||||||
|
|
||||||
- **Docker containers** (unless run with `--privileged` or `--cap-add=NET_ADMIN`)
|
- **Docker containers** (unless run with `--privileged` or `--cap-add=NET_ADMIN`)
|
||||||
- **GitHub Actions** and other CI runners
|
- **GitHub Actions** and other CI runners
|
||||||
@@ -25,7 +25,7 @@ This error occurs when fence tries to create a network namespace but the environ
|
|||||||
|
|
||||||
**What happens now:**
|
**What happens now:**
|
||||||
|
|
||||||
Fence automatically detects this limitation and falls back to running **without network namespace isolation**. The sandbox still provides:
|
Greywall automatically detects this limitation and falls back to running **without network namespace isolation**. The sandbox still provides:
|
||||||
|
|
||||||
- Filesystem restrictions (read-only root, allowWrite paths)
|
- Filesystem restrictions (read-only root, allowWrite paths)
|
||||||
- PID namespace isolation
|
- PID namespace isolation
|
||||||
@@ -41,7 +41,7 @@ Fence automatically detects this limitation and falls back to running **without
|
|||||||
**To check if your environment supports network namespaces:**
|
**To check if your environment supports network namespaces:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence --linux-features
|
greywall --linux-features
|
||||||
```
|
```
|
||||||
|
|
||||||
Look for "Network namespace (--unshare-net): true/false"
|
Look for "Network namespace (--unshare-net): true/false"
|
||||||
@@ -51,7 +51,7 @@ Look for "Network namespace (--unshare-net): true/false"
|
|||||||
1. **Run with elevated privileges:**
|
1. **Run with elevated privileges:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo fence <command>
|
sudo greywall <command>
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **In Docker, add capability:**
|
2. **In Docker, add capability:**
|
||||||
@@ -96,20 +96,20 @@ On most systems with package-manager-installed bwrap, this error shouldn't occur
|
|||||||
This usually means:
|
This usually means:
|
||||||
|
|
||||||
- the process tried to reach a domain that is **not allowed**, and
|
- the process tried to reach a domain that is **not allowed**, and
|
||||||
- the request went through fence's HTTP proxy, which returned `403`.
|
- the request went through greywall's HTTP proxy, which returned `403`.
|
||||||
|
|
||||||
Fix:
|
Fix:
|
||||||
|
|
||||||
- Run with monitor mode to see what was blocked:
|
- Run with monitor mode to see what was blocked:
|
||||||
- `fence -m <command>`
|
- `greywall -m <command>`
|
||||||
- Add the required destination(s) to `network.allowedDomains`.
|
- Add the required destination(s) to `network.allowedDomains`.
|
||||||
|
|
||||||
## "It works outside fence but not inside"
|
## "It works outside greywall but not inside"
|
||||||
|
|
||||||
Start with:
|
Start with:
|
||||||
|
|
||||||
- `fence -m <command>` to see what's being denied
|
- `greywall -m <command>` to see what's being denied
|
||||||
- `fence -d <command>` to see full proxy and sandbox detail
|
- `greywall -d <command>` to see full proxy and sandbox detail
|
||||||
|
|
||||||
Common causes:
|
Common causes:
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ const response = await fetch(url, {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Fence's OS-level sandbox should still block direct connections; the above makes your requests go through the filtering proxy so allowlisting works as intended.
|
Greywall's OS-level sandbox should still block direct connections; the above makes your requests go through the filtering proxy so allowlisting works as intended.
|
||||||
|
|
||||||
## Local services (Redis/Postgres/etc.) fail inside the sandbox
|
## Local services (Redis/Postgres/etc.) fail inside the sandbox
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ If you're running a server inside the sandbox that must accept connections:
|
|||||||
Writes are denied by default.
|
Writes are denied by default.
|
||||||
|
|
||||||
- Add the minimum required writable directories to `filesystem.allowWrite`.
|
- Add the minimum required writable directories to `filesystem.allowWrite`.
|
||||||
- Protect sensitive targets with `filesystem.denyWrite` (and note fence protects some targets regardless).
|
- Protect sensitive targets with `filesystem.denyWrite` (and note greywall protects some targets regardless).
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Why Fence?
|
# Why Greywall?
|
||||||
|
|
||||||
Fence exists to reduce the blast radius of running commands you don't fully trust (or don't fully understand yet).
|
Greywall exists to reduce the blast radius of running commands you don't fully trust (or don't fully understand yet).
|
||||||
|
|
||||||
Common situations:
|
Common situations:
|
||||||
|
|
||||||
@@ -9,11 +9,11 @@ Common situations:
|
|||||||
- Running CI jobs where you want default-deny egress and tightly scoped writes
|
- Running CI jobs where you want default-deny egress and tightly scoped writes
|
||||||
- Auditing what a command *tries* to do before you let it do it
|
- Auditing what a command *tries* to do before you let it do it
|
||||||
|
|
||||||
Fence is intentionally simple: it focuses on network allowlisting (by domain) and filesystem write restrictions (by path), wrapped in a pragmatic OS sandbox (macOS `sandbox-exec`, Linux `bubblewrap`).
|
Greywall is intentionally simple: it focuses on network allowlisting (by domain) and filesystem write restrictions (by path), wrapped in a pragmatic OS sandbox (macOS `sandbox-exec`, Linux `bubblewrap`).
|
||||||
|
|
||||||
## What problem does it solve?
|
## What problem does it solve?
|
||||||
|
|
||||||
Fence helps you answer: "What can this command touch?"
|
Greywall helps you answer: "What can this command touch?"
|
||||||
|
|
||||||
- **Network**: block all outbound by default; then allow only the domains you choose.
|
- **Network**: block all outbound by default; then allow only the domains you choose.
|
||||||
- **Filesystem**: default-deny writes; then allow writes only where you choose (and deny sensitive writes regardless).
|
- **Filesystem**: default-deny writes; then allow writes only where you choose (and deny sensitive writes regardless).
|
||||||
@@ -21,9 +21,9 @@ Fence helps you answer: "What can this command touch?"
|
|||||||
|
|
||||||
This is especially useful for supply-chain risk and "unknown repo" workflows where you want a safer default than "run it and hope".
|
This is especially useful for supply-chain risk and "unknown repo" workflows where you want a safer default than "run it and hope".
|
||||||
|
|
||||||
## When Fence is useful even if tools already sandbox
|
## When Greywall is useful even if tools already sandbox
|
||||||
|
|
||||||
Some coding agents and platforms ship sandboxing (Seatbelt/Landlock/etc.). Fence still provides value when you want:
|
Some coding agents and platforms ship sandboxing (Seatbelt/Landlock/etc.). Greywall still provides value when you want:
|
||||||
|
|
||||||
- **Tool-agnostic policy**: apply the same rules to any command, not only inside one agent.
|
- **Tool-agnostic policy**: apply the same rules to any command, not only inside one agent.
|
||||||
- **Standardization**: commit/review a config once, use it across developers and CI.
|
- **Standardization**: commit/review a config once, use it across developers and CI.
|
||||||
@@ -32,7 +32,7 @@ Some coding agents and platforms ship sandboxing (Seatbelt/Landlock/etc.). Fence
|
|||||||
|
|
||||||
## Non-goals
|
## Non-goals
|
||||||
|
|
||||||
Fence is **not** a hardened containment boundary for actively malicious code.
|
Greywall is **not** a hardened containment boundary for actively malicious code.
|
||||||
|
|
||||||
- It does **not** attempt to prevent resource exhaustion (CPU/RAM/disk), timing attacks, or kernel-level escapes.
|
- It does **not** attempt to prevent resource exhaustion (CPU/RAM/disk), timing attacks, or kernel-level escapes.
|
||||||
- Domain allowlisting is not content inspection: if you allow a domain, code can exfiltrate via that domain.
|
- Domain allowlisting is not content inspection: if you allow a domain, code can exfiltrate via that domain.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Dev Server + Redis Demo
|
# Dev Server + Redis Demo
|
||||||
|
|
||||||
This demo shows how fence controls network access: allowing specific external domains while blocking (or allowing) localhost connections.
|
This demo shows how greywall controls network access: allowing specific external domains while blocking (or allowing) localhost connections.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ npm install
|
|||||||
This shows that requests to Redis (local service) works, but external requests are blocked.
|
This shows that requests to Redis (local service) works, but external requests are blocked.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence -p 3000 --settings fence-external-blocked.json npm start
|
greywall -p 3000 --settings greywall-external-blocked.json npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
Test it:
|
Test it:
|
||||||
@@ -39,7 +39,7 @@ curl http://localhost:3000/api/external
|
|||||||
This shows the opposite: whitelisted external domains work, but Redis (localhost) is blocked.
|
This shows the opposite: whitelisted external domains work, but Redis (localhost) is blocked.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence -p 3000 --settings fence-external-only.json npm start
|
greywall -p 3000 --settings greywall-external-only.json npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
You will immediately notice that Redis connection is blocked on app startup:
|
You will immediately notice that Redis connection is blocked on app startup:
|
||||||
@@ -62,8 +62,8 @@ curl http://localhost:3000/api/users
|
|||||||
|
|
||||||
| Config | Redis (localhost) | External (httpbin.org) |
|
| Config | Redis (localhost) | External (httpbin.org) |
|
||||||
|--------|-------------------|------------------------|
|
|--------|-------------------|------------------------|
|
||||||
| `fence-external-blocked.json` | ✓ Allowed | ✗ Blocked |
|
| `greywall-external-blocked.json` | ✓ Allowed | ✗ Blocked |
|
||||||
| `fence-external-only.json` | ✗ Blocked | ✓ Allowed |
|
| `greywall-external-only.json` | ✗ Blocked | ✓ Allowed |
|
||||||
|
|
||||||
## Key Settings
|
## Key Settings
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ curl http://localhost:3000/api/users
|
|||||||
|
|
||||||
## Note: Node.js Proxy Support
|
## Note: Node.js Proxy Support
|
||||||
|
|
||||||
Node.js's native `http`/`https` modules don't respect proxy environment variables. This demo uses [`undici`](https://github.com/nodejs/undici) with `ProxyAgent` to route requests through fence's proxy:
|
Node.js's native `http`/`https` modules don't respect proxy environment variables. This demo uses [`undici`](https://github.com/nodejs/undici) with `ProxyAgent` to route requests through greywall's proxy:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { ProxyAgent, fetch } from "undici";
|
import { ProxyAgent, fetch } from "undici";
|
||||||
@@ -86,4 +86,4 @@ const response = await fetch(url, {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Without this, external HTTP requests would fail with connection errors (the sandbox blocks them) rather than going through fence's proxy.
|
Without this, external HTTP requests would fail with connection errors (the sandbox blocks them) rather than going through greywall's proxy.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Demo Express app that:
|
* Demo Express app that:
|
||||||
* 1. Serves an API on port 3000
|
* 1. Serves an API on port 3000
|
||||||
* 2. Connects to Redis on localhost:6379
|
* 2. Connects to Redis on localhost:6379
|
||||||
* 3. Attempts to call external APIs (blocked by fence)
|
* 3. Attempts to call external APIs (blocked by greywall)
|
||||||
*
|
*
|
||||||
* This demonstrates allowLocalOutbound - the app can reach
|
* This demonstrates allowLocalOutbound - the app can reach
|
||||||
* local services (Redis) but not the external internet.
|
* local services (Redis) but not the external internet.
|
||||||
@@ -60,7 +60,7 @@ async function fetchExternal(url) {
|
|||||||
signal: AbortSignal.timeout(5000),
|
signal: AbortSignal.timeout(5000),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use proxy if available (set by fence)
|
// Use proxy if available (set by greywall)
|
||||||
if (proxyUrl) {
|
if (proxyUrl) {
|
||||||
options.dispatcher = new ProxyAgent(proxyUrl);
|
options.dispatcher = new ProxyAgent(proxyUrl);
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ app.get("/", (req, res) => {
|
|||||||
"/api/users": "List all users from Redis",
|
"/api/users": "List all users from Redis",
|
||||||
"/api/users/:id": "Get user by ID from Redis",
|
"/api/users/:id": "Get user by ID from Redis",
|
||||||
"/api/health": "Health check",
|
"/api/health": "Health check",
|
||||||
"/api/external": "Try to call external API (blocked by fence)",
|
"/api/external": "Try to call external API (blocked by greywall)",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -160,20 +160,20 @@ app.get("/api/external", async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await fetchExternal("https://httpbin.org/get");
|
const result = await fetchExternal("https://httpbin.org/get");
|
||||||
// Check if we're using a proxy (indicates fence is running)
|
// Check if we're using a proxy (indicates greywall is running)
|
||||||
const usingProxy = !!(process.env.HTTPS_PROXY || process.env.HTTP_PROXY);
|
const usingProxy = !!(process.env.HTTPS_PROXY || process.env.HTTP_PROXY);
|
||||||
res.json({
|
res.json({
|
||||||
status: "success",
|
status: "success",
|
||||||
message: usingProxy
|
message: usingProxy
|
||||||
? "✓ Request allowed (httpbin.org is whitelisted)"
|
? "✓ Request allowed (httpbin.org is whitelisted)"
|
||||||
: "⚠️ No proxy detected - not running in fence",
|
: "⚠️ No proxy detected - not running in greywall",
|
||||||
proxy: usingProxy ? process.env.HTTPS_PROXY : null,
|
proxy: usingProxy ? process.env.HTTPS_PROXY : null,
|
||||||
data: result,
|
data: result,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.json({
|
res.json({
|
||||||
status: "blocked",
|
status: "blocked",
|
||||||
message: "✓ External call blocked by fence",
|
message: "✓ External call blocked by greywall",
|
||||||
error: error.message,
|
error: error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "dev-server-demo",
|
"name": "dev-server-demo",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Demo: Dev server with Redis in fence sandbox",
|
"description": "Demo: Dev server with Redis in greywall sandbox",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Filesystem Sandbox Demo
|
# Filesystem Sandbox Demo
|
||||||
|
|
||||||
This demo shows how fence controls filesystem access with `allowWrite`, `denyWrite`, and `denyRead`.
|
This demo shows how greywall controls filesystem access with `allowWrite`, `denyWrite`, and `denyRead`.
|
||||||
|
|
||||||
## What it demonstrates
|
## What it demonstrates
|
||||||
|
|
||||||
| Operation | Without Fence | With Fence |
|
| Operation | Without Greywall | With Greywall |
|
||||||
|-----------|---------------|------------|
|
|-----------|---------------|------------|
|
||||||
| Write to `./output/` | ✓ | ✓ (in allowWrite) |
|
| Write to `./output/` | ✓ | ✓ (in allowWrite) |
|
||||||
| Write to `./` | ✓ | ✗ (not in allowWrite) |
|
| Write to `./` | ✓ | ✗ (not in allowWrite) |
|
||||||
@@ -16,19 +16,19 @@ This demo shows how fence controls filesystem access with `allowWrite`, `denyWri
|
|||||||
|
|
||||||
## Run the demo
|
## Run the demo
|
||||||
|
|
||||||
### Without fence (all writes succeed)
|
### Without greywall (all writes succeed)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python demo.py
|
python demo.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### With fence (unauthorized operations blocked)
|
### With greywall (unauthorized operations blocked)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fence --settings fence.json python demo.py
|
greywall --settings greywall.json python demo.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fence config
|
## Greywall config
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -58,7 +58,7 @@ fence --settings fence.json python demo.py
|
|||||||
|
|
||||||
## Protected paths
|
## Protected paths
|
||||||
|
|
||||||
Fence also automatically protects certain paths regardless of config:
|
Greywall also automatically protects certain paths regardless of config:
|
||||||
|
|
||||||
- Shell configs: `.bashrc`, `.zshrc`, `.profile`
|
- Shell configs: `.bashrc`, `.zshrc`, `.profile`
|
||||||
- Git hooks: `.git/hooks/*`
|
- Git hooks: `.git/hooks/*`
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
"""
|
"""
|
||||||
Filesystem Sandbox Demo
|
Filesystem Sandbox Demo
|
||||||
|
|
||||||
This script demonstrates fence's filesystem controls:
|
This script demonstrates greywall's filesystem controls:
|
||||||
- allowWrite: Only specific directories are writable
|
- allowWrite: Only specific directories are writable
|
||||||
- denyWrite: Block writes to sensitive files
|
- denyWrite: Block writes to sensitive files
|
||||||
- denyRead: Block reads from sensitive paths
|
- denyRead: Block reads from sensitive paths
|
||||||
|
|
||||||
Run WITHOUT fence to see all operations succeed.
|
Run WITHOUT greywall to see all operations succeed.
|
||||||
Run WITH fence to see unauthorized operations blocked.
|
Run WITH greywall to see unauthorized operations blocked.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -78,7 +78,7 @@ def main():
|
|||||||
╔═══════════════════════════════════════════════════════════╗
|
╔═══════════════════════════════════════════════════════════╗
|
||||||
║ Filesystem Sandbox Demo ║
|
║ Filesystem Sandbox Demo ║
|
||||||
╠═══════════════════════════════════════════════════════════╣
|
╠═══════════════════════════════════════════════════════════╣
|
||||||
║ Tests fence's filesystem controls: ║
|
║ Tests greywall's filesystem controls: ║
|
||||||
║ - allowWrite: Only ./output/ is writable ║
|
║ - allowWrite: Only ./output/ is writable ║
|
||||||
║ - denyWrite: .env and *.key files are protected ║
|
║ - denyWrite: .env and *.key files are protected ║
|
||||||
║ - denyRead: /etc/shadow is blocked ║
|
║ - denyRead: /etc/shadow is blocked ║
|
||||||
@@ -96,7 +96,7 @@ def main():
|
|||||||
"Write to ./output/ (allowed)",
|
"Write to ./output/ (allowed)",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test 2: Write to project root (should fail with fence)
|
# Test 2: Write to project root (should fail with greywall)
|
||||||
try_write(
|
try_write(
|
||||||
"unauthorized.txt",
|
"unauthorized.txt",
|
||||||
"This should not be writable.\n",
|
"This should not be writable.\n",
|
||||||
@@ -133,12 +133,12 @@ def main():
|
|||||||
print(f"({skipped} test(s) skipped - file not found)")
|
print(f"({skipped} test(s) skipped - file not found)")
|
||||||
|
|
||||||
if blocked > 0:
|
if blocked > 0:
|
||||||
print(f"✅ Fence blocked {blocked} unauthorized operation(s)")
|
print(f"✅ Greywall blocked {blocked} unauthorized operation(s)")
|
||||||
print(f"{succeeded} allowed operation(s) succeeded")
|
print(f"{succeeded} allowed operation(s) succeeded")
|
||||||
print("\nFilesystem sandbox is working!\n")
|
print("\nFilesystem sandbox is working!\n")
|
||||||
else:
|
else:
|
||||||
print("⚠️ All operations succeeded - you are likely not running in fence")
|
print("⚠️ All operations succeeded - you are likely not running in greywall")
|
||||||
print("Run with: fence --settings fence.json python demo.py\n")
|
print("Run with: greywall --settings greywall.json python demo.py\n")
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Fence Examples
|
# Greywall Examples
|
||||||
|
|
||||||
Runnable examples demonstrating `fence` capabilities.
|
Runnable examples demonstrating `greywall` capabilities.
|
||||||
|
|
||||||
If you're looking for copy/paste configs and "cookbook" workflows, also see:
|
If you're looking for copy/paste configs and "cookbook" workflows, also see:
|
||||||
|
|
||||||
@@ -11,5 +11,5 @@ If you're looking for copy/paste configs and "cookbook" workflows, also see:
|
|||||||
|
|
||||||
| Example | What it demonstrates | How to run |
|
| Example | What it demonstrates | How to run |
|
||||||
|--------|-----------------------|------------|
|
|--------|-----------------------|------------|
|
||||||
| **[01-dev-server](01-dev-server/README.md)** | Running a dev server in the sandbox, controlling external domains vs localhost outbound (Redis), and exposing an inbound port (`-p`) | `cd examples/01-dev-server && fence -p 3000 --settings fence-external-blocked.json npm start` |
|
| **[01-dev-server](01-dev-server/README.md)** | Running a dev server in the sandbox, controlling external domains vs localhost outbound (Redis), and exposing an inbound port (`-p`) | `cd examples/01-dev-server && greywall -p 3000 --settings greywall-external-blocked.json npm start` |
|
||||||
| **[02-filesystem](02-filesystem/README.md)** | Filesystem controls: `allowWrite`, `denyWrite`, `denyRead` | `cd examples/02-filesystem && fence --settings fence.json python demo.py` |
|
| **[02-filesystem](02-filesystem/README.md)** | Filesystem controls: `allowWrite`, `denyWrite`, `denyRead` | `cd examples/02-filesystem && greywall --settings greywall.json python demo.py` |
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,4 +1,4 @@
|
|||||||
module github.com/Use-Tusk/fence
|
module gitea.app.monadical.io/monadical/greywall
|
||||||
|
|
||||||
go 1.25
|
go 1.25
|
||||||
|
|
||||||
|
|||||||
28
install.sh
28
install.sh
@@ -1,17 +1,17 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Fence Installer (Linux/macOS only)
|
# Greywall Installer (Linux/macOS only)
|
||||||
# For Windows, we recommend using WSL.
|
# For Windows, we recommend using WSL.
|
||||||
# Usage (latest):
|
# Usage (latest):
|
||||||
# curl -fsSL https://raw.githubusercontent.com/Use-Tusk/fence/main/install.sh | sh
|
# curl -fsSL https://gitea.app.monadical.io/monadical/greywall/raw/branch/main/install.sh | sh
|
||||||
# Usage (specific version):
|
# Usage (specific version):
|
||||||
# curl -fsSL https://raw.githubusercontent.com/Use-Tusk/fence/main/install.sh | sh -s -- v0.1.0
|
# curl -fsSL https://gitea.app.monadical.io/monadical/greywall/raw/branch/main/install.sh | sh -s -- v0.1.0
|
||||||
# Or via env var:
|
# Or via env var:
|
||||||
# curl -fsSL https://raw.githubusercontent.com/Use-Tusk/fence/main/install.sh | FENCE_VERSION=0.1.0 sh
|
# curl -fsSL https://gitea.app.monadical.io/monadical/greywall/raw/branch/main/install.sh | GREYWALL_VERSION=0.1.0 sh
|
||||||
|
|
||||||
REPO="Use-Tusk/fence"
|
REPO="monadical/greywall"
|
||||||
BINARY_NAME="fence"
|
BINARY_NAME="greywall"
|
||||||
|
|
||||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
@@ -39,8 +39,8 @@ case "$ARCH" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Determine version to install: use first arg or FENCE_VERSION env var; otherwise fallback to latest
|
# Determine version to install: use first arg or GREYWALL_VERSION env var; otherwise fallback to latest
|
||||||
REQUESTED_VERSION="${1:-${FENCE_VERSION:-}}"
|
REQUESTED_VERSION="${1:-${GREYWALL_VERSION:-}}"
|
||||||
if [ -n "$REQUESTED_VERSION" ]; then
|
if [ -n "$REQUESTED_VERSION" ]; then
|
||||||
case "$REQUESTED_VERSION" in
|
case "$REQUESTED_VERSION" in
|
||||||
v*) VERSION_TAG="$REQUESTED_VERSION" ;;
|
v*) VERSION_TAG="$REQUESTED_VERSION" ;;
|
||||||
@@ -48,11 +48,11 @@ if [ -n "$REQUESTED_VERSION" ]; then
|
|||||||
esac
|
esac
|
||||||
else
|
else
|
||||||
# Try manifest first (fast, no rate limits)
|
# Try manifest first (fast, no rate limits)
|
||||||
VERSION_TAG=$(curl -sL "https://use-tusk.github.io/fence/latest.txt" 2>/dev/null || echo "")
|
VERSION_TAG=$(curl -sL "https://gitea.app.monadical.io/monadical/greywall/latest.txt" 2>/dev/null || echo "")
|
||||||
|
|
||||||
# Fallback to GitHub API if manifest fails
|
# Fallback to GitHub API if manifest fails
|
||||||
if [ -z "$VERSION_TAG" ]; then
|
if [ -z "$VERSION_TAG" ]; then
|
||||||
VERSION_TAG=$(curl -s "https://api.github.com/repos/$REPO/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
VERSION_TAG=$(curl -s "https://gitea.app.monadical.io/api/v1/repos/$REPO/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -75,12 +75,12 @@ TMP_DIR=$(mktemp -d)
|
|||||||
cd "$TMP_DIR"
|
cd "$TMP_DIR"
|
||||||
|
|
||||||
echo "Downloading from $DOWNLOAD_URL..."
|
echo "Downloading from $DOWNLOAD_URL..."
|
||||||
if ! curl -fsSL -o fence.tar.gz "$DOWNLOAD_URL"; then
|
if ! curl -fsSL -o greywall.tar.gz "$DOWNLOAD_URL"; then
|
||||||
echo "Error: Failed to download release"
|
echo "Error: Failed to download release"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tar -xzf fence.tar.gz
|
tar -xzf greywall.tar.gz
|
||||||
|
|
||||||
# Install to /usr/local/bin or ~/.local/bin
|
# Install to /usr/local/bin or ~/.local/bin
|
||||||
INSTALL_DIR="/usr/local/bin"
|
INSTALL_DIR="/usr/local/bin"
|
||||||
@@ -97,9 +97,9 @@ chmod +x "$INSTALL_DIR/$BINARY_NAME"
|
|||||||
cd - > /dev/null
|
cd - > /dev/null
|
||||||
rm -rf "$TMP_DIR"
|
rm -rf "$TMP_DIR"
|
||||||
|
|
||||||
echo "Fence $VERSION_TAG installed successfully!"
|
echo "Greywall $VERSION_TAG installed successfully!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Run 'fence --help' to get started."
|
echo "Run 'greywall --help' to get started."
|
||||||
|
|
||||||
# Check if install dir is in PATH
|
# Check if install dir is in PATH
|
||||||
case ":$PATH:" in
|
case ":$PATH:" in
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Package config defines the configuration types and loading for fence.
|
// Package config defines the configuration types and loading for greywall.
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/tidwall/jsonc"
|
"github.com/tidwall/jsonc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is the main configuration for fence.
|
// Config is the main configuration for greywall.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Extends string `json:"extends,omitempty"`
|
Extends string `json:"extends,omitempty"`
|
||||||
Network NetworkConfig `json:"network"`
|
Network NetworkConfig `json:"network"`
|
||||||
@@ -130,12 +130,12 @@ func Default() *Config {
|
|||||||
|
|
||||||
// DefaultConfigPath returns the default config file path.
|
// DefaultConfigPath returns the default config file path.
|
||||||
// Uses the OS-preferred config directory (XDG on Linux, ~/Library/Application Support on macOS).
|
// Uses the OS-preferred config directory (XDG on Linux, ~/Library/Application Support on macOS).
|
||||||
// Falls back to ~/.fence.json if the new location doesn't exist but the legacy one does.
|
// Falls back to ~/.greywall.json if the new location doesn't exist but the legacy one does.
|
||||||
func DefaultConfigPath() string {
|
func DefaultConfigPath() string {
|
||||||
// Try OS-preferred config directory first
|
// Try OS-preferred config directory first
|
||||||
configDir, err := os.UserConfigDir()
|
configDir, err := os.UserConfigDir()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
newPath := filepath.Join(configDir, "fence", "fence.json")
|
newPath := filepath.Join(configDir, "greywall", "greywall.json")
|
||||||
if _, err := os.Stat(newPath); err == nil {
|
if _, err := os.Stat(newPath); err == nil {
|
||||||
return newPath
|
return newPath
|
||||||
}
|
}
|
||||||
@@ -149,18 +149,18 @@ func DefaultConfigPath() string {
|
|||||||
// Fall back to legacy path if it exists
|
// Fall back to legacy path if it exists
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "fence.json"
|
return "greywall.json"
|
||||||
}
|
}
|
||||||
legacyPath := filepath.Join(home, ".fence.json")
|
legacyPath := filepath.Join(home, ".greywall.json")
|
||||||
if _, err := os.Stat(legacyPath); err == nil {
|
if _, err := os.Stat(legacyPath); err == nil {
|
||||||
return legacyPath
|
return legacyPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neither exists, prefer new XDG-compliant path
|
// Neither exists, prefer new XDG-compliant path
|
||||||
if configDir != "" {
|
if configDir != "" {
|
||||||
return filepath.Join(configDir, "fence", "fence.json")
|
return filepath.Join(configDir, "greywall", "greywall.json")
|
||||||
}
|
}
|
||||||
return filepath.Join(home, ".config", "fence", "fence.json")
|
return filepath.Join(home, ".config", "greywall", "greywall.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load loads configuration from a file path.
|
// Load loads configuration from a file path.
|
||||||
|
|||||||
@@ -249,10 +249,10 @@ func TestDefaultConfigPath(t *testing.T) {
|
|||||||
if path == "" {
|
if path == "" {
|
||||||
t.Error("DefaultConfigPath() returned empty string")
|
t.Error("DefaultConfigPath() returned empty string")
|
||||||
}
|
}
|
||||||
// Should end with fence.json (either new XDG path or legacy .fence.json)
|
// Should end with greywall.json (either new XDG path or legacy .greywall.json)
|
||||||
base := filepath.Base(path)
|
base := filepath.Base(path)
|
||||||
if base != "fence.json" && base != ".fence.json" {
|
if base != "greywall.json" && base != ".greywall.json" {
|
||||||
t.Errorf("DefaultConfigPath() = %q, expected to end with fence.json or .fence.json", path)
|
t.Errorf("DefaultConfigPath() = %q, expected to end with greywall.json or .greywall.json", path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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) {
|
func skipBenchIfSandboxed(b *testing.B) {
|
||||||
b.Helper()
|
b.Helper()
|
||||||
if os.Getenv("FENCE_SANDBOX") == "1" {
|
if os.Getenv("GREYWALL_SANDBOX") == "1" {
|
||||||
b.Skip("already running inside Fence sandbox")
|
b.Skip("already running inside Greywall sandbox")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"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.
|
// CommandBlockedError is returned when a command is blocked by policy.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package sandbox
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Use-Tusk/fence/internal/config"
|
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCheckCommand_BasicDeny(t *testing.T) {
|
func TestCheckCommand_BasicDeny(t *testing.T) {
|
||||||
|
|||||||
@@ -39,14 +39,14 @@ func GetDefaultWritePaths() []string {
|
|||||||
"/dev/tty",
|
"/dev/tty",
|
||||||
"/dev/dtracehelper",
|
"/dev/dtracehelper",
|
||||||
"/dev/autofs_nowait",
|
"/dev/autofs_nowait",
|
||||||
"/tmp/fence",
|
"/tmp/greywall",
|
||||||
"/private/tmp/fence",
|
"/private/tmp/greywall",
|
||||||
}
|
}
|
||||||
|
|
||||||
if home != "" {
|
if home != "" {
|
||||||
paths = append(paths,
|
paths = append(paths,
|
||||||
filepath.Join(home, ".npm/_logs"),
|
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")
|
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 {
|
for _, essential := range essentialPaths {
|
||||||
found := slices.Contains(paths, essential)
|
found := slices.Contains(paths, essential)
|
||||||
if !found {
|
if !found {
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import (
|
|||||||
|
|
||||||
// skipIfLandlockNotUsable skips tests that require the Landlock wrapper.
|
// skipIfLandlockNotUsable skips tests that require the Landlock wrapper.
|
||||||
// The Landlock wrapper re-executes the binary with --landlock-apply, which only
|
// 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
|
// 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 fence CLI.
|
// 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
|
// TODO: consider removing tests that call this function, for now can keep them
|
||||||
// as documentation.
|
// as documentation.
|
||||||
func skipIfLandlockNotUsable(t *testing.T) {
|
func skipIfLandlockNotUsable(t *testing.T) {
|
||||||
@@ -28,8 +28,8 @@ func skipIfLandlockNotUsable(t *testing.T) {
|
|||||||
t.Skip("skipping: Landlock not available on this kernel")
|
t.Skip("skipping: Landlock not available on this kernel")
|
||||||
}
|
}
|
||||||
exePath, _ := os.Executable()
|
exePath, _ := os.Executable()
|
||||||
if !strings.Contains(filepath.Base(exePath), "fence") {
|
if !strings.Contains(filepath.Base(exePath), "greywall") {
|
||||||
t.Skip("skipping: Landlock wrapper requires fence CLI (test binary cannot use --landlock-apply)")
|
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)
|
skipIfLandlockNotUsable(t)
|
||||||
|
|
||||||
workspace := createTempWorkspace(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) }()
|
defer func() { _ = os.Remove(outsideFile) }()
|
||||||
|
|
||||||
cfg := testConfigWithWorkspace(workspace)
|
cfg := testConfigWithWorkspace(workspace)
|
||||||
@@ -198,24 +198,24 @@ func TestLinux_LandlockBlocksWriteSystemFiles(t *testing.T) {
|
|||||||
cfg := testConfigWithWorkspace(workspace)
|
cfg := testConfigWithWorkspace(workspace)
|
||||||
|
|
||||||
// Attempting to write to /etc should fail
|
// 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)
|
assertBlocked(t, result)
|
||||||
assertFileNotExists(t, "/etc/fence-test-file")
|
assertFileNotExists(t, "/etc/greywall-test-file")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLinux_LandlockAllowsTmpFence verifies /tmp/fence is writable.
|
// TestLinux_LandlockAllowsTmpGreywall verifies /tmp/greywall is writable.
|
||||||
func TestLinux_LandlockAllowsTmpFence(t *testing.T) {
|
func TestLinux_LandlockAllowsTmpGreywall(t *testing.T) {
|
||||||
skipIfAlreadySandboxed(t)
|
skipIfAlreadySandboxed(t)
|
||||||
skipIfLandlockNotUsable(t)
|
skipIfLandlockNotUsable(t)
|
||||||
|
|
||||||
workspace := createTempWorkspace(t)
|
workspace := createTempWorkspace(t)
|
||||||
cfg := testConfigWithWorkspace(workspace)
|
cfg := testConfigWithWorkspace(workspace)
|
||||||
|
|
||||||
// Ensure /tmp/fence exists
|
// Ensure /tmp/greywall exists
|
||||||
_ = os.MkdirAll("/tmp/fence", 0o750)
|
_ = 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) }()
|
defer func() { _ = os.Remove(testFile) }()
|
||||||
|
|
||||||
result := runUnderSandbox(t, cfg, "echo 'test' > "+testFile, workspace)
|
result := runUnderSandbox(t, cfg, "echo 'test' > "+testFile, workspace)
|
||||||
@@ -351,8 +351,8 @@ func TestLinux_TransparentProxyRoutesThroughSocks(t *testing.T) {
|
|||||||
cfg.Filesystem.AllowWrite = []string{workspace}
|
cfg.Filesystem.AllowWrite = []string{workspace}
|
||||||
|
|
||||||
// This test requires actual network and a running SOCKS5 proxy
|
// This test requires actual network and a running SOCKS5 proxy
|
||||||
if os.Getenv("FENCE_TEST_NETWORK") != "1" {
|
if os.Getenv("GREYWALL_TEST_NETWORK") != "1" {
|
||||||
t.Skip("skipping: set FENCE_TEST_NETWORK=1 to run network tests (requires SOCKS5 proxy on localhost:1080)")
|
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)
|
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)
|
_ = os.Symlink("/etc", symlinkPath)
|
||||||
|
|
||||||
// Try to write through the symlink
|
// 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)
|
assertBlocked(t, result)
|
||||||
assertFileNotExists(t, "/etc/fence-test")
|
assertFileNotExists(t, "/etc/greywall-test")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLinux_PathTraversalBlocked verifies path traversal attacks are prevented.
|
// TestLinux_PathTraversalBlocked verifies path traversal attacks are prevented.
|
||||||
@@ -470,10 +470,10 @@ func TestLinux_PathTraversalBlocked(t *testing.T) {
|
|||||||
cfg := testConfigWithWorkspace(workspace)
|
cfg := testConfigWithWorkspace(workspace)
|
||||||
|
|
||||||
// Try to escape using ../../../
|
// 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)
|
assertBlocked(t, result)
|
||||||
assertFileNotExists(t, "/tmp/fence-escape-test")
|
assertFileNotExists(t, "/tmp/greywall-escape-test")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLinux_DeviceAccessBlocked verifies device files cannot be accessed.
|
// TestLinux_DeviceAccessBlocked verifies device files cannot be accessed.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestMacOS_SeatbeltBlocksWriteOutsideWorkspace(t *testing.T) {
|
|||||||
skipIfAlreadySandboxed(t)
|
skipIfAlreadySandboxed(t)
|
||||||
|
|
||||||
workspace := createTempWorkspace(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) }()
|
defer func() { _ = os.Remove(outsideFile) }()
|
||||||
|
|
||||||
cfg := testConfigWithWorkspace(workspace)
|
cfg := testConfigWithWorkspace(workspace)
|
||||||
@@ -138,23 +138,23 @@ func TestMacOS_SeatbeltBlocksWriteSystemFiles(t *testing.T) {
|
|||||||
cfg := testConfigWithWorkspace(workspace)
|
cfg := testConfigWithWorkspace(workspace)
|
||||||
|
|
||||||
// Attempting to write to /etc should fail
|
// 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)
|
assertBlocked(t, result)
|
||||||
assertFileNotExists(t, "/etc/fence-test-file")
|
assertFileNotExists(t, "/etc/greywall-test-file")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMacOS_SeatbeltAllowsTmpFence verifies /tmp/fence is writable.
|
// TestMacOS_SeatbeltAllowsTmpGreywall verifies /tmp/greywall is writable.
|
||||||
func TestMacOS_SeatbeltAllowsTmpFence(t *testing.T) {
|
func TestMacOS_SeatbeltAllowsTmpGreywall(t *testing.T) {
|
||||||
skipIfAlreadySandboxed(t)
|
skipIfAlreadySandboxed(t)
|
||||||
|
|
||||||
workspace := createTempWorkspace(t)
|
workspace := createTempWorkspace(t)
|
||||||
cfg := testConfigWithWorkspace(workspace)
|
cfg := testConfigWithWorkspace(workspace)
|
||||||
|
|
||||||
// Ensure /tmp/fence exists
|
// Ensure /tmp/greywall exists
|
||||||
_ = os.MkdirAll("/tmp/fence", 0o750)
|
_ = 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) }()
|
defer func() { _ = os.Remove(testFile) }()
|
||||||
|
|
||||||
result := runUnderSandbox(t, cfg, "echo 'test' > "+testFile, workspace)
|
result := runUnderSandbox(t, cfg, "echo 'test' > "+testFile, workspace)
|
||||||
@@ -221,8 +221,8 @@ func TestMacOS_ProxyAllowsTrafficViaProxy(t *testing.T) {
|
|||||||
cfg.Filesystem.AllowWrite = []string{workspace}
|
cfg.Filesystem.AllowWrite = []string{workspace}
|
||||||
|
|
||||||
// This test requires actual network and a running SOCKS5 proxy
|
// This test requires actual network and a running SOCKS5 proxy
|
||||||
if os.Getenv("FENCE_TEST_NETWORK") != "1" {
|
if os.Getenv("GREYWALL_TEST_NETWORK") != "1" {
|
||||||
t.Skip("skipping: set FENCE_TEST_NETWORK=1 to run network tests (requires SOCKS5 proxy on localhost:1080)")
|
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)
|
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
|
// 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)
|
assertBlocked(t, result)
|
||||||
assertFileNotExists(t, "/etc/fence-test")
|
assertFileNotExists(t, "/etc/greywall-test")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMacOS_PathTraversalBlocked verifies path traversal attacks are prevented.
|
// TestMacOS_PathTraversalBlocked verifies path traversal attacks are prevented.
|
||||||
@@ -303,10 +303,10 @@ func TestMacOS_PathTraversalBlocked(t *testing.T) {
|
|||||||
workspace := createTempWorkspace(t)
|
workspace := createTempWorkspace(t)
|
||||||
cfg := testConfigWithWorkspace(workspace)
|
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)
|
assertBlocked(t, result)
|
||||||
assertFileNotExists(t, "/tmp/fence-escape-test")
|
assertFileNotExists(t, "/tmp/greywall-escape-test")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMacOS_DeviceAccessBlocked verifies device files cannot be written.
|
// 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.
|
// 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".
|
// so "read-only" here means "outside the workspace".
|
||||||
func TestMacOS_ReadOnlyPolicy(t *testing.T) {
|
func TestMacOS_ReadOnlyPolicy(t *testing.T) {
|
||||||
skipIfAlreadySandboxed(t)
|
skipIfAlreadySandboxed(t)
|
||||||
@@ -353,7 +353,7 @@ func TestMacOS_ReadOnlyPolicy(t *testing.T) {
|
|||||||
assertAllowed(t, result)
|
assertAllowed(t, result)
|
||||||
|
|
||||||
// Writing outside workspace should fail
|
// 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) }()
|
defer func() { _ = os.Remove(outsidePath) }()
|
||||||
result = runUnderSandbox(t, cfg, "echo 'outside' > "+outsidePath, workspace)
|
result = runUnderSandbox(t, cfg, "echo 'outside' > "+outsidePath, workspace)
|
||||||
assertBlocked(t, result)
|
assertBlocked(t, result)
|
||||||
@@ -373,7 +373,7 @@ func TestMacOS_WorkspaceWritePolicy(t *testing.T) {
|
|||||||
assertFileExists(t, filepath.Join(workspace, "test.txt"))
|
assertFileExists(t, filepath.Join(workspace, "test.txt"))
|
||||||
|
|
||||||
// Writing outside workspace should fail
|
// Writing outside workspace should fail
|
||||||
outsideFile := "/tmp/fence-test-outside.txt"
|
outsideFile := "/tmp/greywall-test-outside.txt"
|
||||||
defer func() { _ = os.Remove(outsideFile) }()
|
defer func() { _ = os.Remove(outsideFile) }()
|
||||||
result = runUnderSandbox(t, cfg, "echo 'test' > "+outsideFile, workspace)
|
result = runUnderSandbox(t, cfg, "echo 'test' > "+outsideFile, workspace)
|
||||||
assertBlocked(t, result)
|
assertBlocked(t, result)
|
||||||
@@ -399,7 +399,7 @@ func TestMacOS_MultipleWritableRoots(t *testing.T) {
|
|||||||
assertAllowed(t, result)
|
assertAllowed(t, result)
|
||||||
|
|
||||||
// Writing outside both should fail
|
// Writing outside both should fail
|
||||||
outsideFile := "/tmp/fence-test-outside-multi.txt"
|
outsideFile := "/tmp/greywall-test-outside-multi.txt"
|
||||||
defer func() { _ = os.Remove(outsideFile) }()
|
defer func() { _ = os.Remove(outsideFile) }()
|
||||||
result = runUnderSandbox(t, cfg, "echo 'test' > "+outsideFile, workspace1)
|
result = runUnderSandbox(t, cfg, "echo 'test' > "+outsideFile, workspace1)
|
||||||
assertBlocked(t, result)
|
assertBlocked(t, result)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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.
|
// skipIfAlreadySandboxed skips the test if running inside a sandbox.
|
||||||
func skipIfAlreadySandboxed(t *testing.T) {
|
func skipIfAlreadySandboxed(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if os.Getenv("FENCE_SANDBOX") == "1" {
|
if os.Getenv("GREYWALL_SANDBOX") == "1" {
|
||||||
t.Skip("skipping: already running inside Fence sandbox")
|
t.Skip("skipping: already running inside Greywall sandbox")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ func testConfigWithProxy(proxyURL string) *config.Config {
|
|||||||
// Sandbox Execution Helpers
|
// 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.
|
// This uses the sandbox Manager directly for integration testing.
|
||||||
func runUnderSandbox(t *testing.T, cfg *config.Config, command string, workDir string) *SandboxTestResult {
|
func runUnderSandbox(t *testing.T, cfg *config.Config, command string, workDir string) *SandboxTestResult {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -281,7 +281,7 @@ func executeShellCommandWithTimeout(t *testing.T, command string, workDir string
|
|||||||
// createTempWorkspace creates a temporary directory for testing.
|
// createTempWorkspace creates a temporary directory for testing.
|
||||||
func createTempWorkspace(t *testing.T) string {
|
func createTempWorkspace(t *testing.T) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
dir, err := os.MkdirTemp("", "fence-test-*")
|
dir, err := os.MkdirTemp("", "greywall-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create temp workspace: %v", err)
|
t.Fatalf("failed to create temp workspace: %v", err)
|
||||||
}
|
}
|
||||||
@@ -492,10 +492,10 @@ func TestIntegration_EnvWorks(t *testing.T) {
|
|||||||
workspace := createTempWorkspace(t)
|
workspace := createTempWorkspace(t)
|
||||||
cfg := testConfigWithWorkspace(workspace)
|
cfg := testConfigWithWorkspace(workspace)
|
||||||
|
|
||||||
result := runUnderSandbox(t, cfg, "env | grep FENCE", workspace)
|
result := runUnderSandbox(t, cfg, "env | grep GREYWALL", workspace)
|
||||||
|
|
||||||
assertAllowed(t, result)
|
assertAllowed(t, result)
|
||||||
assertContains(t, result.Stdout, "FENCE_SANDBOX=1")
|
assertContains(t, result.Stdout, "GREYWALL_SANDBOX=1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecuteShellCommandBwrapError(t *testing.T) {
|
func TestExecuteShellCommandBwrapError(t *testing.T) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"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.
|
// 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)
|
socketID := hex.EncodeToString(id)
|
||||||
|
|
||||||
tmpDir := os.TempDir()
|
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{
|
bridge := &DnsBridge{
|
||||||
SocketPath: socketPath,
|
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
|
bridge.process = exec.Command("socat", socatArgs...) //nolint:gosec // args constructed from trusted input
|
||||||
if debug {
|
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 {
|
if err := bridge.process.Start(); err != nil {
|
||||||
return nil, fmt.Errorf("failed to start DNS bridge: %w", err)
|
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 {
|
for range 50 {
|
||||||
if fileExists(socketPath) {
|
if fileExists(socketPath) {
|
||||||
if debug {
|
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
|
return bridge, nil
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ func (b *DnsBridge) Cleanup() {
|
|||||||
_ = os.Remove(b.SocketPath)
|
_ = os.Remove(b.SocketPath)
|
||||||
|
|
||||||
if b.debug {
|
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)
|
socketID := hex.EncodeToString(id)
|
||||||
|
|
||||||
tmpDir := os.TempDir()
|
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{
|
bridge := &ProxyBridge{
|
||||||
SocketPath: socketPath,
|
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
|
bridge.process = exec.Command("socat", socatArgs...) //nolint:gosec // args constructed from trusted input
|
||||||
if debug {
|
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 {
|
if err := bridge.process.Start(); err != nil {
|
||||||
return nil, fmt.Errorf("failed to start proxy bridge: %w", err)
|
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 {
|
for range 50 {
|
||||||
if fileExists(socketPath) {
|
if fileExists(socketPath) {
|
||||||
if debug {
|
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
|
return bridge, nil
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ func (b *ProxyBridge) Cleanup() {
|
|||||||
_ = os.Remove(b.SocketPath)
|
_ = os.Remove(b.SocketPath)
|
||||||
|
|
||||||
if b.debug {
|
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 {
|
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)
|
bridge.SocketPaths = append(bridge.SocketPaths, socketPath)
|
||||||
|
|
||||||
// Start reverse bridge: TCP listen on host port -> Unix socket
|
// 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
|
proc := exec.Command("socat", args...) //nolint:gosec // args constructed from trusted input
|
||||||
if debug {
|
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 {
|
if err := proc.Start(); err != nil {
|
||||||
bridge.Cleanup()
|
bridge.Cleanup()
|
||||||
@@ -261,7 +261,7 @@ func NewReverseBridge(ports []int, debug bool) (*ReverseBridge, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if debug {
|
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
|
return bridge, nil
|
||||||
@@ -282,7 +282,7 @@ func (b *ReverseBridge) Cleanup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.debug {
|
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()
|
features := DetectLinuxFeatures()
|
||||||
|
|
||||||
if opts.Debug {
|
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
|
// Build bwrap args with filesystem restrictions
|
||||||
@@ -427,7 +427,7 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
|
|||||||
if features.CanUnshareNet {
|
if features.CanUnshareNet {
|
||||||
bwrapArgs = append(bwrapArgs, "--unshare-net") // Network namespace isolation
|
bwrapArgs = append(bwrapArgs, "--unshare-net") // Network namespace isolation
|
||||||
} else if opts.Debug {
|
} 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
|
bwrapArgs = append(bwrapArgs, "--unshare-pid") // PID namespace isolation
|
||||||
@@ -439,12 +439,12 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
|
|||||||
filterPath, err := filter.GenerateBPFFilter()
|
filterPath, err := filter.GenerateBPFFilter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if opts.Debug {
|
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 {
|
} else {
|
||||||
seccompFilterPath = filterPath
|
seccompFilterPath = filterPath
|
||||||
if opts.Debug {
|
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)
|
// Add seccomp filter via fd 3 (will be set up via shell redirection)
|
||||||
bwrapArgs = append(bwrapArgs, "--seccomp", "3")
|
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
|
// In defaultDenyRead mode, we only bind essential system paths read-only
|
||||||
// and user-specified allowRead paths. Everything else is inaccessible.
|
// and user-specified allowRead paths. Everything else is inaccessible.
|
||||||
if opts.Debug {
|
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
|
// 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)
|
bwrapArgs = append(bwrapArgs, "--ro-bind", target, target)
|
||||||
}
|
}
|
||||||
if opts.Debug {
|
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_ADMIN")
|
||||||
bwrapArgs = append(bwrapArgs, "--cap-add", "CAP_NET_BIND_SERVICE")
|
bwrapArgs = append(bwrapArgs, "--cap-add", "CAP_NET_BIND_SERVICE")
|
||||||
// Bind the tun2socks binary into the sandbox (read-only)
|
// 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
|
// 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
|
// 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.
|
// DNS bridge (Unix socket -> host DNS server) or to TCP through the tunnel.
|
||||||
if dnsBridge != nil || (tun2socksPath != "" && features.CanUseTransparentProxy()) {
|
if dnsBridge != nil || (tun2socksPath != "" && features.CanUseTransparentProxy()) {
|
||||||
tmpResolv, err := os.CreateTemp("", "fence-resolv-*.conf")
|
tmpResolv, err := os.CreateTemp("", "greywall-resolv-*.conf")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_, _ = tmpResolv.WriteString("nameserver 127.0.0.1\n")
|
_, _ = tmpResolv.WriteString("nameserver 127.0.0.1\n")
|
||||||
tmpResolv.Close()
|
tmpResolv.Close()
|
||||||
@@ -705,9 +705,9 @@ func WrapCommandLinuxWithOptions(cfg *config.Config, command string, proxyBridge
|
|||||||
bwrapArgs = append(bwrapArgs, "--ro-bind", dnsRelayResolvConf, "/etc/resolv.conf")
|
bwrapArgs = append(bwrapArgs, "--ro-bind", dnsRelayResolvConf, "/etc/resolv.conf")
|
||||||
if opts.Debug {
|
if opts.Debug {
|
||||||
if dnsBridge != nil {
|
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 {
|
} 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)
|
bwrapArgs = append(bwrapArgs, "--bind", tmpDir, tmpDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get fence executable path for Landlock wrapper
|
// Get greywall executable path for Landlock wrapper
|
||||||
fenceExePath, _ := os.Executable()
|
greywallExePath, _ := os.Executable()
|
||||||
// Skip Landlock wrapper if executable is in /tmp (test binaries are built there)
|
// 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
|
// The wrapper won't work because --tmpfs /tmp hides the test binary
|
||||||
executableInTmp := strings.HasPrefix(fenceExePath, "/tmp/")
|
executableInTmp := strings.HasPrefix(greywallExePath, "/tmp/")
|
||||||
// Skip Landlock wrapper if fence is being used as a library (executable is not fence)
|
// 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 fence understands
|
// The wrapper re-executes the binary with --landlock-apply, which only greywall understands
|
||||||
executableIsFence := strings.Contains(filepath.Base(fenceExePath), "fence")
|
executableIsGreywall := strings.Contains(filepath.Base(greywallExePath), "greywall")
|
||||||
useLandlockWrapper := opts.UseLandlock && features.CanUseLandlock() && fenceExePath != "" && !executableInTmp && executableIsFence
|
useLandlockWrapper := opts.UseLandlock && features.CanUseLandlock() && greywallExePath != "" && !executableInTmp && executableIsGreywall
|
||||||
|
|
||||||
if opts.Debug && executableInTmp {
|
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 {
|
if opts.Debug && !executableIsGreywall {
|
||||||
fmt.Fprintf(os.Stderr, "[fence:linux] Skipping Landlock wrapper (running as library, not fence CLI)\n")
|
fmt.Fprintf(os.Stderr, "[greywall:linux] Skipping Landlock wrapper (running as library, not greywall CLI)\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
bwrapArgs = append(bwrapArgs, "--", shellPath, "-c")
|
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
|
// Build the inner command that sets up tun2socks and runs the user command
|
||||||
var innerScript strings.Builder
|
var innerScript strings.Builder
|
||||||
|
|
||||||
innerScript.WriteString("export FENCE_SANDBOX=1\n")
|
innerScript.WriteString("export GREYWALL_SANDBOX=1\n")
|
||||||
|
|
||||||
if proxyBridge != nil && tun2socksPath != "" && features.CanUseTransparentProxy() {
|
if proxyBridge != nil && tun2socksPath != "" && features.CanUseTransparentProxy() {
|
||||||
// Build the tun2socks proxy URL with credentials if available
|
// 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=$!
|
BRIDGE_PID=$!
|
||||||
|
|
||||||
# Start tun2socks (transparent proxy via gvisor netstack)
|
# 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=$!
|
TUN2SOCKS_PID=$!
|
||||||
|
|
||||||
`, proxyBridge.SocketPath, tun2socksProxyURL))
|
`, proxyBridge.SocketPath, tun2socksProxyURL))
|
||||||
@@ -853,13 +853,13 @@ sleep 0.3
|
|||||||
if cfg != nil {
|
if cfg != nil {
|
||||||
configJSON, err := json.Marshal(cfg)
|
configJSON, err := json.Marshal(cfg)
|
||||||
if err == nil {
|
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
|
// Build wrapper command with proper quoting
|
||||||
// Use bash -c to preserve shell semantics (e.g., "echo hi && ls")
|
// 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 {
|
if opts.Debug {
|
||||||
wrapperArgs = append(wrapperArgs, "--debug")
|
wrapperArgs = append(wrapperArgs, "--debug")
|
||||||
}
|
}
|
||||||
@@ -897,7 +897,7 @@ sleep 0.3
|
|||||||
if reverseBridge != nil && len(reverseBridge.Ports) > 0 {
|
if reverseBridge != nil && len(reverseBridge.Ports) > 0 {
|
||||||
featureList = append(featureList, fmt.Sprintf("inbound:%v", reverseBridge.Ports))
|
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
|
// 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).
|
// 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.
|
// For now, we rely on the eBPF monitor to detect syscall failures.
|
||||||
if opts.Debug && opts.Monitor && features.SeccompLogLevel >= 1 {
|
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
|
// Start eBPF monitor if available and requested
|
||||||
@@ -935,17 +935,17 @@ func StartLinuxMonitor(pid int, opts LinuxSandboxOptions) (*LinuxMonitors, error
|
|||||||
ebpfMon := NewEBPFMonitor(pid, opts.Debug)
|
ebpfMon := NewEBPFMonitor(pid, opts.Debug)
|
||||||
if err := ebpfMon.Start(); err != nil {
|
if err := ebpfMon.Start(); err != nil {
|
||||||
if opts.Debug {
|
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 {
|
} else {
|
||||||
monitors.EBPFMonitor = ebpfMon
|
monitors.EBPFMonitor = ebpfMon
|
||||||
if opts.Debug {
|
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 {
|
} else if opts.Monitor && opts.Debug {
|
||||||
if !features.HasEBPF {
|
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()
|
features := DetectLinuxFeatures()
|
||||||
if !features.HasEBPF {
|
if !features.HasEBPF {
|
||||||
if m.debug {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
@@ -51,14 +51,14 @@ func (m *EBPFMonitor) Start() error {
|
|||||||
// Try multiple eBPF tracing approaches
|
// Try multiple eBPF tracing approaches
|
||||||
if err := m.tryBpftrace(ctx); err != nil {
|
if err := m.tryBpftrace(ctx); err != nil {
|
||||||
if m.debug {
|
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
|
// Fall back to other methods
|
||||||
go m.traceWithPerfEvents()
|
go m.traceWithPerfEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.debug {
|
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
|
return nil
|
||||||
@@ -101,7 +101,7 @@ func (m *EBPFMonitor) tryBpftrace(ctx context.Context) error {
|
|||||||
script := m.generateBpftraceScript()
|
script := m.generateBpftraceScript()
|
||||||
|
|
||||||
// Write script to temp file
|
// Write script to temp file
|
||||||
tmpFile, err := os.CreateTemp("", "fence-ebpf-*.bt")
|
tmpFile, err := os.CreateTemp("", "greywall-ebpf-*.bt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create temp file: %w", err)
|
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() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if m.debug {
|
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 != "" {
|
if violation := m.parseBpftraceOutput(line); violation != "" {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", violation)
|
fmt.Fprintf(os.Stderr, "%s\n", violation)
|
||||||
@@ -150,7 +150,7 @@ func (m *EBPFMonitor) tryBpftrace(ctx context.Context) error {
|
|||||||
scanner := bufio.NewScanner(stderr)
|
scanner := bufio.NewScanner(stderr)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
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(`
|
script := fmt.Sprintf(`
|
||||||
BEGIN
|
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)
|
// Monitor filesystem errors (EPERM=-1, EACCES=-13, EROFS=-30)
|
||||||
@@ -227,7 +227,7 @@ func (m *EBPFMonitor) parseBpftraceOutput(line string) string {
|
|||||||
errorName := getErrnoName(ret)
|
errorName := getErrnoName(ret)
|
||||||
timestamp := time.Now().Format("15:04:05")
|
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)
|
timestamp, syscall, errorName, comm, pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ func (m *EBPFMonitor) traceWithPerfEvents() {
|
|||||||
tracePipe := "/sys/kernel/debug/tracing/trace_pipe"
|
tracePipe := "/sys/kernel/debug/tracing/trace_pipe"
|
||||||
if _, err := os.Stat(tracePipe); err != nil {
|
if _, err := os.Stat(tracePipe); err != nil {
|
||||||
if m.debug {
|
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
|
return
|
||||||
}
|
}
|
||||||
@@ -247,7 +247,7 @@ func (m *EBPFMonitor) traceWithPerfEvents() {
|
|||||||
f, err := os.Open(tracePipe)
|
f, err := os.Open(tracePipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if m.debug {
|
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
|
return
|
||||||
}
|
}
|
||||||
@@ -317,10 +317,10 @@ func (v *ViolationEvent) FormatViolation() string {
|
|||||||
errName := getErrnoName(-v.Errno)
|
errName := getErrnoName(-v.Errno)
|
||||||
|
|
||||||
if v.Path != "" {
|
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)
|
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)
|
timestamp, v.Operation, errName, v.Comm, v.PID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/Use-Tusk/fence/internal/config"
|
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||||
"github.com/bmatcuk/doublestar/v4"
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -22,7 +22,7 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
|||||||
features := DetectLinuxFeatures()
|
features := DetectLinuxFeatures()
|
||||||
if !features.CanUseLandlock() {
|
if !features.CanUseLandlock() {
|
||||||
if debug {
|
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)
|
features.KernelMajor, features.KernelMinor)
|
||||||
}
|
}
|
||||||
return nil // Graceful fallback - Landlock not available
|
return nil // Graceful fallback - Landlock not available
|
||||||
@@ -31,7 +31,7 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
|||||||
ruleset, err := NewLandlockRuleset(debug)
|
ruleset, err := NewLandlockRuleset(debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if debug {
|
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
|
return nil // Graceful fallback
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
|||||||
|
|
||||||
if err := ruleset.Initialize(); err != nil {
|
if err := ruleset.Initialize(); err != nil {
|
||||||
if debug {
|
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
|
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 {
|
if err := ruleset.AllowRead(p); err != nil && debug {
|
||||||
// Ignore errors for paths that don't exist
|
// Ignore errors for paths that don't exist
|
||||||
if !os.IsNotExist(err) {
|
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" {
|
if target, err := filepath.EvalSymlinks("/etc/resolv.conf"); err == nil && target != "/etc/resolv.conf" {
|
||||||
targetDir := filepath.Dir(target)
|
targetDir := filepath.Dir(target)
|
||||||
if err := ruleset.AllowRead(targetDir); err != nil && debug {
|
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)
|
// Current working directory - read access (may be upgraded to write below)
|
||||||
if cwd != "" {
|
if cwd != "" {
|
||||||
if err := ruleset.AllowRead(cwd); err != nil && debug {
|
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
|
// Home directory - read access
|
||||||
if home, err := os.UserHomeDir(); err == nil {
|
if home, err := os.UserHomeDir(); err == nil {
|
||||||
if err := ruleset.AllowRead(home); err != nil && debug {
|
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)
|
// /tmp - allow read+write (many programs need this)
|
||||||
if err := ruleset.AllowReadWrite("/tmp"); err != nil && debug {
|
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.
|
// /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
|
// Landlock doesn't support rules on device files directly, so we allow the whole /dev
|
||||||
if err := ruleset.AllowReadWrite("/dev"); err != nil && debug {
|
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
|
// Socket paths for proxy communication
|
||||||
for _, p := range socketPaths {
|
for _, p := range socketPaths {
|
||||||
dir := filepath.Dir(p)
|
dir := filepath.Dir(p)
|
||||||
if err := ruleset.AllowReadWrite(dir); err != nil && debug {
|
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)
|
expandedPaths := ExpandGlobPatterns(cfg.Filesystem.AllowWrite)
|
||||||
for _, p := range expandedPaths {
|
for _, p := range expandedPaths {
|
||||||
if err := ruleset.AllowReadWrite(p); err != nil && debug {
|
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
|
// Also add non-glob paths directly
|
||||||
@@ -127,7 +127,7 @@ func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []strin
|
|||||||
if !ContainsGlobChars(p) {
|
if !ContainsGlobChars(p) {
|
||||||
normalized := NormalizePath(p)
|
normalized := NormalizePath(p)
|
||||||
if err := ruleset.AllowReadWrite(normalized); err != nil && debug {
|
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
|
// Apply the ruleset
|
||||||
if err := ruleset.Apply(); err != nil {
|
if err := ruleset.Apply(); err != nil {
|
||||||
if debug {
|
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
|
return nil // Graceful fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug {
|
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
|
return nil
|
||||||
@@ -212,7 +212,7 @@ func (l *LandlockRuleset) Initialize() error {
|
|||||||
l.initialized = true
|
l.initialized = true
|
||||||
|
|
||||||
if l.debug {
|
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
|
return nil
|
||||||
@@ -318,7 +318,7 @@ func (l *LandlockRuleset) addPathRule(path string, access uint64) error {
|
|||||||
// Check if path exists
|
// Check if path exists
|
||||||
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||||
if l.debug {
|
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
|
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)
|
fd, err := unix.Open(absPath, unix.O_PATH|unix.O_CLOEXEC, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if l.debug {
|
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
|
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
|
var stat unix.Stat_t
|
||||||
if err := unix.Fstat(fd, &stat); err != nil {
|
if err := unix.Fstat(fd, &stat); err != nil {
|
||||||
if l.debug {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
@@ -370,7 +370,7 @@ func (l *LandlockRuleset) addPathRule(path string, access uint64) error {
|
|||||||
|
|
||||||
if access == 0 {
|
if access == 0 {
|
||||||
if l.debug {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
@@ -391,7 +391,7 @@ func (l *LandlockRuleset) addPathRule(path string, access uint64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if l.debug {
|
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
|
return nil
|
||||||
@@ -420,7 +420,7 @@ func (l *LandlockRuleset) Apply() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if l.debug {
|
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
|
return nil
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package sandbox
|
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.
|
// ApplyLandlockFromConfig is a no-op on non-Linux platforms.
|
||||||
func ApplyLandlockFromConfig(cfg *config.Config, cwd string, socketPaths []string, debug bool) error {
|
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
|
// 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 {
|
if err := os.MkdirAll(tmpDir, 0o700); err != nil {
|
||||||
return "", fmt.Errorf("failed to create seccomp dir: %w", err)
|
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
|
// Generate the filter using the seccomp library or raw BPF
|
||||||
// For now, we'll use bwrap's built-in seccomp support via --seccomp
|
// 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 {
|
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
|
return filterPath, nil
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package sandbox
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Use-Tusk/fence/internal/config"
|
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProxyBridge is a stub for non-Linux platforms.
|
// ProxyBridge is a stub for non-Linux platforms.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package sandbox
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"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,
|
// TestLinux_NoProxyBlocksNetwork verifies that when no ProxyURL is set,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"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.
|
// 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 {
|
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 {
|
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)
|
profile := GenerateSandboxProfile(params)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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,
|
// TestMacOS_NetworkRestrictionWithProxy verifies that when a proxy URL is set,
|
||||||
@@ -274,14 +274,14 @@ func TestExpandMacOSTmpPaths(t *testing.T) {
|
|||||||
want: []string{".", "~/.cache"},
|
want: []string{".", "~/.cache"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mirrors /tmp/fence to /private/tmp/fence",
|
name: "mirrors /tmp/greywall to /private/tmp/greywall",
|
||||||
input: []string{".", "/tmp/fence"},
|
input: []string{".", "/tmp/greywall"},
|
||||||
want: []string{".", "/tmp/fence", "/private/tmp/fence"},
|
want: []string{".", "/tmp/greywall", "/private/tmp/greywall"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mirrors /private/tmp/fence to /tmp/fence",
|
name: "mirrors /private/tmp/greywall to /tmp/greywall",
|
||||||
input: []string{".", "/private/tmp/fence"},
|
input: []string{".", "/private/tmp/greywall"},
|
||||||
want: []string{".", "/private/tmp/fence", "/tmp/fence"},
|
want: []string{".", "/private/tmp/greywall", "/tmp/greywall"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mirrors nested subdirectory",
|
name: "mirrors nested subdirectory",
|
||||||
@@ -290,8 +290,8 @@ func TestExpandMacOSTmpPaths(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no duplicate when mirror already present",
|
name: "no duplicate when mirror already present",
|
||||||
input: []string{".", "/tmp/fence", "/private/tmp/fence"},
|
input: []string{".", "/tmp/greywall", "/private/tmp/greywall"},
|
||||||
want: []string{".", "/tmp/fence", "/private/tmp/fence"},
|
want: []string{".", "/tmp/greywall", "/private/tmp/greywall"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/Use-Tusk/fence/internal/config"
|
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||||
"github.com/Use-Tusk/fence/internal/platform"
|
"gitea.app.monadical.io/monadical/greywall/internal/platform"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manager handles sandbox initialization and command wrapping.
|
// Manager handles sandbox initialization and command wrapping.
|
||||||
@@ -153,6 +153,6 @@ func (m *Manager) Cleanup() {
|
|||||||
|
|
||||||
func (m *Manager) logDebug(format string, args ...interface{}) {
|
func (m *Manager) logDebug(format string, args ...interface{}) {
|
||||||
if m.debug {
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Use-Tusk/fence/internal/platform"
|
"gitea.app.monadical.io/monadical/greywall/internal/platform"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogMonitor monitors sandbox violations via macOS log stream.
|
// LogMonitor monitors sandbox violations via macOS log stream.
|
||||||
@@ -138,9 +138,9 @@ func parseViolation(line string) string {
|
|||||||
timestamp := time.Now().Format("15:04:05")
|
timestamp := time.Now().Format("15:04:05")
|
||||||
|
|
||||||
if details != "" {
|
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.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("tun2socks: failed to create temp file: %w", err)
|
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.
|
// Used on macOS where transparent proxying is not available.
|
||||||
func GenerateProxyEnvVars(proxyURL string) []string {
|
func GenerateProxyEnvVars(proxyURL string) []string {
|
||||||
envVars := []string{
|
envVars := []string{
|
||||||
"FENCE_SANDBOX=1",
|
"GREYWALL_SANDBOX=1",
|
||||||
"TMPDIR=/tmp/fence",
|
"TMPDIR=/tmp/greywall",
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxyURL == "" {
|
if proxyURL == "" {
|
||||||
|
|||||||
@@ -134,8 +134,8 @@ func TestGenerateProxyEnvVars(t *testing.T) {
|
|||||||
name: "no proxy",
|
name: "no proxy",
|
||||||
proxyURL: "",
|
proxyURL: "",
|
||||||
wantEnvs: []string{
|
wantEnvs: []string{
|
||||||
"FENCE_SANDBOX=1",
|
"GREYWALL_SANDBOX=1",
|
||||||
"TMPDIR=/tmp/fence",
|
"TMPDIR=/tmp/greywall",
|
||||||
},
|
},
|
||||||
dontWant: []string{
|
dontWant: []string{
|
||||||
"HTTP_PROXY=",
|
"HTTP_PROXY=",
|
||||||
@@ -147,7 +147,7 @@ func TestGenerateProxyEnvVars(t *testing.T) {
|
|||||||
name: "socks5 proxy",
|
name: "socks5 proxy",
|
||||||
proxyURL: "socks5://localhost:1080",
|
proxyURL: "socks5://localhost:1080",
|
||||||
wantEnvs: []string{
|
wantEnvs: []string{
|
||||||
"FENCE_SANDBOX=1",
|
"GREYWALL_SANDBOX=1",
|
||||||
"ALL_PROXY=socks5://localhost:1080",
|
"ALL_PROXY=socks5://localhost:1080",
|
||||||
"all_proxy=socks5://localhost:1080",
|
"all_proxy=socks5://localhost:1080",
|
||||||
"HTTP_PROXY=socks5://localhost:1080",
|
"HTTP_PROXY=socks5://localhost:1080",
|
||||||
@@ -162,7 +162,7 @@ func TestGenerateProxyEnvVars(t *testing.T) {
|
|||||||
name: "socks5h proxy",
|
name: "socks5h proxy",
|
||||||
proxyURL: "socks5h://proxy.example.com:1080",
|
proxyURL: "socks5h://proxy.example.com:1080",
|
||||||
wantEnvs: []string{
|
wantEnvs: []string{
|
||||||
"FENCE_SANDBOX=1",
|
"GREYWALL_SANDBOX=1",
|
||||||
"ALL_PROXY=socks5h://proxy.example.com:1080",
|
"ALL_PROXY=socks5h://proxy.example.com:1080",
|
||||||
"HTTP_PROXY=socks5h://proxy.example.com:1080",
|
"HTTP_PROXY=socks5h://proxy.example.com:1080",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// Package fence provides a public API for sandboxing commands.
|
// Package greywall provides a public API for sandboxing commands.
|
||||||
package fence
|
package greywall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Use-Tusk/fence/internal/config"
|
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||||
"github.com/Use-Tusk/fence/internal/platform"
|
"gitea.app.monadical.io/monadical/greywall/internal/platform"
|
||||||
"github.com/Use-Tusk/fence/internal/sandbox"
|
"gitea.app.monadical.io/monadical/greywall/internal/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsSupported returns true if the current platform supports sandboxing (macOS/Linux).
|
// IsSupported returns true if the current platform supports sandboxing (macOS/Linux).
|
||||||
@@ -12,7 +12,7 @@ func IsSupported() bool {
|
|||||||
return platform.IsSupported()
|
return platform.IsSupported()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the configuration for fence.
|
// Config is the configuration for greywall.
|
||||||
type Config = config.Config
|
type Config = config.Config
|
||||||
|
|
||||||
// NetworkConfig defines network restrictions.
|
// NetworkConfig defines network restrictions.
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
# ./scripts/benchmark.sh [options]
|
# ./scripts/benchmark.sh [options]
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# -b, --binary PATH Path to fence binary (default: ./fence or builds one)
|
# -b, --binary PATH Path to greywall binary (default: ./greywall or builds one)
|
||||||
# -o, --output DIR Output directory for results (default: ./benchmarks)
|
# -o, --output DIR Output directory for results (default: ./benchmarks)
|
||||||
# -n, --runs N Minimum runs per benchmark (default: 30)
|
# -n, --runs N Minimum runs per benchmark (default: 30)
|
||||||
# -q, --quick Quick mode: fewer runs, skip slow benchmarks
|
# -q, --quick Quick mode: fewer runs, skip slow benchmarks
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
# Requirements:
|
# Requirements:
|
||||||
# - hyperfine (brew install hyperfine / apt install hyperfine)
|
# - hyperfine (brew install hyperfine / apt install hyperfine)
|
||||||
# - go (for building fence if needed)
|
# - go (for building greywall if needed)
|
||||||
# - Optional: python3 (for local-server.py network benchmarks)
|
# - Optional: python3 (for local-server.py network benchmarks)
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -32,7 +32,7 @@ BLUE='\033[0;34m'
|
|||||||
NC='\033[0m'
|
NC='\033[0m'
|
||||||
|
|
||||||
# Defaults
|
# Defaults
|
||||||
FENCE_BIN=""
|
GREYWALL_BIN=""
|
||||||
OUTPUT_DIR="./benchmarks"
|
OUTPUT_DIR="./benchmarks"
|
||||||
MIN_RUNS=30
|
MIN_RUNS=30
|
||||||
WARMUP=3
|
WARMUP=3
|
||||||
@@ -43,7 +43,7 @@ NETWORK=false
|
|||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
-b|--binary)
|
-b|--binary)
|
||||||
FENCE_BIN="$2"
|
GREYWALL_BIN="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
-o|--output)
|
-o|--output)
|
||||||
@@ -75,21 +75,21 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Find or build fence binary
|
# Find or build greywall binary
|
||||||
if [[ -z "$FENCE_BIN" ]]; then
|
if [[ -z "$GREYWALL_BIN" ]]; then
|
||||||
if [[ -x "./fence" ]]; then
|
if [[ -x "./greywall" ]]; then
|
||||||
FENCE_BIN="./fence"
|
GREYWALL_BIN="./greywall"
|
||||||
elif [[ -x "./dist/fence" ]]; then
|
elif [[ -x "./dis./greywall" ]]; then
|
||||||
FENCE_BIN="./dist/fence"
|
GREYWALL_BIN="./dis./greywall"
|
||||||
else
|
else
|
||||||
echo -e "${BLUE}Building fence...${NC}"
|
echo -e "${BLUE}Building greywall...${NC}"
|
||||||
go build -o ./fence ./cmd/fence
|
go build -o ./greywall ./cm./greywall
|
||||||
FENCE_BIN="./fence"
|
GREYWALL_BIN="./greywall"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -x "$FENCE_BIN" ]]; then
|
if [[ ! -x "$GREYWALL_BIN" ]]; then
|
||||||
echo -e "${RED}Error: fence binary not found at $FENCE_BIN${NC}"
|
echo -e "${RED}Error: greywall binary not found at $GREYWALL_BIN${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ WORKSPACE=$(mktemp -d -p .)
|
|||||||
trap 'rm -rf "$WORKSPACE"' EXIT
|
trap 'rm -rf "$WORKSPACE"' EXIT
|
||||||
|
|
||||||
# Create settings file for sandbox
|
# Create settings file for sandbox
|
||||||
SETTINGS_FILE="$WORKSPACE/fence.json"
|
SETTINGS_FILE="$WORKSPAC./greywall.json"
|
||||||
cat > "$SETTINGS_FILE" << EOF
|
cat > "$SETTINGS_FILE" << EOF
|
||||||
{
|
{
|
||||||
"filesystem": {
|
"filesystem": {
|
||||||
@@ -131,13 +131,13 @@ RESULTS_MD="$OUTPUT_DIR/${OS,,}-${ARCH}-${TIMESTAMP}.md"
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BLUE}==========================================${NC}"
|
echo -e "${BLUE}==========================================${NC}"
|
||||||
echo -e "${BLUE}Fence Sandbox Benchmarks${NC}"
|
echo -e "${BLUE}Greywall Sandbox Benchmarks${NC}"
|
||||||
echo -e "${BLUE}==========================================${NC}"
|
echo -e "${BLUE}==========================================${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Platform: $OS $ARCH"
|
echo "Platform: $OS $ARCH"
|
||||||
echo "Kernel: $KERNEL"
|
echo "Kernel: $KERNEL"
|
||||||
echo "Date: $DATE"
|
echo "Date: $DATE"
|
||||||
echo "Fence: $FENCE_BIN"
|
echo "Greywall: $GREYWALL_BIN"
|
||||||
echo "Output: $OUTPUT_DIR"
|
echo "Output: $OUTPUT_DIR"
|
||||||
echo "Min runs: $MIN_RUNS"
|
echo "Min runs: $MIN_RUNS"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -169,11 +169,11 @@ echo ""
|
|||||||
|
|
||||||
run_bench "true" \
|
run_bench "true" \
|
||||||
--command-name "unsandboxed" "true" \
|
--command-name "unsandboxed" "true" \
|
||||||
--command-name "sandboxed" "$FENCE_BIN -s $SETTINGS_FILE -- true"
|
--command-name "sandboxed" "$GREYWALL_BIN -s $SETTINGS_FILE -- true"
|
||||||
|
|
||||||
run_bench "echo" \
|
run_bench "echo" \
|
||||||
--command-name "unsandboxed" "echo hello >/dev/null" \
|
--command-name "unsandboxed" "echo hello >/dev/null" \
|
||||||
--command-name "sandboxed" "$FENCE_BIN -s $SETTINGS_FILE -c 'echo hello' >/dev/null"
|
--command-name "sandboxed" "$GREYWALL_BIN -s $SETTINGS_FILE -c 'echo hello' >/dev/null"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Tool compatibility benchmarks
|
# Tool compatibility benchmarks
|
||||||
@@ -185,7 +185,7 @@ echo ""
|
|||||||
if command -v python3 &> /dev/null; then
|
if command -v python3 &> /dev/null; then
|
||||||
run_bench "python" \
|
run_bench "python" \
|
||||||
--command-name "unsandboxed" "python3 -c 'pass'" \
|
--command-name "unsandboxed" "python3 -c 'pass'" \
|
||||||
--command-name "sandboxed" "$FENCE_BIN -s $SETTINGS_FILE -c \"python3 -c 'pass'\""
|
--command-name "sandboxed" "$GREYWALL_BIN -s $SETTINGS_FILE -c \"python3 -c 'pass'\""
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Skipping python3 (not found)${NC}"
|
echo -e "${YELLOW}Skipping python3 (not found)${NC}"
|
||||||
fi
|
fi
|
||||||
@@ -193,7 +193,7 @@ fi
|
|||||||
if command -v node &> /dev/null && [[ "$QUICK" == "false" ]]; then
|
if command -v node &> /dev/null && [[ "$QUICK" == "false" ]]; then
|
||||||
run_bench "node" \
|
run_bench "node" \
|
||||||
--command-name "unsandboxed" "node -e ''" \
|
--command-name "unsandboxed" "node -e ''" \
|
||||||
--command-name "sandboxed" "$FENCE_BIN -s $SETTINGS_FILE -c \"node -e ''\""
|
--command-name "sandboxed" "$GREYWALL_BIN -s $SETTINGS_FILE -c \"node -e ''\""
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Skipping node (not found or quick mode)${NC}"
|
echo -e "${YELLOW}Skipping node (not found or quick mode)${NC}"
|
||||||
fi
|
fi
|
||||||
@@ -208,7 +208,7 @@ echo ""
|
|||||||
if command -v git &> /dev/null && [[ -d .git ]]; then
|
if command -v git &> /dev/null && [[ -d .git ]]; then
|
||||||
run_bench "git-status" \
|
run_bench "git-status" \
|
||||||
--command-name "unsandboxed" "git status --porcelain >/dev/null" \
|
--command-name "unsandboxed" "git status --porcelain >/dev/null" \
|
||||||
--command-name "sandboxed" "$FENCE_BIN -s $SETTINGS_FILE -- git status --porcelain >/dev/null"
|
--command-name "sandboxed" "$GREYWALL_BIN -s $SETTINGS_FILE -- git status --porcelain >/dev/null"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Skipping git status (not in a git repo)${NC}"
|
echo -e "${YELLOW}Skipping git status (not in a git repo)${NC}"
|
||||||
fi
|
fi
|
||||||
@@ -216,7 +216,7 @@ fi
|
|||||||
if command -v rg &> /dev/null && [[ "$QUICK" == "false" ]]; then
|
if command -v rg &> /dev/null && [[ "$QUICK" == "false" ]]; then
|
||||||
run_bench "ripgrep" \
|
run_bench "ripgrep" \
|
||||||
--command-name "unsandboxed" "rg -n 'package' -S . >/dev/null 2>&1 || true" \
|
--command-name "unsandboxed" "rg -n 'package' -S . >/dev/null 2>&1 || true" \
|
||||||
--command-name "sandboxed" "$FENCE_BIN -s $SETTINGS_FILE -c \"rg -n 'package' -S . >/dev/null 2>&1\" || true"
|
--command-name "sandboxed" "$GREYWALL_BIN -s $SETTINGS_FILE -c \"rg -n 'package' -S . >/dev/null 2>&1\" || true"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Skipping ripgrep (not found or quick mode)${NC}"
|
echo -e "${YELLOW}Skipping ripgrep (not found or quick mode)${NC}"
|
||||||
fi
|
fi
|
||||||
@@ -230,11 +230,11 @@ echo ""
|
|||||||
|
|
||||||
run_bench "file-write" \
|
run_bench "file-write" \
|
||||||
--command-name "unsandboxed" "echo 'test' > $WORKSPACE/test.txt" \
|
--command-name "unsandboxed" "echo 'test' > $WORKSPACE/test.txt" \
|
||||||
--command-name "sandboxed" "$FENCE_BIN -s $SETTINGS_FILE -c \"echo 'test' > $WORKSPACE/test.txt\""
|
--command-name "sandboxed" "$GREYWALL_BIN -s $SETTINGS_FILE -c \"echo 'test' > $WORKSPACE/test.txt\""
|
||||||
|
|
||||||
run_bench "file-read" \
|
run_bench "file-read" \
|
||||||
--command-name "unsandboxed" "cat $WORKSPACE/test.txt >/dev/null" \
|
--command-name "unsandboxed" "cat $WORKSPACE/test.txt >/dev/null" \
|
||||||
--command-name "sandboxed" "$FENCE_BIN -s $SETTINGS_FILE -c 'cat $WORKSPACE/test.txt' >/dev/null"
|
--command-name "sandboxed" "$GREYWALL_BIN -s $SETTINGS_FILE -c 'cat $WORKSPACE/test.txt' >/dev/null"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Monitor mode benchmarks (optional)
|
# Monitor mode benchmarks (optional)
|
||||||
@@ -245,8 +245,8 @@ if [[ "$QUICK" == "false" ]]; then
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
run_bench "monitor-true" \
|
run_bench "monitor-true" \
|
||||||
--command-name "sandboxed" "$FENCE_BIN -s $SETTINGS_FILE -- true" \
|
--command-name "sandboxed" "$GREYWALL_BIN -s $SETTINGS_FILE -- true" \
|
||||||
--command-name "sandboxed+monitor" "$FENCE_BIN -m -s $SETTINGS_FILE -- true"
|
--command-name "sandboxed+monitor" "$GREYWALL_BIN -m -s $SETTINGS_FILE -- true"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -266,7 +266,7 @@ if [[ "$NETWORK" == "true" ]]; then
|
|||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
# Create network settings
|
# Create network settings
|
||||||
NET_SETTINGS="$WORKSPACE/fence-net.json"
|
NET_SETTINGS="$WORKSPAC./greywall-net.json"
|
||||||
cat > "$NET_SETTINGS" << EOF
|
cat > "$NET_SETTINGS" << EOF
|
||||||
{
|
{
|
||||||
"network": {
|
"network": {
|
||||||
@@ -281,7 +281,7 @@ EOF
|
|||||||
if command -v curl &> /dev/null; then
|
if command -v curl &> /dev/null; then
|
||||||
run_bench "network-curl" \
|
run_bench "network-curl" \
|
||||||
--command-name "unsandboxed" "curl -s http://127.0.0.1:8765/ >/dev/null" \
|
--command-name "unsandboxed" "curl -s http://127.0.0.1:8765/ >/dev/null" \
|
||||||
--command-name "sandboxed" "$FENCE_BIN -s $NET_SETTINGS -c 'curl -s http://127.0.0.1:8765/' >/dev/null"
|
--command-name "sandboxed" "$GREYWALL_BIN -s $NET_SETTINGS -c 'curl -s http://127.0.0.1:8765/' >/dev/null"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
kill $SERVER_PID 2>/dev/null || true
|
kill $SERVER_PID 2>/dev/null || true
|
||||||
@@ -303,7 +303,7 @@ echo " \"platform\": \"$OS\"," >> "$RESULTS_JSON"
|
|||||||
echo " \"arch\": \"$ARCH\"," >> "$RESULTS_JSON"
|
echo " \"arch\": \"$ARCH\"," >> "$RESULTS_JSON"
|
||||||
echo " \"kernel\": \"$KERNEL\"," >> "$RESULTS_JSON"
|
echo " \"kernel\": \"$KERNEL\"," >> "$RESULTS_JSON"
|
||||||
echo " \"date\": \"$DATE\"," >> "$RESULTS_JSON"
|
echo " \"date\": \"$DATE\"," >> "$RESULTS_JSON"
|
||||||
echo " \"fence_version\": \"$($FENCE_BIN --version 2>/dev/null || echo unknown)\"," >> "$RESULTS_JSON"
|
echo " \"greywall_version\": \"$($GREYWALL_BIN --version 2>/dev/null || echo unknown)\"," >> "$RESULTS_JSON"
|
||||||
echo " \"benchmarks\": {" >> "$RESULTS_JSON"
|
echo " \"benchmarks\": {" >> "$RESULTS_JSON"
|
||||||
|
|
||||||
first=true
|
first=true
|
||||||
@@ -324,12 +324,12 @@ echo "}" >> "$RESULTS_JSON"
|
|||||||
|
|
||||||
# Generate Markdown report
|
# Generate Markdown report
|
||||||
cat > "$RESULTS_MD" << EOF
|
cat > "$RESULTS_MD" << EOF
|
||||||
# Fence Benchmark Results
|
# Greywall Benchmark Results
|
||||||
|
|
||||||
**Platform:** $OS $ARCH
|
**Platform:** $OS $ARCH
|
||||||
**Kernel:** $KERNEL
|
**Kernel:** $KERNEL
|
||||||
**Date:** $DATE
|
**Date:** $DATE
|
||||||
**Fence:** $($FENCE_BIN --version 2>/dev/null || echo unknown)
|
**Greywall:** $($GREYWALL_BIN --version 2>/dev/null || echo unknown)
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
|||||||
@@ -150,4 +150,4 @@ git push origin "$NEW_VERSION"
|
|||||||
echo ""
|
echo ""
|
||||||
info "✓ Released $NEW_VERSION"
|
info "✓ Released $NEW_VERSION"
|
||||||
info "GitHub Actions will now build and publish the release."
|
info "GitHub Actions will now build and publish the release."
|
||||||
info "Watch progress at: https://github.com/Use-Tusk/fence/actions"
|
info "Watch progress at: https://gitea.app.monadical.io/monadical/greywall/actions"
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# smoke_test.sh - Run smoke tests against the fence binary
|
# smoke_test.sh - Run smoke tests against the greywall binary
|
||||||
#
|
#
|
||||||
# This script tests the compiled fence binary to ensure basic functionality works.
|
# This script tests the compiled greywall binary to ensure basic functionality works.
|
||||||
# Unlike integration tests (which test internal APIs), smoke tests verify the
|
# Unlike integration tests (which test internal APIs), smoke tests verify the
|
||||||
# final artifact behaves correctly.
|
# final artifact behaves correctly.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./scripts/smoke_test.sh [path-to-fence-binary]
|
# ./scripts/smoke_test.sh [path-to-greywall-binary]
|
||||||
#
|
#
|
||||||
# If no path is provided, it will look for ./fence or use 'go run'.
|
# If no path is provided, it will look for ./greywall or use 'go run'.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -21,25 +21,25 @@ PASSED=0
|
|||||||
FAILED=0
|
FAILED=0
|
||||||
SKIPPED=0
|
SKIPPED=0
|
||||||
|
|
||||||
FENCE_BIN="${1:-}"
|
GREYWALL_BIN="${1:-}"
|
||||||
if [[ -z "$FENCE_BIN" ]]; then
|
if [[ -z "$GREYWALL_BIN" ]]; then
|
||||||
if [[ -x "./fence" ]]; then
|
if [[ -x "./greywall" ]]; then
|
||||||
FENCE_BIN="./fence"
|
GREYWALL_BIN="./greywall"
|
||||||
elif [[ -x "./dist/fence" ]]; then
|
elif [[ -x "./dis./greywall" ]]; then
|
||||||
FENCE_BIN="./dist/fence"
|
GREYWALL_BIN="./dis./greywall"
|
||||||
else
|
else
|
||||||
echo "Building fence..."
|
echo "Building greywall..."
|
||||||
go build -o ./fence ./cmd/fence
|
go build -o ./greywall ./cm./greywall
|
||||||
FENCE_BIN="./fence"
|
GREYWALL_BIN="./greywall"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -x "$FENCE_BIN" ]]; then
|
if [[ ! -x "$GREYWALL_BIN" ]]; then
|
||||||
echo "Error: fence binary not found at $FENCE_BIN"
|
echo "Error: greywall binary not found at $GREYWALL_BIN"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Using fence binary: $FENCE_BIN"
|
echo "Using greywall binary: $GREYWALL_BIN"
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
|
|
||||||
# Create temp workspace in current directory (not /tmp, which gets overlaid by bwrap --tmpfs)
|
# Create temp workspace in current directory (not /tmp, which gets overlaid by bwrap --tmpfs)
|
||||||
@@ -100,16 +100,16 @@ echo "=== Basic Functionality ==="
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Test: Version flag works
|
# Test: Version flag works
|
||||||
run_test "version flag" "pass" "$FENCE_BIN" --version
|
run_test "version flag" "pass" "$GREYWALL_BIN" --version
|
||||||
|
|
||||||
# Test: Echo works
|
# Test: Echo works
|
||||||
run_test "echo command" "pass" "$FENCE_BIN" -c "echo hello"
|
run_test "echo command" "pass" "$GREYWALL_BIN" -c "echo hello"
|
||||||
|
|
||||||
# Test: ls works
|
# Test: ls works
|
||||||
run_test "ls command" "pass" "$FENCE_BIN" -- ls
|
run_test "ls command" "pass" "$GREYWALL_BIN" -- ls
|
||||||
|
|
||||||
# Test: pwd works
|
# Test: pwd works
|
||||||
run_test "pwd command" "pass" "$FENCE_BIN" -- pwd
|
run_test "pwd command" "pass" "$GREYWALL_BIN" -- pwd
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Filesystem Restrictions ==="
|
echo "=== Filesystem Restrictions ==="
|
||||||
@@ -117,11 +117,11 @@ echo ""
|
|||||||
|
|
||||||
# Test: Read existing file works
|
# Test: Read existing file works
|
||||||
echo "test content" > "$WORKSPACE/test.txt"
|
echo "test content" > "$WORKSPACE/test.txt"
|
||||||
run_test "read file in workspace" "pass" "$FENCE_BIN" -c "cat $WORKSPACE/test.txt"
|
run_test "read file in workspace" "pass" "$GREYWALL_BIN" -c "cat $WORKSPACE/test.txt"
|
||||||
|
|
||||||
# Test: Write outside workspace blocked
|
# Test: Write outside workspace blocked
|
||||||
# Create a settings file that only allows write to current workspace
|
# Create a settings file that only allows write to current workspace
|
||||||
SETTINGS_FILE="$WORKSPACE/fence.json"
|
SETTINGS_FILE="$WORKSPAC./greywall.json"
|
||||||
cat > "$SETTINGS_FILE" << EOF
|
cat > "$SETTINGS_FILE" << EOF
|
||||||
{
|
{
|
||||||
"filesystem": {
|
"filesystem": {
|
||||||
@@ -131,14 +131,14 @@ cat > "$SETTINGS_FILE" << EOF
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Note: Use /var/tmp since /tmp is mounted as tmpfs (writable but ephemeral) inside the sandbox
|
# Note: Use /var/tmp since /tmp is mounted as tmpfs (writable but ephemeral) inside the sandbox
|
||||||
OUTSIDE_FILE="/var/tmp/outside-fence-test-$$.txt"
|
OUTSIDE_FILE="/var/tmp/outside-greywall-test-$$.txt"
|
||||||
run_test "write outside workspace blocked" "fail" "$FENCE_BIN" -s "$SETTINGS_FILE" -c "touch $OUTSIDE_FILE"
|
run_test "write outside workspace blocked" "fail" "$GREYWALL_BIN" -s "$SETTINGS_FILE" -c "touch $OUTSIDE_FILE"
|
||||||
|
|
||||||
# Cleanup in case it wasn't blocked
|
# Cleanup in case it wasn't blocked
|
||||||
rm -f "$OUTSIDE_FILE" 2>/dev/null || true
|
rm -f "$OUTSIDE_FILE" 2>/dev/null || true
|
||||||
|
|
||||||
# Test: Write inside workspace allowed (using the workspace path in -c)
|
# Test: Write inside workspace allowed (using the workspace path in -c)
|
||||||
run_test "write inside workspace allowed" "pass" "$FENCE_BIN" -s "$SETTINGS_FILE" -c "touch $WORKSPACE/new-file.txt"
|
run_test "write inside workspace allowed" "pass" "$GREYWALL_BIN" -s "$SETTINGS_FILE" -c "touch $WORKSPACE/new-file.txt"
|
||||||
|
|
||||||
# Check file was actually created
|
# Check file was actually created
|
||||||
if [[ -f "$WORKSPACE/new-file.txt" ]]; then
|
if [[ -f "$WORKSPACE/new-file.txt" ]]; then
|
||||||
@@ -166,16 +166,16 @@ cat > "$SETTINGS_FILE" << EOF
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Test: Denied command is blocked
|
# Test: Denied command is blocked
|
||||||
run_test "blocked command (rm -rf)" "fail" "$FENCE_BIN" -s "$SETTINGS_FILE" -c "rm -rf /tmp/test"
|
run_test "blocked command (rm -rf)" "fail" "$GREYWALL_BIN" -s "$SETTINGS_FILE" -c "rm -rf /tmp/test"
|
||||||
|
|
||||||
# Test: Similar but not blocked command works (rm without -rf)
|
# Test: Similar but not blocked command works (rm without -rf)
|
||||||
run_test "allowed command (echo)" "pass" "$FENCE_BIN" -s "$SETTINGS_FILE" -c "echo safe command"
|
run_test "allowed command (echo)" "pass" "$GREYWALL_BIN" -s "$SETTINGS_FILE" -c "echo safe command"
|
||||||
|
|
||||||
# Test: Chained command with blocked command
|
# Test: Chained command with blocked command
|
||||||
run_test "chained blocked command" "fail" "$FENCE_BIN" -s "$SETTINGS_FILE" -c "ls && rm -rf /tmp/test"
|
run_test "chained blocked command" "fail" "$GREYWALL_BIN" -s "$SETTINGS_FILE" -c "ls && rm -rf /tmp/test"
|
||||||
|
|
||||||
# Test: Nested shell with blocked command
|
# Test: Nested shell with blocked command
|
||||||
run_test "nested shell blocked command" "fail" "$FENCE_BIN" -s "$SETTINGS_FILE" -c 'bash -c "rm -rf /tmp/test"'
|
run_test "nested shell blocked command" "fail" "$GREYWALL_BIN" -s "$SETTINGS_FILE" -c 'bash -c "rm -rf /tmp/test"'
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Network Restrictions ==="
|
echo "=== Network Restrictions ==="
|
||||||
@@ -196,7 +196,7 @@ EOF
|
|||||||
if command_exists curl; then
|
if command_exists curl; then
|
||||||
# Test: Network blocked by default - curl should fail or return blocked message
|
# Test: Network blocked by default - curl should fail or return blocked message
|
||||||
# Use curl's own timeout (no need for external timeout command)
|
# Use curl's own timeout (no need for external timeout command)
|
||||||
output=$("$FENCE_BIN" -s "$SETTINGS_FILE" -c "curl -s --connect-timeout 2 --max-time 3 http://example.com" 2>&1) || true
|
output=$("$GREYWALL_BIN" -s "$SETTINGS_FILE" -c "curl -s --connect-timeout 2 --max-time 3 http://example.com" 2>&1) || true
|
||||||
if echo "$output" | grep -qi "blocked\|refused\|denied\|timeout\|error"; then
|
if echo "$output" | grep -qi "blocked\|refused\|denied\|timeout\|error"; then
|
||||||
echo -e "Testing: network blocked (curl)... ${GREEN}PASS${NC}"
|
echo -e "Testing: network blocked (curl)... ${GREEN}PASS${NC}"
|
||||||
PASSED=$((PASSED + 1))
|
PASSED=$((PASSED + 1))
|
||||||
@@ -218,8 +218,8 @@ else
|
|||||||
skip_test "network blocked (curl)" "curl not installed"
|
skip_test "network blocked (curl)" "curl not installed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test with allowed domain (only if FENCE_TEST_NETWORK is set)
|
# Test with allowed domain (only if GREYWALL_TEST_NETWORK is set)
|
||||||
if [[ "${FENCE_TEST_NETWORK:-}" == "1" ]]; then
|
if [[ "${GREYWALL_TEST_NETWORK:-}" == "1" ]]; then
|
||||||
cat > "$SETTINGS_FILE" << EOF
|
cat > "$SETTINGS_FILE" << EOF
|
||||||
{
|
{
|
||||||
"network": {
|
"network": {
|
||||||
@@ -231,12 +231,12 @@ if [[ "${FENCE_TEST_NETWORK:-}" == "1" ]]; then
|
|||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
if command_exists curl; then
|
if command_exists curl; then
|
||||||
run_test "allowed domain works" "pass" "$FENCE_BIN" -s "$SETTINGS_FILE" -c "curl -s --connect-timeout 5 --max-time 10 https://httpbin.org/get"
|
run_test "allowed domain works" "pass" "$GREYWALL_BIN" -s "$SETTINGS_FILE" -c "curl -s --connect-timeout 5 --max-time 10 https://httpbin.org/get"
|
||||||
else
|
else
|
||||||
skip_test "allowed domain works" "curl not installed"
|
skip_test "allowed domain works" "curl not installed"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
skip_test "allowed domain works" "FENCE_TEST_NETWORK not set"
|
skip_test "allowed domain works" "GREYWALL_TEST_NETWORK not set"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -244,25 +244,25 @@ echo "=== Tool Compatibility ==="
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if command_exists python3; then
|
if command_exists python3; then
|
||||||
run_test "python3 works" "pass" "$FENCE_BIN" -c "python3 -c 'print(1+1)'"
|
run_test "python3 works" "pass" "$GREYWALL_BIN" -c "python3 -c 'print(1+1)'"
|
||||||
else
|
else
|
||||||
skip_test "python3 works" "python3 not installed"
|
skip_test "python3 works" "python3 not installed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command_exists node; then
|
if command_exists node; then
|
||||||
run_test "node works" "pass" "$FENCE_BIN" -c "node -e 'console.log(1+1)'"
|
run_test "node works" "pass" "$GREYWALL_BIN" -c "node -e 'console.log(1+1)'"
|
||||||
else
|
else
|
||||||
skip_test "node works" "node not installed"
|
skip_test "node works" "node not installed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command_exists git; then
|
if command_exists git; then
|
||||||
run_test "git version works" "pass" "$FENCE_BIN" -- git --version
|
run_test "git version works" "pass" "$GREYWALL_BIN" -- git --version
|
||||||
else
|
else
|
||||||
skip_test "git version works" "git not installed"
|
skip_test "git version works" "git not installed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command_exists rg; then
|
if command_exists rg; then
|
||||||
run_test "ripgrep works" "pass" "$FENCE_BIN" -- rg --version
|
run_test "ripgrep works" "pass" "$GREYWALL_BIN" -- rg --version
|
||||||
else
|
else
|
||||||
skip_test "ripgrep works" "rg not installed"
|
skip_test "ripgrep works" "rg not installed"
|
||||||
fi
|
fi
|
||||||
@@ -271,8 +271,8 @@ echo ""
|
|||||||
echo "=== Environment ==="
|
echo "=== Environment ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Test: FENCE_SANDBOX env var is set
|
# Test: GREYWALL_SANDBOX env var is set
|
||||||
run_test "FENCE_SANDBOX set" "pass" "$FENCE_BIN" -c 'test "$FENCE_SANDBOX" = "1"'
|
run_test "GREYWALL_SANDBOX set" "pass" "$GREYWALL_BIN" -c 'test "$GREYWALL_SANDBOX" = "1"'
|
||||||
|
|
||||||
# Test: Proxy env vars are set when network is configured
|
# Test: Proxy env vars are set when network is configured
|
||||||
cat > "$SETTINGS_FILE" << EOF
|
cat > "$SETTINGS_FILE" << EOF
|
||||||
@@ -286,7 +286,7 @@ cat > "$SETTINGS_FILE" << EOF
|
|||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
run_test "HTTP_PROXY set" "pass" "$FENCE_BIN" -s "$SETTINGS_FILE" -c 'test -n "$HTTP_PROXY"'
|
run_test "HTTP_PROXY set" "pass" "$GREYWALL_BIN" -s "$SETTINGS_FILE" -c 'test -n "$HTTP_PROXY"'
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
|
|||||||
Reference in New Issue
Block a user