Merge branch 'master' into leaf-verifier-caddyfile

This commit is contained in:
Mohammed Al Sahaf 2025-01-25 02:47:13 +03:00 committed by GitHub
commit 062de5614a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 186 additions and 73 deletions

View file

@ -111,7 +111,7 @@ archives:
- id: default
format_overrides:
- goos: windows
format: zip
formats: zip
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_

View file

@ -12,10 +12,14 @@
@images path /images/*
header @images {
Cache-Control "public, max-age=3600, stale-while-revalidate=86400"
match {
status 200
}
}
header {
+Link "Foo"
+Link "Bar"
match status 200
}
header >Set Defer
header >Replace Deferred Replacement
@ -42,6 +46,11 @@
{
"handler": "headers",
"response": {
"require": {
"status_code": [
200
]
},
"set": {
"Cache-Control": [
"public, max-age=3600, stale-while-revalidate=86400"
@ -136,6 +145,11 @@
"Foo",
"Bar"
]
},
"require": {
"status_code": [
200
]
}
}
},

8
go.mod
View file

@ -9,7 +9,7 @@ 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
github.com/caddyserver/certmagic v0.21.7
github.com/caddyserver/zerossl v0.1.3
github.com/dustin/go-humanize v1.0.1
github.com/go-chi/chi/v5 v5.0.12
@ -17,9 +17,9 @@ require (
github.com/google/uuid v1.6.0
github.com/klauspost/compress v1.17.11
github.com/klauspost/cpuid/v2 v2.2.9
github.com/mholt/acmez/v3 v3.0.0
github.com/mholt/acmez/v3 v3.0.1
github.com/prometheus/client_golang v1.19.1
github.com/quic-go/quic-go v0.48.2
github.com/quic-go/quic-go v0.49.0
github.com/smallstep/certificates v0.26.1
github.com/smallstep/nosql v0.6.1
github.com/smallstep/truststore v0.13.0
@ -74,7 +74,7 @@ require (
go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect
go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect

16
go.sum
View file

@ -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 h1:iIga4nZRgd27EIEbX7RZmoRMul+EVBn/h7bAGL83dnY=
github.com/caddyserver/certmagic v0.21.5/go.mod h1:n1sCo7zV1Ez2j+89wrzDxo4N/T1Ws/Vx8u5NvuBFabw=
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
github.com/caddyserver/certmagic v0.21.7/go.mod h1:LCPG3WLxcnjVKl/xpjzM0gqh0knrKKKiO5WVttX2eEI=
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=
@ -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/v3 v3.0.0 h1:r1NcjuWR0VaKP2BTjDK9LRFBw/WvURx3jlaEUl9Ht8E=
github.com/mholt/acmez/v3 v3.0.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/mholt/acmez/v3 v3.0.1 h1:4PcjKjaySlgXK857aTfDuRbmnM5gb3Ruz3tvoSJAUp8=
github.com/mholt/acmez/v3 v3.0.1/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=
@ -392,8 +392,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
@ -564,8 +564,8 @@ go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=

View file

@ -529,21 +529,6 @@ func (app *App) Start() error {
// enable TLS if there is a policy and if this is not the HTTP port
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
// enable HTTP/3 if configured
if h3ok && useTLS {
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
return err
}
}
if h3ok && !useTLS {
// Can only serve h3 with TLS enabled
app.logger.Warn("HTTP/3 skipped because it requires TLS",
zap.String("network", listenAddr.Network),
zap.String("addr", hostport))
}
if h1ok || h2ok && useTLS || h2cok {
// create the listener for this socket
lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
@ -614,6 +599,33 @@ func (app *App) Start() error {
zap.String("network", listenAddr.Network),
zap.String("addr", hostport))
}
if h3ok {
// Can't serve HTTP/3 on the same socket as HTTP/1 and 2 because it uses
// a different transport mechanism... which is fine, but the OS doesn't
// differentiate between a SOCK_STREAM file and a SOCK_DGRAM file; they
// are still one file on the system. So even though "unixpacket" and
// "unixgram" are different network types just as "tcp" and "udp" are,
// the OS will not let us use the same file as both STREAM and DGRAM.
if listenAddr.IsUnixNetwork() {
app.logger.Warn("HTTP/3 disabled because Unix can't multiplex STREAM and DGRAM on same socket",
zap.String("file", hostport))
continue
}
if useTLS {
// enable HTTP/3 if configured
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
return err
}
} else {
// Can only serve h3 with TLS enabled
app.logger.Warn("HTTP/3 skipped because it requires TLS",
zap.String("network", listenAddr.Network),
zap.String("addr", hostport))
}
}
}
}

View file

@ -99,6 +99,16 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
handler.Response.Deferred = true
continue
}
if field == "match" {
responseMatchers := make(map[string]caddyhttp.ResponseMatcher)
err := caddyhttp.ParseNamedResponseMatcher(h.NewFromNextSegment(), responseMatchers)
if err != nil {
return nil, err
}
matcher := responseMatchers["match"]
handler.Response.Require = &matcher
continue
}
if hasArgs {
return nil, h.Err("cannot specify headers in both arguments and block") // because it would be weird
}

View file

@ -143,6 +143,28 @@ func TestHandler(t *testing.T) {
"Cache-Control": []string{"no-cache"},
},
},
{ // same as above, but checks that response headers are left alone when "Require" conditions are unmet
handler: Handler{
Response: &RespHeaderOps{
Require: &caddyhttp.ResponseMatcher{
Headers: http.Header{
"Cache-Control": nil,
},
},
HeaderOps: &HeaderOps{
Add: http.Header{
"Cache-Control": []string{"no-cache"},
},
},
},
},
respHeader: http.Header{
"Cache-Control": []string{"something"},
},
expectedRespHeader: http.Header{
"Cache-Control": []string{"something"},
},
},
{
handler: Handler{
Response: &RespHeaderOps{

View file

@ -211,6 +211,11 @@ func errLogValues(err error) (status int, msg string, fields func() []zapcore.Fi
}
return
}
fields = func() []zapcore.Field {
return []zapcore.Field{
zap.Error(err),
}
}
status = http.StatusInternalServerError
msg = err.Error()
return

View file

@ -131,15 +131,18 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// is equivalent to a route consisting of:
//
// # Add trailing slash for directory requests
// # This redirection is automatically disabled if "{http.request.uri.path}/index.php"
// # doesn't appear in the try_files list
// @canonicalPath {
// file {path}/index.php
// not path */
// }
// redir @canonicalPath {path}/ 308
//
// # If the requested file does not exist, try index files
// # If the requested file does not exist, try index files and assume index.php always exists
// @indexFiles file {
// try_files {path} {path}/index.php index.php
// try_policy first_exist_fallback
// split_path .php
// }
// rewrite @indexFiles {http.matchers.file.relative}

