11package cli
22
33import (
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
11370func 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