@marcel91200 / caddy Mirror · read-only / commits / c18099a0
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 04:45 UTC

reverse_proxy with versions 3 silently ignores tls_trust_pool and verifies against the system roots (#8042)

* reverseproxy: build TLS client config once so HTTP/3 keeps tls_trust_pool reuse the config built for the HTTP/1.1+2 transport and give the HTTP/3 transport a clone of it to honour the tls_trust_pool Signed-off-by: Timo Stark <tippexs91@googlemail.com> Co-authored-by: Faiyaz Rahman <faiyazrahman03@gmail.com> * Adding issue reference --------- Signed-off-by: Timo Stark <tippexs91@googlemail.com> Co-authored-by: Faiyaz Rahman <faiyazrahman03@gmail.com>

Timo Stark committed Sep 22, 2026 at 00:13 UTC c18099a0af5e88c8d9da0b9d797f6f2c62d60a23
2 files changed +57 -5
modules/caddyhttp/reverseproxy/httptransport.go
+4 -5
@@ -503,11 +503,10 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
503 if len(h.Versions) == 1 && h.Versions[0] == "3" {
504 h.h3Transport = new(http3.Transport)
505 if h.TLS != nil {
506 - var err error
507 - h.h3Transport.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx)
508 - if err != nil {
509 - return nil, fmt.Errorf("making TLS client config for HTTP/3 transport: %v", err)
510 - }
506 + // Reuse the TLS client config built above for the HTTP/1.1+2
507 + // transport rather than calling MakeTLSClientConfig again.
508 + // Fixing: https://github.com/caddyserver/caddy/issues/8041
509 + h.h3Transport.TLSClientConfig = rt.TLSClientConfig.Clone()
510
511 if strings.Contains(h.TLS.ServerName, "{") {
512 // copied from quic-go
modules/caddyhttp/reverseproxy/httptransport_test.go
+53
@@ -353,3 +353,56 @@ func TestHTTPTransport_DialContext_DialInfoOverride(t *testing.T) {
353 })
354 }
355 }
356 +
357 +// TestHTTPTransportH3UsesConfiguredTrustPool ensures that when the transport
358 +// is configured for HTTP/3, the h3 transport's TLS client config carries the
359 +// trust pool configured via the CA module (tls_trust_pool), not nil root CAs
360 +// which would fall back to the system trust store.
361 +//
362 +// Regression test: NewTransport used to call MakeTLSClientConfig twice, once for
363 +// the HTTP/1.1+2 transport and once for the HTTP/3 transport. The first call
364 +// loads the CA module through ctx.LoadModule, which zeroes the raw module bytes
365 +// afterwards, so the second call built a config with RootCAs == nil.
366 +func TestHTTPTransportH3UsesConfiguredTrustPool(t *testing.T) {
367 + const testDER = `MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==`
368 +
369 + caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
370 + defer cancel()
371 +
372 + h := &HTTPTransport{
373 + Versions: []string{"3"},
374 + TLS: &TLSConfig{
375 + ServerName: "backend",
376 + CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":[%q]}`, testDER)),
377 + },
378 + }
379 +
380 + rt, err := h.NewTransport(caddyCtx)
381 + if err != nil {
382 + t.Fatalf("NewTransport() error = %v", err)
383 + }
384 + if rt.TLSClientConfig == nil || rt.TLSClientConfig.RootCAs == nil {
385 + t.Fatal("HTTP/1.1+2 transport: expected RootCAs to be set from the configured trust pool")
386 + }
387 + if h.h3Transport == nil {
388 + t.Fatal("expected h3Transport to be configured when versions is [\"3\"]")
389 + }
390 + h3cfg := h.h3Transport.TLSClientConfig
391 + if h3cfg == nil {
392 + t.Fatal("HTTP/3 transport: expected non-nil TLS client config")
393 + }
394 + if h3cfg.RootCAs == nil {
395 + t.Fatal("HTTP/3 transport: RootCAs is nil; the configured trust pool was lost and the system roots would be used instead")
396 + }
397 + if !h3cfg.RootCAs.Equal(rt.TLSClientConfig.RootCAs) {
398 + t.Fatal("HTTP/3 transport: RootCAs differs from the HTTP/1.1+2 transport's RootCAs")
399 + }
400 + if h3cfg.ServerName != "backend" {
401 + t.Errorf("HTTP/3 transport: ServerName = %q, want %q", h3cfg.ServerName, "backend")
402 + }
403 + // the h3 transport must own its own copy of the config, since both
404 + // transports mutate their TLS config independently
405 + if h3cfg == rt.TLSClientConfig {
406 + t.Fatal("HTTP/3 transport: TLS client config must be a clone, not the same pointer as the HTTP/1.1+2 transport's")
407 + }
408 +}