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