0
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-30 22:34:15 -05:00

Merge branch 'master' into master

This commit is contained in:
Matt Holt 2024-08-23 13:08:42 -06:00 committed by GitHub
commit 7ac050a5b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 873 additions and 157 deletions

View file

@ -23,18 +23,18 @@ jobs:
- mac
- windows
go:
- '1.21'
- '1.22'
- '1.23'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.21'
GO_SEMVER: '~1.21.0'
- go: '1.22'
GO_SEMVER: '~1.22.3'
- go: '1.23'
GO_SEMVER: '~1.23.0'
# Set some variables per OS, usable via ${{ matrix.VAR }}
# OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories)
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
@ -99,7 +99,7 @@ jobs:
env:
CGO_ENABLED: 0
run: |
go build -tags nobdger -trimpath -ldflags="-w -s" -v
go build -tags nobadger -trimpath -ldflags="-w -s" -v
- name: Smoke test Caddy
working-directory: ./cmd/caddy
@ -150,6 +150,7 @@ jobs:
uses: actions/checkout@v4
- name: Run Tests
run: |
set +e
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
# short sha is enough?

View file

@ -28,6 +28,7 @@ jobs:
- 'netbsd'
go:
- '1.22'
- '1.23'
include:
# Set the minimum Go patch version for the given Go minor
@ -35,6 +36,9 @@ jobs:
- go: '1.22'
GO_SEMVER: '~1.22.3'
- go: '1.23'
GO_SEMVER: '~1.23.0'
runs-on: ubuntu-latest
continue-on-error: true
steps:

View file

@ -43,13 +43,13 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '~1.22.3'
go-version: '~1.23'
check-latest: true
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.55
version: v1.60
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
args: --timeout 10m
@ -63,5 +63,5 @@ jobs:
- name: govulncheck
uses: golang/govulncheck-action@v1
with:
go-version-input: '~1.22.3'
go-version-input: '~1.23.0'
check-latest: true

View file

@ -13,13 +13,13 @@ jobs:
os:
- ubuntu-latest
go:
- '1.22'
- '1.23'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.22'
GO_SEMVER: '~1.22.3'
- go: '1.23'
GO_SEMVER: '~1.23.0'
runs-on: ${{ matrix.os }}
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233

View file

@ -1,7 +1,9 @@
linters-settings:
errcheck:
ignore: fmt:.*,go.uber.org/zap/zapcore:^Add.*
ignoretests: true
exclude-functions:
- fmt.*
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
gci:
sections:
- standard # Standard section: captures all standard packages.
@ -130,13 +132,14 @@ linters:
run:
# default concurrency is a available CPU number.
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
deadline: 5m
timeout: 5m
issues-exit-code: 1
tests: false
# output configuration options
output:
format: 'colored-line-number'
formats:
- format: 'colored-line-number'
print-issued-lines: true
print-linter-name: true
@ -166,3 +169,6 @@ issues:
- path: modules/logging/filters.go
linters:
- dupl
- path: _test\.go
linters:
- errcheck

View file

@ -87,7 +87,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
Requirements:
- [Go 1.21 or newer](https://golang.org/dl/)
- [Go 1.22.3 or newer](https://golang.org/dl/)
### For development

View file

@ -415,7 +415,7 @@ func (d *Dispenser) EOFErr() error {
// Err generates a custom parse-time error with a message of msg.
func (d *Dispenser) Err(msg string) error {
return d.Errf(msg)
return d.WrapErr(errors.New(msg))
}
// Errf is like Err, but for formatted error messages

View file

@ -84,7 +84,6 @@ func TestLoadUnorderedJSON(t *testing.T) {
"servers": {
"s_server": {
"listen": [
":9443",
":9080"
],
"routes": [

View file

@ -0,0 +1,36 @@
:80
file_server browse {
sort size desc
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"handle": [
{
"browse": {},
"handler": "file_server",
"hide": [
"./Caddyfile"
],
"sort": [
"size",
"desc"
]
}
]
}
]
}
}
}
}
}

View file

@ -0,0 +1,40 @@
:8884
reverse_proxy 127.0.0.1:65535 {
health_uri /health
health_request_body "test body"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"health_checks": {
"active": {
"body": "test body",
"uri": "/health"
}
},
"upstreams": [
{
"dial": "127.0.0.1:65535"
}
]
}
]
}
]
}
}
}
}
}

View file

@ -0,0 +1,57 @@
https://example.com {
reverse_proxy http://localhost:54321 {
transport http {
local_address 192.168.0.1
}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"local_address": "192.168.0.1",
"protocol": "http"
},
"upstreams": [
{
"dial": "localhost:54321"
}
]
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
}

View file

@ -0,0 +1,2 @@
foo

View file

@ -0,0 +1 @@
foo

View file

