Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CI

on:
push:
pull_request:

jobs:
test:
name: Test
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
check-latest: true

- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Download dependencies
run: go mod download

- name: Verify dependencies
run: go mod verify

- name: Run tests
run: go test -v -race ./...

- name: Run build
run: go build -v ./...
18 changes: 9 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"syscall"
"time"

"github.com/coder/jail/netjail"
"github.com/coder/jail/network"
"github.com/coder/jail/proxy"
"github.com/coder/jail/rules"
"github.com/coder/jail/tls"
Expand Down Expand Up @@ -181,15 +181,15 @@ func runJail(inv *serpent.Invocation) error {
}

// Create network jail configuration
netjailConfig := netjail.Config{
networkConfig := network.JailConfig{
HTTPPort: 8040,
HTTPSPort: 8043,
NetJailName: "jail",
SkipCleanup: noJailCleanup,
}

// Create network jail
netjailInstance, err := netjail.NewNetJail(netjailConfig, logger)
networkInstance, err := network.NewJail(networkConfig, logger)
if err != nil {
logger.Error("Failed to create network jail", "error", err)
return fmt.Errorf("failed to create network jail: %v", err)
Expand All @@ -203,7 +203,7 @@ func runJail(inv *serpent.Invocation) error {
go func() {
sig := <-sigChan
logger.Info("Received signal during setup, cleaning up...", "signal", sig)
if err := netjailInstance.Cleanup(); err != nil {
if err := networkInstance.Cleanup(); err != nil {
logger.Error("Emergency cleanup failed", "error", err)
}
os.Exit(1)
Expand All @@ -212,23 +212,23 @@ func runJail(inv *serpent.Invocation) error {
// Ensure cleanup happens no matter what
defer func() {
logger.Debug("Starting cleanup process")
if err := netjailInstance.Cleanup(); err != nil {
if err := networkInstance.Cleanup(); err != nil {
logger.Error("Failed to cleanup network jail", "error", err)
} else {
logger.Debug("Cleanup completed successfully")
}
}()

// Setup network jail
if err := netjailInstance.Setup(netjailConfig.HTTPPort, netjailConfig.HTTPSPort); err != nil {
if err := networkInstance.Setup(networkConfig.HTTPPort, networkConfig.HTTPSPort); err != nil {
logger.Error("Failed to setup network jail", "error", err)
return fmt.Errorf("failed to setup network jail: %v", err)
}

// Create proxy server
proxyConfig := proxy.Config{
HTTPPort: netjailConfig.HTTPPort,
HTTPSPort: netjailConfig.HTTPSPort,
HTTPPort: networkConfig.HTTPPort,
HTTPSPort: networkConfig.HTTPSPort,
RuleEngine: ruleEngine,
Logger: logger,
TLSConfig: tlsConfig,
Expand All @@ -253,7 +253,7 @@ func runJail(inv *serpent.Invocation) error {
// Execute command in network jail
go func() {
defer cancel()
if err := netjailInstance.Execute(args, extraEnv); err != nil {
if err := networkInstance.Execute(args, extraEnv); err != nil {
logger.Error("Command execution failed", "error", err)
}
}()
Expand Down
13 changes: 0 additions & 13 deletions netjail/linux_stub.go

This file was deleted.

10 changes: 0 additions & 10 deletions netjail/macos_stub.go

This file was deleted.

48 changes: 26 additions & 22 deletions netjail/linux.go → network/linux.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//go:build linux

package netjail
package network

import (
"fmt"
Expand All @@ -11,27 +11,31 @@ import (
"time"
)

// LinuxNetJail implements NetJail using Linux network namespaces
type LinuxNetJail struct {
config Config
const (
namespacePrefix = "coder_jail"
)

// LinuxJail implements NetJail using Linux network namespaces
type LinuxJail struct {
config JailConfig
namespace string
logger *slog.Logger
}

// newLinuxNetJail creates a new Linux network jail instance
func newLinuxNetJail(config Config, logger *slog.Logger) (*LinuxNetJail, error) {
// newLinuxJail creates a new Linux network jail instance
func newLinuxJail(config JailConfig, logger *slog.Logger) (*LinuxJail, error) {
// Generate unique namespace name
namespace := fmt.Sprintf("boundary_%d", time.Now().UnixNano()%10000000)
namespace := fmt.Sprintf("%s_%d", namespacePrefix, time.Now().UnixNano()%10000000)

return &LinuxNetJail{
return &LinuxJail{
config: config,
namespace: namespace,
logger: logger,
}, nil
}

// Setup creates network namespace and configures iptables rules
func (l *LinuxNetJail) Setup(httpPort, httpsPort int) error {
func (l *LinuxJail) Setup(httpPort, httpsPort int) error {
l.logger.Debug("Setup called", "httpPort", httpPort, "httpsPort", httpsPort)
l.config.HTTPPort = httpPort
l.config.HTTPSPort = httpsPort
Expand Down Expand Up @@ -70,7 +74,7 @@ func (l *LinuxNetJail) Setup(httpPort, httpsPort int) error {
}

// Execute runs a command within the network namespace
func (l *LinuxNetJail) Execute(command []string, extraEnv map[string]string) error {
func (l *LinuxJail) Execute(command []string, extraEnv map[string]string) error {
l.logger.Debug("Execute called", "command", command)
if len(command) == 0 {
return fmt.Errorf("no command specified")
Expand All @@ -81,7 +85,7 @@ func (l *LinuxNetJail) Execute(command []string, extraEnv map[string]string) err
cmdArgs := []string{"ip", "netns", "exec", l.namespace}
cmdArgs = append(cmdArgs, command...)
l.logger.Debug("Full command args", "args", cmdArgs)

cmd := exec.Command("ip", cmdArgs[1:]...)

// Set up environment
Expand Down Expand Up @@ -124,7 +128,7 @@ func (l *LinuxNetJail) Execute(command []string, extraEnv map[string]string) err
}

// Cleanup removes the network namespace and iptables rules
func (l *LinuxNetJail) Cleanup() error {
func (l *LinuxJail) Cleanup() error {
if l.config.SkipCleanup {
return nil
}
Expand Down Expand Up @@ -152,7 +156,7 @@ func (l *LinuxNetJail) Cleanup() error {
}

// createNamespace creates a new network namespace
func (l *LinuxNetJail) createNamespace() error {
func (l *LinuxJail) createNamespace() error {
cmd := exec.Command("ip", "netns", "add", l.namespace)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to create namespace: %v", err)
Expand All @@ -161,12 +165,12 @@ func (l *LinuxNetJail) createNamespace() error {
}

// setupNetworking configures networking within the namespace
func (l *LinuxNetJail) setupNetworking() error {
func (l *LinuxJail) setupNetworking() error {
// Create veth pair with short names (Linux interface names limited to 15 chars)
// Generate unique ID to avoid conflicts
uniqueID := fmt.Sprintf("%d", time.Now().UnixNano()%10000000) // 7 digits max
vethHost := fmt.Sprintf("veth_h_%s", uniqueID) // veth_h_1234567 = 14 chars
vethNetJail := fmt.Sprintf("veth_n_%s", uniqueID) // veth_n_1234567 = 14 chars
vethHost := fmt.Sprintf("veth_h_%s", uniqueID) // veth_h_1234567 = 14 chars
vethNetJail := fmt.Sprintf("veth_n_%s", uniqueID) // veth_n_1234567 = 14 chars

cmd := exec.Command("ip", "link", "add", vethHost, "type", "veth", "peer", "name", vethNetJail)
if err := cmd.Run(); err != nil {
Expand Down Expand Up @@ -218,7 +222,7 @@ func (l *LinuxNetJail) setupNetworking() error {
// setupDNS configures DNS resolution for the namespace
// This ensures reliable DNS resolution by using public DNS servers
// instead of relying on the host's potentially complex DNS configuration
func (l *LinuxNetJail) setupDNS() error {
func (l *LinuxJail) setupDNS() error {
// Always create namespace-specific resolv.conf with reliable public DNS servers
// This avoids issues with systemd-resolved, Docker DNS, and other complex setups
netnsEtc := fmt.Sprintf("/etc/netns/%s", l.namespace)
Expand All @@ -228,7 +232,7 @@ func (l *LinuxNetJail) setupDNS() error {

// Write custom resolv.conf with multiple reliable public DNS servers
resolvConfPath := fmt.Sprintf("%s/resolv.conf", netnsEtc)
dnsConfig := `# Custom DNS for boundary namespace
dnsConfig := `# Custom DNS for network namespace
nameserver 8.8.8.8
nameserver 8.8.4.4
nameserver 1.1.1.1
Expand All @@ -244,7 +248,7 @@ options timeout:2 attempts:2
}

// setupIptables configures iptables rules for traffic redirection
func (l *LinuxNetJail) setupIptables() error {
func (l *LinuxJail) setupIptables() error {
// Enable IP forwarding
cmd := exec.Command("sysctl", "-w", "net.ipv4.ip_forward=1")
cmd.Run() // Ignore error
Expand Down Expand Up @@ -273,7 +277,7 @@ func (l *LinuxNetJail) setupIptables() error {
}

// removeIptables removes iptables rules
func (l *LinuxNetJail) removeIptables() error {
func (l *LinuxJail) removeIptables() error {
// Remove NAT rule
cmd := exec.Command("iptables", "-t", "nat", "-D", "POSTROUTING", "-s", "192.168.100.0/24", "-j", "MASQUERADE")
cmd.Run() // Ignore errors during cleanup
Expand All @@ -282,10 +286,10 @@ func (l *LinuxNetJail) removeIptables() error {
}

// removeNamespace removes the network namespace
func (l *LinuxNetJail) removeNamespace() error {
func (l *LinuxJail) removeNamespace() error {
cmd := exec.Command("ip", "netns", "del", l.namespace)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to remove namespace: %v", err)
}
return nil
}
}
13 changes: 13 additions & 0 deletions network/linux_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build !linux

package network

import (
"fmt"
"log/slog"
)

// newLinuxJail is not available on non-Linux platforms
func newLinuxJail(_ JailConfig, _ *slog.Logger) (Jail, error) {
return nil, fmt.Errorf("linux network jail not supported on this platform")
}
19 changes: 9 additions & 10 deletions netjail/macos.go → network/macos.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
//go:build darwin

package netjail
package network

import (
"fmt"
"io/ioutil"
"log/slog"
"os"
"os/exec"
Expand All @@ -14,21 +13,21 @@ import (
)

const (
PF_ANCHOR_NAME = "boundary"
GROUP_NAME = "boundary"
PF_ANCHOR_NAME = "network"
GROUP_NAME = "network"
)

// MacOSNetJail implements network jail using macOS PF (Packet Filter) and group-based isolation
type MacOSNetJail struct {
config Config
config JailConfig
groupID int
pfRulesPath string
mainRulesPath string
logger *slog.Logger
}

// newMacOSNetJail creates a new macOS network jail instance
func newMacOSNetJail(config Config, logger *slog.Logger) (*MacOSNetJail, error) {
// newMacOSJail creates a new macOS network jail instance
func newMacOSJail(config JailConfig, logger *slog.Logger) (*MacOSNetJail, error) {
pfRulesPath := fmt.Sprintf("/tmp/%s.pf", config.NetJailName)
mainRulesPath := fmt.Sprintf("/tmp/%s_main.pf", config.NetJailName)

Expand Down Expand Up @@ -266,7 +265,7 @@ func (m *MacOSNetJail) setupPFRules() error {
}

// Write rules to temp file
if err := ioutil.WriteFile(m.pfRulesPath, []byte(rules), 0644); err != nil {
if err := os.WriteFile(m.pfRulesPath, []byte(rules), 0644); err != nil {
return fmt.Errorf("failed to write PF rules file: %v", err)
}

Expand Down Expand Up @@ -297,7 +296,7 @@ anchor "%s"
`, PF_ANCHOR_NAME, PF_ANCHOR_NAME)

// Write and load the main ruleset
if err := ioutil.WriteFile(m.mainRulesPath, []byte(mainRules), 0644); err != nil {
if err := os.WriteFile(m.mainRulesPath, []byte(mainRules), 0644); err != nil {
return fmt.Errorf("failed to write main PF rules: %v", err)
}

Expand Down Expand Up @@ -335,4 +334,4 @@ func (m *MacOSNetJail) cleanupTempFiles() {
if m.mainRulesPath != "" {
os.Remove(m.mainRulesPath)
}
}
}
10 changes: 10 additions & 0 deletions network/macos_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build !darwin

package network

import "log/slog"

// newMacOSJail is not available on non-macOS platforms
func newMacOSJail(config JailConfig, logger *slog.Logger) (Jail, error) {
panic("macOS network jail not available on this platform")
}
Loading
Loading