Skip to content

Commit b0e7507

Browse files
blink-so[bot]f0ssel
andcommitted
create jail package interface in repository root
Moved jail logic from CLI into a reusable library interface: - jail.Config struct with all configuration options - jail.Jail struct representing a jail instance - jail.New() constructor - jail.Start(), Execute(), Run(), Stop() methods Simplified CLI to use the jail package as a thin wrapper. Maintained full CLI compatibility while enabling library usage. Co-authored-by: f0ssel <[email protected]>
1 parent 22ade90 commit b0e7507

File tree

2 files changed

+335
-209
lines changed

2 files changed

+335
-209
lines changed

cli/cli.go

Lines changed: 40 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
11
package cli
22

33
import (
4-
"context"
5-
cryptotls "crypto/tls"
64
"fmt"
75
"log/slog"
86
"os"
9-
"os/signal"
10-
"path/filepath"
11-
"strings"
12-
"syscall"
13-
"time"
147

15-
"github.com/coder/jail/audit"
16-
"github.com/coder/jail/network"
17-
"github.com/coder/jail/proxy"
18-
"github.com/coder/jail/rules"
19-
"github.com/coder/jail/tls"
8+
"github.com/coder/jail"
209
"github.com/coder/serpent"
2110
)
2211

@@ -49,32 +38,24 @@ Examples:
4938
# Block everything by default (implicit)`,
5039
Options: serpent.OptionSet{
5140
{
52-
Name: "allow",
5341
Flag: "allow",
54-
Env: "JAIL_ALLOW",
55-
Description: "Allow rule (can be specified multiple times). Format: 'pattern' or 'METHOD[,METHOD] pattern'.",
42+
Description: "Allow rule (repeatable). Format: \"pattern\" or \"METHOD[,METHOD] pattern\"",
5643
Value: serpent.StringArrayOf(&config.AllowStrings),
5744
},
5845
{
59-
Name: "no-tls-intercept",
60-
Flag: "no-tls-intercept",
61-
Env: "JAIL_NO_TLS_INTERCEPT",
62-
Description: "Disable HTTPS interception.",
63-
Value: serpent.BoolOf(&config.NoTLSIntercept),
64-
},
65-
{
66-
Name: "log-level",
6746
Flag: "log-level",
68-
Env: "JAIL_LOG_LEVEL",
69-
Description: "Set log level (error, warn, info, debug).",
47+
Description: "Set log level (error, warn, info, debug)",
7048
Default: "warn",
7149
Value: serpent.StringOf(&config.LogLevel),
7250
},
7351
{
74-
Name: "no-jail-cleanup",
52+
Flag: "no-tls-intercept",
53+
Description: "Disable HTTPS interception",
54+
Value: serpent.BoolOf(&config.NoTLSIntercept),
55+
},
56+
{
7557
Flag: "no-jail-cleanup",
76-
Env: "JAIL_NO_JAIL_CLEANUP",
77-
Description: "Skip jail cleanup (hidden flag for testing).",
58+
Description: "Disable jail cleanup (for debugging)",
7859
Value: serpent.BoolOf(&config.NoJailCleanup),
7960
Hidden: true,
8061
},
@@ -85,31 +66,7 @@ Examples:
8566
}
8667
}
8768