@ -8,9 +8,10 @@ import (
"github.com/caddyserver/caddy/v2"
)
var rootCmd = &cobra.Command{
Use: "caddy",
Long: `Caddy is an extensible server platform written in Go.
var defaultFactory = newRootCommandFactory(func() *cobra.Command {
return &cobra.Command{
Use: "caddy",
Long: `Caddy is an extensible server platform written in Go.
At its core, Caddy merely manages configuration. Modules are plugged
in statically at compile-time to provide useful functionality. Caddy's
@ -91,23 +92,26 @@ package installers: https://caddyserver.com/docs/install
Instructions for running Caddy in production are also available:
https://caddyserver.com/docs/running
`,
Example: ` $ caddy run
Example: ` $ caddy run
$ caddy run --config caddy.json
$ caddy reload --config caddy.json
$ caddy stop`,
// kind of annoying to have all the help text printed out if
// caddy has an error provisioning its modules, for instance...
SilenceUsage: true,
Version: onlyVersionText(),
}
// kind of annoying to have all the help text printed out if
// caddy has an error provisioning its modules, for instance...
SilenceUsage: true,
Version: onlyVersionText(),
}
})
const fullDocsFooter = `Full documentation is available at:
https://caddyserver.com/docs/command-line`
func init() {
rootCmd.SetVersionTemplate("{{.Version}}\n")
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
defaultFactory.Use(func(rootCmd *cobra.Command) {
rootCmd.SetVersionTemplate("{{.Version}}\n")
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
})
}
func onlyVersionText() string {

28
cmd/commandfactory.go Normal file
View file

@ -0,0 +1,28 @@
package caddycmd
import (
"github.com/spf13/cobra"
)
type rootCommandFactory struct {
constructor func() *cobra.Command
options []func(*cobra.Command)
}
func newRootCommandFactory(fn func() *cobra.Command) *rootCommandFactory {
return &rootCommandFactory{
constructor: fn,
}
}
func (f *rootCommandFactory) Use(fn func(cmd *cobra.Command)) {
f.options = append(f.options, fn)
}
func (f *rootCommandFactory) Build() *cobra.Command {
o := f.constructor()
for _, v := range f.options {
v(o)
}
return o
}

View file

@ -74,6 +74,10 @@ func cmdStart(fl Flags) (int, error) {
// sure by giving it some random bytes and having it echo
// them back to us)
cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String())
// we should be able to run caddy in relative paths
if errors.Is(cmd.Err, exec.ErrDot) {
cmd.Err = nil
}
if configFlag != "" {
cmd.Args = append(cmd.Args, "--config", configFlag)
}

View file

@ -438,43 +438,44 @@ EXPERIMENTAL: May be changed or removed.
},
})
RegisterCommand(Command{
Name: "manpage",
Usage: "--directory <path>",
Short: "Generates the manual pages for Caddy commands",
Long: `
defaultFactory.Use(func(rootCmd *cobra.Command) {
RegisterCommand(Command{
Name: "manpage",
Usage: "--directory <path>",
Short: "Generates the manual pages for Caddy commands",
Long: `
Generates the manual pages for Caddy commands into the designated directory
tagged into section 8 (System Administration).
The manual page files are generated into the directory specified by the
argument of --directory. If the directory does not exist, it will be created.
`,
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("directory", "o", "", "The output directory where the manpages are generated")
cmd.RunE = WrapCommandFuncForCobra(func(fl Flags) (int, error) {
dir := strings.TrimSpace(fl.String("directory"))
if dir == "" {
return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required")
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return caddy.ExitCodeFailedQuit, err
}
if err := doc.GenManTree(rootCmd, &doc.GenManHeader{
Title: "Caddy",
Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections
}, dir); err != nil {
return caddy.ExitCodeFailedQuit, err
}
return caddy.ExitCodeSuccess, nil
})
},
})
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("directory", "o", "", "The output directory where the manpages are generated")
cmd.RunE = WrapCommandFuncForCobra(func(fl Flags) (int, error) {
dir := strings.TrimSpace(fl.String("directory"))
if dir == "" {
return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required")
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return caddy.ExitCodeFailedQuit, err
}
if err := doc.GenManTree(rootCmd, &doc.GenManHeader{
Title: "Caddy",
Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections
}, dir); err != nil {
return caddy.ExitCodeFailedQuit, err
}
return caddy.ExitCodeSuccess, nil
})
},
})
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md
rootCmd.AddCommand(&cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: fmt.Sprintf(`To load completions:
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md
rootCmd.AddCommand(&cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: fmt.Sprintf(`To load completions:
Bash:
@ -513,23 +514,24 @@ argument of --directory. If the directory does not exist, it will be created.
PS> %[1]s completion powershell > %[1]s.ps1
# and source this file from your PowerShell profile.
`, rootCmd.Root().Name()),
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
RunE: func(cmd *cobra.Command, args []string) error {
switch args[0] {
case "bash":
return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
return cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
return cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
default:
return fmt.Errorf("unrecognized shell: %s", args[0])
}
},
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
RunE: func(cmd *cobra.Command, args []string) error {
switch args[0] {
case "bash":
return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
return cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
return cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
default:
return fmt.Errorf("unrecognized shell: %s", args[0])
}
},
})
})
}
@ -563,7 +565,9 @@ func RegisterCommand(cmd Command) {
if !commandNameRegex.MatchString(cmd.Name) {
panic("invalid command name")
}
rootCmd.AddCommand(caddyCmdToCobra(cmd))
defaultFactory.Use(func(rootCmd *cobra.Command) {
rootCmd.AddCommand(caddyCmdToCobra(cmd))
})
}
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)

View file