View file

@ -683,7 +683,7 @@ func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http.
req.Header.Set("Early-Data", "1")
}
reqUpType := upgradeType(req.Header)
reqUpgradeType := upgradeType(req.Header)
removeConnectionHeaders(req.Header)
// Remove hop-by-hop headers to the backend. Especially
@ -704,9 +704,9 @@ func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http.
// After stripping all the hop-by-hop connection headers above, add back any
// necessary for protocol upgrades, such as for websockets.
if reqUpType != "" {
if reqUpgradeType != "" {
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", reqUpType)
req.Header.Set("Upgrade", reqUpgradeType)
normalizeWebsocketHeaders(req.Header)
}
@ -732,6 +732,9 @@ func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http.
return nil, err
}
// Via header(s)
req.Header.Add("Via", fmt.Sprintf("%d.%d Caddy", req.ProtoMajor, req.ProtoMinor))
return req, nil
}
@ -882,13 +885,15 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
}),
)
const logMessage = "upstream roundtrip"
if err != nil {
if c := logger.Check(zapcore.DebugLevel, "upstream roundtrip"); c != nil {
if c := logger.Check(zapcore.DebugLevel, logMessage); c != nil {
c.Write(zap.Error(err))
}
return err
}
if c := logger.Check(zapcore.DebugLevel, "upstream roundtrip"); c != nil {
if c := logger.Check(zapcore.DebugLevel, logMessage); c != nil {
c.Write(
zap.Object("headers", caddyhttp.LoggableHTTPHeader{
Header: res.Header,
@ -1024,6 +1029,14 @@ func (h *Handler) finalizeResponse(
res.Header.Del(h)
}
// delete our Server header and use Via instead (see #6275)
rw.Header().Del("Server")
var protoPrefix string
if !strings.HasPrefix(strings.ToUpper(res.Proto), "HTTP/") {
protoPrefix = res.Proto[:strings.Index(res.Proto, "/")+1]
}
rw.Header().Add("Via", fmt.Sprintf("%s%d.%d Caddy", protoPrefix, res.ProtoMajor, res.ProtoMinor))
// apply any response header operations
if h.Headers != nil && h.Headers.Response != nil {
if h.Headers.Response.Require == nil ||

View file

@ -60,6 +60,14 @@ type ACMEIssuer struct {
// other than ACME transactions.
Email string `json:"email,omitempty"`
// Optionally select an ACME profile to use for certificate
// orders. Must be a profile name offered by the ACME server,
// which are listed at its directory endpoint.
//
// EXPERIMENTAL: Subject to change.
// See https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/
Profile string `json:"profile,omitempty"`
// If you have an existing account with the ACME server, put
// the private key here in PEM format. The ACME client will
// look up your account information with this key first before
@ -184,6 +192,7 @@ func (iss *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEIssuer, error) {
CA: iss.CA,
TestCA: iss.TestCA,
Email: iss.Email,
Profile: iss.Profile,
AccountKeyPEM: iss.AccountKey,
CertObtainTimeout: time.Duration(iss.ACMETimeout),
TrustedRoots: iss.rootPool,
@ -338,6 +347,7 @@ func (iss *ACMEIssuer) generateZeroSSLEABCredentials(ctx context.Context, acct a
// dir <directory_url>
// test_dir <test_directory_url>
// email <email>
// profile <profile_name>
// timeout <duration>
// disable_http_challenge
// disable_tlsalpn_challenge
@ -400,6 +410,11 @@ func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.ArgErr()
}
case "profile":
if !d.AllArgs(&iss.Profile) {
return d.ArgErr()
}
case "timeout":
var timeoutStr string
if !d.AllArgs(&timeoutStr) {

View file

@ -20,6 +20,7 @@ import (
"io"
"math"
"os"
"path/filepath"
"strconv"
"github.com/dustin/go-humanize"
@ -146,51 +147,68 @@ func (fw FileWriter) WriterKey() string {
// OpenWriter opens a new file writer.
func (fw FileWriter) OpenWriter() (io.WriteCloser, error) {
if fw.Mode == 0 {
fw.Mode = 0o600
modeIfCreating := os.FileMode(fw.Mode)
if modeIfCreating == 0 {
modeIfCreating = 0o600
}
// roll log files by default
if fw.Roll == nil || *fw.Roll {
if fw.RollSizeMB == 0 {
fw.RollSizeMB = 100
}
if fw.RollCompress == nil {
compress := true
fw.RollCompress = &compress
}
if fw.RollKeep == 0 {
fw.RollKeep = 10
}
if fw.RollKeepDays == 0 {
fw.RollKeepDays = 90
}
// roll log files as a sensible default to avoid disk space exhaustion
roll := fw.Roll == nil || *fw.Roll
// create the file if it does not exist with the right mode.
// lumberjack will reuse the file mode across log rotation.
f_tmp, err := os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(fw.Mode))
// create the file if it does not exist; create with the configured mode, or default
// to restrictive if not set. (lumberjack will reuse the file mode across log rotation)
if err := os.MkdirAll(filepath.Dir(fw.Filename), 0o700); err != nil {
return nil, err
}
file, err := os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, modeIfCreating)
if err != nil {
return nil, err
}
info, err := file.Stat()
if roll {
file.Close() // lumberjack will reopen it on its own
}
// Ensure already existing files have the right mode, since OpenFile will not set the mode in such case.
if configuredMode := os.FileMode(fw.Mode); configuredMode != 0 {
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to stat log file to see if we need to set permissions: %v", err)
}
f_tmp.Close()
// ensure already existing files have the right mode,
// since OpenFile will not set the mode in such case.
if err = os.Chmod(fw.Filename, os.FileMode(fw.Mode)); err != nil {
return nil, err
// only chmod if the configured mode is different
if info.Mode()&os.ModePerm != configuredMode&os.ModePerm {
if err = os.Chmod(fw.Filename, configuredMode); err != nil {
return nil, err
}
}
return &lumberjack.Logger{
Filename: fw.Filename,
MaxSize: fw.RollSizeMB,
MaxAge: fw.RollKeepDays,
MaxBackups: fw.RollKeep,
LocalTime: fw.RollLocalTime,
Compress: *fw.RollCompress,
}, nil
}
// otherwise just open a regular file
return os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(fw.Mode))
// if not rolling, then the plain file handle is all we need
if !roll {
return file, nil
}
// otherwise, return a rolling log
if fw.RollSizeMB == 0 {
fw.RollSizeMB = 100
}
if fw.RollCompress == nil {
compress := true
fw.RollCompress = &compress
}
if fw.RollKeep == 0 {
fw.RollKeep = 10
}
if fw.RollKeepDays == 0 {
fw.RollKeepDays = 90
}
return &lumberjack.Logger{
Filename: fw.Filename,
MaxSize: fw.RollSizeMB,
MaxAge: fw.RollKeepDays,
MaxBackups: fw.RollKeep,
LocalTime: fw.RollLocalTime,
Compress: *fw.RollCompress,
}, nil
}
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:

View file

@ -20,6 +20,7 @@ import (
"encoding/json"
"os"
"path"
"path/filepath"
"syscall"
"testing"
@ -77,7 +78,7 @@ func TestFileCreationMode(t *testing.T) {
t.Fatalf("failed to create tempdir: %v", err)
}
defer os.RemoveAll(dir)
fpath := path.Join(dir, "test.log")
fpath := filepath.Join(dir, "test.log")
tt.fw.Filename = fpath
logger, err := tt.fw.OpenWriter()
@ -92,7 +93,7 @@ func TestFileCreationMode(t *testing.T) {
}
if st.Mode() != tt.wantMode {
t.Errorf("file mode is %v, want %v", st.Mode(), tt.wantMode)
t.Errorf("%s: file mode is %v, want %v", tt.name, st.Mode(), tt.wantMode)
}
})
}