mirror of
https://github.com/caddyserver/caddy.git
synced 2025-03-09 15:39:02 -04:00
Compare commits
5 Commits
19876208c7
...
220cd1c2bc
Author | SHA1 | Date | |
---|---|---|---|
|
220cd1c2bc | ||
|
1975408d89 | ||
|
4ebcfed9c9 | ||
|
d2a2311bfd | ||
|
adbe7f87e6 |
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -12,6 +12,10 @@ on:
|
|||||||
- master
|
- master
|
||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
|
env:
|
||||||
|
# https://github.com/actions/setup-go/issues/491
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
@ -95,7 +99,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
run: |
|
run: |
|
||||||
go build -tags nobadger -trimpath -ldflags="-w -s" -v
|
go build -tags nobadger,nomysql,nopgx -trimpath -ldflags="-w -s" -v
|
||||||
|
|
||||||
- name: Smoke test Caddy
|
- name: Smoke test Caddy
|
||||||
working-directory: ./cmd/caddy
|
working-directory: ./cmd/caddy
|
||||||
@ -118,7 +122,7 @@ jobs:
|
|||||||
# continue-on-error: true
|
# continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
||||||
go test -tags nobadger -v -coverprofile="cover-profile.out" -short -race ./...
|
go test -tags nobadger,nomysql,nopgx -v -coverprofile="cover-profile.out" -short -race ./...
|
||||||
# echo "status=$?" >> $GITHUB_OUTPUT
|
# echo "status=$?" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# Relevant step if we reinvestigate publishing test/coverage reports
|
# Relevant step if we reinvestigate publishing test/coverage reports
|
||||||
@ -166,7 +170,7 @@ jobs:
|
|||||||
retries=3
|
retries=3
|
||||||
exit_code=0
|
exit_code=0
|
||||||
while ((retries > 0)); do
|
while ((retries > 0)); do
|
||||||
CGO_ENABLED=0 go test -p 1 -tags nobadger -v ./...
|
CGO_ENABLED=0 go test -p 1 -tags nobadger,nomysql,nopgx -v ./...
|
||||||
exit_code=$?
|
exit_code=$?
|
||||||
if ((exit_code == 0)); then
|
if ((exit_code == 0)); then
|
||||||
break
|
break
|
||||||
|
4
.github/workflows/cross-build.yml
vendored
4
.github/workflows/cross-build.yml
vendored
@ -10,6 +10,10 @@ on:
|
|||||||
- master
|
- master
|
||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
|
env:
|
||||||
|
# https://github.com/actions/setup-go/issues/491
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
|
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@ -13,6 +13,10 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
# https://github.com/actions/setup-go/issues/491
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# From https://github.com/golangci/golangci-lint-action
|
# From https://github.com/golangci/golangci-lint-action
|
||||||
golangci:
|
golangci:
|
||||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -5,6 +5,10 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- 'v*.*.*'
|
- 'v*.*.*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
# https://github.com/actions/setup-go/issues/491
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
|
@ -191,7 +191,7 @@ func (st ServerType) Setup(
|
|||||||
metrics, _ := options["metrics"].(*caddyhttp.Metrics)
|
metrics, _ := options["metrics"].(*caddyhttp.Metrics)
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
if s.Metrics != nil {
|
if s.Metrics != nil {
|
||||||
metrics = cmp.Or[*caddyhttp.Metrics](metrics, &caddyhttp.Metrics{})
|
metrics = cmp.Or(metrics, &caddyhttp.Metrics{})
|
||||||
metrics = &caddyhttp.Metrics{
|
metrics = &caddyhttp.Metrics{
|
||||||
PerHost: metrics.PerHost || s.Metrics.PerHost,
|
PerHost: metrics.PerHost || s.Metrics.PerHost,
|
||||||
}
|
}
|
||||||
@ -350,7 +350,7 @@ func (st ServerType) Setup(
|
|||||||
|
|
||||||
// avoid duplicates by sorting + compacting
|
// avoid duplicates by sorting + compacting
|
||||||
sort.Strings(defaultLog.Exclude)
|
sort.Strings(defaultLog.Exclude)
|
||||||
defaultLog.Exclude = slices.Compact[[]string, string](defaultLog.Exclude)
|
defaultLog.Exclude = slices.Compact(defaultLog.Exclude)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we may have not actually added anything, so remove if empty
|
// we may have not actually added anything, so remove if empty
|
||||||
|
@ -207,7 +207,7 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
|
|
||||||
if srv.Metrics != nil {
|
if srv.Metrics != nil {
|
||||||
srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead")
|
srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead")
|
||||||
app.Metrics = cmp.Or[*Metrics](app.Metrics, &Metrics{
|
app.Metrics = cmp.Or(app.Metrics, &Metrics{
|
||||||
init: sync.Once{},
|
init: sync.Once{},
|
||||||
httpMetrics: &httpMetrics{},
|
httpMetrics: &httpMetrics{},
|
||||||
})
|
})
|
||||||
|
84
modules/caddyhttp/reverseproxy/buffering_test.go
Normal file
84
modules/caddyhttp/reverseproxy/buffering_test.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package reverseproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type zeroReader struct{}
|
||||||
|
|
||||||
|
func (zeroReader) Read(p []byte) (int, error) {
|
||||||
|
for i := range p {
|
||||||
|
p[i] = 0
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuffering(t *testing.T) {
|
||||||
|
var (
|
||||||
|
h Handler
|
||||||
|
zr zeroReader
|
||||||
|
)
|
||||||
|
type args struct {
|
||||||
|
body io.ReadCloser
|
||||||
|
limit int64
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
resultCheck func(io.ReadCloser, int64, args) bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "0 limit, body is returned as is",
|
||||||
|
args: args{
|
||||||
|
body: io.NopCloser(&zr),
|
||||||
|
limit: 0,
|
||||||
|
},
|
||||||
|
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
|
||||||
|
return res == args.body && read == args.limit && read == 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative limit, body is read completely",
|
||||||
|
args: args{
|
||||||
|
body: io.NopCloser(io.LimitReader(&zr, 100)),
|
||||||
|
limit: -1,
|
||||||
|
},
|
||||||
|
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
|
||||||
|
brc, ok := res.(bodyReadCloser)
|
||||||
|
return ok && brc.body == nil && brc.buf.Len() == 100 && read == 100
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive limit, body is read partially",
|
||||||
|
args: args{
|
||||||
|
body: io.NopCloser(io.LimitReader(&zr, 100)),
|
||||||
|
limit: 50,
|
||||||
|
},
|
||||||
|
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
|
||||||
|
brc, ok := res.(bodyReadCloser)
|
||||||
|
return ok && brc.body != nil && brc.buf.Len() == 50 && read == 50
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive limit, body is read completely",
|
||||||
|
args: args{
|
||||||
|
body: io.NopCloser(io.LimitReader(&zr, 100)),
|
||||||
|
limit: 101,
|
||||||
|
},
|
||||||
|
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
|
||||||
|
brc, ok := res.(bodyReadCloser)
|
||||||
|
return ok && brc.body == nil && brc.buf.Len() == 100 && read == 100
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
res, read := h.bufferedBody(tt.args.body, tt.args.limit)
|
||||||
|
if !tt.resultCheck(res, read, tt.args) {
|
||||||
|
t.Error("Handler.bufferedBody() test failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1234,6 +1234,10 @@ func (h Handler) provisionUpstream(upstream *Upstream) {
|
|||||||
// then returns a reader for the buffer along with how many bytes were buffered. Always close
|
// then returns a reader for the buffer along with how many bytes were buffered. Always close
|
||||||
// the return value when done with it, just like if it was the original body! If limit is 0
|
// the return value when done with it, just like if it was the original body! If limit is 0
|
||||||
// (which it shouldn't be), this function returns its input; i.e. is a no-op, for safety.
|
// (which it shouldn't be), this function returns its input; i.e. is a no-op, for safety.
|
||||||
|
// Otherwise, it returns bodyReadCloser, the original body will be closed and body will be nil
|
||||||
|
// if it's explicitly configured to buffer all or EOF is reached when reading.
|
||||||
|
// TODO: the error during reading is discarded if the limit is negative, should the error be propagated
|
||||||
|
// to upstream/downstream?
|
||||||
func (h Handler) bufferedBody(originalBody io.ReadCloser, limit int64) (io.ReadCloser, int64) {
|
func (h Handler) bufferedBody(originalBody io.ReadCloser, limit int64) (io.ReadCloser, int64) {
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
return originalBody, 0
|
return originalBody, 0
|
||||||
|
@ -146,8 +146,8 @@ func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
|
|||||||
iss.AccountKey = accountKey
|
iss.AccountKey = accountKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNS challenge provider
|
// DNS challenge provider, if not already established
|
||||||
if iss.Challenges != nil && iss.Challenges.DNS != nil {
|
if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.solver == nil {
|
||||||
var prov certmagic.DNSProvider
|
var prov certmagic.DNSProvider
|
||||||
if iss.Challenges.DNS.ProviderRaw != nil {
|
if iss.Challenges.DNS.ProviderRaw != nil {
|
||||||
// a challenge provider has been locally configured - use it
|
// a challenge provider has been locally configured - use it
|
||||||
|
@ -182,17 +182,6 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
|||||||
t.dns = dnsMod
|
t.dns = dnsMod
|
||||||
}
|
}
|
||||||
|
|
||||||
// ECH (Encrypted ClientHello) initialization
|
|
||||||
if t.EncryptedClientHello != nil {
|
|
||||||
t.EncryptedClientHello.configs = make(map[string][]echConfig)
|
|
||||||
outerNames, err := t.EncryptedClientHello.Provision(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("provisioning Encrypted ClientHello components: %v", err)
|
|
||||||
}
|
|
||||||
// outer names should have certificates to reduce client brittleness
|
|
||||||
t.automateNames = append(t.automateNames, outerNames...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up a new certificate cache; this (re)loads all certificates
|
// set up a new certificate cache; this (re)loads all certificates
|
||||||
cacheOpts := certmagic.CacheOptions{
|
cacheOpts := certmagic.CacheOptions{
|
||||||
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
|
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
@ -243,31 +232,34 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
|||||||
t.certificateLoaders = append(t.certificateLoaders, modIface.(CertificateLoader))
|
t.certificateLoaders = append(t.certificateLoaders, modIface.(CertificateLoader))
|
||||||
}
|
}
|
||||||
|
|
||||||
// on-demand permission module
|
// using the certificate loaders we just initialized, load
|
||||||
if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.PermissionRaw != nil {
|
// manual/static (unmanaged) certificates - we do this in
|
||||||
if t.Automation.OnDemand.Ask != "" {
|
// provision so that other apps (such as http) can know which
|
||||||
return fmt.Errorf("on-demand TLS config conflict: both 'ask' endpoint and a 'permission' module are specified; 'ask' is deprecated, so use only the permission module")
|
// certificates have been manually loaded, and also so that
|
||||||
}
|
// commands like validate can be a better test
|
||||||
val, err := ctx.LoadModule(t.Automation.OnDemand, "PermissionRaw")
|
certCacheMu.RLock()
|
||||||
|
magic := certmagic.New(certCache, certmagic.Config{
|
||||||
|
Storage: ctx.Storage(),
|
||||||
|
Logger: t.logger,
|
||||||
|
OnEvent: t.onEvent,
|
||||||
|
OCSP: certmagic.OCSPConfig{
|
||||||
|
DisableStapling: t.DisableOCSPStapling,
|
||||||
|
},
|
||||||
|
DisableStorageCheck: t.DisableStorageCheck,
|
||||||
|
})
|
||||||
|
certCacheMu.RUnlock()
|
||||||
|
for _, loader := range t.certificateLoaders {
|
||||||
|
certs, err := loader.LoadCertificates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading on-demand TLS permission module: %v", err)
|
return fmt.Errorf("loading certificates: %v", err)
|
||||||
}
|
}
|
||||||
t.Automation.OnDemand.permission = val.(OnDemandPermission)
|
for _, cert := range certs {
|
||||||
}
|
hash, err := magic.CacheUnmanagedTLSCertificate(ctx, cert.Certificate, cert.Tags)
|
||||||
|
if err != nil {
|
||||||
// run replacer on ask URL (for environment variables) -- return errors to prevent surprises (#5036)
|
return fmt.Errorf("caching unmanaged certificate: %v", err)
|
||||||
if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.Ask != "" {
|
}
|
||||||
t.Automation.OnDemand.Ask, err = repl.ReplaceOrErr(t.Automation.OnDemand.Ask, true, true)
|
t.loaded[hash] = ""
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("preparing 'ask' endpoint: %v", err)
|
|
||||||
}
|
}
|
||||||
perm := PermissionByHTTP{
|
|
||||||
Endpoint: t.Automation.OnDemand.Ask,
|
|
||||||
}
|
|
||||||
if err := perm.Provision(ctx); err != nil {
|
|
||||||
return fmt.Errorf("provisioning 'ask' module: %v", err)
|
|
||||||
}
|
|
||||||
t.Automation.OnDemand.permission = perm
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// automation/management policies
|
// automation/management policies
|
||||||
@ -302,6 +294,33 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// on-demand permission module
|
||||||
|
if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.PermissionRaw != nil {
|
||||||
|
if t.Automation.OnDemand.Ask != "" {
|
||||||
|
return fmt.Errorf("on-demand TLS config conflict: both 'ask' endpoint and a 'permission' module are specified; 'ask' is deprecated, so use only the permission module")
|
||||||
|
}
|
||||||
|
val, err := ctx.LoadModule(t.Automation.OnDemand, "PermissionRaw")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading on-demand TLS permission module: %v", err)
|
||||||
|
}
|
||||||
|
t.Automation.OnDemand.permission = val.(OnDemandPermission)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run replacer on ask URL (for environment variables) -- return errors to prevent surprises (#5036)
|
||||||
|
if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.Ask != "" {
|
||||||
|
t.Automation.OnDemand.Ask, err = repl.ReplaceOrErr(t.Automation.OnDemand.Ask, true, true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("preparing 'ask' endpoint: %v", err)
|
||||||
|
}
|
||||||
|
perm := PermissionByHTTP{
|
||||||
|
Endpoint: t.Automation.OnDemand.Ask,
|
||||||
|
}
|
||||||
|
if err := perm.Provision(ctx); err != nil {
|
||||||
|
return fmt.Errorf("provisioning 'ask' module: %v", err)
|
||||||
|
}
|
||||||
|
t.Automation.OnDemand.permission = perm
|
||||||
|
}
|
||||||
|
|
||||||
// session ticket ephemeral keys (STEK) service and provider
|
// session ticket ephemeral keys (STEK) service and provider
|
||||||
if t.SessionTickets != nil {
|
if t.SessionTickets != nil {
|
||||||
err := t.SessionTickets.provision(ctx)
|
err := t.SessionTickets.provision(ctx)
|
||||||
@ -310,32 +329,19 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load manual/static (unmanaged) certificates - we do this in
|
// ECH (Encrypted ClientHello) initialization
|
||||||
// provision so that other apps (such as http) can know which
|
if t.EncryptedClientHello != nil {
|
||||||
// certificates have been manually loaded, and also so that
|
t.EncryptedClientHello.configs = make(map[string][]echConfig)
|
||||||
// commands like validate can be a better test
|
outerNames, err := t.EncryptedClientHello.Provision(ctx)
|
||||||
certCacheMu.RLock()
|
|
||||||
magic := certmagic.New(certCache, certmagic.Config{
|
|
||||||
Storage: ctx.Storage(),
|
|
||||||
Logger: t.logger,
|
|
||||||
OnEvent: t.onEvent,
|
|
||||||
OCSP: certmagic.OCSPConfig{
|
|
||||||
DisableStapling: t.DisableOCSPStapling,
|
|
||||||
},
|
|
||||||
DisableStorageCheck: t.DisableStorageCheck,
|
|
||||||
})
|
|
||||||
certCacheMu.RUnlock()
|
|
||||||
for _, loader := range t.certificateLoaders {
|
|
||||||
certs, err := loader.LoadCertificates()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading certificates: %v", err)
|
return fmt.Errorf("provisioning Encrypted ClientHello components: %v", err)
|
||||||
}
|
}
|
||||||
for _, cert := range certs {
|
|
||||||
hash, err := magic.CacheUnmanagedTLSCertificate(ctx, cert.Certificate, cert.Tags)
|
// outer names should have certificates to reduce client brittleness
|
||||||
if err != nil {
|
for _, outerName := range outerNames {
|
||||||
return fmt.Errorf("caching unmanaged certificate: %v", err)
|
if !t.HasCertificateForSubject(outerName) {
|
||||||
|
t.automateNames = append(t.automateNames, outerNames...)
|
||||||
}
|
}
|
||||||
t.loaded[hash] = ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user