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
+}