9
9
"log/slog"
10
10
"net"
11
11
"net/http"
12
- "net/http/httptest"
13
12
"net/url"
14
13
"strings"
15
14
"sync"
@@ -317,55 +316,55 @@ func (p *Server) handleConnect(w http.ResponseWriter, r *http.Request) {
317
316
p .logger .Debug ("HTTPS request handling completed" , "hostname" , hostname )
318
317
}
319
318
320
- // handleTLSConnection processes decrypted HTTPS requests over the TLS connection
319
+ // handleTLSConnection processes decrypted HTTPS requests over the TLS connection with streaming support
321
320
func (p * Server ) handleTLSConnection (tlsConn * tls.Conn , hostname string ) {
322
- p .logger .Debug ("Creating HTTP server for TLS connection" , "hostname" , hostname )
321
+ p .logger .Debug ("Creating streaming HTTP handler for TLS connection" , "hostname" , hostname )
323
322
324
- // Set read timeout to detect hanging connections
325
- tlsConn .SetReadDeadline (time .Now ().Add (5 * time .Second ))
326
-
327
- // Use ReadRequest to manually read HTTP requests from the TLS connection
323
+ // Use streaming HTTP parsing instead of ReadRequest
328
324
bufReader := bufio .NewReader (tlsConn )
329
325
for {
330
- // Read HTTP request from TLS connection
331
- req , err := http . ReadRequest (bufReader )
326
+ // Parse HTTP request headers incrementally
327
+ req , err := p . parseHTTPRequestHeaders (bufReader , hostname )
332
328
if err != nil {
333
329
if err == io .EOF {
334
330
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 )
337
331
} else {
338
- p .logger .Debug ("Failed to read HTTP request" , "hostname" , hostname , "error" , err )
332
+ p .logger .Debug ("Failed to parse HTTP request headers " , "hostname" , hostname , "error" , err )
339
333
}
340
334
break
341
335
}
342
336
343
- p .logger .Debug ("Processing decrypted HTTPS request" , "hostname" , hostname , "method" , req .Method , "path" , req .URL .Path )
337
+ p .logger .Debug ("Processing streaming HTTPS request" , "hostname" , hostname , "method" , req .Method , "path" , req .URL .Path )
344
338
345
- // Set the hostname and scheme if not already set
346
- if req .URL .Host == "" {
347
- req .URL .Host = hostname
348
- }
349
- if req .URL .Scheme == "" {
350
- req .URL .Scheme = "https"
339
+ // Handle CONNECT method for HTTPS tunneling
340
+ if req .Method == "CONNECT" {
341
+ p .handleConnectStreaming (tlsConn , req , hostname )
342
+ return // CONNECT takes over the entire connection
351
343
}
352
344
353
- // Create a response recorder to capture the response
354
- recorder := httptest .NewRecorder ()
345
+ // Check if request should be allowed (based on headers only)
346
+ fullURL := p .constructFullURL (req , hostname )
347
+ result := p .ruleEngine .Evaluate (req .Method , fullURL )
355
348
356
- // Process the HTTPS request
357
- p .handleDecryptedHTTPS (recorder , req )
349
+ // Audit the request
350
+ p .auditor .AuditRequest (audit.Request {
351
+ Method : req .Method ,
352
+ URL : fullURL ,
353
+ Allowed : result .Allowed ,
354
+ Rule : result .Rule ,
355
+ })
356
+
357
+ if ! result .Allowed {
358
+ p .writeBlockedResponseStreaming (tlsConn , req )
359
+ continue
360
+ }
358
361
359
- // Write the response back to the TLS connection
360
- resp := recorder .Result ()
361
- err = resp .Write (tlsConn )
362
+ // Stream the request to target server
363
+ err = p .streamRequestToTarget (tlsConn , bufReader , req , hostname )
362
364
if err != nil {
363
- p .logger .Debug ("Failed to write response " , "hostname" , hostname , "error" , err )
365
+ p .logger .Debug ("Error streaming request " , "hostname" , hostname , "error" , err )
364
366
break
365
367
}
366
-
367
- // Reset read deadline for next request
368
- tlsConn .SetReadDeadline (time .Now ().Add (5 * time .Second ))
369
368
}
370
369
371
370
p .logger .Debug ("TLS connection handling completed" , "hostname" , hostname )
@@ -523,4 +522,155 @@ func (sl *singleConnectionListener) Addr() net.Addr {
523
522
return nil
524
523
}
525
524
return sl .conn .LocalAddr ()
525
+ }
526
+
527
+ // parseHTTPRequestHeaders parses HTTP request headers incrementally without reading the body
528
+ func (p * Server ) parseHTTPRequestHeaders (bufReader * bufio.Reader , hostname string ) (* http.Request , error ) {
529
+ // Read the request line (e.g., "GET /path HTTP/1.1")
530
+ requestLine , _ , err := bufReader .ReadLine ()
531
+ if err != nil {
532
+ return nil , err
533
+ }
534
+
535
+ // Parse request line
536
+ parts := strings .Fields (string (requestLine ))
537
+ if len (parts ) != 3 {
538
+ return nil , fmt .Errorf ("invalid request line: %s" , requestLine )
539
+ }
540
+
541
+ method := parts [0 ]
542
+ requestURI := parts [1 ]
543
+ proto := parts [2 ]
544
+
545
+ // Parse URL
546
+ var url * url.URL
547
+ if strings .HasPrefix (requestURI , "http://" ) || strings .HasPrefix (requestURI , "https://" ) {
548
+ url , err = url .Parse (requestURI )
549
+ } else {
550
+ // Relative URL, construct with hostname
551
+ url , err = url .Parse ("https://" + hostname + requestURI )
552
+ }
553
+ if err != nil {
554
+ return nil , fmt .Errorf ("invalid request URI: %s" , requestURI )
555
+ }
556
+
557
+ // Read headers
558
+ headers := make (http.Header )
559
+ for {
560
+ headerLine , _ , err := bufReader .ReadLine ()
561
+ if err != nil {
562
+ return nil , err
563
+ }
564
+
565
+ // Empty line indicates end of headers
566
+ if len (headerLine ) == 0 {
567
+ break
568
+ }
569
+
570
+ // Parse header
571
+ headerStr := string (headerLine )
572
+ colonIdx := strings .Index (headerStr , ":" )
573
+ if colonIdx == - 1 {
574
+ continue // Skip malformed headers
575
+ }
576
+
577
+ headerName := strings .TrimSpace (headerStr [:colonIdx ])
578
+ headerValue := strings .TrimSpace (headerStr [colonIdx + 1 :])
579
+ headers .Add (headerName , headerValue )
580
+ }
581
+
582
+ // Create request object (without body)
583
+ req := & http.Request {
584
+ Method : method ,
585
+ URL : url ,
586
+ Proto : proto ,
587
+ Header : headers ,
588
+ Host : url .Host ,
589
+ // Note: Body is intentionally nil - we'll stream it separately
590
+ }
591
+
592
+ return req , nil
593
+ }
594
+
595
+ // constructFullURL builds the full URL from request and hostname
596
+ func (p * Server ) constructFullURL (req * http.Request , hostname string ) string {
597
+ if req .URL .Host == "" {
598
+ req .URL .Host = hostname
599
+ }
600
+ if req .URL .Scheme == "" {
601
+ req .URL .Scheme = "https"
602
+ }
603
+ return req .URL .String ()
604
+ }
605
+
606
+ // writeBlockedResponseStreaming writes a blocked response directly to the TLS connection
607
+ func (p * Server ) writeBlockedResponseStreaming (tlsConn * tls.Conn , req * http.Request ) {
608
+ response := fmt .Sprintf ("HTTP/1.1 403 Forbidden\r \n Content-Type: text/plain\r \n Connection: close\r \n \r \n 🚫 Request Blocked by Boundary\n \n Request: %s %s\n Host: %s\n \n To allow this request, restart boundary with:\n --allow \" %s\" \n " ,
609
+ req .Method , req .URL .Path , req .Host , req .Host )
610
+ tlsConn .Write ([]byte (response ))
611
+ }
612
+
613
+ // streamRequestToTarget streams the HTTP request (including body) to the target server
614
+ func (p * Server ) streamRequestToTarget (clientConn * tls.Conn , bufReader * bufio.Reader , req * http.Request , hostname string ) error {
615
+ // Connect to target server
616
+ targetConn , err := tls .Dial ("tcp" , hostname + ":443" , & tls.Config {ServerName : hostname })
617
+ if err != nil {
618
+ return fmt .Errorf ("failed to connect to target %s: %v" , hostname , err )
619
+ }
620
+ defer targetConn .Close ()
621
+
622
+ // Send HTTP request headers to target
623
+ reqLine := fmt .Sprintf ("%s %s %s\r \n " , req .Method , req .URL .RequestURI (), req .Proto )
624
+ targetConn .Write ([]byte (reqLine ))
625
+
626
+ // Send headers
627
+ for name , values := range req .Header {
628
+ for _ , value := range values {
629
+ headerLine := fmt .Sprintf ("%s: %s\r \n " , name , value )
630
+ targetConn .Write ([]byte (headerLine ))
631
+ }
632
+ }
633
+ targetConn .Write ([]byte ("\r \n " )) // End of headers
634
+
635
+ // Stream request body and response bidirectionally
636
+ go func () {
637
+ // Stream request body: client -> target
638
+ io .Copy (targetConn , bufReader )
639
+ }()
640
+
641
+ // Stream response: target -> client
642
+ io .Copy (clientConn , targetConn )
643
+ return nil
644
+ }
645
+
646
+ // handleConnectStreaming handles CONNECT requests with streaming TLS termination
647
+ func (p * Server ) handleConnectStreaming (tlsConn * tls.Conn , req * http.Request , hostname string ) {
648
+ p .logger .Debug ("Handling CONNECT request with streaming" , "hostname" , hostname )
649
+
650
+ // For CONNECT, we need to establish a tunnel but still maintain TLS termination
651
+ // This is the tricky part - we're already inside a TLS connection from the client
652
+ // The client is asking us to CONNECT to another server, but we want to intercept that too
653
+
654
+ // Send CONNECT response
655
+ response := "HTTP/1.1 200 Connection established\r \n \r \n "
656
+ tlsConn .Write ([]byte (response ))
657
+
658
+ // Now the client will try to do TLS handshake for the target server
659
+ // But we want to intercept and terminate it
660
+ // This means we need to do another level of TLS termination
661
+
662
+ // For now, let's create a simple tunnel and log that we're not inspecting
663
+ p .logger .Warn ("CONNECT tunnel established - content not inspected" , "hostname" , hostname )
664
+
665
+ // Create connection to real target
666
+ targetConn , err := net .Dial ("tcp" , req .Host )
667
+ if err != nil {
668
+ p .logger .Error ("Failed to connect to CONNECT target" , "target" , req .Host , "error" , err )
669
+ return
670
+ }
671
+ defer targetConn .Close ()
672
+
673
+ // Bidirectional copy
674
+ go io .Copy (targetConn , tlsConn )
675
+ io .Copy (tlsConn , targetConn )
526
676
}
0 commit comments