Merge pull request #3751 from Infisical/daniel/gateway-http-handle-multple-requests

fix(gateway): allow multiple requests when using http proxy
This commit is contained in:
Daniel Hougaard
2025-06-06 20:54:22 +04:00
committed by GitHub
2 changed files with 94 additions and 74 deletions
+41 -21
View File
@@ -26,9 +26,13 @@ func handleConnection(ctx context.Context, quicConn quic.Connection) {
log.Info().Msgf("New connection from: %s", quicConn.RemoteAddr().String()) log.Info().Msgf("New connection from: %s", quicConn.RemoteAddr().String())
// Use WaitGroup to track all streams // Use WaitGroup to track all streams
var wg sync.WaitGroup var wg sync.WaitGroup
contextWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
for { for {
// Accept the first stream, which we'll use for commands // Accept the first stream, which we'll use for commands
stream, err := quicConn.AcceptStream(ctx) stream, err := quicConn.AcceptStream(contextWithTimeout)
if err != nil { if err != nil {
log.Printf("Failed to accept QUIC stream: %v", err) log.Printf("Failed to accept QUIC stream: %v", err)
break break
@@ -52,7 +56,12 @@ func handleStream(stream quic.Stream, quicConn quic.Connection) {
// Use buffered reader for better handling of fragmented data // Use buffered reader for better handling of fragmented data
reader := bufio.NewReader(stream) reader := bufio.NewReader(stream)
defer stream.Close() defer func() {
log.Info().Msgf("Closing stream %d", streamID)
if stream != nil {
stream.Close()
}
}()
for { for {
msg, err := reader.ReadBytes('\n') msg, err := reader.ReadBytes('\n')
@@ -166,7 +175,6 @@ func handleHTTPProxy(stream quic.Stream, reader *bufio.Reader, targetURL string,
} }
} }
// set certificate verification based on what the gateway client sent
if verifyParam != "" { if verifyParam != "" {
tlsConfig.InsecureSkipVerify = verifyParam == "false" tlsConfig.InsecureSkipVerify = verifyParam == "false"
log.Info().Msgf("TLS verification set to: %s", verifyParam) log.Info().Msgf("TLS verification set to: %s", verifyParam)
@@ -175,30 +183,39 @@ func handleHTTPProxy(stream quic.Stream, reader *bufio.Reader, targetURL string,
transport.TLSClientConfig = tlsConfig transport.TLSClientConfig = tlsConfig
} }
// read and parse the http request from the stream client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
// Loop to handle multiple HTTP requests on the same stream
for {
req, err := http.ReadRequest(reader) req, err := http.ReadRequest(reader)
if err != nil { if err != nil {
if errors.Is(err, io.EOF) {
log.Info().Msg("Client closed HTTP connection")
return nil
}
return fmt.Errorf("failed to read HTTP request: %v", err) return fmt.Errorf("failed to read HTTP request: %v", err)
} }
log.Info().Msgf("Received HTTP request: %s", req.URL.Path)
actionHeader := req.Header.Get("x-infisical-action") actionHeader := req.Header.Get("x-infisical-action")
if actionHeader != "" { if actionHeader != "" {
if actionHeader == "inject-k8s-sa-auth-token" { if actionHeader == "inject-k8s-sa-auth-token" {
token, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token") token, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
if err != nil { if err != nil {
stream.Write([]byte(buildHttpInternalServerError("failed to read k8s sa auth token"))) stream.Write([]byte(buildHttpInternalServerError("failed to read k8s sa auth token")))
return fmt.Errorf("failed to read k8s sa auth token: %v", err) continue // Continue to next request instead of returning
} }
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(token))) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(token)))
log.Info().Msgf("Injected gateway k8s SA auth token in request to %s", targetURL) log.Info().Msgf("Injected gateway k8s SA auth token in request to %s", targetURL)
} }
req.Header.Del("x-infisical-action") req.Header.Del("x-infisical-action")
} }
// Build full target URL
var targetFullURL string var targetFullURL string
if strings.HasPrefix(targetURL, "http://") || strings.HasPrefix(targetURL, "https://") { if strings.HasPrefix(targetURL, "http://") || strings.HasPrefix(targetURL, "https://") {
baseURL := strings.TrimSuffix(targetURL, "/") baseURL := strings.TrimSuffix(targetURL, "/")
@@ -216,25 +233,21 @@ func handleHTTPProxy(stream quic.Stream, reader *bufio.Reader, targetURL string,
// create the request to the target // create the request to the target
proxyReq, err := http.NewRequest(req.Method, targetFullURL, req.Body) proxyReq, err := http.NewRequest(req.Method, targetFullURL, req.Body)
proxyReq.Header = req.Header.Clone()
if err != nil { if err != nil {
return fmt.Errorf("failed to create proxy request: %v", err) log.Error().Msgf("Failed to create proxy request: %v", err)
stream.Write([]byte(buildHttpInternalServerError("failed to create proxy request")))
continue // Continue to next request
} }
proxyReq.Header = req.Header.Clone()
log.Info().Msgf("Proxying %s %s to %s", req.Method, req.URL.Path, targetFullURL) log.Info().Msgf("Proxying %s %s to %s", req.Method, req.URL.Path, targetFullURL)
client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
// make the request to the target
resp, err := client.Do(proxyReq) resp, err := client.Do(proxyReq)
if err != nil { if err != nil {
log.Error().Msgf("Failed to reach target: %v", err)
stream.Write([]byte(buildHttpInternalServerError(fmt.Sprintf("failed to reach target due to networking error: %s", err.Error())))) stream.Write([]byte(buildHttpInternalServerError(fmt.Sprintf("failed to reach target due to networking error: %s", err.Error()))))
return fmt.Errorf("failed to reach target due to networking error: %v", err) continue // Continue to next request
} }
defer resp.Body.Close()
// Write the entire response (status line, headers, body) to the stream // Write the entire response (status line, headers, body) to the stream
// http.Response.Write handles this for "Connection: close" correctly. // http.Response.Write handles this for "Connection: close" correctly.
@@ -243,15 +256,22 @@ func handleHTTPProxy(stream quic.Stream, reader *bufio.Reader, targetURL string,
resp.Header.Del("Connection") // Good practice for proxies resp.Header.Del("Connection") // Good practice for proxies
log.Info().Msgf("Writing response to stream: %s", resp.Status) log.Info().Msgf("Writing response to stream: %s", resp.Status)
if err := resp.Write(stream); err != nil { if err := resp.Write(stream); err != nil {
// If writing the response fails, the connection to the client might be broken.
// Logging the error is important. The original error will be returned.
log.Error().Err(err).Msg("Failed to write response to stream") log.Error().Err(err).Msg("Failed to write response to stream")
resp.Body.Close()
return fmt.Errorf("failed to write response to stream: %w", err) return fmt.Errorf("failed to write response to stream: %w", err)
} }
resp.Body.Close()
// Check if client wants to close connection
if req.Header.Get("Connection") == "close" {
log.Info().Msg("Client requested connection close")
return nil return nil
} }
}
}
func buildHttpInternalServerError(message string) string { func buildHttpInternalServerError(message string) string {
return fmt.Sprintf("HTTP/1.1 500 Internal Server Error\r\nContent-Type: application/json\r\n\r\n{\"message\": \"gateway: %s\"}", message) return fmt.Sprintf("HTTP/1.1 500 Internal Server Error\r\nContent-Type: application/json\r\n\r\n{\"message\": \"gateway: %s\"}", message)
+1 -1
View File
@@ -1,6 +1,6 @@
image: image:
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
tag: "0.41.81" tag: "0.41.82"
secret: secret:
# The secret that contains the environment variables to be used by the gateway, such as INFISICAL_API_URL and TOKEN # The secret that contains the environment variables to be used by the gateway, such as INFISICAL_API_URL and TOKEN