@marcel91200 / caddy Mirror · read-only
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, 01:00 UTC

master
go 990 lines 34.4 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package reverseproxy
16
17 import (
18 "context"
19 "crypto/tls"
20 "crypto/x509"
21 "encoding/base64"
22 "encoding/json"
23 "fmt"
24 weakrand "math/rand/v2"
25 "net"
26 "net/http"
27 "net/url"
28 "os"
29 "reflect"
30 "slices"
31 "strings"
32 "time"
33
34 "github.com/pires/go-proxyproto"
35 "github.com/quic-go/quic-go"
36 "github.com/quic-go/quic-go/http3"
37 "go.uber.org/zap"
38 "go.uber.org/zap/zapcore"
39 "golang.org/x/net/http2"
40
41 "github.com/caddyserver/caddy/v2"
42 "github.com/caddyserver/caddy/v2/caddyconfig"
43 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
44 "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
45 "github.com/caddyserver/caddy/v2/modules/caddytls"
46 "github.com/caddyserver/caddy/v2/modules/internal/network"
47 )
48
49 func init() {
50 caddy.RegisterModule(HTTPTransport{})
51 }
52
53 // HTTPTransport is essentially a configuration wrapper for http.Transport.
54 // It defines a JSON structure useful when configuring the HTTP transport
55 // for Caddy's reverse proxy. It builds its http.Transport at Provision.
56 type HTTPTransport struct {
57 // TODO: It's possible that other transports (like fastcgi) might be
58 // able to borrow/use at least some of these config fields; if so,
59 // maybe move them into a type called CommonTransport and embed it?
60
61 // Configures the DNS resolver used to resolve the IP address of upstream hostnames.
62 Resolver *UpstreamResolver `json:"resolver,omitempty"`
63
64 // Configures TLS to the upstream. Setting this to an empty struct
65 // is sufficient to enable TLS with reasonable defaults.
66 TLS *TLSConfig `json:"tls,omitempty"`
67
68 // Configures HTTP Keep-Alive (enabled by default). Should only be
69 // necessary if rigorous testing has shown that tuning this helps
70 // improve performance.
71 KeepAlive *KeepAlive `json:"keep_alive,omitempty"`
72
73 // Whether to enable compression to upstream. Default: true
74 Compression *bool `json:"compression,omitempty"`
75
76 // Maximum number of connections per host. Default: 0 (no limit)
77 MaxConnsPerHost int `json:"max_conns_per_host,omitempty"`
78
79 // If non-empty, which PROXY protocol version to send when
80 // connecting to an upstream. Default: off.
81 ProxyProtocol string `json:"proxy_protocol,omitempty"`
82
83 // URL to the server that the HTTP transport will use to proxy
84 // requests to the upstream. See http.Transport.Proxy for
85 // information regarding supported protocols. This value takes
86 // precedence over `HTTP_PROXY`, etc.
87 //
88 // Providing a value to this parameter results in
89 // requests flowing through the reverse_proxy in the following
90 // way:
91 //
92 // User Agent ->
93 // reverse_proxy ->
94 // forward_proxy_url -> upstream
95 //
96 // Default: http.ProxyFromEnvironment
97 // DEPRECATED: Use NetworkProxyRaw|`network_proxy` instead. Subject to removal.
98 ForwardProxyURL string `json:"forward_proxy_url,omitempty"`
99
100 // How long to wait before timing out trying to connect to
101 // an upstream. Default: `3s`.
102 DialTimeout caddy.Duration `json:"dial_timeout,omitempty"`
103
104 // How long to wait before spawning an RFC 6555 Fast Fallback
105 // connection. A negative value disables this. Default: `300ms`.
106 FallbackDelay caddy.Duration `json:"dial_fallback_delay,omitempty"`
107
108 // How long to wait for reading response headers from server. Default: No timeout.
109 ResponseHeaderTimeout caddy.Duration `json:"response_header_timeout,omitempty"`
110
111 // The length of time to wait for a server's first response
112 // headers after fully writing the request headers if the
113 // request has a header "Expect: 100-continue". Default: No timeout.
114 ExpectContinueTimeout caddy.Duration `json:"expect_continue_timeout,omitempty"`
115
116 // The maximum bytes to read from response headers. Default: `10MiB`.
117 MaxResponseHeaderSize int64 `json:"max_response_header_size,omitempty"`
118
119 // The size of the write buffer in bytes. Default: `4KiB`.
120 WriteBufferSize int `json:"write_buffer_size,omitempty"`
121
122 // The size of the read buffer in bytes. Default: `4KiB`.
123 ReadBufferSize int `json:"read_buffer_size,omitempty"`
124
125 // The maximum time to wait for next read from backend. Default: no timeout.
126 ReadTimeout caddy.Duration `json:"read_timeout,omitempty"`
127
128 // The maximum time to wait for next write to backend. Default: no timeout.
129 WriteTimeout caddy.Duration `json:"write_timeout,omitempty"`
130
131 // The versions of HTTP to support. As a special case, "h2c"
132 // can be specified to use H2C (HTTP/2 over Cleartext) to the
133 // upstream (this feature is experimental and subject to
134 // change or removal). Default: ["1.1", "2"]
135 //
136 // EXPERIMENTAL: "3" enables HTTP/3, but it must be the only
137 // version specified if enabled. Additionally, HTTPS must be
138 // enabled to the upstream as HTTP/3 requires TLS. Subject
139 // to change or removal while experimental.
140 Versions []string `json:"versions,omitempty"`
141
142 // Specify the address to bind to when connecting to an upstream. In other words,
143 // it is the address the upstream sees as the remote address.
144 LocalAddress string `json:"local_address,omitempty"`
145
146 // The pre-configured underlying HTTP transport.
147 Transport *http.Transport `json:"-"`
148
149 // The module that provides the network (forward) proxy
150 // URL that the HTTP transport will use to proxy
151 // requests to the upstream. See [http.Transport.Proxy](https://pkg.go.dev/net/http#Transport.Proxy)
152 // for information regarding supported protocols.
153 //
154 // Providing a value to this parameter results in requests
155 // flowing through the reverse_proxy in the following way:
156 //
157 // User Agent ->
158 // reverse_proxy ->
159 // [proxy provided by the module] -> upstream
160 //
161 // If nil, defaults to reading the `HTTP_PROXY`,
162 // `HTTPS_PROXY`, and `NO_PROXY` environment variables.
163 NetworkProxyRaw json.RawMessage `json:"network_proxy,omitempty" caddy:"namespace=caddy.network_proxy inline_key=from"`
164
165 h3Transport *http3.Transport // TODO: EXPERIMENTAL (May 2024)
166 quicTransport *quic.Transport // used by h3Transport if sni placeholder is used, otherwise nil
167 }
168
169 // CaddyModule returns the Caddy module information.
170 func (HTTPTransport) CaddyModule() caddy.ModuleInfo {
171 return caddy.ModuleInfo{
172 ID: "http.reverse_proxy.transport.http",
173 New: func() caddy.Module { return new(HTTPTransport) },
174 }
175 }
176
177 var (
178 allowedVersions = []string{"1.1", "2", "h2c", "3"}
179 allowedVersionsString = strings.Join(allowedVersions, ", ")
180 )
181
182 // Provision sets up h.Transport with a *http.Transport
183 // that is ready to use.
184 func (h *HTTPTransport) Provision(ctx caddy.Context) error {
185 if len(h.Versions) == 0 {
186 h.Versions = []string{"1.1", "2"}
187 }
188 // some users may provide http versions not recognized by caddy, instead of trying to
189 // guess the version, we just error out and let the user fix their config
190 // see: https://github.com/caddyserver/caddy/issues/7111
191 for _, v := range h.Versions {
192 if !slices.Contains(allowedVersions, v) {
193 return fmt.Errorf("unsupported HTTP version: %s, supported version: %s", v, allowedVersionsString)
194 }
195 }
196
197 rt, err := h.NewTransport(ctx)
198 if err != nil {
199 return err
200 }
201 h.Transport = rt
202
203 return nil
204 }
205
206 // NewTransport builds a standard-lib-compatible http.Transport value from h.
207 func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, error) {
208 // Set keep-alive defaults if it wasn't otherwise configured
209 if h.KeepAlive == nil {
210 h.KeepAlive = new(KeepAlive)
211 }
212 if h.KeepAlive.ProbeInterval == 0 {
213 h.KeepAlive.ProbeInterval = caddy.Duration(30 * time.Second)
214 }
215 if h.KeepAlive.IdleConnTimeout == 0 {
216 h.KeepAlive.IdleConnTimeout = caddy.Duration(2 * time.Minute)
217 }
218 if h.KeepAlive.MaxIdleConnsPerHost == 0 {
219 h.KeepAlive.MaxIdleConnsPerHost = 32 // seems about optimal, see #2805
220 }
221
222 // Set a relatively short default dial timeout.
223 // This is helpful to make load-balancer retries more speedy.
224 if h.DialTimeout == 0 {
225 h.DialTimeout = caddy.Duration(3 * time.Second)
226 }
227
228 dialer := &net.Dialer{
229 Timeout: time.Duration(h.DialTimeout),
230 FallbackDelay: time.Duration(h.FallbackDelay),
231 }
232
233 if h.LocalAddress != "" {
234 netaddr, err := caddy.ParseNetworkAddressWithDefaults(h.LocalAddress, "tcp", 0)
235 if err != nil {
236 return nil, err
237 }
238 if netaddr.PortRangeSize() > 1 {
239 return nil, fmt.Errorf("local_address must be a single address, not a port range")
240 }
241 switch netaddr.Network {
242 case "tcp", "tcp4", "tcp6":
243 dialer.LocalAddr, err = net.ResolveTCPAddr(netaddr.Network, netaddr.JoinHostPort(0))
244 if err != nil {
245 return nil, err
246 }
247 case "unix", "unixgram", "unixpacket":
248 dialer.LocalAddr, err = net.ResolveUnixAddr(netaddr.Network, netaddr.JoinHostPort(0))
249 if err != nil {
250 return nil, err
251 }
252 case "udp", "udp4", "udp6":
253 return nil, fmt.Errorf("local_address must be a TCP address, not a UDP address")
254 default:
255 return nil, fmt.Errorf("unsupported network")
256 }
257 }
258 if h.Resolver != nil {
259 err := h.Resolver.ParseAddresses()
260 if err != nil {
261 return nil, err
262 }
263 d := &net.Dialer{
264 Timeout: time.Duration(h.DialTimeout),
265 FallbackDelay: time.Duration(h.FallbackDelay),
266 }
267 dialer.Resolver = &net.Resolver{
268 PreferGo: true,
269 Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
270 //nolint:gosec
271 addr := h.Resolver.netAddrs[weakrand.IntN(len(h.Resolver.netAddrs))]
272 return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0))
273 },
274 }
275 }
276
277 dialContext := func(ctx context.Context, network, address string) (net.Conn, error) {
278 // The network is usually tcp, and the address is the host in http.Request.URL.Host
279 // and that's been overwritten in directRequest
280 // However, if proxy is used according to http.ProxyFromEnvironment or proxy providers,
281 // address will be the address of the proxy server.
282
283 // This means we can safely use the address in dialInfo if proxy is not used (the address and network will be same any way)
284 // or if the upstream is unix (because there is no way socks or http proxy can be used for unix address).
285 if dialInfo, ok := GetDialInfo(ctx); ok {
286 if caddyhttp.GetVar(ctx, proxyVarKey) == nil || strings.HasPrefix(dialInfo.Network, "unix") {
287 network = dialInfo.Network
288 address = dialInfo.Address
289 }
290 }
291
292 conn, err := dialer.DialContext(ctx, network, address)
293 if err != nil {
294 // identify this error as one that occurred during
295 // dialing, which can be important when trying to
296 // decide whether to retry a request
297 return nil, DialError{err}
298 }
299 keepConn := false
300 defer func() {
301 if !keepConn {
302 _ = conn.Close()
303 }
304 }()
305
306 if h.ProxyProtocol != "" {
307 proxyProtocolInfo, ok := caddyhttp.GetVar(ctx, proxyProtocolInfoVarKey).(ProxyProtocolInfo)
308 if !ok {
309 return nil, fmt.Errorf("failed to get proxy protocol info from context")
310 }
311 var proxyv byte
312 switch h.ProxyProtocol {
313 case "v1":
314 proxyv = 1
315 case "v2":
316 proxyv = 2
317 default:
318 return nil, fmt.Errorf("unexpected proxy protocol version")
319 }
320
321 // The src and dst have to be of the same address family. As we don't know the original
322 // dst address (it's kind of impossible to know) and this address is generally of very
323 // little interest, we just set it to all zeros.
324 var destAddr net.Addr
325 switch {
326 case proxyProtocolInfo.AddrPort.Addr().Is4():
327 destAddr = &net.TCPAddr{
328 IP: net.IPv4zero,
329 }
330 case proxyProtocolInfo.AddrPort.Addr().Is6():
331 destAddr = &net.TCPAddr{
332 IP: net.IPv6zero,
333 }
334 default:
335 return nil, fmt.Errorf("unexpected remote addr type in proxy protocol info")
336 }
337 sourceAddr := &net.TCPAddr{
338 IP: proxyProtocolInfo.AddrPort.Addr().AsSlice(),
339 Port: int(proxyProtocolInfo.AddrPort.Port()),
340 Zone: proxyProtocolInfo.AddrPort.Addr().Zone(),
341 }
342 header := proxyproto.HeaderProxyFromAddrs(proxyv, sourceAddr, destAddr)
343
344 // retain the log message structure
345 switch h.ProxyProtocol {
346 case "v1":
347 caddyCtx.Logger().Debug("sending proxy protocol header v1", zap.Any("header", header))
348 case "v2":
349 caddyCtx.Logger().Debug("sending proxy protocol header v2", zap.Any("header", header))
350 }
351
352 _, err = header.WriteTo(conn)
353 if err != nil {
354 // identify this error as one that occurred during
355 // dialing, which can be important when trying to
356 // decide whether to retry a request
357 return nil, DialError{err}
358 }
359 }
360
361 // if read/write timeouts are configured and this is a TCP connection,
362 // enforce the timeouts by wrapping the connection with our own type
363 if tcpConn, ok := conn.(*net.TCPConn); ok && (h.ReadTimeout > 0 || h.WriteTimeout > 0) {
364 conn = &tcpRWTimeoutConn{
365 TCPConn: tcpConn,
366 readTimeout: time.Duration(h.ReadTimeout),
367 writeTimeout: time.Duration(h.WriteTimeout),
368 logger: caddyCtx.Logger(),
369 }
370 }
371
372 keepConn = true
373 return conn, nil
374 }
375
376 // negotiate any HTTP/SOCKS proxy for the HTTP transport
377 proxy := http.ProxyFromEnvironment
378 if h.ForwardProxyURL != "" {
379 caddyCtx.Logger().Warn("forward_proxy_url is deprecated; use network_proxy instead")
380 u := network.ProxyFromURL{URL: h.ForwardProxyURL}
381 h.NetworkProxyRaw = caddyconfig.JSONModuleObject(u, "from", "url", nil)
382 }
383 if len(h.NetworkProxyRaw) != 0 {
384 proxyMod, err := caddyCtx.LoadModule(h, "NetworkProxyRaw")
385 if err != nil {
386 return nil, fmt.Errorf("failed to load network_proxy module: %v", err)
387 }
388 if m, ok := proxyMod.(caddy.ProxyFuncProducer); ok {
389 proxy = m.ProxyFunc()
390 } else {
391 return nil, fmt.Errorf("network_proxy module is not `(func(*http.Request) (*url.URL, error))``")
392 }
393 }
394 // we need to keep track if a proxy is used for a request
395 proxyWrapper := func(req *http.Request) (*url.URL, error) {
396 if proxy == nil {
397 return nil, nil
398 }
399 u, err := proxy(req)
400 if u == nil || err != nil {
401 return u, err
402 }
403 // there must be a proxy for this request
404 caddyhttp.SetVar(req.Context(), proxyVarKey, u)
405 return u, nil
406 }
407
408 rt := &http.Transport{
409 Proxy: proxyWrapper,
410 DialContext: dialContext,
411 MaxConnsPerHost: h.MaxConnsPerHost,
412 ResponseHeaderTimeout: time.Duration(h.ResponseHeaderTimeout),
413 ExpectContinueTimeout: time.Duration(h.ExpectContinueTimeout),
414 MaxResponseHeaderBytes: h.MaxResponseHeaderSize,
415 WriteBufferSize: h.WriteBufferSize,
416 ReadBufferSize: h.ReadBufferSize,
417 }
418
419 if h.TLS != nil {
420 rt.TLSHandshakeTimeout = time.Duration(h.TLS.HandshakeTimeout)
421 var err error
422 rt.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx)
423 if err != nil {
424 return nil, fmt.Errorf("making TLS client config: %v", err)
425 }
426
427 serverNameHasPlaceholder := strings.Contains(h.TLS.ServerName, "{")
428
429 // We need to use custom DialTLSContext if:
430 // 1. ServerName has a placeholder that needs to be replaced at request-time, OR
431 // 2. ProxyProtocol is enabled, because req.URL.Host is modified to include
432 // client address info with "->" separator which breaks Go's address parsing
433 if serverNameHasPlaceholder || h.ProxyProtocol != "" {
434 rt.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
435 // reuses the dialer from above to establish a plaintext connection
436 conn, err := dialContext(ctx, network, addr)
437 if err != nil {
438 return nil, err
439 }
440
441 // but add our own handshake logic
442 tlsConfig := rt.TLSClientConfig.Clone()
443 if serverNameHasPlaceholder {
444 repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
445 tlsConfig.ServerName = repl.ReplaceAll(tlsConfig.ServerName, "")
446 }
447
448 // h1 only
449 if caddyhttp.GetVar(ctx, tlsH1OnlyVarKey) == true {
450 // stdlib does this
451 // https://github.com/golang/go/blob/4837fbe4145cd47b43eed66fee9eed9c2b988316/src/net/http/transport.go#L1701
452 tlsConfig.NextProtos = nil
453 }
454
455 tlsConn := tls.Client(conn, tlsConfig)
456
457 // complete the handshake before returning the connection
458 if rt.TLSHandshakeTimeout != 0 {
459 var cancel context.CancelFunc
460 ctx, cancel = context.WithTimeoutCause(ctx, rt.TLSHandshakeTimeout, fmt.Errorf("HTTP transport TLS handshake %ds timeout", int(rt.TLSHandshakeTimeout.Seconds())))
461 defer cancel()
462 }
463 err = tlsConn.HandshakeContext(ctx)
464 if err != nil {
465 _ = tlsConn.Close()
466 return nil, err
467 }
468 return tlsConn, nil
469 }
470 }
471 }
472
473 if h.KeepAlive != nil {
474 // according to https://pkg.go.dev/net#Dialer.KeepAliveConfig,
475 // KeepAlive is ignored if KeepAliveConfig.Enable is true.
476 // If configured to 0, a system-dependent default is used.
477 // To disable tcp keepalive, choose a negative value,
478 // so KeepAliveConfig.Enable is false and KeepAlive is negative.
479
480 // This is different from http keepalive where a tcp connection
481 // can transfer multiple http requests/responses.
482 dialer.KeepAlive = time.Duration(h.KeepAlive.ProbeInterval)
483 dialer.KeepAliveConfig = net.KeepAliveConfig{
484 Enable: h.KeepAlive.ProbeInterval > 0,
485 Interval: time.Duration(h.KeepAlive.ProbeInterval),
486 }
487 if h.KeepAlive.Enabled != nil {
488 rt.DisableKeepAlives = !*h.KeepAlive.Enabled
489 }
490 rt.MaxIdleConns = h.KeepAlive.MaxIdleConns
491 rt.MaxIdleConnsPerHost = h.KeepAlive.MaxIdleConnsPerHost
492 rt.IdleConnTimeout = time.Duration(h.KeepAlive.IdleConnTimeout)
493 }
494
495 if h.Compression != nil {
496 rt.DisableCompression = !*h.Compression
497 }
498
499 // configure HTTP/3 transport if enabled; however, this does not
500 // automatically fall back to lower versions like most web browsers
501 // do (that'd add latency and complexity, besides, we expect that
502 // site owners control the backends), so it must be exclusive
503 if len(h.Versions) == 1 && h.Versions[0] == "3" {
504 h.h3Transport = new(http3.Transport)
505 if h.TLS != nil {
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
513 udpConn, err := net.ListenUDP("udp", nil)
514 if err != nil {
515 return nil, fmt.Errorf("making udp socket for HTTP/3 transport: %v", err)
516 }
517 h.quicTransport = &quic.Transport{Conn: udpConn}
518 h.h3Transport.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
519 // tlsCfg is already cloned from h3Transport.TLSClientConfig
520 repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
521 tlsCfg.ServerName = repl.ReplaceAll(tlsCfg.ServerName, "")
522 udpAddr, err := resolveUDPAddr(ctx, "udp", addr)
523 if err != nil {
524 return nil, err
525 }
526 return h.quicTransport.DialEarly(ctx, udpAddr, tlsCfg, cfg)
527 }
528 }
529 }
530 } else if len(h.Versions) > 1 && slices.Contains(h.Versions, "3") {
531 return nil, fmt.Errorf("if HTTP/3 is enabled to the upstream, no other HTTP versions are supported")
532 }
533
534 // if h2/c is enabled, configure it explicitly
535 if slices.Contains(h.Versions, "2") || slices.Contains(h.Versions, "h2c") {
536 // TODO: Migrate to http.Transport.Protocols once transport behaviour is covered.
537 //nolint:staticcheck
538 if err := http2.ConfigureTransport(rt); err != nil {
539 return nil, err
540 }
541
542 // DisableCompression from h2 is configured by http2.ConfigureTransport
543 // Likewise, DisableKeepAlives from h1 is used too.
544
545 // Protocols field is only used when the request is not using TLS,
546 // http1/2 over tls is still allowed
547 if slices.Contains(h.Versions, "h2c") {
548 rt.Protocols = new(http.Protocols)
549 rt.Protocols.SetUnencryptedHTTP2(true)
550 rt.Protocols.SetHTTP1(false)
551 }
552 }
553
554 return rt, nil
555 }
556
557 // TODO: EXPERIMENTAL (May 2025)
558 // copied from quic-go
559 func resolveUDPAddr(ctx context.Context, network, addr string) (*net.UDPAddr, error) {
560 host, portStr, err := net.SplitHostPort(addr)
561 if err != nil {
562 return nil, err
563 }
564 port, err := net.LookupPort(network, portStr)
565 if err != nil {
566 return nil, err
567 }
568 resolver := net.DefaultResolver
569 ipAddrs, err := resolver.LookupIPAddr(ctx, host)
570 if err != nil {
571 return nil, err
572 }
573 addrs := addrList(ipAddrs)
574 ip := addrs.forResolve(network, addr)
575 return &net.UDPAddr{IP: ip.IP, Port: port, Zone: ip.Zone}, nil
576 }
577
578 // TODO: EXPERIMENTAL (May 2025)
579 // copied from quic-go
580 // An addrList represents a list of network endpoint addresses.
581 // Copy from [net.addrList] and change type from [net.Addr] to [net.IPAddr]
582 type addrList []net.IPAddr
583
584 // isIPv4 reports whether addr contains an IPv4 address.
585 func isIPv4(addr net.IPAddr) bool {
586 return addr.IP.To4() != nil
587 }
588
589 // isNotIPv4 reports whether addr does not contain an IPv4 address.
590 func isNotIPv4(addr net.IPAddr) bool { return !isIPv4(addr) }
591
592 // forResolve returns the most appropriate address in address for
593 // a call to ResolveTCPAddr, ResolveUDPAddr, or ResolveIPAddr.
594 // IPv4 is preferred, unless addr contains an IPv6 literal.
595 func (addrs addrList) forResolve(network, addr string) net.IPAddr {
596 var want6 bool
597 switch network {
598 case "ip":
599 // IPv6 literal (addr does NOT contain a port)
600 want6 = strings.ContainsRune(addr, ':')
601 case "tcp", "udp":
602 // IPv6 literal. (addr contains a port, so look for '[')
603 want6 = strings.ContainsRune(addr, '[')
604 }
605 if want6 {
606 return addrs.first(isNotIPv4)
607 }
608 return addrs.first(isIPv4)
609 }
610
611 // first returns the first address which satisfies strategy, or if
612 // none do, then the first address of any kind.
613 func (addrs addrList) first(strategy func(net.IPAddr) bool) net.IPAddr {
614 for _, addr := range addrs {
615 if strategy(addr) {
616 return addr
617 }
618 }
619 return addrs[0]
620 }
621
622 // RequestHeaderOps implements TransportHeaderOpsProvider. It returns header
623 // operations for requests when the transport's configuration indicates they
624 // should be applied. In particular, when TLS is enabled for this transport,
625 // return an operation to set the Host header to the upstream host:port
626 // placeholder so HTTPS upstreams get the proper Host by default.
627 //
628 // Note: this is a provision-time hook; the Handler will call this during
629 // its Provision and cache the resulting HeaderOps. The HeaderOps are
630 // applied per-request (so placeholders are expanded at request time).
631 func (h *HTTPTransport) RequestHeaderOps() *headers.HeaderOps {
632 // If TLS is not configured for this transport, don't inject Host
633 // defaults. TLS being non-nil indicates HTTPS to the upstream.
634 if h.TLS == nil {
635 return nil
636 }
637 return &headers.HeaderOps{
638 Set: http.Header{
639 "Host": []string{"{http.reverse_proxy.upstream.hostport}"},
640 },
641 }
642 }
643
644 // RoundTrip implements http.RoundTripper.
645 func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
646 h.SetScheme(req)
647
648 // use HTTP/3 if enabled (TODO: This is EXPERIMENTAL)
649 if h.h3Transport != nil {
650 return h.h3Transport.RoundTrip(req)
651 }
652
653 return h.Transport.RoundTrip(req)
654 }
655
656 // SetScheme ensures that the outbound request req
657 // has the scheme set in its URL; the underlying
658 // http.Transport requires a scheme to be set.
659 //
660 // This method may be used by other transport modules
661 // that wrap/use this one.
662 func (h *HTTPTransport) SetScheme(req *http.Request) {
663 if req.URL.Scheme != "" {
664 return
665 }
666 if h.shouldUseTLS(req) {
667 req.URL.Scheme = "https"
668 } else {
669 req.URL.Scheme = "http"
670 }
671 }
672
673 // shouldUseTLS returns true if TLS should be used for req.
674 func (h *HTTPTransport) shouldUseTLS(req *http.Request) bool {
675 if h.TLS == nil {
676 return false
677 }
678
679 port := req.URL.Port()
680 return !slices.Contains(h.TLS.ExceptPorts, port)
681 }
682
683 // TLSEnabled returns true if TLS is enabled.
684 func (h HTTPTransport) TLSEnabled() bool {
685 return h.TLS != nil
686 }
687
688 // EnableTLS enables TLS on the transport.
689 func (h *HTTPTransport) EnableTLS(base *TLSConfig) error {
690 h.TLS = base
691 return nil
692 }
693
694 // EnableH2C enables H2C (HTTP/2 over Cleartext) on the transport.
695 func (h *HTTPTransport) EnableH2C() error {
696 h.Versions = []string{"h2c", "2"}
697 return nil
698 }
699
700 // OverrideHealthCheckScheme overrides the scheme of the given URL
701 // used for health checks.
702 func (h HTTPTransport) OverrideHealthCheckScheme(base *url.URL, port string) {
703 // if tls is enabled and the port isn't in the except list, use HTTPs
704 if h.TLSEnabled() && !slices.Contains(h.TLS.ExceptPorts, port) {
705 base.Scheme = "https"
706 }
707 }
708
709 // ProxyProtocolEnabled returns true if proxy protocol is enabled.
710 func (h HTTPTransport) ProxyProtocolEnabled() bool {
711 return h.ProxyProtocol != ""
712 }
713
714 // Cleanup implements caddy.CleanerUpper and closes any idle connections.
715 func (h HTTPTransport) Cleanup() error {
716 if h.Transport == nil {
717 return nil
718 }
719 h.Transport.CloseIdleConnections()
720 // h3 related cleanup, errors are ignored as nothing can be done.
721 // TODO: log these errors if any
722 if h.h3Transport != nil {
723 h.h3Transport.CloseIdleConnections()
724 _ = h.h3Transport.Close()
725 if h.quicTransport != nil {
726 _ = h.quicTransport.Close()
727 _ = h.quicTransport.Conn.Close()
728 }
729 }
730 return nil
731 }
732
733 // TLSConfig holds configuration related to the TLS configuration for the
734 // transport/client.
735 type TLSConfig struct {
736 // Certificate authority module which provides the certificate pool of trusted certificates
737 CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
738
739 // Deprecated: Use the `ca` field with the `tls.ca_pool.source.inline` module instead.
740 // Optional list of base64-encoded DER-encoded CA certificates to trust.
741 RootCAPool []string `json:"root_ca_pool,omitempty"`
742
743 // Deprecated: Use the `ca` field with the `tls.ca_pool.source.file` module instead.
744 // List of PEM-encoded CA certificate files to add to the same trust
745 // store as RootCAPool (or root_ca_pool in the JSON).
746 RootCAPEMFiles []string `json:"root_ca_pem_files,omitempty"`
747
748 // PEM-encoded client certificate filename to present to servers.
749 ClientCertificateFile string `json:"client_certificate_file,omitempty"`
750
751 // PEM-encoded key to use with the client certificate.
752 ClientCertificateKeyFile string `json:"client_certificate_key_file,omitempty"`
753
754 // If specified, Caddy will use and automate a client certificate
755 // with this subject name.
756 ClientCertificateAutomate string `json:"client_certificate_automate,omitempty"`
757
758 // If true, TLS verification of server certificates will be disabled.
759 // This is insecure and may be removed in the future. Do not use this
760 // option except in testing or local development environments.
761 InsecureSkipVerify bool `json:"insecure_skip_verify,omitempty"`
762
763 // The duration to allow a TLS handshake to a server. Default: No timeout.
764 HandshakeTimeout caddy.Duration `json:"handshake_timeout,omitempty"`
765
766 // The server name used when verifying the certificate received in the TLS
767 // handshake. By default, this will use the upstream address' host part.
768 // You only need to override this if your upstream address does not match the
769 // certificate the upstream is likely to use. For example if the upstream
770 // address is an IP address, then you would need to configure this to the
771 // hostname being served by the upstream server. Currently, this does not
772 // support placeholders because the TLS config is not provisioned on each
773 // connection, so a static value must be used.
774 ServerName string `json:"server_name,omitempty"`
775
776 // TLS renegotiation level. TLS renegotiation is the act of performing
777 // subsequent handshakes on a connection after the first.
778 // The level can be:
779 // - "never": (the default) disables renegotiation.
780 // - "once": allows a remote server to request renegotiation once per connection.
781 // - "freely": allows a remote server to repeatedly request renegotiation.
782 Renegotiation string `json:"renegotiation,omitempty"`
783
784 // Skip TLS ports specifies a list of upstream ports on which TLS should not be
785 // attempted even if it is configured. Handy when using dynamic upstreams that
786 // return HTTP and HTTPS endpoints too.
787 // When specified, TLS will automatically be configured on the transport.
788 // The value can be a list of any valid tcp port numbers, default empty.
789 ExceptPorts []string `json:"except_ports,omitempty"`
790
791 // The list of elliptic curves to support. Caddy's
792 // defaults are modern and secure.
793 Curves []string `json:"curves,omitempty"`
794 }
795
796 // MakeTLSClientConfig returns a tls.Config usable by a client to a backend.
797 // If there is no custom TLS configuration, a nil config may be returned.
798 func (t *TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
799 cfg := new(tls.Config)
800
801 // client auth
802 if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile == "" {
803 return nil, fmt.Errorf("client_certificate_file specified without client_certificate_key_file")
804 }
805 if t.ClientCertificateFile == "" && t.ClientCertificateKeyFile != "" {
806 return nil, fmt.Errorf("client_certificate_key_file specified without client_certificate_file")
807 }
808 if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile != "" {
809 cert, err := tls.LoadX509KeyPair(t.ClientCertificateFile, t.ClientCertificateKeyFile)
810 if err != nil {
811 return nil, fmt.Errorf("loading client certificate key pair: %v", err)
812 }
813 cfg.Certificates = []tls.Certificate{cert}
814 }
815 if t.ClientCertificateAutomate != "" {
816 // TODO: use or enable ctx.IdentityCredentials() ...
817 tlsAppIface, err := ctx.App("tls")
818 if err != nil {
819 return nil, fmt.Errorf("getting tls app: %v", err)
820 }
821 tlsApp := tlsAppIface.(*caddytls.TLS)
822 err = tlsApp.Manage(map[string]struct{}{t.ClientCertificateAutomate: {}})
823 if err != nil {
824 return nil, fmt.Errorf("managing client certificate: %v", err)
825 }
826 cfg.GetClientCertificate = func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
827 certs := caddytls.AllMatchingCertificates(t.ClientCertificateAutomate)
828 var err error
829 for _, cert := range certs {
830 certCertificate := cert.Certificate // avoid taking address of iteration variable (gosec warning)
831 err = cri.SupportsCertificate(&certCertificate)
832 if err == nil {
833 return &cert.Certificate, nil
834 }
835 }
836 if err == nil {
837 err = fmt.Errorf("no client certificate found for automate name: %s", t.ClientCertificateAutomate)
838 }
839 return nil, err
840 }
841 }
842
843 // trusted root CAs
844 if len(t.RootCAPool) > 0 || len(t.RootCAPEMFiles) > 0 {
845 ctx.Logger().Warn("root_ca_pool and root_ca_pem_files are deprecated. Use one of the tls.ca_pool.source modules instead")
846 rootPool := x509.NewCertPool()
847 for _, encodedCACert := range t.RootCAPool {
848 caCert, err := decodeBase64DERCert(encodedCACert)
849 if err != nil {
850 return nil, fmt.Errorf("parsing CA certificate: %v", err)
851 }
852 rootPool.AddCert(caCert)
853 }
854 for _, pemFile := range t.RootCAPEMFiles {
855 pemData, err := os.ReadFile(pemFile)
856 if err != nil {
857 return nil, fmt.Errorf("failed reading ca cert: %v", err)
858 }
859 rootPool.AppendCertsFromPEM(pemData)
860 }
861 cfg.RootCAs = rootPool
862 }
863
864 if t.CARaw != nil {
865 if len(t.RootCAPool) > 0 || len(t.RootCAPEMFiles) > 0 {
866 return nil, fmt.Errorf("conflicting config for Root CA pool")
867 }
868 caRaw, err := ctx.LoadModule(t, "CARaw")
869 if err != nil {
870 return nil, fmt.Errorf("failed to load ca module: %v", err)
871 }
872 ca, ok := caRaw.(caddytls.CA)
873 if !ok {
874 return nil, fmt.Errorf("CA module '%s' is not a certificate pool provider", ca)
875 }
876 cfg.RootCAs = ca.CertPool()
877 }
878
879 // Renegotiation
880 switch t.Renegotiation {
881 case "never", "":
882 cfg.Renegotiation = tls.RenegotiateNever
883 case "once":
884 cfg.Renegotiation = tls.RenegotiateOnceAsClient
885 case "freely":
886 cfg.Renegotiation = tls.RenegotiateFreelyAsClient
887 default:
888 return nil, fmt.Errorf("invalid TLS renegotiation level: %v", t.Renegotiation)
889 }
890
891 // override for the server name used verify the TLS handshake
892 cfg.ServerName = t.ServerName
893
894 // throw all security out the window
895 cfg.InsecureSkipVerify = t.InsecureSkipVerify
896
897 curvesAdded := make(map[tls.CurveID]struct{})
898 for _, curveName := range t.Curves {
899 curveID, ok := caddytls.SupportedCurves[curveName]
900 if !ok {
901 return nil, fmt.Errorf("unsupported curve: %s", curveName)
902 }
903 if _, ok := curvesAdded[curveID]; !ok {
904 curvesAdded[curveID] = struct{}{}
905 cfg.CurvePreferences = append(cfg.CurvePreferences, curveID)
906 }
907 }
908
909 // only return a config if it's not empty
910 if reflect.DeepEqual(cfg, new(tls.Config)) {
911 return nil, nil
912 }
913
914 return cfg, nil
915 }
916
917 // KeepAlive holds configuration pertaining to HTTP Keep-Alive.
918 type KeepAlive struct {
919 // Whether HTTP Keep-Alive is enabled. Default: `true`
920 Enabled *bool `json:"enabled,omitempty"`
921
922 // How often to probe for liveness. Default: `30s`.
923 ProbeInterval caddy.Duration `json:"probe_interval,omitempty"`
924
925 // Maximum number of idle connections. Default: `0`, which means no limit.
926 MaxIdleConns int `json:"max_idle_conns,omitempty"`
927
928 // Maximum number of idle connections per host. Default: `32`.
929 MaxIdleConnsPerHost int `json:"max_idle_conns_per_host,omitempty"`
930
931 // How long connections should be kept alive when idle. Default: `2m`.
932 IdleConnTimeout caddy.Duration `json:"idle_timeout,omitempty"`
933 }
934
935 // tcpRWTimeoutConn enforces read/write timeouts for a TCP connection.
936 // If it fails to set deadlines, the error is logged but does not abort
937 // the read/write attempt (ignoring the error is consistent with what
938 // the standard library does: https://github.com/golang/go/blob/c5da4fb7ac5cb7434b41fc9a1df3bee66c7f1a4d/src/net/http/server.go#L981-L986)
939 type tcpRWTimeoutConn struct {
940 *net.TCPConn
941 readTimeout, writeTimeout time.Duration
942 logger *zap.Logger
943 }
944
945 func (c *tcpRWTimeoutConn) Read(b []byte) (int, error) {
946 if c.readTimeout > 0 {
947 err := c.TCPConn.SetReadDeadline(time.Now().Add(c.readTimeout))
948 if err != nil {
949 if ce := c.logger.Check(zapcore.ErrorLevel, "failed to set read deadline"); ce != nil {
950 ce.Write(zap.Error(err))
951 }
952 }
953 }
954 return c.TCPConn.Read(b)
955 }
956
957 func (c *tcpRWTimeoutConn) Write(b []byte) (int, error) {
958 if c.writeTimeout > 0 {
959 err := c.TCPConn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
960 if err != nil {
961 if ce := c.logger.Check(zapcore.ErrorLevel, "failed to set write deadline"); ce != nil {
962 ce.Write(zap.Error(err))
963 }
964 }
965 }
966 return c.TCPConn.Write(b)
967 }
968
969 // decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
970 func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
971 // decode base64
972 derBytes, err := base64.StdEncoding.DecodeString(certStr)
973 if err != nil {
974 return nil, err
975 }
976
977 // parse the DER-encoded certificate
978 return x509.ParseCertificate(derBytes)
979 }
980
981 // Interface guards
982 var (
983 _ caddy.Provisioner = (*HTTPTransport)(nil)
984 _ http.RoundTripper = (*HTTPTransport)(nil)
985 _ caddy.CleanerUpper = (*HTTPTransport)(nil)
986 _ TLSTransport = (*HTTPTransport)(nil)
987 _ H2CTransport = (*HTTPTransport)(nil)
988 _ HealthCheckSchemeOverriderTransport = (*HTTPTransport)(nil)
989 _ ProxyProtocolTransport = (*HTTPTransport)(nil)
990 )