Skip to content

Commit d16da48

Browse files
committed
Revert "create jail package interface in repository root"
This reverts commit b0e7507.
1 parent 4288e19 commit d16da48

File tree

2 files changed

+209
-335
lines changed

2 files changed

+209
-335
lines changed

cli/cli.go

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

33
import (
4+
"context"
5+
cryptotls "crypto/tls"
46
"fmt"
57
"log/slog"
68
"os"
9+
"os/signal"
10+
"path/filepath"
11+
"strings"
12+
"syscall"
13+
"time"
714

8-
"github.com/coder/jail"
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"
920
"github.com/coder/serpent"
1021
)
1122

@@ -38,24 +49,32 @@ Examples:
3849
# Block everything by default (implicit)`,
3950
Options: serpent.OptionSet{
4051
{
52+
Name: "allow",
4153
Flag: "allow",
42-
Description: "Allow rule (repeatable). Format: \"pattern\" or \"METHOD[,METHOD] pattern\"",
54+
Env: "JAIL_ALLOW",
55+
Description: "Allow rule (can be specified multiple times). Format: 'pattern' or 'METHOD[,METHOD] pattern'.",
4356
Value: serpent.StringArrayOf(&config.AllowStrings),
4457
},
4558
{
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",
4667
Flag: "log-level",
47-
Description: "Set log level (error, warn, info, debug)",
68+
Env: "JAIL_LOG_LEVEL",
69+
Description: "Set log level (error, warn, info, debug).",
4870
Default: "warn",
4971
Value: serpent.StringOf(&config.LogLevel),
5072
},
5173
{
52-
Flag: "no-tls-intercept",
53-
Description: "Disable HTTPS interception",
54-
Value: serpent.BoolOf(&config.NoTLSIntercept),
55-
},
56-
{
74+
Name: "no-jail-cleanup",
5775
Flag: "no-jail-cleanup",
58-
Description: "Disable jail cleanup (for debugging)",
76+
Env: "JAIL_NO_JAIL_CLEANUP",
77+
Description: "Skip jail cleanup (hidden flag for testing).",
5978
Value: serpent.BoolOf(&config.NoJailCleanup),
6079
Hidden: true,
6180
},
@@ -66,7 +85,31 @@ Examples:
6685
}
6786
}
6887

69-
// Run executes the jail with the given configuration and command arguments
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
70113
func Run(config Config, args []string) error {
71114
logger := setupLogging(config.LogLevel)
72115

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

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

83-
// Create jail configuration
84-
jailConfig := jail.Config{
85-
AllowRules: config.AllowStrings,
86-
NoTLSIntercept: config.NoTLSIntercept,
87-
Logger: logger,
88-
SkipCleanup: config.NoJailCleanup,
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)
89130
}
90131

91-
// Create jail instance
92-
j, err := jail.New(jailConfig)
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()
93141
if err != nil {
94-
return fmt.Errorf("failed to create jail: %v", err)
142+
logger.Error("Failed to get config directory", "error", err)
143+
return fmt.Errorf("failed to get config directory: %v", err)
95144
}
96145

97-
// Run the command in the jail
98-
return j.Run(args, nil)
99-
}
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)
100150

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
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
184+
}
185+
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+
}
281+
282+
// Stop proxy server
283+
err = proxyServer.Stop()
284+
if err != nil {
285+
logger.Error("Failed to stop proxy server", "error", err)
115286
}
116287

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

0 commit comments

Comments
 (0)