Skip to content

Commit 9bf2f0f

Browse files
committed
initial cli
0 parents  commit 9bf2f0f

File tree

4 files changed

+200
-0
lines changed

4 files changed

+200
-0
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/coder/squeeze
2+
3+
go 1.25.0

isolation-flow.puml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
@startuml Isolation Flow
2+
participant "User" as U
3+
participant "squeeze (parent)" as P
4+
participant "squeeze (child)" as C
5+
participant "target command" as T
6+
7+
U -> P: squeeze -- command args
8+
activate P
9+
10+
note over P: Parse config, setup proxy
11+
12+
P -> P: fork()
13+
activate C
14+
15+
note over C: Child process starts\nin parent's namespaces
16+
17+
C -> C: unshare(CLONE_NEWUSER)
18+
note over C: Create user namespace\n(enables root inside)
19+
20+
C -> C: unshare(CLONE_NEWNS)
21+
note over C: Create mount namespace\n(isolate filesystem view)
22+
23+
C -> C: unshare(CLONE_NEWNET)
24+
note over C: Create network namespace\n(isolate network)
25+
26+
note over C: Setup mount binds\nfor allowed paths
27+
28+
note over C: Setup network interfaces\nand iptables rules
29+
30+
C -> T: exec(target command)
31+
deactivate C
32+
activate T
33+
34+
note over T: Target runs in\nisolated namespaces
35+
36+
P -> P: wait for child
37+
note over P: Parent monitors child\nand manages proxy
38+
39+
T -> C: exit
40+
deactivate T
41+
C -> P: child exit status
42+
P -> U: return exit status
43+
deactivate P
44+
45+
@enduml

main.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"strings"
8+
9+
"github.com/coder/squeeze/squeeze"
10+
)
11+
12+
func main() {
13+
var configFile = flag.String("config", "", "path to configuration file")
14+
15+
flag.Usage = func() {
16+
fmt.Fprintf(os.Stderr, "Usage: %s [options] -- command [args...]\n", os.Args[0])
17+
fmt.Fprintf(os.Stderr, "\nOptions:\n")
18+
flag.PrintDefaults()
19+
}
20+
21+
flag.Parse()
22+
command := flag.Args()
23+
24+
if len(command) < 1 {
25+
fmt.Fprintf(os.Stderr, "Error: no command specified after --\n")
26+
flag.Usage()
27+
os.Exit(1)
28+
}
29+
30+
// Create basic config for testing (config file loading not implemented yet)
31+
config := squeeze.NewConfig(
32+
squeeze.WithCommand(command[0], command[1:]...),
33+
squeeze.WithWorkingDir("."),
34+
)
35+
36+
if *configFile != "" {
37+
fmt.Printf("Config file specified: %s (not implemented yet)\n", *configFile)
38+
}
39+
40+
fmt.Printf("Running isolated: %s\n", strings.Join(command, " "))
41+
42+
if err := config.RunIsolated(); err != nil {
43+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
44+
os.Exit(1)
45+
}
46+
}

squeeze/squeeze.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package squeeze
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"syscall"
7+
)
8+
9+
const (
10+
CLONE_NEWNET = 0x40000000 // Network namespace
11+
CLONE_NEWNS = 0x00020000 // Mount namespace
12+
CLONE_NEWUSER = 0x10000000 // User namespace
13+
)
14+
15+
// IsolationConfig holds the configuration for running a process in isolated namespaces
16+
type IsolationConfig struct {
17+
ProxyAddr string // Address where the transparent HTTP proxy will listen
18+
AllowedPaths []string // Filesystem paths that will be visible in the mount namespace
19+
Command []string // Command and arguments to execute in isolation
20+
WorkingDir string // Working directory for the isolated process
21+
}
22+
23+
// Option is a functional option for configuring IsolationConfig
24+
type Option func(*IsolationConfig)
25+
26+
// WithProxy sets the address where the transparent HTTP proxy will listen.
27+
// All network traffic from the isolated process will be routed through this proxy.
28+
func WithProxy(addr string) Option {
29+
return func(c *IsolationConfig) {
30+
c.ProxyAddr = addr
31+
}
32+
}
33+
34+
// WithAllowedPath adds a filesystem path that will be visible in the mount namespace.
35+
// This can be called multiple times to allow access to multiple paths.
36+
func WithAllowedPath(path string) Option {
37+
return func(c *IsolationConfig) {
38+
c.AllowedPaths = append(c.AllowedPaths, path)
39+
}
40+
}
41+
42+
// WithCommand sets the command and arguments to execute in the isolated environment.
43+
func WithCommand(cmd string, args ...string) Option {
44+
return func(c *IsolationConfig) {
45+
c.Command = append([]string{cmd}, args...)
46+
}
47+
}
48+
49+
// WithWorkingDir sets the working directory for the isolated process.
50+
func WithWorkingDir(dir string) Option {
51+
return func(c *IsolationConfig) {
52+
c.WorkingDir = dir
53+
}
54+
}
55+
56+
// NewConfig creates a new IsolationConfig with the given options applied.
57+
// It returns a configuration with sensible defaults that can be customized
58+
// using the provided functional options.
59+
func NewConfig(options ...Option) *IsolationConfig {
60+
config := &IsolationConfig{
61+
ProxyAddr: "127.0.0.1:0", // Let OS choose port
62+
WorkingDir: "/tmp", // Safe default working directory
63+
}
64+
65+
for _, option := range options {
66+
option(config)
67+
}
68+
69+
return config
70+
}
71+
72+
// RunIsolated executes the configured command in isolated namespaces.
73+
// The parent process remains in the original namespaces while the child
74+
// runs in isolation with network, mount, and user namespace separation.
75+
func (c *IsolationConfig) RunIsolated() error {
76+
if len(c.Command) == 0 {
77+
return fmt.Errorf("no command specified")
78+
}
79+
80+
// Fork a child process
81+
pid, err := syscall.ForkExec(
82+
"/proc/self/exe", // Re-execute ourselves
83+
[]string{"squeeze-child"}, // Special arg to indicate child mode
84+
&syscall.ProcAttr{
85+
Dir: c.WorkingDir,
86+
Env: os.Environ(),
87+
Files: []uintptr{0, 1, 2}, // stdin, stdout, stderr
88+
},
89+
)
90+
if err != nil {
91+
return fmt.Errorf("failed to fork child process: %w", err)
92+
}
93+
94+
// Parent: wait for child to complete
95+
var status syscall.WaitStatus
96+
_, err = syscall.Wait4(pid, &status, 0, nil)
97+
if err != nil {
98+
return fmt.Errorf("failed to wait for child: %w", err)
99+
}
100+
101+
if !status.Exited() || status.ExitStatus() != 0 {
102+
return fmt.Errorf("child process failed with status: %d", status.ExitStatus())
103+
}
104+
105+
return nil
106+
}

0 commit comments

Comments
 (0)