@ -72,7 +72,7 @@ func Main() {
caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err))
}
if err := rootCmd.Execute(); err != nil {
if err := defaultFactory.Build().Execute(); err != nil {
var exitError *exitError
if errors.As(err, &exitError) {
os.Exit(exitError.ExitCode)

22
go.mod
View file

@ -1,8 +1,8 @@
module github.com/caddyserver/caddy/v2
go 1.21.0
go 1.22.3
toolchain go1.22.2
toolchain go1.23.0
require (
github.com/BurntSushi/toml v1.3.2
@ -19,7 +19,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.7
github.com/mholt/acmez/v2 v2.0.1
github.com/prometheus/client_golang v1.19.1
github.com/quic-go/quic-go v0.44.0
github.com/quic-go/quic-go v0.46.0
github.com/smallstep/certificates v0.26.1
github.com/smallstep/nosql v0.6.1
github.com/smallstep/truststore v0.13.0
@ -37,11 +37,11 @@ require (
go.uber.org/automaxprocs v1.5.3
go.uber.org/zap v1.27.0
go.uber.org/zap/exp v0.2.0
golang.org/x/crypto v0.23.0
golang.org/x/crypto v0.26.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595
golang.org/x/net v0.25.0
golang.org/x/sync v0.7.0
golang.org/x/term v0.20.0
golang.org/x/net v0.28.0
golang.org/x/sync v0.8.0
golang.org/x/term v0.23.0
golang.org/x/time v0.5.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
@ -123,7 +123,7 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pires/go-proxyproto v0.7.0
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
@ -147,9 +147,9 @@ require (
go.step.sm/linkedca v0.20.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sys v0.20.0
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.0 // indirect
golang.org/x/sys v0.23.0
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.34.1 // indirect
howett.net/plist v1.0.0 // indirect

36
go.sum
View file

@ -320,8 +320,8 @@ github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8P
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964 h1:ct/vxNBgHpASQ4sT8NaBX9LtsEtluZqaUJydLG50U3E=
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -339,8 +339,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.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y=
github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
@ -510,8 +510,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
@ -532,15 +532,15 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -567,8 +567,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -576,8 +576,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -588,8 +588,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -603,8 +603,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

14
internal/ranges.go Normal file
View file

@ -0,0 +1,14 @@
package internal
// PrivateRangesCIDR returns a list of private CIDR range
// strings, which can be used as a configuration shortcut.
func PrivateRangesCIDR() []string {
return []string{
"192.168.0.0/16",
"172.16.0.0/12",
"10.0.0.0/8",
"127.0.0.1/8",
"fd00::/8",
"::1",
}
}

View file

@ -340,7 +340,7 @@ func (celTypeAdapter) NativeToValue(value any) ref.Val {
case time.Time:
return types.Timestamp{Time: v}
case error:
types.NewErr(v.Error())
return types.WrapErr(v)
}
return types.DefaultTypeAdapter.NativeToValue(value)
}
@ -499,7 +499,7 @@ func CELMatcherRuntimeFunction(funcName string, fac CELMatcherFactory) functions
return func(celReq, matcherData ref.Val) ref.Val {
matcher, err := fac(matcherData)
if err != nil {
return types.NewErr(err.Error())
return types.WrapErr(err)
}
httpReq := celReq.Value().(celHTTPRequest)
return types.Bool(matcher.Match(httpReq.Request))

View file

@ -266,6 +266,14 @@ func (rw *responseWriter) FlushError() error {
// to rw.Write (see bug in #4314)
return nil
}
// also flushes the encoder, if any
// see: https://github.com/jjiang-stripe/caddy-slow-gzip
if rw.w != nil {
err := rw.w.Flush()
if err != nil {
return err
}
}
//nolint:bodyclose
return http.NewResponseController(rw.ResponseWriter).Flush()
}
@ -475,6 +483,7 @@ type encodingPreference struct {
type Encoder interface {
io.WriteCloser
Reset(io.Writer)
Flush() error // encoder by default buffers data to maximize compressing rate
}
// Encoding is a type which can create encoders of its kind

View file

@ -206,11 +206,34 @@ func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs
// browseApplyQueryParams applies query parameters to the listing.
// It mutates the listing and may set cookies.
func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseTemplateContext) {
var orderParam, sortParam string
// The configs in Caddyfile have lower priority than Query params,
// so put it at first.
for idx, item := range fsrv.SortOptions {
// Only `sort` & `order`, 2 params are allowed
if idx >= 2 {
break
}
switch item {
case sortByName, sortByNameDirFirst, sortBySize, sortByTime:
sortParam = item
case sortOrderAsc, sortOrderDesc:
orderParam = item
}
}
layoutParam := r.URL.Query().Get("layout")
sortParam := r.URL.Query().Get("sort")
orderParam := r.URL.Query().Get("order")
limitParam := r.URL.Query().Get("limit")
offsetParam := r.URL.Query().Get("offset")
sortParamTmp := r.URL.Query().Get("sort")
if sortParamTmp != "" {
sortParam = sortParamTmp
}
orderParamTmp := r.URL.Query().Get("order")
if orderParamTmp != "" {
orderParam = orderParamTmp
}
switch layoutParam {
case "list", "grid", "":
@ -233,11 +256,11 @@ func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Re
// then figure out the order
switch orderParam {
case "":
orderParam = "asc"
orderParam = sortOrderAsc
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
orderParam = orderCookie.Value
}
case "asc", "desc":
case sortOrderAsc, sortOrderDesc:
http.SetCookie(w, &http.Cookie{Name: "order", Value: orderParam, Secure: r.TLS != nil})
}

View file

@ -373,4 +373,7 @@ const (
sortByNameDirFirst = "namedirfirst"
sortBySize = "size"
sortByTime = "time"
sortOrderAsc = "asc"
sortOrderDesc = "desc"
)

View file

@ -171,6 +171,17 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
fsrv.EtagFileExtensions = etagFileExtensions
case "sort":
for d.NextArg() {
dVal := d.Val()
switch dVal {
case sortByName, sortBySize, sortByTime, sortOrderAsc, sortOrderDesc:
fsrv.SortOptions = append(fsrv.SortOptions, dVal)
default:
return d.Errf("unknown sort option '%s'", dVal)
}
}
default:
return d.Errf("unknown subdirective '%s'", d.Val())
}

View file

@ -153,6 +153,16 @@ type FileServer struct {
// a 404 error. By default, this is false (disabled).
PassThru bool `json:"pass_thru,omitempty"`
// Override the default sort.
// It includes the following options:
// - sort_by: name(default), namedirfirst, size, time
// - order: asc(default), desc
// eg.:
// - `sort time desc` will sort by time in descending order
// - `sort size` will sort by size in ascending order
// The first option must be `sort_by` and the second option must be `order` (if exists).
SortOptions []string `json:"sort,omitempty"`
// Selection of encoders to use to check for precompressed files.
PrecompressedRaw caddy.ModuleMap `json:"precompressed,omitempty" caddy:"namespace=http.precompressed"`
@ -236,6 +246,22 @@ func (fsrv *FileServer) Provision(ctx caddy.Context) error {
fsrv.precompressors[ae] = p
}
// check sort options
for idx, sortOption := range fsrv.SortOptions {
switch idx {
case 0:
if sortOption != sortByName && sortOption != sortByNameDirFirst && sortOption != sortBySize && sortOption != sortByTime {
return fmt.Errorf("the first option must be one of the following: %s, %s, %s, %s, but got %s", sortByName, sortByNameDirFirst, sortBySize, sortByTime, sortOption)
}
case 1:
if sortOption != sortOrderAsc && sortOption != sortOrderDesc {
return fmt.Errorf("the second option must be one of the following: %s, %s, but got %s", sortOrderAsc, sortOrderDesc, sortOption)
}
default:
return fmt.Errorf("only max 2 sort options are allowed, but got %d", idx+1)
}
}
return nil
}

View file

@ -29,6 +29,7 @@ import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/internal"
)
// MatchRemoteIP matches requests by the remote IP address,
@ -79,7 +80,7 @@ func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.Err("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead")
}
if d.Val() == "private_ranges" {
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
continue
}
m.Ranges = append(m.Ranges, d.Val())
@ -173,7 +174,7 @@ func (m *MatchClientIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
for d.NextArg() {
if d.Val() == "private_ranges" {
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
continue
}
m.Ranges = append(m.Ranges, d.Val())
@ -250,7 +251,9 @@ func (m MatchClientIP) Match(r *http.Request) bool {
func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
cidrs := []*netip.Prefix{}
zones := []string{}
repl := caddy.NewReplacer()
for _, str := range ranges {
str = repl.ReplaceAll(str, "")
// Exclude the zone_id from the IP
if strings.Contains(str, "%") {
split := strings.Split(str, "%")

View file

@ -22,6 +22,7 @@ import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/internal"
)
func init() {
@ -92,7 +93,7 @@ func (m *StaticIPRange) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
for d.NextArg() {
if d.Val() == "private_ranges" {
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
continue
}
m.Ranges = append(m.Ranges, d.Val())
@ -121,22 +122,16 @@ func CIDRExpressionToPrefix(expr string) (netip.Prefix, error) {
return prefix, nil
}
// PrivateRangesCIDR returns a list of private CIDR range
// strings, which can be used as a configuration shortcut.
func PrivateRangesCIDR() []string {
return []string{
"192.168.0.0/16",
"172.16.0.0/12",
"10.0.0.0/8",
"127.0.0.1/8",
"fd00::/8",
"::1",
}
}
// Interface guards
var (
_ caddy.Provisioner = (*StaticIPRange)(nil)
_ caddyfile.Unmarshaler = (*StaticIPRange)(nil)
_ IPRangeSource = (*StaticIPRange)(nil)
)
// PrivateRangesCIDR returns a list of private CIDR range
// strings, which can be used as a configuration shortcut.
// Note: this function is used at least by mholt/caddy-l4.
func PrivateRangesCIDR() []string {
return internal.PrivateRangesCIDR()
}

View file

@ -40,7 +40,7 @@ type ListenerWrapper struct {
Allow []string `json:"allow,omitempty"`
allow []netip.Prefix
// Denby is an optional list of CIDR ranges to
// Deny is an optional list of CIDR ranges to
// deny PROXY headers from.
Deny []string `json:"deny,omitempty"`
deny []netip.Prefix
@ -50,7 +50,7 @@ type ListenerWrapper struct {
// Policy definitions are here: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy
FallbackPolicy Policy `json:"fallback_policy,omitempty"`
policy goproxy.PolicyFunc
policy goproxy.ConnPolicyFunc
}
// Provision sets up the listener wrapper.
@ -69,13 +69,14 @@ func (pp *ListenerWrapper) Provision(ctx caddy.Context) error {
}
pp.deny = append(pp.deny, ipnet)
}
pp.policy = func(upstream net.Addr) (goproxy.Policy, error) {
pp.policy = func(options goproxy.ConnPolicyOptions) (goproxy.Policy, error) {
// trust unix sockets
if network := upstream.Network(); caddy.IsUnixNetwork(network) {
if network := options.Upstream.Network(); caddy.IsUnixNetwork(network) {
return goproxy.USE, nil
}
ret := pp.FallbackPolicy
host, _, err := net.SplitHostPort(upstream.String())
host, _, err := net.SplitHostPort(options.Upstream.String())
if err != nil {
return goproxy.REJECT, err
}
@ -106,6 +107,6 @@ func (pp *ListenerWrapper) WrapListener(l net.Listener) net.Listener {
Listener: l,
ReadHeaderTimeout: time.Duration(pp.Timeout),
}
pl.Policy = pp.policy
pl.ConnPolicy = pp.policy
return pl
}

View file

@ -28,6 +28,7 @@ import (
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/internal"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
@ -68,19 +69,20 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
// lb_retry_match <request-matcher>
//
// # active health checking
// health_uri <uri>
// health_port <port>
// health_interval <interval>
// health_passes <num>
// health_fails <num>
// health_timeout <duration>
// health_status <status>
// health_body <regexp>
// health_uri <uri>
// health_port <port>
// health_interval <interval>
// health_passes <num>
// health_fails <num>
// health_timeout <duration>
// health_status <status>
// health_body <regexp>
// health_method <value>
// health_request_body <value>
// health_follow_redirects
// health_headers {
// <field> [<values...>]
// }
// health_method <value>
//
// # passive health checking
// fail_duration <duration>
@ -424,6 +426,18 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
h.HealthChecks.Active.Method = d.Val()
case "health_request_body":
if !d.NextArg() {
return d.ArgErr()
}
if h.HealthChecks == nil {
h.HealthChecks = new(HealthChecks)
}
if h.HealthChecks.Active == nil {
h.HealthChecks.Active = new(ActiveHealthChecks)
}
h.HealthChecks.Active.Body = d.Val()
case "health_interval":
if !d.NextArg() {
return d.ArgErr()
@ -688,7 +702,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
case "trusted_proxies":
for d.NextArg() {
if d.Val() == "private_ranges" {
h.TrustedProxies = append(h.TrustedProxies, caddyhttp.PrivateRangesCIDR()...)
h.TrustedProxies = append(h.TrustedProxies, internal.PrivateRangesCIDR()...)
continue
}
h.TrustedProxies = append(h.TrustedProxies, d.Val())
@ -1312,7 +1326,11 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.Err("cannot specify \"tls_trust_pool\" twice in caddyfile")
}
h.TLS.CARaw = caddyconfig.JSONModuleObject(ca, "provider", modStem, nil)
case "local_address":
if !d.NextArg() {
return d.ArgErr()
}
h.LocalAddress = d.Val()
default:
return d.Errf("unrecognized subdirective %s", d.Val())
}

View file

@ -24,6 +24,7 @@ import (
"regexp"
"runtime/debug"
"strconv"
"strings"
"time"
"go.uber.org/zap"
@ -93,6 +94,9 @@ type ActiveHealthChecks struct {
// The HTTP method to use for health checks (default "GET").
Method string `json:"method,omitempty"`
// The body to send with the health check request.
Body string `json:"body,omitempty"`
// Whether to follow HTTP redirects in response to active health checks (default off).
FollowRedirects bool `json:"follow_redirects,omitempty"`
@ -396,6 +400,16 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ
u.Path = h.HealthChecks.Active.Path
}
// replacer used for both body and headers. Only globals (env vars, system info, etc.) are available
repl := caddy.NewReplacer()
// if body is provided, create a reader for it, otherwise nil
var requestBody io.Reader
if h.HealthChecks.Active.Body != "" {
// set body, using replacer
requestBody = strings.NewReader(repl.ReplaceAll(h.HealthChecks.Active.Body, ""))
}
// attach dialing information to this request, as well as context values that
// may be expected by handlers of this request
ctx := h.ctx.Context
@ -403,15 +417,14 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ
ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{
dialInfoVarKey: dialInfo,
})
req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), nil)
req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), requestBody)
if err != nil {
return fmt.Errorf("making request: %v", err)
}
ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *req)
req = req.WithContext(ctx)
// set headers, using a replacer with only globals (env vars, system info, etc.)
repl := caddy.NewReplacer()
// set headers, using replacer
repl.Set("http.reverse_proxy.active.target_upstream", networkAddr)
for key, vals := range h.HealthChecks.Active.Headers {
key = repl.ReplaceAll(key, "")

View file

@ -132,6 +132,10 @@ type HTTPTransport struct {
// to change or removal while experimental.
Versions []string `json:"versions,omitempty"`
// Specify the address to bind to when connecting to an upstream. In other words,
// it is the address the upstream sees as the remote address.
LocalAddress string `json:"local_address,omitempty"`
// The pre-configured underlying HTTP transport.
Transport *http.Transport `json:"-"`
@ -185,6 +189,31 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
FallbackDelay: time.Duration(h.FallbackDelay),
}
if h.LocalAddress != "" {
netaddr, err := caddy.ParseNetworkAddressWithDefaults(h.LocalAddress, "tcp", 0)
if err != nil {
return nil, err
}
if netaddr.PortRangeSize() > 1 {
return nil, fmt.Errorf("local_address must be a single address, not a port range")
}
switch netaddr.Network {
case "tcp", "tcp4", "tcp6":
dialer.LocalAddr, err = net.ResolveTCPAddr(netaddr.Network, netaddr.JoinHostPort(0))
if err != nil {
return nil, err
}
case "unix", "unixgram", "unixpacket":
dialer.LocalAddr, err = net.ResolveUnixAddr(netaddr.Network, netaddr.JoinHostPort(0))
if err != nil {
return nil, err
}
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("local_address must be a TCP address, not a UDP address")
default:
return nil, fmt.Errorf("unsupported network")
}
}
if h.Resolver != nil {
err := h.Resolver.ParseAddresses()
if err != nil {
@ -446,6 +475,9 @@ func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// if H2C ("HTTP/2 over cleartext") is enabled and the upstream request is
// HTTP without TLS, use the alternate H2C-capable transport instead
if req.URL.Scheme == "http" && h.h2cTransport != nil {
// There is no dedicated DisableKeepAlives field in *http2.Transport.
// This is an alternative way to disable keep-alive.
req.Close = h.Transport.DisableKeepAlives
return h.h2cTransport.RoundTrip(req)
}

View file

@ -979,7 +979,7 @@ func (h *Handler) finalizeResponse(
// we'll just log the error and abort the stream here and panic just as
// the standard lib's proxy to propagate the stream error.
// see issue https://github.com/caddyserver/caddy/issues/5951
logger.Error("aborting with incomplete response", zap.Error(err))
logger.Warn("aborting with incomplete response", zap.Error(err))
// no extra logging from stdlib
panic(http.ErrAbortHandler)
}

View file

@ -602,6 +602,7 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error
QUICConfig: &quic.Config{
Versions: []quic.Version{quic.Version1, quic.Version2},
},
IdleTimeout: time.Duration(s.IdleTimeout),
ConnContext: func(ctx context.Context, c quic.Connection) context.Context {
return context.WithValue(ctx, quicConnCtxKey, c)
},

View file

@ -105,8 +105,7 @@ func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler
}
statusCode = intVal
}
return Error(statusCode, fmt.Errorf("%s", e.Error))
return Error(statusCode, fmt.Errorf("%s", repl.ReplaceKnown(e.Error, "")))
}
// Interface guard

View file

@ -22,6 +22,8 @@ import (
"math/big"
"github.com/caddyserver/certmagic"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)
// CustomCertSelectionPolicy represents a policy for selecting the certificate
@ -122,6 +124,79 @@ nextChoice:
return certmagic.DefaultCertificateSelector(hello, viable)
}
// UnmarshalCaddyfile sets up the CustomCertSelectionPolicy from Caddyfile tokens. Syntax:
//
// cert_selection {
// all_tags <values...>
// any_tag <values...>
// public_key_algorithm <dsa|ecdsa|rsa>
// serial_number <big_integers...>
// subject_organization <values...>
// }
func (p *CustomCertSelectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
_, wrapper := d.Next(), d.Val() // consume wrapper name
// No same-line options are supported
if d.CountRemainingArgs() > 0 {
return d.ArgErr()
}
var hasPublicKeyAlgorithm bool
for nesting := d.Nesting(); d.NextBlock(nesting); {
optionName := d.Val()
switch optionName {
case "all_tags":
if d.CountRemainingArgs() == 0 {
return d.ArgErr()
}
p.AllTags = append(p.AllTags, d.RemainingArgs()...)
case "any_tag":
if d.CountRemainingArgs() == 0 {
return d.ArgErr()
}
p.AnyTag = append(p.AnyTag, d.RemainingArgs()...)
case "public_key_algorithm":
if hasPublicKeyAlgorithm {
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
}
if d.CountRemainingArgs() != 1 {
return d.ArgErr()
}
d.NextArg()
if err := p.PublicKeyAlgorithm.UnmarshalJSON([]byte(d.Val())); err != nil {
return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
}
hasPublicKeyAlgorithm = true
case "serial_number":
if d.CountRemainingArgs() == 0 {
return d.ArgErr()
}
for d.NextArg() {
val, bi := d.Val(), bigInt{}
_, ok := bi.SetString(val, 10)
if !ok {
return d.Errf("parsing %s option '%s': invalid big.int value %s", wrapper, optionName, val)
}
p.SerialNumber = append(p.SerialNumber, bi)
}
case "subject_organization":
if d.CountRemainingArgs() == 0 {
return d.ArgErr()
}
p.SubjectOrganization = append(p.SubjectOrganization, d.RemainingArgs()...)
default:
return d.ArgErr()
}
// No nested blocks are supported
if d.NextBlock(nesting + 1) {
return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
}
}
return nil
}
// bigInt is a big.Int type that interops with JSON encodings as a string.
type bigInt struct{ big.Int }
@ -144,3 +219,6 @@ func (bi *bigInt) UnmarshalJSON(p []byte) error {
}
return nil
}
// Interface guard
var _ caddyfile.Unmarshaler = (*CustomCertSelectionPolicy)(nil)

View file

@ -363,6 +363,136 @@ func (p ConnectionPolicy) SettingsEmpty() bool {
p.InsecureSecretsLog == ""
}
// UnmarshalCaddyfile sets up the ConnectionPolicy from Caddyfile tokens. Syntax:
//
// connection_policy {
// alpn <values...>
// cert_selection {
// ...
// }
// ciphers <cipher_suites...>
// client_auth {
// ...
// }
// curves <curves...>
// default_sni <server_name>
// match {
// ...
// }
// protocols <min> [<max>]
// # EXPERIMENTAL:
// drop
// fallback_sni <server_name>
// insecure_secrets_log <log_file>
// }
func (cp *ConnectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
_, wrapper := d.Next(), d.Val()
// No same-line options are supported
if d.CountRemainingArgs() > 0 {
return d.ArgErr()
}
var hasCertSelection, hasClientAuth, hasDefaultSNI, hasDrop,
hasFallbackSNI, hasInsecureSecretsLog, hasMatch, hasProtocols bool
for nesting := d.Nesting(); d.NextBlock(nesting); {
optionName := d.Val()
switch optionName {
case "alpn":
if d.CountRemainingArgs() == 0 {
return d.ArgErr()
}
cp.ALPN = append(cp.ALPN, d.RemainingArgs()...)
case "cert_selection":
if hasCertSelection {
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
}
p := &CustomCertSelectionPolicy{}
if err := p.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil {
return err
}
cp.CertSelection, hasCertSelection = p, true
case "client_auth":
if hasClientAuth {
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
}
ca := &ClientAuthentication{}
if err := ca.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil {
return err
}
cp.ClientAuthentication, hasClientAuth = ca, true
case "ciphers":
if d.CountRemainingArgs() == 0 {
return d.ArgErr()
}
cp.CipherSuites = append(cp.CipherSuites, d.RemainingArgs()...)
case "curves":
if d.CountRemainingArgs() == 0 {
return d.ArgErr()
}
cp.Curves = append(cp.Curves, d.RemainingArgs()...)
case "default_sni":
if hasDefaultSNI {
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
}
if d.CountRemainingArgs() != 1 {
return d.ArgErr()
}
_, cp.DefaultSNI, hasDefaultSNI = d.NextArg(), d.Val(), true
case "drop": // EXPERIMENTAL
if hasDrop {
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
}
cp.Drop, hasDrop = true, true
case "fallback_sni": // EXPERIMENTAL
if hasFallbackSNI {
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
}
if d.CountRemainingArgs() != 1 {
return d.ArgErr()
}
_, cp.FallbackSNI, hasFallbackSNI = d.NextArg(), d.Val(), true
case "insecure_secrets_log": // EXPERIMENTAL
if hasInsecureSecretsLog {
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
}
if d.CountRemainingArgs() != 1 {
return d.ArgErr()
}
_, cp.InsecureSecretsLog, hasInsecureSecretsLog = d.NextArg(), d.Val(), true
case "match":
if hasMatch {
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
}
matcherSet, err := ParseCaddyfileNestedMatcherSet(d)
if err != nil {
return err
}
cp.MatchersRaw, hasMatch = matcherSet, true
case "protocols":
if hasProtocols {
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
}
if d.CountRemainingArgs() == 0 || d.CountRemainingArgs() > 2 {
return d.ArgErr()
}
_, cp.ProtocolMin, hasProtocols = d.NextArg(), d.Val(), true
if d.NextArg() {
cp.ProtocolMax = d.Val()
}
default:
return d.ArgErr()
}
// No nested blocks are supported
if d.NextBlock(nesting + 1) {
return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
}
}
return nil
}
// ClientAuthentication configures TLS client auth.
type ClientAuthentication struct {
// Certificate authority module which provides the certificate pool of trusted certificates
@ -819,4 +949,46 @@ func (d destructableWriter) Destruct() error { return d.Close() }
var secretsLogPool = caddy.NewUsagePool()
var _ caddyfile.Unmarshaler = (*ClientAuthentication)(nil)
// Interface guards
var (
_ caddyfile.Unmarshaler = (*ClientAuthentication)(nil)
_ caddyfile.Unmarshaler = (*ConnectionPolicy)(nil)
)
// ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested
// matcher set, and returns its raw module map value.
func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
matcherMap := make(map[string]ConnectionMatcher)
tokensByMatcherName := make(map[string][]caddyfile.Token)
for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
matcherName := d.Val()
tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
}
for matcherName, tokens := range tokensByMatcherName {
dd := caddyfile.NewDispenser(tokens)
dd.Next() // consume wrapper name
unm, err := caddyfile.UnmarshalModule(dd, "tls.handshake_match."+matcherName)
if err != nil {
return nil, err
}
cm, ok := unm.(ConnectionMatcher)
if !ok {
return nil, fmt.Errorf("matcher module '%s' is not a connection matcher", matcherName)
}
matcherMap[matcherName] = cm
}
matcherSet := make(caddy.ModuleMap)
for name, matcher := range matcherMap {
jsonBytes, err := json.Marshal(matcher)
if err != nil {
return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
}
matcherSet[name] = jsonBytes
}
return matcherSet, nil
}

View file

@ -25,6 +25,8 @@ import (
"go.uber.org/zap"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/internal"
)
func init() {
@ -48,14 +50,47 @@ func (MatchServerName) CaddyModule() caddy.ModuleInfo {
// Match matches hello based on SNI.
func (m MatchServerName) Match(hello *tls.ClientHelloInfo) bool {
repl := caddy.NewReplacer()
// caddytls.TestServerNameMatcher calls this function without any context
if ctx := hello.Context(); ctx != nil {
// In some situations the existing context may have no replacer
if replAny := ctx.Value(caddy.ReplacerCtxKey); replAny != nil {
repl = replAny.(*caddy.Replacer)
}
}
for _, name := range m {
if certmagic.MatchWildcard(hello.ServerName, name) {
rs := repl.ReplaceAll(name, "")
if certmagic.MatchWildcard(hello.ServerName, rs) {
return true
}
}
return false
}
// UnmarshalCaddyfile sets up the MatchServerName from Caddyfile tokens. Syntax:
//
// sni <domains...>
func (m *MatchServerName) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
wrapper := d.Val()
// At least one same-line option must be provided
if d.CountRemainingArgs() == 0 {
return d.ArgErr()
}
*m = append(*m, d.RemainingArgs()...)
// No blocks are supported
if d.NextBlock(d.Nesting()) {
return d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper)
}
}
return nil
}
// MatchRemoteIP matches based on the remote IP of the
// connection. Specific IPs or CIDR ranges can be specified.
//
@ -83,16 +118,19 @@ func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
// Provision parses m's IP ranges, either from IP or CIDR expressions.
func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
repl := caddy.NewReplacer()
m.logger = ctx.Logger()
for _, str := range m.Ranges {
cidrs, err := m.parseIPRange(str)
rs := repl.ReplaceAll(str, "")
cidrs, err := m.parseIPRange(rs)
if err != nil {
return err
}
m.cidrs = append(m.cidrs, cidrs...)
}
for _, str := range m.NotRanges {
cidrs, err := m.parseIPRange(str)
rs := repl.ReplaceAll(str, "")
cidrs, err := m.parseIPRange(rs)
if err != nil {
return err
}
@ -145,6 +183,46 @@ func (MatchRemoteIP) matches(ip netip.Addr, ranges []netip.Prefix) bool {
return false
}
// UnmarshalCaddyfile sets up the MatchRemoteIP from Caddyfile tokens. Syntax:
//
// remote_ip <ranges...>
//
// Note: IPs and CIDRs prefixed with ! symbol are treated as not_ranges
func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
wrapper := d.Val()
// At least one same-line option must be provided
if d.CountRemainingArgs() == 0 {
return d.ArgErr()
}
for d.NextArg() {
val := d.Val()
var exclamation bool
if len(val) > 1 && val[0] == '!' {
exclamation, val = true, val[1:]
}
ranges := []string{val}
if val == "private_ranges" {
ranges = internal.PrivateRangesCIDR()
}
if exclamation {
m.NotRanges = append(m.NotRanges, ranges...)
} else {
m.Ranges = append(m.Ranges, ranges...)
}
}
// No blocks are supported
if d.NextBlock(d.Nesting()) {
return d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper)
}
}
return nil
}
// MatchLocalIP matches based on the IP address of the interface
// receiving the connection. Specific IPs or CIDR ranges can be specified.
type MatchLocalIP struct {
@ -165,9 +243,11 @@ func (MatchLocalIP) CaddyModule() caddy.ModuleInfo {
// Provision parses m's IP ranges, either from IP or CIDR expressions.
func (m *MatchLocalIP) Provision(ctx caddy.Context) error {
repl := caddy.NewReplacer()
m.logger = ctx.Logger()
for _, str := range m.Ranges {
cidrs, err := m.parseIPRange(str)
rs := repl.ReplaceAll(str, "")
cidrs, err := m.parseIPRange(rs)
if err != nil {
return err
}
@ -219,6 +299,36 @@ func (MatchLocalIP) matches(ip netip.Addr, ranges []netip.Prefix) bool {
return false
}
// UnmarshalCaddyfile sets up the MatchLocalIP from Caddyfile tokens. Syntax:
//
// local_ip <ranges...>
func (m *MatchLocalIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
wrapper := d.Val()
// At least one same-line option must be provided
if d.CountRemainingArgs() == 0 {
return d.ArgErr()
}
for d.NextArg() {
val := d.Val()
if val == "private_ranges" {
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
continue
}
m.Ranges = append(m.Ranges, val)
}
// No blocks are supported
if d.NextBlock(d.Nesting()) {
return d.Errf("malformed TLS handshake matcher '%s': blocks are not supported", wrapper)
}
}
return nil
}
// Interface guards
var (
_ ConnectionMatcher = (*MatchServerName)(nil)
@ -226,4 +336,8 @@ var (
_ caddy.Provisioner = (*MatchLocalIP)(nil)
_ ConnectionMatcher = (*MatchLocalIP)(nil)
_ caddyfile.Unmarshaler = (*MatchLocalIP)(nil)
_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
_ caddyfile.Unmarshaler = (*MatchServerName)(nil)
)

View file

@ -15,6 +15,7 @@
package caddy
import (
"bytes"
"fmt"
"io"
"net/http"
@ -354,6 +355,8 @@ func (f fileReplacementProvider) replace(key string) (any, bool) {
zap.Error(err))
return nil, true
}
body = bytes.TrimSuffix(body, []byte("\n"))
body = bytes.TrimSuffix(body, []byte("\r"))
return string(body), true
}

View file

@ -431,6 +431,14 @@ func TestReplacerNew(t *testing.T) {
variable: "file.caddytest/integration/testdata/foo.txt",
value: "foo",
},
{
variable: "file.caddytest/integration/testdata/foo_with_trailing_newline.txt",
value: "foo",
},
{
variable: "file.caddytest/integration/testdata/foo_with_multiple_trailing_newlines.txt",
value: "foo" + getEOL(),
},
} {
if val, ok := repl.providers[1].replace(tc.variable); ok {
if val != tc.value {
@ -442,6 +450,13 @@ func TestReplacerNew(t *testing.T) {
}
}
func getEOL() string {
if os.PathSeparator == '\\' {
return "\r\n" // Windows EOL
}
return "\n" // Unix and modern macOS EOL
}
func TestReplacerNewWithoutFile(t *testing.T) {
repl := NewReplacer().WithoutFile()