diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 44cc5b7cd..557b4bac1 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -5,11 +5,11 @@ The Caddy project would like to make sure that it stays on top of all practicall ## Supported Versions -| Version | Supported | -| ------- | ------------------ | -| 2.x | ✔️ | -| 1.x | :x: | -| < 1.x | :x: | +| Version | Supported | +| -------- | ----------| +| 2.latest | ✔️ | +| 1.x | :x: | +| < 1.x | :x: | ## Acceptable Scope diff --git a/caddy.go b/caddy.go index b3e8889fa..758b0b2f6 100644 --- a/caddy.go +++ b/caddy.go @@ -725,8 +725,10 @@ func Validate(cfg *Config) error { // Errors are logged along the way, and an appropriate exit // code is emitted. func exitProcess(ctx context.Context, logger *zap.Logger) { - // let the rest of the program know we're quitting - atomic.StoreInt32(exiting, 1) + // let the rest of the program know we're quitting; only do it once + if !atomic.CompareAndSwapInt32(exiting, 0, 1) { + return + } // give the OS or service/process manager our 2 weeks' notice: we quit if err := notify.Stopping(); err != nil { diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go index eca6a2d64..45570d016 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -24,7 +24,7 @@ import ( "time" "github.com/caddyserver/certmagic" - "github.com/mholt/acmez/v2/acme" + "github.com/mholt/acmez/v3/acme" "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" @@ -84,7 +84,7 @@ func parseBind(h Helper) ([]ConfigValue, error) { // parseTLS parses the tls directive. Syntax: // -// tls [|internal]|[ ] { +// tls [|internal|force_automate]|[ ] { // protocols [] // ciphers // curves @@ -107,6 +107,7 @@ func parseBind(h Helper) ([]ConfigValue, error) { // dns_challenge_override_domain // on_demand // reuse_private_keys +// force_automate // eab // issuer [...] // get_certificate [...] @@ -126,6 +127,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) { var certManagers []certmagic.Manager var onDemand bool var reusePrivateKeys bool + var forceAutomate bool firstLine := h.RemainingArgs() switch len(firstLine) { @@ -133,8 +135,10 @@ func parseTLS(h Helper) ([]ConfigValue, error) { case 1: if firstLine[0] == "internal" { internalIssuer = new(caddytls.InternalIssuer) + } else if firstLine[0] == "force_automate" { + forceAutomate = true } else if !strings.Contains(firstLine[0], "@") { - return nil, h.Err("single argument must either be 'internal' or an email address") + return nil, h.Err("single argument must either be 'internal', 'force_automate', or an email address") } else { acmeIssuer = &caddytls.ACMEIssuer{ Email: firstLine[0], @@ -569,6 +573,15 @@ func parseTLS(h Helper) ([]ConfigValue, error) { }) } + // if enabled, the names in the site addresses will be + // added to the automation policies + if forceAutomate { + configVals = append(configVals, ConfigValue{ + Class: "tls.force_automate", + Value: true, + }) + } + // custom certificate selection if len(certSelector.AnyTag) > 0 { cp.CertSelection = &certSelector diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index c169b92af..37a6f6b23 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -763,6 +763,14 @@ func (st *ServerType) serversFromPairings( } } + // collect hosts that are forced to be automated + forceAutomatedNames := make(map[string]struct{}) + if _, ok := sblock.pile["tls.force_automate"]; ok { + for _, host := range hosts { + forceAutomatedNames[host] = struct{}{} + } + } + // tls: connection policies if cpVals, ok := sblock.pile["tls.connection_policy"]; ok { // tls connection policies @@ -794,7 +802,7 @@ func (st *ServerType) serversFromPairings( } // only append this policy if it actually changes something - if !cp.SettingsEmpty() { + if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) { srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp) hasCatchAllTLSConnPolicy = len(hosts) == 0 } @@ -1661,6 +1669,18 @@ func listenersUseAnyPortOtherThan(addresses []string, otherPort string) bool { return false } +func mapContains[K comparable, V any](m map[K]V, keys []K) bool { + if len(m) == 0 || len(keys) == 0 { + return false + } + for _, key := range keys { + if _, ok := m[key]; ok { + return true + } + } + return false +} + // specificity returns len(s) minus any wildcards (*) and // placeholders ({...}). Basically, it's a length count // that penalizes the use of wildcards and placeholders. diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index cfd8f709d..d4a424624 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -19,7 +19,7 @@ import ( "strconv" "github.com/caddyserver/certmagic" - "github.com/mholt/acmez/v2/acme" + "github.com/mholt/acmez/v3/acme" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go index ea5ac92c7..71b524926 100644 --- a/caddyconfig/httpcaddyfile/tlsapp.go +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -25,7 +25,7 @@ import ( "strings" "github.com/caddyserver/certmagic" - "github.com/mholt/acmez/v2/acme" + "github.com/mholt/acmez/v3/acme" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" @@ -94,6 +94,9 @@ func (st ServerType) buildTLSApp( // collect all hosts that have a wildcard in them, and arent HTTP wildcardHosts := []string{} + // hosts that have been explicitly marked to be automated, + // even if covered by another wildcard + forcedAutomatedNames := make(map[string]struct{}) for _, p := range pairings { var addresses []string for _, addressWithProtocols := range p.addressesWithProtocols { @@ -150,6 +153,13 @@ func (st ServerType) buildTLSApp( ap.OnDemand = true } + // collect hosts that are forced to be automated + if _, ok := sblock.pile["tls.force_automate"]; ok { + for _, host := range sblockHosts { + forcedAutomatedNames[host] = struct{}{} + } + } + // reuse private keys tls if _, ok := sblock.pile["tls.reuse_private_keys"]; ok { ap.ReusePrivateKeys = true @@ -407,6 +417,13 @@ func (st ServerType) buildTLSApp( } } } + for name := range forcedAutomatedNames { + if slices.Contains(al, name) { + continue + } + al = append(al, name) + } + slices.Sort(al) // to stabilize the adapt output if len(al) > 0 { tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings) } diff --git a/caddytest/integration/acme_test.go b/caddytest/integration/acme_test.go index ceacd1db0..d7e4c296d 100644 --- a/caddytest/integration/acme_test.go +++ b/caddytest/integration/acme_test.go @@ -6,6 +6,7 @@ import ( "crypto/elliptic" "crypto/rand" "fmt" + "log/slog" "net" "net/http" "strings" @@ -13,10 +14,11 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddytest" - "github.com/mholt/acmez/v2" - "github.com/mholt/acmez/v2/acme" + "github.com/mholt/acmez/v3" + "github.com/mholt/acmez/v3/acme" smallstepacme "github.com/smallstep/certificates/acme" "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" ) const acmeChallengePort = 9081 @@ -48,7 +50,7 @@ func TestACMEServerWithDefaults(t *testing.T) { Client: &acme.Client{ Directory: "https://acme.localhost:9443/acme/local/directory", HTTPClient: tester.Client, - Logger: logger, + Logger: slog.New(zapslog.NewHandler(logger.Core())), }, ChallengeSolvers: map[string]acmez.Solver{ acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, @@ -117,7 +119,7 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) { Client: &acme.Client{ Directory: "https://acme.localhost:9443/acme/local/directory", HTTPClient: tester.Client, - Logger: logger, + Logger: slog.New(zapslog.NewHandler(logger.Core())), }, ChallengeSolvers: map[string]acmez.Solver{ acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, diff --git a/caddytest/integration/acmeserver_test.go b/caddytest/integration/acmeserver_test.go index 22b716f84..ca5845f87 100644 --- a/caddytest/integration/acmeserver_test.go +++ b/caddytest/integration/acmeserver_test.go @@ -5,13 +5,15 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "log/slog" "strings" "testing" "github.com/caddyserver/caddy/v2/caddytest" - "github.com/mholt/acmez/v2" - "github.com/mholt/acmez/v2/acme" + "github.com/mholt/acmez/v3" + "github.com/mholt/acmez/v3/acme" "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" ) func TestACMEServerDirectory(t *testing.T) { @@ -76,7 +78,7 @@ func TestACMEServerAllowPolicy(t *testing.T) { Client: &acme.Client{ Directory: "https://acme.localhost:9443/acme/local/directory", HTTPClient: tester.Client, - Logger: logger, + Logger: slog.New(zapslog.NewHandler(logger.Core())), }, ChallengeSolvers: map[string]acmez.Solver{ acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, @@ -165,7 +167,7 @@ func TestACMEServerDenyPolicy(t *testing.T) { Client: &acme.Client{ Directory: "https://acme.localhost:9443/acme/local/directory", HTTPClient: tester.Client, - Logger: logger, + Logger: slog.New(zapslog.NewHandler(logger.Core())), }, ChallengeSolvers: map[string]acmez.Solver{ acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest new file mode 100644 index 000000000..4eb6c4f1c --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest @@ -0,0 +1,180 @@ +automated1.example.com { + tls force_automate + respond "Automated!" +} + +automated2.example.com { + tls force_automate + respond "Automated!" +} + +shadowed.example.com { + respond "Shadowed!" +} + +*.example.com { + tls cert.pem key.pem + respond "Wildcard!" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "automated1.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Automated!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "automated2.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Automated!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "shadowed.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Shadowed!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Wildcard!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "automated1.example.com" + ] + } + }, + { + "match": { + "sni": [ + "automated2.example.com" + ] + } + }, + { + "match": { + "sni": [ + "*.example.com" + ] + }, + "certificate_selection": { + "any_tag": [ + "cert0" + ] + } + }, + {} + ] + } + } + }, + "tls": { + "certificates": { + "automate": [ + "automated1.example.com", + "automated2.example.com" + ], + "load_files": [ + { + "certificate": "cert.pem", + "key": "key.pem", + "tags": [ + "cert0" + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_shadowing.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_shadowing.caddyfiletest new file mode 100644 index 000000000..2be543779 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_shadowing.caddyfiletest @@ -0,0 +1,102 @@ +subdomain.example.com { + respond "Subdomain!" +} + +*.example.com { + tls cert.pem key.pem + respond "Wildcard!" +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "subdomain.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Subdomain!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + }, + { + "match": [ + { + "host": [ + "*.example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "body": "Wildcard!", + "handler": "static_response" + } + ] + } + ] + } + ], + "terminal": true + } + ], + "tls_connection_policies": [ + { + "match": { + "sni": [ + "*.example.com" + ] + }, + "certificate_selection": { + "any_tag": [ + "cert0" + ] + } + }, + {} + ] + } + } + }, + "tls": { + "certificates": { + "load_files": [ + { + "certificate": "cert.pem", + "key": "key.pem", + "tags": [ + "cert0" + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/cmd/caddy/main.go b/cmd/caddy/main.go index f1aeda0a4..48fa149aa 100644 --- a/cmd/caddy/main.go +++ b/cmd/caddy/main.go @@ -1,8 +1,3 @@ -// The below line is required to enable post-quantum key agreement in Go 1.23 -// by default without insisting on setting a minimum version of 1.23 in go.mod. -// See https://github.com/caddyserver/caddy/issues/6540#issuecomment-2313094905 -//go:debug tlskyber=1 - // Copyright 2015 Matthew Holt and The Caddy Authors // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/go.mod b/go.mod index 87b0e434a..67443562f 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,15 @@ require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/alecthomas/chroma/v2 v2.14.0 github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b - github.com/caddyserver/certmagic v0.21.5-0.20241105180249-4293198e094d + github.com/caddyserver/certmagic v0.21.5 github.com/caddyserver/zerossl v0.1.3 github.com/dustin/go-humanize v1.0.1 github.com/go-chi/chi/v5 v5.0.12 github.com/google/cel-go v0.21.0 github.com/google/uuid v1.6.0 github.com/klauspost/compress v1.17.11 - github.com/klauspost/cpuid/v2 v2.2.8 - github.com/mholt/acmez/v2 v2.0.3 + github.com/klauspost/cpuid/v2 v2.2.9 + github.com/mholt/acmez/v3 v3.0.0 github.com/prometheus/client_golang v1.19.1 github.com/quic-go/quic-go v0.48.2 github.com/smallstep/certificates v0.26.1 @@ -37,9 +37,9 @@ require ( go.uber.org/automaxprocs v1.6.0 go.uber.org/zap v1.27.0 go.uber.org/zap/exp v0.3.0 - golang.org/x/crypto v0.30.0 + golang.org/x/crypto v0.31.0 golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 - golang.org/x/net v0.32.0 + golang.org/x/net v0.33.0 golang.org/x/sync v0.10.0 golang.org/x/term v0.27.0 golang.org/x/time v0.7.0 diff --git a/go.sum b/go.sum index 98306f793..538304a28 100644 --- a/go.sum +++ b/go.sum @@ -89,8 +89,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/caddyserver/certmagic v0.21.5-0.20241105180249-4293198e094d h1:+zOduGxxC4WBAnlDf5Uf0TXbWXRqjUXkJKevDZZa79A= -github.com/caddyserver/certmagic v0.21.5-0.20241105180249-4293198e094d/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE= +github.com/caddyserver/certmagic v0.21.5 h1:iIga4nZRgd27EIEbX7RZmoRMul+EVBn/h7bAGL83dnY= +github.com/caddyserver/certmagic v0.21.5/go.mod h1:n1sCo7zV1Ez2j+89wrzDxo4N/T1Ws/Vx8u5NvuBFabw= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -304,8 +304,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= -github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -344,8 +344,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw= -github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw= +github.com/mholt/acmez/v3 v3.0.0 h1:r1NcjuWR0VaKP2BTjDK9LRFBw/WvURx3jlaEUl9Ht8E= +github.com/mholt/acmez/v3 v3.0.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= @@ -595,8 +595,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 h1:4cEcP5+OjGppY79LCQ5Go2B1Boix2x0v6pvA01P3FoA= golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -628,8 +628,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go index 597772ccc..bea86083a 100644 --- a/modules/caddyhttp/encode/encode.go +++ b/modules/caddyhttp/encode/encode.go @@ -347,6 +347,49 @@ func (rw *responseWriter) Write(p []byte) (int, error) { } } +// used to mask ReadFrom method +type writerOnly struct { + io.Writer +} + +// copied from stdlib +const sniffLen = 512 + +// ReadFrom will try to use sendfile to copy from the reader to the response writer. +// It's only used if the response writer implements io.ReaderFrom and the data can't be compressed. +// It's based on stdlin http1.1 response writer implementation. +// https://github.com/golang/go/blob/f4e3ec3dbe3b8e04a058d266adf8e048bab563f2/src/net/http/server.go#L586 +func (rw *responseWriter) ReadFrom(r io.Reader) (int64, error) { + rf, ok := rw.ResponseWriter.(io.ReaderFrom) + // sendfile can't be used anyway + if !ok { + // mask ReadFrom to avoid infinite recursion + return io.Copy(writerOnly{rw}, r) + } + + var ns int64 + // try to sniff the content type and determine if the response should be compressed + if !rw.wroteHeader && rw.config.MinLength > 0 { + var ( + err error + buf [sniffLen]byte + ) + // mask ReadFrom to let Write determine if the response should be compressed + ns, err = io.CopyBuffer(writerOnly{rw}, io.LimitReader(r, sniffLen), buf[:]) + if err != nil || ns < sniffLen { + return ns, err + } + } + + // the response will be compressed, no sendfile support + if rw.w != nil { + nr, err := io.Copy(rw.w, r) + return nr + ns, err + } + nr, err := rf.ReadFrom(r) + return nr + ns, err +} + // Close writes any remaining buffered response and // deallocates any active resources. func (rw *responseWriter) Close() error { diff --git a/modules/caddyhttp/marshalers.go b/modules/caddyhttp/marshalers.go index c985bb926..9bce377f4 100644 --- a/modules/caddyhttp/marshalers.go +++ b/modules/caddyhttp/marshalers.go @@ -51,6 +51,9 @@ func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error { Header: r.Header, ShouldLogCredentials: r.ShouldLogCredentials, }) + if r.TransferEncoding != nil { + enc.AddArray("transfer_encoding", LoggableStringArray(r.TransferEncoding)) + } if r.TLS != nil { enc.AddObject("tls", LoggableTLSConnState(*r.TLS)) } diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 25fdc1fea..e5ca28b95 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -978,7 +978,7 @@ func (m MatchHeader) Match(r *http.Request) bool { // MatchWithError returns true if r matches m. func (m MatchHeader) MatchWithError(r *http.Request) (bool, error) { repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) - return matchHeaders(r.Header, http.Header(m), r.Host, repl), nil + return matchHeaders(r.Header, http.Header(m), r.Host, r.TransferEncoding, repl), nil } // CELLibrary produces options that expose this matcher for use in CEL @@ -1004,22 +1004,26 @@ func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) { } // getHeaderFieldVals returns the field values for the given fieldName from input. -// The host parameter should be obtained from the http.Request.Host field since -// net/http removes it from the header map. -func getHeaderFieldVals(input http.Header, fieldName, host string) []string { +// The host parameter should be obtained from the http.Request.Host field, and the +// transferEncoding from http.Request.TransferEncoding, since net/http removes them +// from the header map. +func getHeaderFieldVals(input http.Header, fieldName, host string, transferEncoding []string) []string { fieldName = textproto.CanonicalMIMEHeaderKey(fieldName) if fieldName == "Host" && host != "" { return []string{host} } + if fieldName == "Transfer-Encoding" && input[fieldName] == nil { + return transferEncoding + } return input[fieldName] } // matchHeaders returns true if input matches the criteria in against without regex. // The host parameter should be obtained from the http.Request.Host field since // net/http removes it from the header map. -func matchHeaders(input, against http.Header, host string, repl *caddy.Replacer) bool { +func matchHeaders(input, against http.Header, host string, transferEncoding []string, repl *caddy.Replacer) bool { for field, allowedFieldVals := range against { - actualFieldVals := getHeaderFieldVals(input, field, host) + actualFieldVals := getHeaderFieldVals(input, field, host, transferEncoding) if allowedFieldVals != nil && len(allowedFieldVals) == 0 && actualFieldVals != nil { // a non-nil but empty list of allowed values means // match if the header field exists at all @@ -1119,7 +1123,7 @@ func (m MatchHeaderRE) Match(r *http.Request) bool { // MatchWithError returns true if r matches m. func (m MatchHeaderRE) MatchWithError(r *http.Request) (bool, error) { for field, rm := range m { - actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host) + actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host, r.TransferEncoding) match := false fieldVal: for _, actualFieldVal := range actualFieldVals { diff --git a/modules/caddyhttp/responsematchers.go b/modules/caddyhttp/responsematchers.go index cf96b00cd..a6b34c76d 100644 --- a/modules/caddyhttp/responsematchers.go +++ b/modules/caddyhttp/responsematchers.go @@ -41,7 +41,7 @@ func (rm ResponseMatcher) Match(statusCode int, hdr http.Header) bool { if !rm.matchStatusCode(statusCode) { return false } - return matchHeaders(hdr, rm.Headers, "", nil) + return matchHeaders(hdr, rm.Headers, "", []string{}, nil) } func (rm ResponseMatcher) matchStatusCode(statusCode int) bool { diff --git a/modules/caddytls/acmeissuer.go b/modules/caddytls/acmeissuer.go index 9dfeff724..29a5954e7 100644 --- a/modules/caddytls/acmeissuer.go +++ b/modules/caddytls/acmeissuer.go @@ -28,7 +28,7 @@ import ( "github.com/caddyserver/certmagic" "github.com/caddyserver/zerossl" - "github.com/mholt/acmez/v2/acme" + "github.com/mholt/acmez/v3/acme" "go.uber.org/zap" "go.uber.org/zap/zapcore" diff --git a/modules/caddytls/automation.go b/modules/caddytls/automation.go index f6a535077..1bc86020d 100644 --- a/modules/caddytls/automation.go +++ b/modules/caddytls/automation.go @@ -25,7 +25,7 @@ import ( "strings" "github.com/caddyserver/certmagic" - "github.com/mholt/acmez/v2" + "github.com/mholt/acmez/v3" "go.uber.org/zap" "go.uber.org/zap/zapcore" diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index 9332cf2ed..d9fc6bcfe 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -26,7 +26,7 @@ import ( "os" "strings" - "github.com/mholt/acmez/v2" + "github.com/mholt/acmez/v3" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -350,6 +350,20 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error { if err := p.ClientAuthentication.ConfigureTLSConfig(cfg); err != nil { return fmt.Errorf("configuring TLS client authentication: %v", err) } + + // Prevent privilege escalation in case multiple vhosts are configured for + // this TLS server; we could potentially figure out if that's the case, but + // that might be complex to get right every time. Actually, two proper + // solutions could leave tickets enabled, but I am not sure how to do them + // properly without significant time investment; there may be new Go + // APIs that alloaw this (Wrap/UnwrapSession?) but I do not know how to use + // them at this time. TODO: one of these is a possible future enhancement: + // A) Prevent resumptions across server identities (certificates): binding the ticket to the + // certificate we would serve in a full handshake, or even bind a ticket to the exact SNI + // it was issued under (though there are proposals for session resumption across hostnames). + // B) Prevent resumptions falsely authenticating a client: include the realm in the ticket, + // so that it can be validated upon resumption. + cfg.SessionTicketsDisabled = true } if p.InsecureSecretsLog != "" {