Skip to content

Commit 00ac2f7

Browse files
Fix command output, proxy setup, and HTTPS forwarding (#47)
* fix: always pipe command to stdout * basic fixes * Fix CONNECT handling in TLS termination path CONNECT requests coming through TLS termination were incorrectly forwarded as regular HTTP requests instead of being handled by handleConnect. This caused 400 errors when clients used CONNECT. The fix adds proper CONNECT detection in handleDecryptedHTTPS, matching the behavior in handleHTTP. * Use http:// for HTTPS_PROXY in unprivileged mode HTTPS_PROXY should use http:// to establish CONNECT tunnels to the proxy, not https://. This is the standard approach for HTTP proxies handling HTTPS traffic via CONNECT. The proxy will still perform TLS termination on the tunneled connections for full request visibility. * Add debugging for TLS connection hangs in CONNECT handling - Add read timeout to detect clients that don't send HTTP requests - Add detailed TLS connection state logging - Better error handling for timeout scenarios This should help diagnose why CONNECT tunnels hang after TLS handshake. * Fix SetReadTimeout compilation error Replace SetReadTimeout (which doesn't exist) with SetReadDeadline. Also reset the deadline after each successful request to allow multiple requests on the same TLS connection. * fix --------- Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
1 parent a5b051d commit 00ac2f7

File tree

3 files changed

+56
-14
lines changed

3 files changed

+56
-14
lines changed

cli/cli.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,13 @@ func Run(ctx context.Context, config Config, args []string) error {
180180
// Execute command in boundary
181181
go func() {
182182
defer cancel()
183-
err := boundaryInstance.Command(args).Run()
183+
cmd := boundaryInstance.Command(args)
184+
cmd.Stderr = os.Stderr
185+
cmd.Stdout = os.Stdout
186+
cmd.Stdin = os.Stdin
187+
188+
logger.Debug("Executing command in boundary", "command", strings.Join(args, " "))
189+
err := cmd.Run()
184190
if err != nil {
185191
logger.Error("Command execution failed", "error", err)
186192
}
@@ -292,4 +298,4 @@ func createJailer(config jail.Config, unprivileged bool) (jail.Jailer, error) {
292298

293299
// Use the DefaultOS function for platform-specific jail creation
294300
return jail.DefaultOS(config)
295-
}
301+
}

jail/unprivileged.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package jail
22

33
import (
4+
"fmt"
45
"log/slog"
56
"os/exec"
67
)
@@ -33,10 +34,15 @@ func NewUnprivileged(config Config) (*Unprivileged, error) {
3334
func (u *Unprivileged) Start() error {
3435
u.logger.Debug("Starting in unprivileged mode")
3536
e := getEnvs(u.configDir, u.caCertPath)
37+
p := fmt.Sprintf("http://localhost:%d", u.httpProxyPort)
3638
u.commandEnv = mergeEnvs(e, map[string]string{
37-
"HOME": u.homeDir,
38-
"USER": u.username,
39-
"LOGNAME": u.username,
39+
"HOME": u.homeDir,
40+
"USER": u.username,
41+
"LOGNAME": u.username,
42+
"HTTP_PROXY": p,
43+
"HTTPS_PROXY": p,
44+
"http_proxy": p,
45+
"https_proxy": p,
4046
})
4147
return nil
4248
}
@@ -53,4 +59,4 @@ func (u *Unprivileged) Command(command []string) *exec.Cmd {
5359
func (u *Unprivileged) Close() error {
5460
u.logger.Debug("Closing unprivileged jail")
5561
return nil
56-
}
62+
}

proxy/proxy.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,20 @@ func (p *Server) handleHTTP(w http.ResponseWriter, r *http.Request) {
133133
}
134134

135135
// Forward regular HTTP request
136-
p.forwardHTTPRequest(w, r)
136+
p.forwardRequest(w, r, false)
137137
}
138138

139-
// forwardHTTPRequest forwards a regular HTTP request
140-
func (p *Server) forwardHTTPRequest(w http.ResponseWriter, r *http.Request) {
139+
// forwardRequest forwards a regular HTTP request
140+
func (p *Server) forwardRequest(w http.ResponseWriter, r *http.Request, https bool) {
141141
p.logger.Debug("forwardHTTPRequest called", "method", r.Method, "url", r.URL.String(), "host", r.Host)
142142

143+
s := "http"
144+
if https {
145+
s = "https"
146+
}
143147
// Create a new request to the target server
144148
targetURL := &url.URL{
145-
Scheme: "http",
149+
Scheme: s,
146150
Host: r.Host,
147151
Path: r.URL.Path,
148152
RawQuery: r.URL.RawQuery,
@@ -303,6 +307,10 @@ func (p *Server) handleConnect(w http.ResponseWriter, r *http.Request) {
303307
}
304308
p.logger.Debug("TLS handshake successful", "hostname", hostname)
305309

310+
// Log connection state after handshake
311+
state := tlsConn.ConnectionState()
312+
p.logger.Debug("TLS connection established", "hostname", hostname, "version", state.Version, "cipher_suite", state.CipherSuite, "negotiated_protocol", state.NegotiatedProtocol)
313+
306314
// Now we have a TLS connection - handle HTTPS requests
307315
p.logger.Debug("Starting HTTPS request handling", "hostname", hostname)
308316
p.handleTLSConnection(tlsConn, hostname)
@@ -313,6 +321,9 @@ func (p *Server) handleConnect(w http.ResponseWriter, r *http.Request) {
313321
func (p *Server) handleTLSConnection(tlsConn *tls.Conn, hostname string) {
314322
p.logger.Debug("Creating HTTP server for TLS connection", "hostname", hostname)
315323

324+
// Set read timeout to detect hanging connections
325+
tlsConn.SetReadDeadline(time.Now().Add(5 * time.Second))
326+
316327
// Use ReadRequest to manually read HTTP requests from the TLS connection
317328
bufReader := bufio.NewReader(tlsConn)
318329
for {
@@ -321,6 +332,8 @@ func (p *Server) handleTLSConnection(tlsConn *tls.Conn, hostname string) {
321332
if err != nil {
322333
if err == io.EOF {
323334
p.logger.Debug("TLS connection closed by client", "hostname", hostname)
335+
} else if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
336+
p.logger.Debug("TLS connection read timeout - client not sending HTTP requests", "hostname", hostname)
324337
} else {
325338
p.logger.Debug("Failed to read HTTP request", "hostname", hostname, "error", err)
326339
}
@@ -350,20 +363,37 @@ func (p *Server) handleTLSConnection(tlsConn *tls.Conn, hostname string) {
350363
p.logger.Debug("Failed to write response", "hostname", hostname, "error", err)
351364
break
352365
}
366+
367+
// Reset read deadline for next request
368+
tlsConn.SetReadDeadline(time.Now().Add(5 * time.Second))
353369
}
354370

355371
p.logger.Debug("TLS connection handling completed", "hostname", hostname)
356372
}
357373

358374
// handleDecryptedHTTPS handles decrypted HTTPS requests and applies rules
359375
func (p *Server) handleDecryptedHTTPS(w http.ResponseWriter, r *http.Request) {
376+
// Handle CONNECT method for HTTPS tunneling
377+
if r.Method == "CONNECT" {
378+
p.handleConnect(w, r)
379+
return
380+
}
381+
382+
fullURL := r.URL.String()
383+
if r.URL.Host == "" {
384+
// Fallback: construct URL from Host header
385+
fullURL = fmt.Sprintf("https://%s%s", r.Host, r.URL.Path)
386+
if r.URL.RawQuery != "" {
387+
fullURL += "?" + r.URL.RawQuery
388+
}
389+
}
360390
// Check if request should be allowed
361-
result := p.ruleEngine.Evaluate(r.Method, r.URL.String())
391+
result := p.ruleEngine.Evaluate(r.Method, fullURL)
362392

363393
// Audit the request
364394
p.auditor.AuditRequest(audit.Request{
365395
Method: r.Method,
366-
URL: r.URL.String(),
396+
URL: fullURL,
367397
Allowed: result.Allowed,
368398
Rule: result.Rule,
369399
})
@@ -374,7 +404,7 @@ func (p *Server) handleDecryptedHTTPS(w http.ResponseWriter, r *http.Request) {
374404
}
375405

376406
// Forward the HTTPS request (now handled same as HTTP after TLS termination)
377-
p.forwardHTTPRequest(w, r)
407+
p.forwardRequest(w, r, true)
378408
}
379409

380410
// handleConnectionWithTLSDetection detects TLS vs HTTP and handles appropriately
@@ -493,4 +523,4 @@ func (sl *singleConnectionListener) Addr() net.Addr {
493523
return nil
494524
}
495525
return sl.conn.LocalAddr()
496-
}
526+
}

0 commit comments

Comments
 (0)