88-
// setupLogging creates a slog logger with the specified level
89-
func setupLogging(logLevel string) *slog.Logger {
90-
var level slog.Level
91-
switch strings.ToLower(logLevel) {
92-
case "error":
93-
level = slog.LevelError
94-
case "warn":
95-
level = slog.LevelWarn
96-
case "info":
97-
level = slog.LevelInfo
98-
case "debug":
99-
level = slog.LevelDebug
100-
default:
101-
level = slog.LevelWarn // Default to warn if invalid level
102-
}
103-
104-
// Create a standard slog logger with the appropriate level
105-
handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
106-
Level: level,
107-
})
108-
109-
return slog.New(handler)
110-
}
111-
112-
// Run executes the jail command with the given configuration and arguments
69+
// Run executes the jail with the given configuration and command arguments
11370
func Run(config Config, args []string) error {
11471
logger := setupLogging(config.LogLevel)
11572

@@ -118,172 +75,46 @@ func Run(config Config, args []string) error {
11875
return fmt.Errorf("no command specified")
11976
}
12077

121-
// Parse allow list; default to deny-all if none provided
78+
// Warn if no allow rules specified
12279
if len(config.AllowStrings) == 0 {
12380
logger.Warn("No allow rules specified; all network traffic will be denied by default")
12481
}
12582

126-
allowRules, err := rules.ParseAllowSpecs(config.AllowStrings)
127-
if err != nil {
128-
logger.Error("Failed to parse allow rules", "error", err)
129-
return fmt.Errorf("failed to parse allow rules: %v", err)
83+
// Create jail configuration
84+
jailConfig := jail.Config{
85+
AllowRules: config.AllowStrings,
86+
NoTLSIntercept: config.NoTLSIntercept,
87+
Logger: logger,
88+
SkipCleanup: config.NoJailCleanup,
13089
}
13190

132-
// Implicit final deny-all is handled by the RuleEngine default behavior when no rules match.
133-
// Build final rules slice in order: user allows only.
134-
ruleList := allowRules
135-
136-
// Create rule engine
137-
ruleEngine := rules.NewRuleEngine(ruleList, logger)
138-
139-
// Get configuration directory
140-
configDir, err := tls.GetConfigDir()
91+
// Create jail instance
92+
j, err := jail.New(jailConfig)
14193
if err != nil {
142-
logger.Error("Failed to get config directory", "error", err)
143-
return fmt.Errorf("failed to get config directory: %v", err)
144-
}
145-
146-
// Create certificate manager (if TLS interception is enabled)
147-
var certManager *tls.CertificateManager
148-
var tlsConfig *cryptotls.Config
149-
var extraEnv map[string]string = make(map[string]string)
150-
151-
if !config.NoTLSIntercept {
152-
certManager, err = tls.NewCertificateManager(configDir, logger)
153-
if err != nil {
154-
logger.Error("Failed to create certificate manager", "error", err)
155-
return fmt.Errorf("failed to create certificate manager: %v", err)
156-
}
157-
158-
tlsConfig = certManager.GetTLSConfig()
159-
160-
// Get CA certificate for environment
161-
caCertPEM, err := certManager.GetCACertPEM()
162-
if err != nil {
163-
logger.Error("Failed to get CA certificate", "error", err)
164-
return fmt.Errorf("failed to get CA certificate: %v", err)
165-
}
166-
167-
// Write CA certificate to a temporary file for tools that need a file path
168-
caCertPath := filepath.Join(configDir, "ca-cert.pem")
169-
err = os.WriteFile(caCertPath, caCertPEM, 0644)
170-
if err != nil {
171-
logger.Error("Failed to write CA certificate file", "error", err)
172-
return fmt.Errorf("failed to write CA certificate file: %v", err)
173-
}
174-
175-
// Set standard CA certificate environment variables for common tools
176-
// This makes tools like curl, git, etc. trust our dynamically generated CA
177-
extraEnv["SSL_CERT_FILE"] = caCertPath // OpenSSL/LibreSSL-based tools
178-
extraEnv["SSL_CERT_DIR"] = configDir // OpenSSL certificate directory
179-
extraEnv["CURL_CA_BUNDLE"] = caCertPath // curl
180-
extraEnv["GIT_SSL_CAINFO"] = caCertPath // Git
181-
extraEnv["REQUESTS_CA_BUNDLE"] = caCertPath // Python requests
182-
extraEnv["NODE_EXTRA_CA_CERTS"] = caCertPath // Node.js
183-
extraEnv["JAIL_CA_CERT"] = string(caCertPEM) // Keep for backward compatibility
94+
return fmt.Errorf("failed to create jail: %v", err)
18495
}
18596

186-
// Create network jail configuration
187-
networkConfig := network.JailConfig{
188-
HTTPPort: 8040,
189-
HTTPSPort: 8043,
190-
NetJailName: "jail",
191-
SkipCleanup: config.NoJailCleanup,
192-
}
193-
194-
// Create network jail
195-
networkInstance, err := network.NewJail(networkConfig, logger)
196-
if err != nil {
197-
logger.Error("Failed to create network jail", "error", err)
198-
return fmt.Errorf("failed to create network jail: %v", err)
199-
}
200-
201-
// Setup signal handling BEFORE any network setup
202-
sigChan := make(chan os.Signal, 1)
203-
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
204-
205-
// Handle signals immediately in background
206-
go func() {
207-
sig := <-sigChan
208-
logger.Info("Received signal during setup, cleaning up...", "signal", sig)
209-
err := networkInstance.Cleanup()
210-
if err != nil {
211-
logger.Error("Emergency cleanup failed", "error", err)
212-
}
213-
os.Exit(1)
214-
}()
215-
216-
// Ensure cleanup happens no matter what
217-
defer func() {
218-
logger.Debug("Starting cleanup process")
219-
err := networkInstance.Cleanup()
220-
if err != nil {
221-
logger.Error("Failed to cleanup network jail", "error", err)
222-
} else {
223-
logger.Debug("Cleanup completed successfully")
224-
}
225-
}()
226-
227-
// Setup network jail
228-
err = networkInstance.Setup(networkConfig.HTTPPort, networkConfig.HTTPSPort)
229-
if err != nil {
230-
logger.Error("Failed to setup network jail", "error", err)
231-
return fmt.Errorf("failed to setup network jail: %v", err)
232-
}
233-
234-
// Create auditor
235-
auditor := audit.NewLoggingAuditor(logger)
236-
237-
// Create proxy server
238-
proxyConfig := proxy.Config{
239-
HTTPPort: networkConfig.HTTPPort,
240-
HTTPSPort: networkConfig.HTTPSPort,
241-
RuleEngine: ruleEngine,
242-
Auditor: auditor,
243-
Logger: logger,
244-
TLSConfig: tlsConfig,
245-
}
246-
247-
proxyServer := proxy.NewProxyServer(proxyConfig)
248-
249-
// Create context for graceful shutdown
250-
ctx, cancel := context.WithCancel(context.Background())
251-
defer cancel()
252-
253-
// Start proxy server in background
254-
go func() {
255-
err := proxyServer.Start(ctx)
256-
if err != nil {
257-
logger.Error("Proxy server error", "error", err)
258-
}
259-
}()
260-
261-
// Give proxy time to start
262-
time.Sleep(100 * time.Millisecond)
263-
264-
// Execute command in network jail
265-
go func() {
266-
defer cancel()
267-
err := networkInstance.Execute(args, extraEnv)
268-
if err != nil {
269-
logger.Error("Command execution failed", "error", err)
270-
}
271-
}()
272-
273-
// Wait for signal or context cancellation
274-
select {
275-
case sig := <-sigChan:
276-
logger.Info("Received signal, shutting down...", "signal", sig)
277-
cancel()
278-
case <-ctx.Done():
279-
// Context cancelled by command completion
280-
}
97+
// Run the command in the jail
98+
return j.Run(args, nil)
99+
}
281100

282-
// Stop proxy server
283-
err = proxyServer.Stop()
284-
if err != nil {
285-
logger.Error("Failed to stop proxy server", "error", err)
101+
// setupLogging configures and returns a logger based on the log level
102+
func setupLogging(level string) *slog.Logger {
103+
var slogLevel slog.Level
104+
switch level {
105+
case "debug":
106+
slogLevel = slog.LevelDebug
107+
case "info":
108+
slogLevel = slog.LevelInfo
109+
case "warn":
110+
slogLevel = slog.LevelWarn
111+
case "error":
112+
slogLevel = slog.LevelError
113+
default:
114+
slogLevel = slog.LevelWarn
286115
}
287116

288-
return nil
117+
return slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
118+
Level: slogLevel,
119+
}))
289120
}

0 commit comments

Comments
 (0)