Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions caddytest/integration/caddyfile_adapt/header.caddyfiletest
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -42,6 +46,11 @@
{
"handler": "headers",
"response": {
"require": {
"status_code": [
200
]
},
"set": {
"Cache-Control": [
"public, max-age=3600, stale-while-revalidate=86400"
Expand Down Expand Up @@ -136,6 +145,11 @@
"Foo",
"Bar"
]
},
"require": {
"status_code": [
200
]
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
github.com/caddyserver/certmagic v0.21.8-0.20250120145635-3a89ceab7a8a
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.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/smallstep/certificates v0.26.1
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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.8-0.20250120145635-3a89ceab7a8a h1:Vp1Tr5XY8HziGGqidnXopftx6kQky/7OS3dBeyxndlw=
github.com/caddyserver/certmagic v0.21.8-0.20250120145635-3a89ceab7a8a/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=
Expand Down Expand Up @@ -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=
Expand Down
42 changes: 27 additions & 15 deletions modules/caddyhttp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)})
Expand Down Expand Up @@ -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))
}
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions modules/caddyhttp/headers/caddyfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
22 changes: 22 additions & 0 deletions modules/caddyhttp/headers/headers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
14 changes: 7 additions & 7 deletions modules/caddyhttp/responsewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,16 @@ func (rr *responseRecorder) WriteHeader(statusCode int) {
// connections by manually setting headers and writing status 101
rr.statusCode = statusCode

// decide whether we should buffer the response
if rr.shouldBuffer == nil {
rr.stream = true
} else {
rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header())
}

// 1xx responses aren't final; just informational
if statusCode < 100 || statusCode > 199 {
rr.wroteHeader = true

// decide whether we should buffer the response
if rr.shouldBuffer == nil {
rr.stream = true
} else {
rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header())
}
}

// if informational or not buffered, immediately write header
Expand Down
90 changes: 54 additions & 36 deletions modules/logging/filewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"io"
"math"
"os"
"path/filepath"
"strconv"

"github.com/dustin/go-humanize"
Expand Down Expand Up @@ -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
// if not rolling, then the plain file handle is all we need
if !roll {
return file, 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))
// 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:
Expand Down
5 changes: 3 additions & 2 deletions modules/logging/filewriter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"encoding/json"
"os"
"path"
"path/filepath"
"syscall"
"testing"

Expand Down Expand Up @@ -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()
Expand All @@ -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)
}
})
}
Expand Down
Loading