Compare commits

..

114 Commits

Author SHA1 Message Date
Mohammed Al Sahaf 01828e38bb Merge branch 'master' into hurl-tests 2025-11-12 02:05:15 +03:00
Mohammed Al Sahaf 59aa8588c9 add delay because CI hosts aren't fast enough
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-11-12 01:50:33 +03:00
Mohammed Al Sahaf a63a87e4ef ci upgrades
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-11-12 01:42:27 +03:00
Mohammed Al Sahaf 501f525d35 fix Tests workflow
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-11-12 01:39:32 +03:00
Mohammed Al Sahaf 47ef562bbd add route tests
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-11-12 01:37:04 +03:00
Mohammed Al Sahaf d87760adab add vars tests
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-11-12 01:33:09 +03:00
Mohammed Al Sahaf 1904c37234 add request_header cases
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-11-12 01:16:11 +03:00
dependabot[bot] 07d2aaf22e build(deps): bump the all-updates group with 4 updates (#7333)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 19s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 26s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 24s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 15s
Lint / lint (ubuntu-latest, linux) (push) Failing after 15s
Lint / govulncheck (push) Successful in 1m59s
Lint / dependency-review (push) Failing after 15s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 15s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
Bumps the all-updates group with 4 updates: [github.com/DeRuina/timberjack](https://github.com/DeRuina/timberjack), [github.com/KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit), [github.com/klauspost/compress](https://github.com/klauspost/compress) and [go.step.sm/crypto](https://github.com/smallstep/crypto).


Updates `github.com/DeRuina/timberjack` from 1.3.8 to 1.3.9
- [Release notes](https://github.com/DeRuina/timberjack/releases)
- [Changelog](https://github.com/DeRuina/timberjack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/DeRuina/timberjack/compare/v1.3.8...v1.3.9)

Updates `github.com/KimMachineGun/automemlimit` from 0.7.4 to 0.7.5
- [Release notes](https://github.com/KimMachineGun/automemlimit/releases)
- [Commits](https://github.com/KimMachineGun/automemlimit/compare/v0.7.4...v0.7.5)

Updates `github.com/klauspost/compress` from 1.18.0 to 1.18.1
- [Release notes](https://github.com/klauspost/compress/releases)
- [Changelog](https://github.com/klauspost/compress/blob/master/.goreleaser.yml)
- [Commits](https://github.com/klauspost/compress/compare/v1.18.0...v1.18.1)

Updates `go.step.sm/crypto` from 0.72.0 to 0.73.0
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.72.0...v0.73.0)

---
updated-dependencies:
- dependency-name: github.com/DeRuina/timberjack
  dependency-version: 1.3.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: github.com/KimMachineGun/automemlimit
  dependency-version: 0.7.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: github.com/klauspost/compress
  dependency-version: 1.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: go.step.sm/crypto
  dependency-version: 0.73.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-08 11:13:16 +03:00
Marten Seemann f2199d48b2 chore: update quic-go to v0.56.0, enable qlog for HTTP/3 (#7345) 2025-11-08 00:41:15 -05:00
Kévin Dunglas 8285eba842 caddyhttp: allow customizing the Server header (#7338)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 16s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 24s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 17s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 14s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m17s
Lint / dependency-review (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 45s
2025-11-03 21:00:27 -07:00
Mohammed Al Sahaf cb72512d17 Merge branch 'master' into hurl-tests 2025-11-03 08:43:02 +03:00
Cooper de Nicola 895b56063a chore: fix golangci-lint error G602 in caddyhttp (#7334)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 16s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 13s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m15s
Lint / dependency-review (push) Failing after 14s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
2025-11-03 03:04:55 +00:00
Mohammed Al Sahaf 8d422f0d7f spec: uri handler
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-11-02 20:59:26 +03:00
Mohammed Al Sahaf 336d514797 spec: forward_auth handler
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-11-02 20:46:53 +03:00
Mohammed Al Sahaf 6d89bc3942 spec: error handler
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-11-02 20:42:20 +03:00
Mohammed Al Sahaf ddec1838b3 caddytls: correct documentation of LeafFolderLoader (#7327)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 1m10s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 14s
Lint / lint (ubuntu-latest, linux) (push) Failing after 13s
Lint / govulncheck (push) Successful in 1m35s
Lint / dependency-review (push) Failing after 13s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
* caddytls: correct documentation of `LeafFolderLoader`

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* fmt...

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-11-01 18:29:55 +03:00
Ted 92c8bc7322 caddyfile: fix nested quotes formatted incorrectly by fmt (#7045)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 49s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 14s
Lint / lint (ubuntu-latest, linux) (push) Failing after 13s
Lint / govulncheck (push) Successful in 1m37s
Lint / dependency-review (push) Failing after 15s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 2m12s
* Fix incorrectly formatted quote within quotes with fmt

* Fix incorrectly formatted quote within quotes with fmt
2025-10-27 16:13:47 -06:00
WeidiDeng abe0acabb6 reverseproxy: set default values for keepalive if only some of them are set (#7318)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 15s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 28s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m19s
Lint / dependency-review (push) Failing after 14s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 13s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
2025-10-25 05:15:55 -04:00
Philipp Keidel b54e870b26 tracing: switch to autoexport for OpenTelemetry span exporter (#7317)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 14s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 16s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m18s
Lint / dependency-review (push) Failing after 14s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
2025-10-23 17:54:50 -06:00
dependabot[bot] 6d90c7707d build(deps): bump github.com/slackhq/nebula from 1.9.5 to 1.9.7 (#7315)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 16s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 14s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m15s
Lint / dependency-review (push) Failing after 14s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 15s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
Bumps [github.com/slackhq/nebula](https://github.com/slackhq/nebula) from 1.9.5 to 1.9.7.
- [Release notes](https://github.com/slackhq/nebula/releases)
- [Changelog](https://github.com/slackhq/nebula/blob/v1.9.7/CHANGELOG.md)
- [Commits](https://github.com/slackhq/nebula/compare/v1.9.5...v1.9.7)

---
updated-dependencies:
- dependency-name: github.com/slackhq/nebula
  dependency-version: 1.9.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 11:37:02 -06:00
dependabot[bot] cd1c203777 build(deps): bump the all-updates group across 1 directory with 2 updates (#7307)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 15s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 14s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m12s
Lint / dependency-review (push) Failing after 15s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 13s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
Bumps the all-updates group with 2 updates in the / directory: [github.com/spf13/pflag](https://github.com/spf13/pflag) and [go.step.sm/crypto](https://github.com/smallstep/crypto).


Updates `github.com/spf13/pflag` from 1.0.9 to 1.0.10
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.9...v1.0.10)

Updates `go.step.sm/crypto` from 0.70.0 to 0.72.0
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.70.0...v0.72.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: go.step.sm/crypto
  dependency-version: 0.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 22:32:36 -06:00
wyrapeseed 57587ed18e refactor: use reflect.TypeFor (#7313)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 47s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 44s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 14s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m31s
Lint / dependency-review (push) Failing after 14s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
Signed-off-by: wyrapeseed <wyrapeseed@outlook.com>
2025-10-21 13:26:30 -06:00
WeidiDeng 1e21b660c4 reverseproxy: use http.Protocols to handle h2c requests (#6990)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 15s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 16s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m10s
Lint / dependency-review (push) Failing after 15s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
2025-10-21 16:05:43 +00:00
Mohammed Al Sahaf 595aab8bc0 metrics: resolve per-host inifinite cardinality (#7306)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 16s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 15s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m16s
Lint / dependency-review (push) Failing after 14s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
2025-10-21 06:06:56 +00:00
WeidiDeng 8aca108d2c reverseproxy: do not disable keepalive if proxy protocol is used (#7300) 2025-10-21 05:09:37 +00:00
Pavel 156ce99d3a listeners: Add support for named socket activation (#7243) 2025-10-21 04:55:42 +00:00
WeidiDeng a7885aabec intercept: use already buffered response if possible when intercepting (#7028) 2025-10-21 04:48:07 +00:00
Mohammed Al Sahaf d186879da5 Merge branch 'master' into hurl-tests 2025-10-21 01:35:18 +03:00
Bashayer Alrumahi f5f25d845a logging: fix multiple regexp filters on same field (fixes #7049) (#7061)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 42s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 17s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 13s
Lint / lint (ubuntu-latest, linux) (push) Failing after 13s
Lint / govulncheck (push) Successful in 1m33s
Lint / dependency-review (push) Failing after 13s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
* logging: fix multiple regexp filters on same field (fixes #7049)

* fix: add proper error handling in MultiRegexpFilter tests

* fix: resolve linter and test issues - Fix GCI import formatting issues - Fix MultiRegexpFilter input size limit test by ensuring output doesn't exceed max length after each operation - All tests now pass and linter issues resolved

* fix: update integration test for proper JSON encoding - Fix expected JSON output to use Unicode escape sequence for ampersand character - Integration tests now pass
2025-10-16 05:08:53 +00:00
WeidiDeng 1ce2a13ad1 caddyhttp: wrap accepted connection to suppress tls.ConnectionState (#7247) 2025-10-16 03:13:40 +00:00
Chris Seufert d7185fd002 caddyhttp: Add trusted_proxies_unix for trusting unix socket X-Forwarded-* headers (#7265) 2025-10-16 02:47:32 +00:00
Anthony Biondo 7fb39ec1e5 reverseproxy: Use http1.1 upgrade for websocket for extended connect of http2 and http3 (#7305)
Co-authored-by: WeidiDeng <weidi_deng@icloud.com>
2025-10-16 02:20:20 +00:00
aeris 10ac7da037 logging: Switch from lumberjack to timberjack, add time-rolling options (#7244)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2025-10-15 17:11:10 -04:00
wyrapeseed d115cd1042 chore: fix some comments (#7303) 2025-10-15 03:58:53 +00:00
joshuamcbeth de6b78009b caddyhttp: Add server options keepalive_idle and keepalive_count (#7298)
* Add Server options KeepAliveIdle (keepalive_idle) and KeepAliveCount (keepalive_count)

Signed-off-by: Joshua McBeth <joshua.mcbeth@gmail.com>

* Add Server option KeepAliveDisable (keepalive_disable)

Signed-off-by: Joshua McBeth <joshua.mcbeth@gmail.com>

* Remove Server option KeepAliveDisable (keepalive_disable), disable when interval is negative

Signed-off-by: Joshua McBeth <joshua.mcbeth@gmail.com>

* Add keepalive parameters to caddyfiletest

Signed-off-by: Joshua McBeth <joshua.mcbeth@gmail.com>

---------

Signed-off-by: Joshua McBeth <joshua.mcbeth@gmail.com>
2025-10-14 12:03:23 -06:00
WeidiDeng 2ec28bca43 reverse_proxy: use http1 for outbound tls requests with placeholder that are likely websockets (#7296) 2025-10-09 10:36:49 -06:00
Marten Seemann 178294e9d7 chore: Update quic-go to v0.55.0 (#7288)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-10-06 19:43:28 -04:00
GreyXor 13a4ec7597 basicauth: Implement argon2id (#7186)
* feat: add argon2id hash-password command

* feat: ardon2id owasp safe value

* feat: add argon2id compare method

* chore: fmt argon2id

* docs: more argon2id docs

* chore: upgrade x/crypto dep

* revert: remove golangci

* refactor: argon2id decode

* chore: update deps

* refactor: simplify argon2id compare return

* chore: upgrade dependencies

* chore: upgrade dependencies
2025-10-06 17:27:06 -06:00
Monviech 2f1d270968 httpcaddyfile: Map default_bind to BindHost in globalACMEDefaults (#7278)
* Implement BindHost fallback in ACME issuer

* Fix indentation

* Skip creating empty challenges stub in adapted json config

* Skip setting BindHost for DNS Challenge

* golangci-lint fix

---------

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-10-06 16:48:38 -06:00
Aditya Bhargava 3c003deec6 httpcaddyfile: Add missing DNS challenge check for acme_dns (#7270)
* add optional argument to `mock` DNS provider

* preserve local DNS challenge settings when `acme_dns` is specified

* add missing check for `acme_dns`
2025-10-03 14:05:46 -06:00
dependabot[bot] afbdcec08b build(deps): bump the actions-deps group with 8 updates (#7284)
Bumps the actions-deps group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [step-security/harden-runner](https://github.com/step-security/harden-runner) | `2.13.0` | `2.13.1` |
| [actions/setup-go](https://github.com/actions/setup-go) | `5.5.0` | `6.0.0` |
| [actions/dependency-review-action](https://github.com/actions/dependency-review-action) | `4.7.3` | `4.8.0` |
| [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) | `3.9.2` | `3.10.0` |
| [anchore/sbom-action](https://github.com/anchore/sbom-action) | `0.20.5` | `0.20.6` |
| [peter-evans/repository-dispatch](https://github.com/peter-evans/repository-dispatch) | `3.0.0` | `4.0.0` |
| [ossf/scorecard-action](https://github.com/ossf/scorecard-action) | `2.4.2` | `2.4.3` |
| [github/codeql-action](https://github.com/github/codeql-action) | `3.30.0` | `3.30.5` |


Updates `step-security/harden-runner` from 2.13.0 to 2.13.1
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](https://github.com/step-security/harden-runner/compare/ec9f2d5744a09debf3a187a3f4f675c53b671911...f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a)

Updates `actions/setup-go` from 5.5.0 to 6.0.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/d35c59abb061a4a6fb18e82ac0862c26744d6ab5...44694675825211faa026b3c33043df3e48a5fa00)

Updates `actions/dependency-review-action` from 4.7.3 to 4.8.0
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/595b5aeba73380359d98a5e087f648dbb0edce1b...56339e523c0409420f6c2c9a2f4292bbb3c07dd3)

Updates `sigstore/cosign-installer` from 3.9.2 to 3.10.0
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/d58896d6a1865668819e1d91763c7751a165e159...d7543c93d881b35a8faa02e8e3605f69b7a1ce62)

Updates `anchore/sbom-action` from 0.20.5 to 0.20.6
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/sbom-action/compare/da167eac915b4e86f08b264dbdbc867b61be6f0c...f8bdd1d8ac5e901a77a92f111440fdb1b593736b)

Updates `peter-evans/repository-dispatch` from 3.0.0 to 4.0.0
- [Release notes](https://github.com/peter-evans/repository-dispatch/releases)
- [Commits](https://github.com/peter-evans/repository-dispatch/compare/ff45666b9427631e3450c54a1bcbee4d9ff4d7c0...5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f)

Updates `ossf/scorecard-action` from 2.4.2 to 2.4.3
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](https://github.com/ossf/scorecard-action/compare/05b42c624433fc40578a4040d5cf5e36ddca8cde...4eaacf0543bb3f2c246792bd56e8cdeffafb205a)

Updates `github/codeql-action` from 3.30.0 to 3.30.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d...3599b3baa15b485a2e49ef411a7a4bb2452e7f93)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-version: 2.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-deps
- dependency-name: actions/setup-go
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions-deps
- dependency-name: actions/dependency-review-action
  dependency-version: 4.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-deps
- dependency-name: sigstore/cosign-installer
  dependency-version: 3.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-deps
- dependency-name: anchore/sbom-action
  dependency-version: 0.20.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-deps
- dependency-name: peter-evans/repository-dispatch
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions-deps
- dependency-name: ossf/scorecard-action
  dependency-version: 2.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-deps
- dependency-name: github/codeql-action
  dependency-version: 3.30.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 23:11:09 +00:00
Francis Lavoie 65e0ddc221 core: Reloading with SIGUSR1 if config never changed via admin (#7258)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 54s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 28s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 18s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 13s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m38s
Lint / dependency-review (push) Failing after 15s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Has started running
2025-09-26 16:50:15 +00:00
WeidiDeng b2ab419922 core: use reflect.TypeFor to check for encoding/json.RawMessage (#7274)
* check if the raw message type is the json v2 type

* use reflect.TypeFor to check for encoding/json.RawMessage

---------

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-09-26 10:46:18 -06:00
asttool bc0e184130 caddyhttp: omit unnecessary reassignment (#7276)
Signed-off-by: asttool <asttool@outlook.com>
2025-09-26 10:44:58 -06:00
Y.Horie 1e82f9652e caddypki: check intermediate lifetime to actual root cert lifetime (#7272)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 15s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 22s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 17s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 23s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 14s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m14s
Lint / dependency-review (push) Failing after 14s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
2025-09-26 10:24:52 -06:00
Mohammed Al Sahaf 25be2f26fc chore: ugh, lint fix... (#7275)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 14s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 22s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 15s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 14s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m32s
Lint / dependency-review (push) Failing after 16s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
* chore: ugh, lint fix...

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* more lint fixes

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-09-26 03:14:48 -04:00
Marten Seemann 0c8798fce3 go.mod: update quic-go to v0.54.1 (#7273) 2025-09-25 19:24:26 -06:00
Gilbert Gilb's f5c3094050 cmd: prevent commas in header values from being split (#7268)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 1s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 0s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 0s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 0s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 0s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 0s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 0s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 0s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 0s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 0s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 1s
Lint / lint (ubuntu-latest, linux) (push) Failing after 0s
Lint / govulncheck (push) Failing after 0s
Lint / dependency-review (push) Failing after 0s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 47s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
`pflag.GetStringSlice` treats commas as delimiters, which causes issues
when passing headers whose values contain commas (`X-Robots-Tag:
noindex, nofollow`). These are incorrectly split into multiple headers
and errors out:

- `X-Robots-Tag: noindex`
- ` nofollow`

Switch to `pflag.GetStringArray`, which does not split on commas[1].

Note that this changes behavior for cases where multiple headers were
provided in a single argument with commas (`--header-down "X-Foo:
Bar,X-Bar: Foo"`). Such cases will now be treated as a single header
value. If this breaking change is unacceptable, we will need a smarter
fallback mechanism.

[1] https://github.com/spf13/pflag/pull/90
2025-09-22 21:12:06 -06:00
Francis Lavoie 39ace450de logging: Adjustments to BufferedLog to keep logs in the correct order (#7257)
* logging: Adjustments to BufferedLog to keep logs in the correct order

* Ignore lints
2025-09-15 09:29:50 -06:00
Artur H. 0ba8786b35 caddyfile: Allow block to do nothing if nothing passed to import (#7206)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 49s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 28s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 16s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 19s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 54s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 14s
Lint / lint (ubuntu-latest, linux) (push) Failing after 15s
Lint / govulncheck (push) Successful in 1m42s
Lint / dependency-review (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Has started running
2025-09-12 20:29:09 +00:00
mickychang9 bcd4055e89 Use WaitGroup.Go to simplify code (#7253)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 57s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 13s
Lint / lint (ubuntu-latest, linux) (push) Failing after 13s
Lint / govulncheck (push) Successful in 1m31s
Lint / dependency-review (push) Failing after 14s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 14s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
Signed-off-by: mickychang9 <mickychang9@outlook.com>
2025-09-11 10:15:09 -06:00
WeidiDeng b462615439 fileserver: set Content-Length for precompressed files (#7251)
* set Content-Length for precompressed files

* Update modules/caddyhttp/fileserver/staticfiles.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>

---------

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-09-10 22:48:03 -06:00
Francis Lavoie 012b4b3d40 logging: Buffer the logs before config is loaded (#7245) 2025-09-10 16:03:52 +00:00
Pavel d9cc24f3df caddypki: Disable internal auto-CA when auto_https is disabled (fix #7211) (#7238)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 50s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 13s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 14s
Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
Lint / govulncheck (push) Successful in 1m53s
Lint / dependency-review (push) Failing after 14s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 15s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-09-05 09:41:06 -06:00
Matthew Holt 38848f7f25 caddytls: Allow disabling distributed solving (except http-01) 2025-09-04 08:51:36 -06:00
Siomachkin 5473eb95d8 encode: fix response corruption when handle_errors is used (#7235)
* encode: fix response corruption when handle_errors is used

* Move disabled check before hdr assignment
2025-09-02 15:34:56 -06:00
dependabot[bot] 2d0f3f887b build(deps): bump the actions-deps group with 5 updates (#7237)
Bumps the actions-deps group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `4.2.2` | `5.0.0` |
| [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) | `6.3.0` | `6.4.0` |
| [actions/dependency-review-action](https://github.com/actions/dependency-review-action) | `4.7.1` | `4.7.3` |
| [anchore/sbom-action](https://github.com/anchore/sbom-action) | `0.20.4` | `0.20.5` |
| [github/codeql-action](https://github.com/github/codeql-action) | `3.29.7` | `3.30.0` |


Updates `actions/checkout` from 4.2.2 to 5.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.2.2...08c6903cd8c0fde910a37f88322edcfb5dd907a8)

Updates `goreleaser/goreleaser-action` from 6.3.0 to 6.4.0
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/9c156ee8a17a598857849441385a2041ef570552...e435ccd777264be153ace6237001ef4d979d3a7a)

Updates `actions/dependency-review-action` from 4.7.1 to 4.7.3
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/da24556b548a50705dd671f47852072ea4c105d9...595b5aeba73380359d98a5e087f648dbb0edce1b)

Updates `anchore/sbom-action` from 0.20.4 to 0.20.5
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/sbom-action/compare/7b36ad622f042cab6f59a75c2ac24ccb256e9b45...da167eac915b4e86f08b264dbdbc867b61be6f0c)

Updates `github/codeql-action` from 3.29.7 to 3.30.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/51f77329afa6477de8c49fc9c7046c15b9a4e79d...2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions-deps
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-deps
- dependency-name: actions/dependency-review-action
  dependency-version: 4.7.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-deps
- dependency-name: anchore/sbom-action
  dependency-version: 0.20.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-deps
- dependency-name: github/codeql-action
  dependency-version: 3.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-02 13:05:58 -06:00
dependabot[bot] 39357d3e5c build(deps): bump the all-updates group with 17 updates (#7236)
Bumps the all-updates group with 17 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) | `5.2.2` | `5.2.3` |
| [github.com/google/cel-go](https://github.com/google/cel-go) | `0.26.0` | `0.26.1` |
| [github.com/spf13/cobra](https://github.com/spf13/cobra) | `1.9.1` | `1.10.1` |
| [github.com/spf13/pflag](https://github.com/spf13/pflag) | `1.0.7` | `1.0.9` |
| [github.com/stretchr/testify](https://github.com/stretchr/testify) | `1.10.0` | `1.11.1` |
| [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://github.com/open-telemetry/opentelemetry-go-contrib) | `0.61.0` | `0.63.0` |
| [go.opentelemetry.io/contrib/propagators/autoprop](https://github.com/open-telemetry/opentelemetry-go-contrib) | `0.62.0` | `0.63.0` |
| [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) | `1.37.0` | `1.38.0` |
| [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) | `1.37.0` | `1.38.0` |
| [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) | `1.37.0` | `1.38.0` |
| [golang.org/x/crypto](https://github.com/golang/crypto) | `0.40.0` | `0.41.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.42.0` | `0.43.0` |
| [golang.org/x/term](https://github.com/golang/term) | `0.33.0` | `0.34.0` |
| [github.com/libdns/libdns](https://github.com/libdns/libdns) | `1.1.0` | `1.1.1` |
| [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) | `1.37.0` | `1.38.0` |
| [go.step.sm/crypto](https://github.com/smallstep/crypto) | `0.67.0` | `0.70.0` |
| [golang.org/x/sys](https://github.com/golang/sys) | `0.34.0` | `0.35.0` |


Updates `github.com/go-chi/chi/v5` from 5.2.2 to 5.2.3
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.2.2...v5.2.3)

Updates `github.com/google/cel-go` from 0.26.0 to 0.26.1
- [Release notes](https://github.com/google/cel-go/releases)
- [Commits](https://github.com/google/cel-go/compare/v0.26.0...v0.26.1)

Updates `github.com/spf13/cobra` from 1.9.1 to 1.10.1
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.9.1...v1.10.1)

Updates `github.com/spf13/pflag` from 1.0.7 to 1.0.9
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.7...v1.0.9)

Updates `github.com/stretchr/testify` from 1.10.0 to 1.11.1
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.1)

Updates `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` from 0.61.0 to 0.63.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.61.0...zpages/v0.63.0)

Updates `go.opentelemetry.io/contrib/propagators/autoprop` from 0.62.0 to 0.63.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.62.0...zpages/v0.63.0)

Updates `go.opentelemetry.io/otel` from 1.37.0 to 1.38.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.37.0...v1.38.0)

Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc` from 1.37.0 to 1.38.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.37.0...v1.38.0)

Updates `go.opentelemetry.io/otel/sdk` from 1.37.0 to 1.38.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.37.0...v1.38.0)

Updates `golang.org/x/crypto` from 0.40.0 to 0.41.0
- [Commits](https://github.com/golang/crypto/compare/v0.40.0...v0.41.0)

Updates `golang.org/x/net` from 0.42.0 to 0.43.0
- [Commits](https://github.com/golang/net/compare/v0.42.0...v0.43.0)

Updates `golang.org/x/term` from 0.33.0 to 0.34.0
- [Commits](https://github.com/golang/term/compare/v0.33.0...v0.34.0)

Updates `github.com/libdns/libdns` from 1.1.0 to 1.1.1
- [Release notes](https://github.com/libdns/libdns/releases)
- [Commits](https://github.com/libdns/libdns/compare/v1.1.0...v1.1.1)

Updates `go.opentelemetry.io/otel/trace` from 1.37.0 to 1.38.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.37.0...v1.38.0)

Updates `go.step.sm/crypto` from 0.67.0 to 0.70.0
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.67.0...v0.70.0)

Updates `golang.org/x/sys` from 0.34.0 to 0.35.0
- [Commits](https://github.com/golang/sys/compare/v0.34.0...v0.35.0)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: github.com/google/cel-go
  dependency-version: 0.26.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: github.com/stretchr/testify
  dependency-version: 1.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  dependency-version: 0.63.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/contrib/propagators/autoprop
  dependency-version: 0.63.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/otel
  dependency-version: 1.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
  dependency-version: 1.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/otel/sdk
  dependency-version: 1.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: golang.org/x/crypto
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: golang.org/x/net
  dependency-version: 0.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: golang.org/x/term
  dependency-version: 0.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: github.com/libdns/libdns
  dependency-version: 1.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-version: 1.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.step.sm/crypto
  dependency-version: 0.70.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: golang.org/x/sys
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-02 11:35:09 -06:00
Max Celant 3553cfb6ad caddyhttp: remove redundant middleware next copy (#7217) 2025-09-01 09:30:34 -06:00
aro-lew 806fef85be encode: add graphql-response header to list (#7214)
Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 2m15s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.25.0, 1.25, aix) (push) Successful in 1m18s
Cross-Build / build (~1.25.0, 1.25, darwin) (push) Successful in 1m16s
Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Successful in 1m22s
Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Successful in 1m16s
Cross-Build / build (~1.25.0, 1.25, illumos) (push) Successful in 1m16s
Cross-Build / build (~1.25.0, 1.25, linux) (push) Successful in 1m14s
Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Successful in 1m17s
Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Successful in 1m15s
Cross-Build / build (~1.25.0, 1.25, solaris) (push) Successful in 1m18s
Cross-Build / build (~1.25.0, 1.25, windows) (push) Successful in 1m16s
Lint / lint (ubuntu-latest, linux) (push) Successful in 2m38s
Lint / govulncheck (push) Successful in 1m44s
Lint / dependency-review (push) Failing after 56s
Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Has started running
2025-08-27 14:58:14 -06:00
Arpan Saha 6d73d85c1f caddyfile: prevent adding trailing space on line before env variable (#7215) 2025-08-26 15:13:54 -06:00
Matthew Holt e0a8f9541d caddyhttp: Normalize (lowercase) {label.N} placeholders 2025-08-25 13:18:13 -06:00
Matthew Holt b866a9e099 It can't be in the subfolder!? 2025-08-25 13:13:27 -06:00
Matthew Holt 1db26128a6 Uhh I guess it has to be named something specific 2025-08-25 13:11:30 -06:00
Matthew Holt 02c9f0ff90 Tweak issue form 2025-08-25 13:07:17 -06:00
Matthew Holt 63ec1f4e1c Issue template chooser config 2025-08-25 13:05:02 -06:00
Matthew Holt 293de94f34 Fix issue form ... again 2025-08-25 13:02:54 -06:00
Matthew Holt d8d359eca2 Fix syntax of issue form 2025-08-25 12:59:41 -06:00
Matt Holt 11a95cee6d AI assistance disclosure (#7212)
* AI disclosure templates

* Update assistance disclosure

* Add AI moderator

* Set read permissions by default

* Pin to hash
2025-08-25 12:50:26 -06:00
Matthew Holt b7c022a61a Set read permissions as default 2025-08-25 10:18:40 -06:00
Bang Lee 5e2953670e caddyhttp: add replacer placeholders for escaped values (#7181) 2025-08-25 09:07:51 -06:00
Artur H. 551f793700 caddyfile: Fix importing nested tokens for {block} (#7189) 2025-08-22 21:29:34 +00:00
Francis Lavoie 4564261d83 httpcaddyfile: Fix acme_dns regression (#7199) 2025-08-22 15:09:25 -06:00
Francis Lavoie 16fe83c7af http: Make logger first, before TLS provisioning (#7198) 2025-08-22 14:24:08 -06:00
Matthew Holt 3723e89585 go.mod: Upgrade CertMagic to v0.24.0 2025-08-22 09:41:47 -06:00
WeidiDeng 14a63a26b9 caddyhttp: use the new http.Protocols to handle h1, h2 and h2c requests (#6961)
* use the new http.Protocols to handle h1, h2 and h2c requests

* fix lint

* keep ConnCtxKey for now

* fix handling for h2c

* check http version while reading the connection

* check if connection implements connectionStater when it should

* add comments about either h1 or h2 must be used in the listener

* fix if check

* return a net.Conn that implements connectionStater if applicable

* remove http/1.1 from alpn if h1 is disabled

* fix matching if only h1 is enabled

---------

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-08-22 14:30:42 +00:00
WeidiDeng 67debd0e11 fileserver: set Range header for precompressed static files to force Content Length header to appear (#7042)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-08-22 08:23:13 -06:00
Luka T. Korošec b9710c6af4 fileserver: Add a few doc lines about Etag file content (#7173)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-08-21 16:02:10 -06:00
Kévin Dunglas 493898d9bd ci: set proper build tags in golangci and minor cleanup (#7183)
* ci: set proper build tags in golangci and minor cleanup

* clean

* review

---------

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-08-21 20:43:38 +00:00
WeidiDeng 1c596e3c5a reverse_proxy: use the new KeepAliveConfig to set probe interval (#7157)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-08-21 14:36:54 -06:00
WeidiDeng f11c780fdc http: clean up listeners if some of the listeners fail to bind (#7176)
* http: clean up listeners if some of the listeners fail to bind

* check for nil server due to failure to start

---------

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-08-21 12:14:40 -06:00
WeidiDeng fdf610850b http: disable keepalive when KeepAliveInterval is negative (#7158)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-08-20 17:31:15 -04:00
joemicky 5125fbed41 use a more modern writing style to simplify code (#7182)
Signed-off-by: joemicky <joemickychang@outlook.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-08-20 11:41:21 -06:00
cui b15ed9b084 caddyhttp: refactor to use reflect.TypeFor (#7187) 2025-08-18 17:08:46 -06:00
Kévin Dunglas 05acc5131e chore: bump Go to v1.25 (#7184) 2025-08-14 08:38:42 -06:00
WeidiDeng 7590c9ca1b caddyhttp: Free up quic listener when stopping (#7177) 2025-08-13 12:35:06 -06:00
avery b898873b90 caddytls: fix regression in external certificate manager support (#7179)
revert changes to automation.go from e276994174
2025-08-13 09:30:26 -06:00
youzichuan 09b53a753c chore: fix inconsistent function name in comment (#7174)
Signed-off-by: youzichuan <youzichuan6@outlook.com>
2025-08-13 07:05:54 +00:00
GreyXor 4bfc3b95b5 bcrypt: wrong cost flag name (#7168)
Tests / test (./cmd/caddy/caddy, ~1.24.1, ubuntu-latest, 0, 1.24, linux) (push) Failing after 2m14s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.24.1, 1.24, aix) (push) Successful in 1m22s
Cross-Build / build (~1.24.1, 1.24, darwin) (push) Successful in 1m36s
Cross-Build / build (~1.24.1, 1.24, dragonfly) (push) Successful in 1m27s
Cross-Build / build (~1.24.1, 1.24, freebsd) (push) Successful in 1m29s
Cross-Build / build (~1.24.1, 1.24, illumos) (push) Successful in 1m29s
Cross-Build / build (~1.24.1, 1.24, linux) (push) Successful in 1m31s
Cross-Build / build (~1.24.1, 1.24, netbsd) (push) Successful in 1m33s
Cross-Build / build (~1.24.1, 1.24, openbsd) (push) Successful in 1m36s
Cross-Build / build (~1.24.1, 1.24, solaris) (push) Successful in 1m26s
Cross-Build / build (~1.24.1, 1.24, windows) (push) Successful in 1m27s
Lint / lint (ubuntu-latest, linux) (push) Successful in 2m24s
Lint / govulncheck (push) Successful in 1m41s
Lint / dependency-review (push) Failing after 54s
Tests / test (./cmd/caddy/caddy, ~1.24.1, macos-14, 0, 1.24, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.24.1, windows-latest, True, 1.24, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Has started running
2025-08-11 15:46:32 +03:00
GreyXor 49dac61b07 bcrypt: add cost parameter to hash-password (#7149)
* feat: add bcrypt cost parameter to hash-password

* revert: typos

* refactor: take the cost out of interface

* fix: default bcrypt cost to 14

* fix: follow bcrypt library for min and max cost

* doc: mention defaultBcryptCost in cost parameter description

* chore: gci format

* fix: more specific bcrypt cost algorithm flag

* feat: bcrypt cost provisioning

* Revert "feat: bcrypt cost provisioning"

This reverts commit e09d4bd036.

* chore: gci format

* chore: gci format

* chore: gci format

* chore: golangcilint fmted

---------

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-08-11 14:26:18 +03:00
Bobby Dhillon 19ff47a63b cmd: Allow caddy adapt to read from stdin (#7163)
Tests / test (./cmd/caddy/caddy, ~1.24.1, ubuntu-latest, 0, 1.24, linux) (push) Failing after 2m55s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.24.1, 1.24, aix) (push) Successful in 1m31s
Cross-Build / build (~1.24.1, 1.24, darwin) (push) Successful in 1m31s
Cross-Build / build (~1.24.1, 1.24, dragonfly) (push) Successful in 1m33s
Cross-Build / build (~1.24.1, 1.24, freebsd) (push) Successful in 1m34s
Cross-Build / build (~1.24.1, 1.24, illumos) (push) Successful in 1m34s
Cross-Build / build (~1.24.1, 1.24, linux) (push) Successful in 1m39s
Cross-Build / build (~1.24.1, 1.24, netbsd) (push) Successful in 1m36s
Cross-Build / build (~1.24.1, 1.24, openbsd) (push) Successful in 1m34s
Cross-Build / build (~1.24.1, 1.24, solaris) (push) Successful in 1m35s
Cross-Build / build (~1.24.1, 1.24, windows) (push) Successful in 1m33s
Lint / lint (ubuntu-latest, linux) (push) Successful in 2m29s
Lint / govulncheck (push) Successful in 1m48s
Lint / dependency-review (push) Failing after 59s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Has started running
Tests / test (./cmd/caddy/caddy, ~1.24.1, macos-14, 0, 1.24, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.24.1, windows-latest, True, 1.24, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
2025-08-06 20:04:28 -04:00
dependabot[bot] 007f4066f6 build(deps): bump the all-updates group across 1 directory with 17 updates (#7155)
Tests / test (./cmd/caddy/caddy, ~1.24.1, ubuntu-latest, 0, 1.24, linux) (push) Failing after 2m16s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Has been skipped
Cross-Build / build (~1.24.1, 1.24, aix) (push) Successful in 1m46s
Cross-Build / build (~1.24.1, 1.24, darwin) (push) Successful in 1m21s
Cross-Build / build (~1.24.1, 1.24, dragonfly) (push) Successful in 1m21s
Cross-Build / build (~1.24.1, 1.24, freebsd) (push) Successful in 1m20s
Cross-Build / build (~1.24.1, 1.24, illumos) (push) Successful in 1m21s
Cross-Build / build (~1.24.1, 1.24, linux) (push) Successful in 1m21s
Cross-Build / build (~1.24.1, 1.24, netbsd) (push) Successful in 1m21s
Cross-Build / build (~1.24.1, 1.24, openbsd) (push) Successful in 1m23s
Cross-Build / build (~1.24.1, 1.24, solaris) (push) Successful in 1m24s
Cross-Build / build (~1.24.1, 1.24, windows) (push) Successful in 1m24s
Lint / lint (ubuntu-latest, linux) (push) Successful in 2m21s
Lint / govulncheck (push) Successful in 1m41s
Lint / dependency-review (push) Failing after 55s
OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Failing after 1m39s
Tests / test (./cmd/caddy/caddy, ~1.24.1, macos-14, 0, 1.24, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.24.1, windows-latest, True, 1.24, windows) (push) Has been cancelled
Lint / lint (macos-14, mac) (push) Has been cancelled
Lint / lint (windows-latest, windows) (push) Has been cancelled
Bumps the all-updates group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit) | `0.7.1` | `0.7.4` |
| [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) | `2.19.0` | `2.20.0` |
| [github.com/google/cel-go](https://github.com/google/cel-go) | `0.24.1` | `0.26.0` |
| [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) | `1.19.1` | `1.23.0` |
| [github.com/smallstep/certificates](https://github.com/smallstep/certificates) | `0.26.1` | `0.28.4` |
| [github.com/yuin/goldmark](https://github.com/yuin/goldmark) | `1.7.8` | `1.7.13` |
| [go.opentelemetry.io/contrib/propagators/autoprop](https://github.com/open-telemetry/opentelemetry-go-contrib) | `0.42.0` | `0.62.0` |
| [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) | `1.31.0` | `1.37.0` |
| [github.com/libdns/libdns](https://github.com/libdns/libdns) | `1.0.0-beta.1` | `1.1.0` |
| [github.com/pires/go-proxyproto](https://github.com/pires/go-proxyproto) | `0.7.1-0.20240628150027-b718e7ce4964` | `0.8.1` |



Updates `github.com/KimMachineGun/automemlimit` from 0.7.1 to 0.7.4
- [Release notes](https://github.com/KimMachineGun/automemlimit/releases)
- [Commits](https://github.com/KimMachineGun/automemlimit/compare/v0.7.1...v0.7.4)

Updates `github.com/alecthomas/chroma/v2` from 2.19.0 to 2.20.0
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.19.0...v2.20.0)

Updates `github.com/google/cel-go` from 0.24.1 to 0.26.0
- [Release notes](https://github.com/google/cel-go/releases)
- [Commits](https://github.com/google/cel-go/compare/v0.24.1...v0.26.0)

Updates `github.com/prometheus/client_golang` from 1.19.1 to 1.23.0
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.19.1...v1.23.0)

Updates `github.com/smallstep/certificates` from 0.26.1 to 0.28.4
- [Release notes](https://github.com/smallstep/certificates/releases)
- [Changelog](https://github.com/smallstep/certificates/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smallstep/certificates/compare/v0.26.1...v0.28.4)

Updates `github.com/smallstep/nosql` from 0.6.1 to 0.7.0
- [Commits](https://github.com/smallstep/nosql/compare/v0.6.1...v0.7.0)

Updates `github.com/yuin/goldmark` from 1.7.8 to 1.7.13
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.7.8...v1.7.13)

Updates `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` from 0.56.0 to 0.61.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.56.0...zpages/v0.61.0)

Updates `go.opentelemetry.io/contrib/propagators/autoprop` from 0.42.0 to 0.62.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.42.0...zpages/v0.62.0)

Updates `go.opentelemetry.io/otel` from 1.31.0 to 1.37.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.31.0...v1.37.0)

Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc` from 1.31.0 to 1.37.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.31.0...v1.37.0)

Updates `go.opentelemetry.io/otel/sdk` from 1.31.0 to 1.37.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.31.0...v1.37.0)

Updates `github.com/libdns/libdns` from 1.0.0-beta.1 to 1.1.0
- [Release notes](https://github.com/libdns/libdns/releases)
- [Commits](https://github.com/libdns/libdns/compare/v1.0.0-beta.1...v1.1.0)

Updates `github.com/pires/go-proxyproto` from 0.7.1-0.20240628150027-b718e7ce4964 to 0.8.1
- [Release notes](https://github.com/pires/go-proxyproto/releases)
- [Commits](https://github.com/pires/go-proxyproto/commits/v0.8.1)

Updates `github.com/prometheus/client_model` from 0.5.0 to 0.6.2
- [Release notes](https://github.com/prometheus/client_model/releases)
- [Commits](https://github.com/prometheus/client_model/compare/v0.5.0...v0.6.2)

Updates `go.opentelemetry.io/otel/trace` from 1.31.0 to 1.37.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.31.0...v1.37.0)

Updates `go.step.sm/crypto` from 0.45.0 to 0.67.0
- [Release notes](https://github.com/smallstep/crypto/releases)
- [Commits](https://github.com/smallstep/crypto/compare/v0.45.0...v0.67.0)

---
updated-dependencies:
- dependency-name: github.com/KimMachineGun/automemlimit
  dependency-version: 0.7.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: github.com/alecthomas/chroma/v2
  dependency-version: 2.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: github.com/google/cel-go
  dependency-version: 0.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: github.com/smallstep/certificates
  dependency-version: 0.28.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: github.com/smallstep/nosql
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: github.com/yuin/goldmark
  dependency-version: 1.7.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
  dependency-version: 0.61.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/contrib/propagators/autoprop
  dependency-version: 0.62.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/otel
  dependency-version: 1.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
  dependency-version: 1.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/otel/sdk
  dependency-version: 1.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: github.com/libdns/libdns
  dependency-version: 1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: github.com/pires/go-proxyproto
  dependency-version: 0.8.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: github.com/prometheus/client_model
  dependency-version: 0.6.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-version: 1.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: go.step.sm/crypto
  dependency-version: 0.67.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-05 09:45:32 +03:00
Matthew Holt 42c888ee1d Ignore irrelevant linter warning 2025-08-04 16:28:13 -06:00
Matthew Holt 731e6c2482 caddytls: Improve ECH error logging (close #7152) 2025-08-04 16:22:25 -06:00
Matthew Holt 0badb071ef httpcaddyfile: Fix generated config related to ACME global options
If global DNS provider is configured, it does not need to be repeated in the JSON.

If acme_* options are used, base automation policies should populate their issuers accordingly.

Global issuer settings like acme_* options don't need to specify subjects in the automation policy since they should apply as a global default.
2025-08-04 16:22:25 -06:00
Mohammed Al Sahaf 9f9f5ab4de pin deps
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-06-16 23:55:22 +03:00
Mohammed Al Sahaf 535e40c342 Merge branch 'master' into hurl-tests 2025-06-16 23:48:47 +03:00
Mohammed Al Sahaf 218b3b192b Merge branch 'master' into hurl-tests 2024-12-30 17:51:38 +03:00
Mohammed Al Sahaf df59b09cad Merge branch 'master' into hurl-tests 2024-12-29 12:42:12 +03:00
Mohammed Al Sahaf c718744483 Keep report to HTML only 2024-10-31 07:39:31 +00:00
Mohammed Al Sahaf e75fca007e include txt and html coverage profile 2024-10-31 07:30:50 +00:00
Mohammed Al Sahaf 25d94ffe2a generate coverage profile 2024-10-29 22:03:13 +00:00
Mohammed Al Sahaf 7ea59f0d49 remove of --cafile in hurl 2024-10-29 21:23:34 +00:00
Mohammed Al Sahaf ddc2ca3e10 use Caddy CA in hurl options 2024-10-29 21:19:04 +00:00
Mohammed Al Sahaf b34c13c5cf limit hurl jobs to 1 2024-10-29 21:10:09 +00:00
Mohammed Al Sahaf 18a15d84ef Merge branch 'master' into hurl-tests 2024-10-29 21:03:10 +00:00
Mohammed Al Sahaf 9ba7ea76a9 add test case for request_body handler 2024-10-29 20:53:52 +00:00
Mohammed Al Sahaf 01ae168f92 print curl and hurl versions
Tests / test (./cmd/caddy/caddy, ~1.22.3, ubuntu-latest, 0, 1.22, linux) (push) Failing after 2m47s
Tests / test (./cmd/caddy/caddy, ~1.23.0, ubuntu-latest, 0, 1.23, linux) (push) Failing after 2m14s
Tests / spec-test (./cmd/caddy/caddy, ~1.23.0, ubuntu-latest, 0, 1.23, linux) (push) Failing after 2m17s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Successful in 27s
Tests / test (./cmd/caddy/caddy, ~1.22.3, macos-14, 0, 1.22, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy, ~1.23.0, macos-14, 0, 1.23, mac) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.22.3, windows-latest, True, 1.22, windows) (push) Has been cancelled
Tests / test (./cmd/caddy/caddy.exe, ~1.23.0, windows-latest, True, 1.23, windows) (push) Has been cancelled
2024-09-09 11:27:38 +00:00
Mohammed Al Sahaf 8d2ed344c1 update hurl to 5.0.1
Tests / test (./cmd/caddy/caddy, ~1.22.3, macos-14, 0, 1.22, mac) (push) Waiting to run
Tests / test (./cmd/caddy/caddy, ~1.23.0, macos-14, 0, 1.23, mac) (push) Waiting to run
Tests / test (./cmd/caddy/caddy.exe, ~1.22.3, windows-latest, True, 1.22, windows) (push) Waiting to run
Tests / test (./cmd/caddy/caddy.exe, ~1.23.0, windows-latest, True, 1.23, windows) (push) Waiting to run
Tests / test (./cmd/caddy/caddy, ~1.22.3, ubuntu-latest, 0, 1.22, linux) (push) Failing after 3m33s
Tests / test (./cmd/caddy/caddy, ~1.23.0, ubuntu-latest, 0, 1.23, linux) (push) Failing after 2m52s
Tests / spec-test (./cmd/caddy/caddy, ~1.23.0, ubuntu-latest, 0, 1.23, linux) (push) Successful in 2m59s
Tests / test (s390x on IBM Z) (push) Has been skipped
Tests / goreleaser-check (push) Successful in 28s
2024-09-09 08:29:23 +00:00
Mohammed Al Sahaf 3bdc6c035a Merge branch 'master' into hurl-tests 2024-09-09 10:20:16 +03:00
Mohammed Al Sahaf c1cdc25b77 add file_server test
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2024-06-20 20:24:19 +03:00
Mohammed Al Sahaf 0ecb1ba262 add basic_auth test
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2024-06-15 00:48:08 +03:00
Mohammed Al Sahaf eb6934f784 Merge branch 'master' into hurl-tests 2024-06-06 12:52:46 +03:00
Mohammed Al Sahaf 1b4bd3ee1b testing: use Hurl in CI to test Caddy against spec 2024-04-20 14:49:56 +03:00
130 changed files with 5677 additions and 3109 deletions
+31
View File
@@ -0,0 +1,31 @@
name: Issue
description: An actionable development item, like a bug report or feature request
body:
- type: markdown
attributes:
value: |
Thank you for opening an issue! This is for actionable development items like bug reports and feature requests.
If you have a question about using Caddy, please [post on our forums](https://caddy.community) instead.
- type: textarea
id: content
attributes:
label: Issue Details
placeholder: Describe the issue here. Be specific by providing complete logs and minimal instructions to reproduce, or a thoughtful proposal, etc.
validations:
required: true
- type: dropdown
id: assistance-disclosure
attributes:
label: Assistance Disclosure
description: "Our project allows assistance by AI/LLM tools as long as it is disclosed and described so we can better respond. Please certify whether you have used any such tooling related to this issue:"
options:
-
- AI used
- AI not used
validations:
required: true
- type: input
id: assistance-description
attributes:
label: If AI was used, describe the extent to which it was used.
description: 'Examples: "ChatGPT translated from my native language" or "Claude proposed this change/feature"'
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Caddy forum
url: https://caddy.community
about: If you have questions (or answers!) about using Caddy, please use our forum
+29
View File
@@ -0,0 +1,29 @@
## Assistance Disclosure
<!--
Thank you for contributing! Please note:
The use of AI/LLM tools is allowed so long as it is disclosed, so
that we can provide better code review and maintain project quality.
If you used AI/LLM tooling in any way related to this PR, please
let us know to what extent it was utilized.
Examples:
"No AI was used."
"I wrote the code, but Claude generated the tests."
"I consulted ChatGPT for a solution, but I authored/coded it myself."
"Cody generated the code, and I verified it is correct."
"Copilot provided tab completion for code and comments."
We expect that you have vetted your contributions for correctness.
Additionally, signing our CLA certifies that you have the rights to
contribute this change.
Replace the text below with your disclosure:
-->
_This PR is missing an assistance disclosure._
+30
View File
@@ -0,0 +1,30 @@
name: AI Moderator
permissions: read-all
on:
issues:
types: [opened]
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
spam-detection:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
models: read
contents: read
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: github/ai-moderator@6bcdb2a79c2e564db8d76d7d4439d91a044c4eb6
with:
token: ${{ secrets.GITHUB_TOKEN }}
spam-label: 'spam'
ai-label: 'ai-generated'
minimize-detected-comments: true
# Built-in prompt configuration (all enabled by default)
enable-spam-detection: true
enable-link-spam-detection: true
enable-ai-detection: true
# custom-prompt-path: '.github/prompts/my-custom.prompt.yml' # Optional
+124 -17
View File
@@ -13,6 +13,7 @@ on:
- 2.*
env:
GOFLAGS: '-tags=nobadger,nomysql,nopgx'
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
@@ -30,13 +31,13 @@ jobs:
- mac
- windows
go:
- '1.24'
- '1.25'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.24'
GO_SEMVER: '~1.24.1'
- go: '1.25'
GO_SEMVER: '~1.25.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)
@@ -62,17 +63,18 @@ jobs:
contents: read
pull-requests: read
actions: write # to allow uploading artifacts and cache
checks: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
@@ -110,7 +112,7 @@ jobs:
env:
CGO_ENABLED: 0
run: |
go build -tags nobadger,nomysql,nopgx -trimpath -ldflags="-w -s" -v
go build -trimpath -ldflags="-w -s" -v
- name: Smoke test Caddy
working-directory: ./cmd/caddy
@@ -133,7 +135,7 @@ jobs:
# continue-on-error: true
run: |
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
go test -tags nobadger,nomysql,nopgx -v -coverprofile="cover-profile.out" -short -race ./...
go test -v -coverprofile="cover-profile.out" -short -race ./...
# echo "status=$?" >> $GITHUB_OUTPUT
# Relevant step if we reinvestigate publishing test/coverage reports
@@ -151,6 +153,111 @@ jobs:
# echo "step_test ${{ steps.step_test.outputs.status }}\n"
# exit 1
spec-test:
permissions:
checks: write
pull-requests: write
strategy:
matrix:
os:
- linux
go:
- '1.25'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.25'
GO_SEMVER: '~1.25.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
# SUCCESS: the typical value for $? per OS (Windows/pwsh returns 'True')
- os: linux
OS_LABEL: ubuntu-latest
CADDY_BIN_PATH: ./cmd/caddy/caddy
SUCCESS: 0
runs-on: ${{ matrix.OS_LABEL }}
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
- name: Print Go version and environment
id: vars
shell: bash
run: |
printf "curl version: $(curl --version)\n"
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
printf "Git version: $(git version)\n\n"
# Calculate the short SHA1 hash of the git commit
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Get dependencies
run: |
go get -v -t ./...
# mkdir test-results
- name: Build Caddy
working-directory: ./cmd/caddy
env:
CGO_ENABLED: 0
run: |
go build -cover -tags nobadger,nopgx,nomysql -trimpath -ldflags="-w -s" -v
- name: Install Hurl
env:
HURL_VERSION: "7.0.0"
run: |
curl --location --remote-name https://github.com/Orange-OpenSource/hurl/releases/download/${HURL_VERSION}/hurl_${HURL_VERSION}_amd64.deb
sudo dpkg -i hurl_${HURL_VERSION}_amd64.deb
hurl --version
- name: Run Caddy
run: |
./cmd/caddy/caddy environ
mkdir coverdir
export GOCOVERDIR=./coverdir
./cmd/caddy/caddy start
sleep 5
- name: Run tests with Hurl
run: |
mkdir hurl-report
find . -name *.hurl -exec hurl --jobs 1 --variables-file caddytest/spec/hurl_vars.properties --very-verbose --verbose --test --report-junit hurl-report/junit.xml --color {} \;
- name: Publish Test Results
uses: EnricoMi/publish-unit-test-result-action@3a74b2957438d0b6e2e61d67b05318aa25c9e6c6 # v2.20.0
with:
files: |
hurl-report/junit.xml
- name: Generate Coverage Data
run: |
export GOCOVERDIR=./coverdir
./cmd/caddy/caddy stop
go tool covdata textfmt -i=coverdir -o hurl-report/caddy_cover_${{ steps.vars.outputs.short_sha }}.txt
go tool cover -html hurl-report/caddy_cover_${{ steps.vars.outputs.short_sha }}.txt -o hurl-report/caddy_cover_${{ steps.vars.outputs.short_sha }}.html
- name: Publish Coverage Profile
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
path: hurl-report/caddy_cover_${{ steps.vars.outputs.short_sha }}.html
compression-level: 0
s390x-test:
name: test (s390x on IBM Z)
permissions:
@@ -161,13 +268,13 @@ jobs:
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
allowed-endpoints: ci-s390x.caddyserver.com:22
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Run Tests
run: |
set +e
@@ -190,7 +297,7 @@ jobs:
retries=3
exit_code=0
while ((retries > 0)); do
CGO_ENABLED=0 go test -p 1 -tags nobadger,nomysql,nopgx -v ./...
CGO_ENABLED=0 go test -p 1 -v ./...
exit_code=$?
if ((exit_code == 0)); then
break
@@ -220,27 +327,27 @@ jobs:
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
- uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
version: latest
args: check
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: "~1.24"
go-version: "~1.25"
check-latest: true
- name: Install xcaddy
run: |
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy version
- uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
- uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
version: latest
args: build --single-target --snapshot
+9 -9
View File
@@ -11,6 +11,8 @@ on:
- 2.*
env:
GOFLAGS: '-tags=nobadger,nomysql,nopgx'
CGO_ENABLED: '0'
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
@@ -34,13 +36,13 @@ jobs:
- 'darwin'
- 'netbsd'
go:
- '1.24'
- '1.25'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.24'
GO_SEMVER: '~1.24.1'
- go: '1.25'
GO_SEMVER: '~1.25.0'
runs-on: ubuntu-latest
permissions:
@@ -49,15 +51,15 @@ jobs:
continue-on-error: true
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
@@ -74,11 +76,9 @@ jobs:
- name: Run Build
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goos == 'aix' && 'ppc64' || 'amd64' }}
shell: bash
continue-on-error: true
working-directory: ./cmd/caddy
run: |
GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
run: go build -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
+9 -9
View File
@@ -45,14 +45,14 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: '~1.24'
go-version: '~1.25'
check-latest: true
- name: golangci-lint
@@ -73,14 +73,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
- name: govulncheck
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
with:
go-version-input: '~1.24.1'
go-version-input: '~1.25.0'
check-latest: true
dependency-review:
@@ -90,14 +90,14 @@ jobs:
pull-requests: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
- name: 'Checkout Repository'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: 'Dependency Review'
uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1
uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4.8.0
with:
comment-summary-in-pr: on-failure
# https://github.com/actions/dependency-review-action/issues/430#issuecomment-1468975566
+10 -10
View File
@@ -20,13 +20,13 @@ jobs:
os:
- ubuntu-latest
go:
- '1.24'
- '1.25'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.24'
GO_SEMVER: '~1.24.1'
- go: '1.25'
GO_SEMVER: '~1.25.0'
runs-on: ${{ matrix.os }}
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
@@ -39,23 +39,23 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
# Force fetch upstream tags -- because 65 minutes
# tl;dr: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 runs this line:
# tl;dr: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 runs this line:
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
# git fetch --prune --unshallow
@@ -109,11 +109,11 @@ jobs:
git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1
- name: Install Cosign
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # main
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # main
- name: Cosign version
run: cosign version
- name: Install Syft
uses: anchore/sbom-action/download-syft@7b36ad622f042cab6f59a75c2ac24ccb256e9b45 # main
uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # main
- name: Syft version
run: syft version
- name: Install xcaddy
@@ -122,7 +122,7 @@ jobs:
xcaddy version
# GoReleaser will take care of publishing those artifacts into the release
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
version: latest
args: release --clean --timeout 60m
+3 -3
View File
@@ -24,12 +24,12 @@ jobs:
# See https://github.com/peter-evans/repository-dispatch
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
- name: Trigger event on caddyserver/dist
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0
uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/dist
@@ -37,7 +37,7 @@ jobs:
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
- name: Trigger event on caddyserver/caddy-docker
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0
uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/caddy-docker
+4 -4
View File
@@ -37,17 +37,17 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
- name: "Checkout code"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
@@ -81,6 +81,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5
uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5
with:
sarif_file: results.sarif
+4
View File
@@ -2,6 +2,10 @@ version: "2"
run:
issues-exit-code: 1
tests: false
build-tags:
- nobadger
- nomysql
- nopgx
output:
formats:
text:
+1 -1
View File
@@ -89,7 +89,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
Requirements:
- [Go 1.24.0 or newer](https://golang.org/dl/)
- [Go 1.25.0 or newer](https://golang.org/dl/)
### For development
+7
View File
@@ -1029,6 +1029,13 @@ func handleConfig(w http.ResponseWriter, r *http.Request) error {
return err
}
// If this request changed the config, clear the last
// config info we have stored, if it is different from
// the original source.
ClearLastConfigIfDifferent(
r.Header.Get("Caddy-Config-Source-File"),
r.Header.Get("Caddy-Config-Source-Adapter"))
default:
return APIError{
HTTPStatus: http.StatusMethodNotAllowed,
+3 -5
View File
@@ -149,11 +149,9 @@ func TestLoadConcurrent(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
wg.Go(func() {
_ = Load(testCfg, true)
wg.Done()
}()
})
}
wg.Wait()
}
@@ -207,7 +205,7 @@ func TestETags(t *testing.T) {
}
func BenchmarkLoad(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
Load(testCfg, true)
}
}
+88 -3
View File
@@ -975,11 +975,11 @@ func Version() (simple, full string) {
if CustomVersion != "" {
full = CustomVersion
simple = CustomVersion
return
return simple, full
}
full = "unknown"
simple = "unknown"
return
return simple, full
}
// find the Caddy module in the dependency list
for _, dep := range bi.Deps {
@@ -1059,7 +1059,7 @@ func Version() (simple, full string) {
}
}
return
return simple, full
}
// Event represents something that has happened or is happening.
@@ -1197,6 +1197,91 @@ var (
rawCfgMu sync.RWMutex
)
// lastConfigFile and lastConfigAdapter remember the source config
// file and adapter used when Caddy was started via the CLI "run" command.
// These are consulted by the SIGUSR1 handler to attempt reloading from
// the same source. They are intentionally not set for other entrypoints
// such as "caddy start" or subcommands like file-server.
var (
lastConfigMu sync.RWMutex
lastConfigFile string
lastConfigAdapter string
)
// reloadFromSourceFunc is the type of stored callback
// which is called when we receive a SIGUSR1 signal.
type reloadFromSourceFunc func(file, adapter string) error
// reloadFromSourceCallback is the stored callback
// which is called when we receive a SIGUSR1 signal.
var reloadFromSourceCallback reloadFromSourceFunc
// errReloadFromSourceUnavailable is returned when no reload-from-source callback is set.
var errReloadFromSourceUnavailable = errors.New("reload from source unavailable in this process") //nolint:unused
// SetLastConfig records the given source file and adapter as the
// last-known external configuration source. Intended to be called
// only when starting via "caddy run --config <file> --adapter <adapter>".
func SetLastConfig(file, adapter string, fn reloadFromSourceFunc) {
lastConfigMu.Lock()
lastConfigFile = file
lastConfigAdapter = adapter
reloadFromSourceCallback = fn
lastConfigMu.Unlock()
}
// ClearLastConfigIfDifferent clears the recorded last-config if the provided
// source file/adapter do not match the recorded last-config. If both srcFile
// and srcAdapter are empty, the last-config is cleared.
func ClearLastConfigIfDifferent(srcFile, srcAdapter string) {
if (srcFile != "" || srcAdapter != "") && lastConfigMatches(srcFile, srcAdapter) {
return
}
SetLastConfig("", "", nil)
}
// getLastConfig returns the last-known config file and adapter.
func getLastConfig() (file, adapter string, fn reloadFromSourceFunc) {
lastConfigMu.RLock()
f, a, cb := lastConfigFile, lastConfigAdapter, reloadFromSourceCallback
lastConfigMu.RUnlock()
return f, a, cb
}
// lastConfigMatches returns true if the provided source file and/or adapter
// matches the recorded last-config. Matching rules (in priority order):
// 1. If srcAdapter is provided and differs from the recorded adapter, no match.
// 2. If srcFile exactly equals the recorded file, match.
// 3. If both sides can be made absolute and equal, match.
// 4. If basenames are equal, match.
func lastConfigMatches(srcFile, srcAdapter string) bool {
lf, la, _ := getLastConfig()
// If adapter is provided, it must match.
if srcAdapter != "" && srcAdapter != la {
return false
}
// Quick equality check.
if srcFile == lf {
return true
}
// Try absolute path comparison.
sAbs, sErr := filepath.Abs(srcFile)
lAbs, lErr := filepath.Abs(lf)
if sErr == nil && lErr == nil && sAbs == lAbs {
return true
}
// Final fallback: basename equality.
if filepath.Base(srcFile) == filepath.Base(lf) {
return true
}
return false
}
// errSameConfig is returned if the new config is the same
// as the old one. This isn't usually an actual, actionable
// error; it's mostly a sentinel value.
+18 -6
View File
@@ -308,9 +308,9 @@ func (d *Dispenser) CountRemainingArgs() int {
}
// RemainingArgs loads any more arguments (tokens on the same line)
// into a slice and returns them. Open curly brace tokens also indicate
// the end of arguments, and the curly brace is not included in
// the return value nor is it loaded.
// into a slice of strings and returns them. Open curly brace tokens
// also indicate the end of arguments, and the curly brace is not
// included in the return value nor is it loaded.
func (d *Dispenser) RemainingArgs() []string {
var args []string
for d.NextArg() {
@@ -320,9 +320,9 @@ func (d *Dispenser) RemainingArgs() []string {
}
// RemainingArgsRaw loads any more arguments (tokens on the same line,
// retaining quotes) into a slice and returns them. Open curly brace
// tokens also indicate the end of arguments, and the curly brace is
// not included in the return value nor is it loaded.
// retaining quotes) into a slice of strings and returns them.
// Open curly brace tokens also indicate the end of arguments,
// and the curly brace is not included in the return value nor is it loaded.
func (d *Dispenser) RemainingArgsRaw() []string {
var args []string
for d.NextArg() {
@@ -331,6 +331,18 @@ func (d *Dispenser) RemainingArgsRaw() []string {
return args
}
// RemainingArgsAsTokens loads any more arguments (tokens on the same line)
// into a slice of Token-structs and returns them. Open curly brace tokens
// also indicate the end of arguments, and the curly brace is not included
// in the return value nor is it loaded.
func (d *Dispenser) RemainingArgsAsTokens() []Token {
var args []Token
for d.NextArg() {
args = append(args, d.Token())
}
return args
}
// NewFromNextSegment returns a new dispenser with a copy of
// the tokens from the current token until the end of the
// "directive" whether that be to the end of the line or
+60
View File
@@ -274,6 +274,66 @@ func TestDispenser_RemainingArgs(t *testing.T) {
}
}
func TestDispenser_RemainingArgsAsTokens(t *testing.T) {
input := `dir1 arg1 arg2 arg3
dir2 arg4 arg5
dir3 arg6 { arg7
dir4`
d := NewTestDispenser(input)
d.Next() // dir1
args := d.RemainingArgsAsTokens()
tokenTexts := make([]string, 0, len(args))
for _, arg := range args {
tokenTexts = append(tokenTexts, arg.Text)
}
if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(tokenTexts, expected) {
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
}
d.Next() // dir2
args = d.RemainingArgsAsTokens()
tokenTexts = tokenTexts[:0]
for _, arg := range args {
tokenTexts = append(tokenTexts, arg.Text)
}
if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(tokenTexts, expected) {
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
}
d.Next() // dir3
args = d.RemainingArgsAsTokens()
tokenTexts = tokenTexts[:0]
for _, arg := range args {
tokenTexts = append(tokenTexts, arg.Text)
}
if expected := []string{"arg6"}; !reflect.DeepEqual(tokenTexts, expected) {
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
}
d.Next() // {
d.Next() // arg7
d.Next() // dir4
args = d.RemainingArgsAsTokens()
tokenTexts = tokenTexts[:0]
for _, arg := range args {
tokenTexts = append(tokenTexts, arg.Text)
}
if len(args) != 0 {
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", []string{}, tokenTexts)
}
}
func TestDispenser_ArgErr_Err(t *testing.T) {
input := `dir1 {
}
+35 -18
View File
@@ -52,17 +52,16 @@ func Format(input []byte) []byte {
newLines int // count of newlines consumed
comment bool // whether we're in a comment
quoted bool // whether we're in a quoted segment
escaped bool // whether current char is escaped
comment bool // whether we're in a comment
quotes string // encountered quotes ('', '`', '"', '"`', '`"')
escaped bool // whether current char is escaped
heredoc heredocState // whether we're in a heredoc
heredocEscaped bool // whether heredoc is escaped
heredocMarker []rune
heredocClosingMarker []rune
nesting int // indentation level
withinBackquote bool
nesting int // indentation level
)
write := func(ch rune) {
@@ -89,12 +88,8 @@ func Format(input []byte) []byte {
}
panic(err)
}
if ch == '`' {
withinBackquote = !withinBackquote
}
// detect whether we have the start of a heredoc
if !quoted && (heredoc == heredocClosed && !heredocEscaped) &&
if quotes == "" && (heredoc == heredocClosed && !heredocEscaped) &&
space && last == '<' && ch == '<' {
write(ch)
heredoc = heredocOpening
@@ -180,16 +175,38 @@ func Format(input []byte) []byte {
continue
}
if quoted {
if ch == '`' {
switch quotes {
case "\"`":
quotes = "\""
case "`":
quotes = ""
case "\"":
quotes = "\"`"
default:
quotes = "`"
}
}
if quotes == "\"" {
if ch == '"' {
quoted = false
quotes = ""
}
write(ch)
continue
}
if space && ch == '"' {
quoted = true
if ch == '"' {
switch quotes {
case "":
if space {
quotes = "\""
}
case "`\"":
quotes = "`"
case "\"`":
quotes = ""
}
}
if unicode.IsSpace(ch) {
@@ -224,7 +241,7 @@ func Format(input []byte) []byte {
openBrace = false
if beginningOfLine {
indent()
} else if !openBraceSpace {
} else if !openBraceSpace || !unicode.IsSpace(last) {
write(' ')
}
write('{')
@@ -241,11 +258,11 @@ func Format(input []byte) []byte {
case ch == '{':
openBrace = true
openBraceSpace = spacePrior && !beginningOfLine
if openBraceSpace {
if openBraceSpace && newLines == 0 {
write(' ')
}
openBraceWritten = false
if withinBackquote {
if quotes == "`" {
write('{')
openBraceWritten = true
continue
@@ -253,7 +270,7 @@ func Format(input []byte) []byte {
continue
case ch == '}' && (spacePrior || !openBrace):
if withinBackquote {
if quotes == "`" {
write('}')
continue
}
+20
View File
@@ -444,6 +444,26 @@ block2 {
input: "block {respond \"All braces should remain: {{now | date `2006`}}\"}",
expect: "block {respond \"All braces should remain: {{now | date `2006`}}\"}",
},
{
description: "Preserve quoted backticks and backticked quotes",
input: "block { respond \"`\" } block { respond `\"`}",
expect: "block {\n\trespond \"`\"\n}\n\nblock {\n\trespond `\"`\n}",
},
{
description: "No trailing space on line before env variable",
input: `{
a
{$ENV_VAR}
}
`,
expect: `{
a
{$ENV_VAR}
}
`,
},
} {
// the formatter should output a trailing newline,
// even if the tests aren't written to expect that
+18 -28
View File
@@ -379,28 +379,23 @@ func (p *parser) doImport(nesting int) error {
if len(blockTokens) > 0 {
// use such tokens to create a new dispenser, and then use it to parse each block
bd := NewDispenser(blockTokens)
// one iteration processes one sub-block inside the import
for bd.Next() {
// see if we can grab a key
var currentMappingKey string
if bd.Val() == "{" {
currentMappingKey := bd.Val()
if currentMappingKey == "{" {
return p.Err("anonymous blocks are not supported")
}
currentMappingKey = bd.Val()
currentMappingTokens := []Token{}
// read all args until end of line / {
if bd.NextArg() {
// load up all arguments (if there even are any)
currentMappingTokens := bd.RemainingArgsAsTokens()
// load up the entire block
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
currentMappingTokens = append(currentMappingTokens, bd.Token())
for bd.NextArg() {
currentMappingTokens = append(currentMappingTokens, bd.Token())
}
// TODO(elee1766): we don't enter another mapping here because it's annoying to extract the { and } properly.
// maybe someone can do that in the future
} else {
// attempt to enter a block and add tokens to the currentMappingTokens
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
currentMappingTokens = append(currentMappingTokens, bd.Token())
}
}
blockMapping[currentMappingKey] = currentMappingTokens
}
}
@@ -538,29 +533,24 @@ func (p *parser) doImport(nesting int) error {
}
// if it is {block}, we substitute with all tokens in the block
// if it is {blocks.*}, we substitute with the tokens in the mapping for the *
var skip bool
var tokensToAdd []Token
foundBlockDirective := false
switch {
case token.Text == "{block}":
foundBlockDirective = true
tokensToAdd = blockTokens
case strings.HasPrefix(token.Text, "{blocks.") && strings.HasSuffix(token.Text, "}"):
foundBlockDirective = true
// {blocks.foo.bar} will be extracted to key `foo.bar`
blockKey := strings.TrimPrefix(strings.TrimSuffix(token.Text, "}"), "{blocks.")
val, ok := blockMapping[blockKey]
if ok {
tokensToAdd = val
}
default:
skip = true
}
if !skip {
if len(tokensToAdd) == 0 {
// if there is no content in the snippet block, don't do any replacement
// this allows snippets which contained {block}/{block.*} before this change to continue functioning as normal
tokensCopy = append(tokensCopy, token)
} else {
tokensCopy = append(tokensCopy, tokensToAdd...)
}
if foundBlockDirective {
tokensCopy = append(tokensCopy, tokensToAdd...)
continue
}
+46
View File
@@ -18,6 +18,7 @@ import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)
@@ -884,6 +885,51 @@ func TestRejectsGlobalMatcher(t *testing.T) {
}
}
func TestRejectAnonymousImportBlock(t *testing.T) {
p := testParser(`
(site) {
http://{args[0]} https://{args[0]} {
{block}
}
}
import site test.domain {
{
header_up Host {host}
header_up X-Real-IP {remote_host}
}
}
`)
_, err := p.parseAll()
if err == nil {
t.Fatal("Expected an error, but got nil")
}
expected := "anonymous blocks are not supported"
if !strings.HasPrefix(err.Error(), "anonymous blocks are not supported") {
t.Errorf("Expected error to start with '%s' but got '%v'", expected, err)
}
}
func TestAcceptSiteImportWithBraces(t *testing.T) {
p := testParser(`
(site) {
http://{args[0]} https://{args[0]} {
{block}
}
}
import site test.domain {
reverse_proxy http://192.168.1.1:8080 {
header_up Host {host}
}
}
`)
_, err := p.parseAll()
if err != nil {
t.Errorf("Expected error to be nil but got '%v'", err)
}
}
func testParser(input string) parser {
return parser{Dispenser: NewTestDispenser(input)}
}
+2 -2
View File
@@ -91,7 +91,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// curves <curves...>
// client_auth {
// mode [request|require|verify_if_given|require_and_verify]
// trust_pool <module_name> [...]
// trust_pool <module_name> [...]
// trusted_leaf_cert <base64_der>
// trusted_leaf_cert_file <filename>
// }
@@ -481,7 +481,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
// Validate DNS challenge config: any DNS challenge option except "dns" requires a DNS provider
if acmeIssuer != nil && acmeIssuer.Challenges != nil && acmeIssuer.Challenges.DNS != nil {
dnsCfg := acmeIssuer.Challenges.DNS
providerSet := dnsCfg.ProviderRaw != nil || h.Option("dns") != nil
providerSet := dnsCfg.ProviderRaw != nil || h.Option("dns") != nil || h.Option("acme_dns") != nil
if len(dnsOptionsSet) > 0 && !providerSet {
return nil, h.Errf(
"setting DNS challenge options [%s] requires a DNS provider (set with the 'dns' subdirective or 'acme_dns' global option)",
-2
View File
@@ -458,8 +458,6 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
case "disable_certs":
case "ignore_loaded_certs":
case "prefer_wildcard":
break
default:
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', 'ignore_loaded_certs', or 'prefer_wildcard'")
}
+13 -1
View File
@@ -15,6 +15,8 @@
package httpcaddyfile
import (
"slices"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
@@ -178,6 +180,15 @@ func (st ServerType) buildPKIApp(
if _, ok := options["skip_install_trust"]; ok {
skipInstallTrust = true
}
// check if auto_https is off - in that case we should not create
// any PKI infrastructure even with skip_install_trust directive
autoHTTPS := []string{}
if ah, ok := options["auto_https"].([]string); ok {
autoHTTPS = ah
}
autoHTTPSOff := slices.Contains(autoHTTPS, "off")
falseBool := false
// Load the PKI app configured via global options
@@ -218,7 +229,8 @@ func (st ServerType) buildPKIApp(
// if there was no CAs defined in any of the servers,
// and we were requested to not install trust, then
// add one for the default/local CA to do so
if len(pkiApp.CAs) == 0 && skipInstallTrust {
// only if auto_https is not completely disabled
if len(pkiApp.CAs) == 0 && skipInstallTrust && !autoHTTPSOff {
ca := new(caddypki.CA)
ca.ID = caddypki.DefaultCAID
ca.InstallTrust = &falseBool
@@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"slices"
"strconv"
"github.com/dustin/go-humanize"
@@ -42,12 +43,15 @@ type serverOptions struct {
WriteTimeout caddy.Duration
IdleTimeout caddy.Duration
KeepAliveInterval caddy.Duration
KeepAliveIdle caddy.Duration
KeepAliveCount int
MaxHeaderBytes int
EnableFullDuplex bool
Protocols []string
StrictSNIHost *bool
TrustedProxiesRaw json.RawMessage
TrustedProxiesStrict int
TrustedProxiesUnix bool
ClientIPHeaders []string
ShouldLogCredentials bool
Metrics *caddyhttp.Metrics
@@ -142,6 +146,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
return nil, d.Errf("unrecognized timeouts option '%s'", d.Val())
}
}
case "keepalive_interval":
if !d.NextArg() {
return nil, d.ArgErr()
@@ -152,6 +157,26 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
}
serverOpts.KeepAliveInterval = caddy.Duration(dur)
case "keepalive_idle":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing keepalive idle duration: %v", err)
}
serverOpts.KeepAliveIdle = caddy.Duration(dur)
case "keepalive_count":
if !d.NextArg() {
return nil, d.ArgErr()
}
cnt, err := strconv.ParseInt(d.Val(), 10, 32)
if err != nil {
return nil, d.Errf("parsing keepalive count int: %v", err)
}
serverOpts.KeepAliveCount = int(cnt)
case "max_header_size":
var sizeStr string
if !d.AllArgs(&sizeStr) {
@@ -227,6 +252,12 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
}
serverOpts.TrustedProxiesStrict = 1
case "trusted_proxies_unix":
if d.NextArg() {
return nil, d.ArgErr()
}
serverOpts.TrustedProxiesUnix = true
case "client_ip_headers":
headers := d.RemainingArgs()
for _, header := range headers {
@@ -309,6 +340,8 @@ func applyServerOptions(
server.WriteTimeout = opts.WriteTimeout
server.IdleTimeout = opts.IdleTimeout
server.KeepAliveInterval = opts.KeepAliveInterval
server.KeepAliveIdle = opts.KeepAliveIdle
server.KeepAliveCount = opts.KeepAliveCount
server.MaxHeaderBytes = opts.MaxHeaderBytes
server.EnableFullDuplex = opts.EnableFullDuplex
server.Protocols = opts.Protocols
@@ -316,6 +349,7 @@ func applyServerOptions(
server.TrustedProxiesRaw = opts.TrustedProxiesRaw
server.ClientIPHeaders = opts.ClientIPHeaders
server.TrustedProxiesStrict = opts.TrustedProxiesStrict
server.TrustedProxiesUnix = opts.TrustedProxiesUnix
server.Metrics = opts.Metrics
if opts.ShouldLogCredentials {
if server.Logs == nil {
+3
View File
@@ -64,10 +64,13 @@ func placeholderShorthands() []string {
"{orig_?query}", "{http.request.orig_uri.prefixed_query}",
"{method}", "{http.request.method}",
"{uri}", "{http.request.uri}",
"{%uri}", "{http.request.uri_escaped}",
"{path}", "{http.request.uri.path}",
"{%path}", "{http.request.uri.path_escaped}",
"{dir}", "{http.request.uri.path.dir}",
"{file}", "{http.request.uri.path.file}",
"{query}", "{http.request.uri.query}",
"{%query}", "{http.request.uri.query_escaped}",
"{?query}", "{http.request.uri.prefixed_query}",
"{remote}", "{http.request.remote}",
"{remote_host}", "{http.request.remote.host}",
+45 -12
View File
@@ -554,6 +554,7 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
globalPreferredChains := options["preferred_chains"]
globalCertLifetime := options["cert_lifetime"]
globalHTTPPort, globalHTTPSPort := options["http_port"], options["https_port"]
globalDefaultBind := options["default_bind"]
if globalEmail != nil && acmeIssuer.Email == "" {
acmeIssuer.Email = globalEmail.(string)
@@ -564,17 +565,21 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
}
if globalACMEDNSok && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
if globalACMEDNS == nil {
globalACMEDNS = options["dns"]
if globalACMEDNS == nil {
return fmt.Errorf("acme_dns specified without DNS provider config, but no provider specified with 'dns' global option")
}
if globalACMEDNSok && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil || acmeIssuer.Challenges.DNS.ProviderRaw == nil) {
globalDNS := options["dns"]
if globalDNS == nil && globalACMEDNS == nil {
return fmt.Errorf("acme_dns specified without DNS provider config, but no provider specified with 'dns' global option")
}
acmeIssuer.Challenges = &caddytls.ChallengesConfig{
DNS: &caddytls.DNSChallengeConfig{
ProviderRaw: caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil),
},
if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
}
if acmeIssuer.Challenges.DNS == nil {
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
}
// If global `dns` is set, do NOT set provider in issuer, just set empty dns config
if globalDNS == nil && acmeIssuer.Challenges.DNS.ProviderRaw == nil {
// Set a global DNS provider if `acme_dns` is set and `dns` is NOT set
acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil)
}
}
if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
@@ -602,6 +607,20 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
}
acmeIssuer.Challenges.TLSALPN.AlternatePort = globalHTTPSPort.(int)
}
// If BindHost is still unset, fall back to the first default_bind address if set
// This avoids binding the automation policy to the wildcard socket, which is unexpected behavior when a more selective socket is specified via default_bind
// In BSD it is valid to bind to the wildcard socket even though a more selective socket is already open (still unexpected behavior by the caller though)
// In Linux the same call will error with EADDRINUSE whenever the listener for the automation policy is opened
if acmeIssuer.Challenges == nil || (acmeIssuer.Challenges.DNS == nil && acmeIssuer.Challenges.BindHost == "") {
if defBinds, ok := globalDefaultBind.([]ConfigValue); ok && len(defBinds) > 0 {
if abp, ok := defBinds[0].Value.(addressesWithProtocols); ok && len(abp.addresses) > 0 {
if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
}
acmeIssuer.Challenges.BindHost = abp.addresses[0]
}
}
}
if globalCertLifetime != nil && acmeIssuer.CertificateLifetime == 0 {
acmeIssuer.CertificateLifetime = globalCertLifetime.(caddy.Duration)
}
@@ -622,12 +641,18 @@ func newBaseAutomationPolicy(
_, hasLocalCerts := options["local_certs"]
keyType, hasKeyType := options["key_type"]
ocspStapling, hasOCSPStapling := options["ocsp_stapling"]
hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling
globalACMECA := options["acme_ca"]
globalACMECARoot := options["acme_ca_root"]
_, globalACMEDNS := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
globalACMEEAB := options["acme_eab"]
globalPreferredChains := options["preferred_chains"]
hasGlobalACMEDefaults := globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS || globalACMEEAB != nil || globalPreferredChains != nil
// if there are no global options related to automation policies
// set, then we can just return right away
if !hasGlobalAutomationOpts {
if !hasGlobalAutomationOpts && !hasGlobalACMEDefaults {
if always {
return new(caddytls.AutomationPolicy), nil
}
@@ -649,6 +674,14 @@ func newBaseAutomationPolicy(
ap.Issuers = []certmagic.Issuer{new(caddytls.InternalIssuer)}
}
if hasGlobalACMEDefaults {
for i := range ap.Issuers {
if err := fillInGlobalACMEDefaults(ap.Issuers[i], options); err != nil {
return nil, fmt.Errorf("filling in global issuer defaults for issuer %d: %v", i, err)
}
}
}
if hasOCSPStapling {
ocspConfig := ocspStapling.(certmagic.OCSPConfig)
ap.DisableOCSPStapling = ocspConfig.DisableStapling
+7
View File
@@ -121,6 +121,13 @@ func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
}
}
// If this request changed the config, clear the last
// config info we have stored, if it is different from
// the original source.
caddy.ClearLastConfigIfDifferent(
r.Header.Get("Caddy-Config-Source-File"),
r.Header.Get("Caddy-Config-Source-Adapter"))
caddy.Log().Named("admin.api").Info("load complete")
return nil
@@ -0,0 +1,69 @@
{
acme_dns mock foo
}
example.com {
respond "Hello World"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Hello World",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"argument": "foo",
"name": "mock"
}
}
},
"module": "acme"
}
]
}
]
}
}
}
}
@@ -34,17 +34,10 @@ example.com {
"automation": {
"policies": [
{
"subjects": [
"example.com"
],
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"name": "mock"
}
}
"dns": {}
},
"module": "acme"
}
@@ -0,0 +1,9 @@
{
acme_dns
}
example.com {
respond "Hello World"
}
----------
acme_dns specified without DNS provider config, but no provider specified with 'dns' global option
@@ -31,9 +31,6 @@ example.com
"automation": {
"policies": [
{
"subjects": [
"example.com"
],
"issuers": [
{
"module": "acme",
@@ -18,6 +18,9 @@
trusted_proxies static private_ranges
client_ip_headers Custom-Real-Client-IP X-Forwarded-For
client_ip_headers A-Third-One
keepalive_interval 20s
keepalive_idle 20s
keepalive_count 10
}
}
@@ -45,6 +48,9 @@ foo.com {
"read_header_timeout": 30000000000,
"write_timeout": 30000000000,
"idle_timeout": 30000000000,
"keepalive_interval": 20000000000,
"keepalive_idle": 20000000000,
"keepalive_count": 10,
"max_header_bytes": 100000000,
"enable_full_duplex": true,
"routes": [
@@ -89,4 +95,4 @@ foo.com {
}
}
}
}
}
@@ -0,0 +1,13 @@
(site) {
http://{args[0]} https://{args[0]} {
{block}
}
}
import site test.domain {
{
header_up Host {host}
header_up X-Real-IP {remote_host}
}
}
----------
anonymous blocks are not supported
@@ -0,0 +1,57 @@
(snippet) {
header {
reverse_proxy localhost:3000
{block}
}
}
example.com {
import snippet
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "headers",
"response": {
"set": {
"Reverse_proxy": [
"localhost:3000"
]
}
}
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
}
@@ -0,0 +1,57 @@
(snippet) {
header {
reverse_proxy localhost:3000
{blocks.content_type}
}
}
example.com {
import snippet
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "headers",
"response": {
"set": {
"Reverse_proxy": [
"localhost:3000"
]
}
}
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
}
@@ -0,0 +1,65 @@
(site) {
https://{args[0]} {
{block}
}
}
import site test.domain {
reverse_proxy http://192.168.1.1:8080 {
header_up Host {host}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"test.domain"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"headers": {
"request": {
"set": {
"Host": [
"{http.request.host}"
]
}
}
},
"upstreams": [
{
"dial": "192.168.1.1:8080"
}
]
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
}
@@ -0,0 +1,95 @@
:80
log {
output stdout
format filter {
wrap console
# Multiple regexp filters for the same field - this should work now!
request>headers>Authorization regexp "Bearer\s+([A-Za-z0-9_-]+)" "Bearer [REDACTED]"
request>headers>Authorization regexp "Basic\s+([A-Za-z0-9+/=]+)" "Basic [REDACTED]"
request>headers>Authorization regexp "token=([^&\s]+)" "token=[REDACTED]"
# Single regexp filter - this should continue to work as before
request>headers>Cookie regexp "sessionid=[^;]+" "sessionid=[REDACTED]"
# Mixed filters (non-regexp) - these should work normally
request>headers>Server delete
request>remote_ip ip_mask {
ipv4 24
ipv6 32
}
}
}
----------
{
"logging": {
"logs": {
"default": {
"exclude": [
"http.log.access.log0"
]
},
"log0": {
"writer": {
"output": "stdout"
},
"encoder": {
"fields": {
"request\u003eheaders\u003eAuthorization": {
"filter": "multi_regexp",
"operations": [
{
"regexp": "Bearer\\s+([A-Za-z0-9_-]+)",
"value": "Bearer [REDACTED]"
},
{
"regexp": "Basic\\s+([A-Za-z0-9+/=]+)",
"value": "Basic [REDACTED]"
},
{
"regexp": "token=([^\u0026\\s]+)",
"value": "token=[REDACTED]"
}
]
},
"request\u003eheaders\u003eCookie": {
"filter": "regexp",
"regexp": "sessionid=[^;]+",
"value": "sessionid=[REDACTED]"
},
"request\u003eheaders\u003eServer": {
"filter": "delete"
},
"request\u003eremote_ip": {
"filter": "ip_mask",
"ipv4_cidr": 24,
"ipv6_cidr": 32
}
},
"format": "filter",
"wrap": {
"format": "console"
}
},
"include": [
"http.log.access.log0"
]
}
}
},
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"logs": {
"default_logger_name": "log0"
}
}
}
}
}
}
@@ -0,0 +1,59 @@
{
servers {
trusted_proxies_unix
}
}
example.com {
reverse_proxy https://local:8080
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"protocol": "http",
"tls": {}
},
"upstreams": [
{
"dial": "local:8080"
}
]
}
]
}
]
}
],
"terminal": true
}
],
"trusted_proxies_unix": true
}
}
}
}
}
@@ -0,0 +1,79 @@
{
acme_dns mock foo
}
localhost {
tls {
dns mock bar
resolvers 8.8.8.8 8.8.4.4
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"localhost"
]
}
],
"terminal": true
}
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"localhost"
],
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"argument": "bar",
"name": "mock"
},
"resolvers": [
"8.8.8.8",
"8.8.4.4"
]
}
},
"module": "acme"
}
]
},
{
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"argument": "foo",
"name": "mock"
}
}
},
"module": "acme"
}
]
}
]
}
}
}
}
@@ -0,0 +1,68 @@
{
dns mock foo
}
localhost {
tls {
dns mock bar
resolvers 8.8.8.8 8.8.4.4
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"localhost"
]
}
],
"terminal": true
}
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"localhost"
],
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"argument": "bar",
"name": "mock"
},
"resolvers": [
"8.8.8.8",
"8.8.4.4"
]
}
},
"module": "acme"
}
]
}
]
},
"dns": {
"argument": "foo",
"name": "mock"
}
}
}
}
@@ -0,0 +1,76 @@
{
acme_dns mock
}
localhost {
tls {
resolvers 8.8.8.8 8.8.4.4
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"localhost"
]
}
],
"terminal": true
}
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"localhost"
],
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"name": "mock"
},
"resolvers": [
"8.8.8.8",
"8.8.4.4"
]
}
},
"module": "acme"
}
]
},
{
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"name": "mock"
}
}
},
"module": "acme"
}
]
}
]
}
}
}
}
+129
View File
@@ -0,0 +1,129 @@
package integration
import (
"fmt"
"net/http"
"slices"
"strings"
"testing"
"github.com/caddyserver/caddy/v2/caddytest"
)
func newH2ListenerWithVersionsWithTLSTester(t *testing.T, serverVersions []string, clientVersions []string) *caddytest.Tester {
const baseConfig = `
{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
servers :9443 {
protocols %s
}
}
localhost {
respond "{http.request.tls.proto} {http.request.proto}"
}
`
tester := caddytest.NewTester(t)
tester.InitServer(fmt.Sprintf(baseConfig, strings.Join(serverVersions, " ")), "caddyfile")
tr := tester.Client.Transport.(*http.Transport)
tr.TLSClientConfig.NextProtos = clientVersions
tr.Protocols = new(http.Protocols)
if slices.Contains(clientVersions, "h2") {
tr.ForceAttemptHTTP2 = true
tr.Protocols.SetHTTP2(true)
}
if !slices.Contains(clientVersions, "http/1.1") {
tr.Protocols.SetHTTP1(false)
}
return tester
}
func TestH2ListenerWithTLS(t *testing.T) {
tests := []struct {
serverVersions []string
clientVersions []string
expectedBody string
failed bool
}{
{[]string{"h2"}, []string{"h2"}, "h2 HTTP/2.0", false},
{[]string{"h2"}, []string{"http/1.1"}, "", true},
{[]string{"h1"}, []string{"http/1.1"}, "http/1.1 HTTP/1.1", false},
{[]string{"h1"}, []string{"h2"}, "", true},
{[]string{"h2", "h1"}, []string{"h2"}, "h2 HTTP/2.0", false},
{[]string{"h2", "h1"}, []string{"http/1.1"}, "http/1.1 HTTP/1.1", false},
}
for _, tc := range tests {
tester := newH2ListenerWithVersionsWithTLSTester(t, tc.serverVersions, tc.clientVersions)
t.Logf("running with server versions %v and client versions %v:", tc.serverVersions, tc.clientVersions)
if tc.failed {
resp, err := tester.Client.Get("https://localhost:9443")
if err == nil {
t.Errorf("unexpected response: %d", resp.StatusCode)
}
} else {
tester.AssertGetResponse("https://localhost:9443", 200, tc.expectedBody)
}
}
}
func newH2ListenerWithVersionsWithoutTLSTester(t *testing.T, serverVersions []string, clientVersions []string) *caddytest.Tester {
const baseConfig = `
{
skip_install_trust
admin localhost:2999
http_port 9080
servers :9080 {
protocols %s
}
}
http://localhost {
respond "{http.request.proto}"
}
`
tester := caddytest.NewTester(t)
tester.InitServer(fmt.Sprintf(baseConfig, strings.Join(serverVersions, " ")), "caddyfile")
tr := tester.Client.Transport.(*http.Transport)
tr.Protocols = new(http.Protocols)
if slices.Contains(clientVersions, "h2c") {
tr.Protocols.SetHTTP1(false)
tr.Protocols.SetUnencryptedHTTP2(true)
} else if slices.Contains(clientVersions, "http/1.1") {
tr.Protocols.SetHTTP1(true)
tr.Protocols.SetUnencryptedHTTP2(false)
}
return tester
}
func TestH2ListenerWithoutTLS(t *testing.T) {
tests := []struct {
serverVersions []string
clientVersions []string
expectedBody string
failed bool
}{
{[]string{"h2c"}, []string{"h2c"}, "HTTP/2.0", false},
{[]string{"h2c"}, []string{"http/1.1"}, "", true},
{[]string{"h1"}, []string{"http/1.1"}, "HTTP/1.1", false},
{[]string{"h1"}, []string{"h2c"}, "", true},
{[]string{"h2c", "h1"}, []string{"h2c"}, "HTTP/2.0", false},
{[]string{"h2c", "h1"}, []string{"http/1.1"}, "HTTP/1.1", false},
}
for _, tc := range tests {
tester := newH2ListenerWithVersionsWithoutTLSTester(t, tc.serverVersions, tc.clientVersions)
t.Logf("running with server versions %v and client versions %v:", tc.serverVersions, tc.clientVersions)
if tc.failed {
resp, err := tester.Client.Get("http://localhost:9080")
if err == nil {
t.Errorf("unexpected response: %d", resp.StatusCode)
}
} else {
tester.AssertGetResponse("http://localhost:9080", 200, tc.expectedBody)
}
}
}
+12 -2
View File
@@ -15,7 +15,9 @@ func init() {
}
// MockDNSProvider is a mock DNS provider, for testing config with DNS modules.
type MockDNSProvider struct{}
type MockDNSProvider struct {
Argument string `json:"argument,omitempty"` // optional argument useful for testing
}
// CaddyModule returns the Caddy module information.
func (MockDNSProvider) CaddyModule() caddy.ModuleInfo {
@@ -31,7 +33,15 @@ func (MockDNSProvider) Provision(ctx caddy.Context) error {
}
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
func (p *MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next() // consume directive name
if d.NextArg() {
p.Argument = d.Val()
}
if d.NextArg() {
return d.Errf("unexpected argument '%s'", d.Val())
}
return nil
}
+38
View File
@@ -0,0 +1,38 @@
# Configure Caddy
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
debug
}
localhost {
log
basic_auth {
john $2a$14$x4HlYwA9Zeer4RkMEYbUzug9XxWmncneR.dcMs.UjalR95URnHg5.
}
respond "Hello, World!"
}
```
# requests without `Authorization` header are rejected with 401
GET https://localhost:9443
[Options]
insecure: true
HTTP 401
[Asserts]
header "WWW-Authenticate" == "Basic realm=\"restricted\""
# requests with `Authorization` header are accepted with 200
GET https://localhost:9443
[BasicAuth]
john:password
[Options]
insecure: true
HTTP 200
[Asserts]
`Hello, World!`
+150
View File
@@ -0,0 +1,150 @@
# Configure Caddy with error directive
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
error /forbidden* "Access denied" 403
respond "OK"
}
```
# error directive triggers 403 for matching paths
GET https://localhost:9443/forbidden/resource
[Options]
insecure: true
HTTP 403
# error directive does not trigger for non-matching paths
GET https://localhost:9443/allowed
[Options]
insecure: true
HTTP 200
[Asserts]
body == "OK"
# Configure Caddy with error and handle_errors
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
error /admin* "Forbidden" 403
handle_errors {
respond "Custom error: {err.status_code} - {err.status_text}"
}
}
```
# error with handle_errors shows custom error page
GET https://localhost:9443/admin/panel
[Options]
insecure: true
HTTP 403
[Asserts]
body == "Custom error: 403 - Forbidden"
# Configure Caddy with conditional error
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
@admin path /admin*
error @admin 404
respond "Public content"
}
```
# error with named matcher triggers on match
GET https://localhost:9443/admin/users
[Options]
insecure: true
HTTP 404
# error with named matcher doesn't trigger on non-match
GET https://localhost:9443/public
[Options]
insecure: true
HTTP 200
[Asserts]
body == "Public content"
# Configure Caddy with error for specific methods
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
@post method POST
error @post "Method not allowed" 405
respond "GET OK"
}
```
# error blocks POST requests
POST https://localhost:9443
[Options]
insecure: true
HTTP 405
# error allows GET requests
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
body == "GET OK"
# Configure Caddy with dynamic error message
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
error /error* "Path {path} not found" 404
handle_errors {
respond "{err.message}"
}
}
```
# error message can use placeholders
GET https://localhost:9443/error/test
[Options]
insecure: true
HTTP 404
[Asserts]
body == "Path /error/test not found"
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>Index.html Title</title>
</head>
<body>
Index.html
</body>
</html>
@@ -0,0 +1 @@
index.txt
+119
View File
@@ -0,0 +1,119 @@
# Configure Caddy with default configuration
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
debug
}
localhost {
root {{indexed_root}}
file_server
}
```
# requests without specific file receive index file per
# the default index list: index.html, index.txt
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
```
<!DOCTYPE html>
<html>
<head>
<title>Index.html Title</title>
</head>
<body>
Index.html
</body>
</html>```
# if index.txt is specifically requested, we expect index.txt
GET https://localhost:9443/index.txt
[Options]
insecure: true
HTTP 200
[Asserts]
body == "index.txt"
# requests for sub-folder followed by .. result in sanitized path
GET https://localhost:9443/non-existent/../index.txt
[Options]
insecure: true
HTTP 200
[Asserts]
body == "index.txt"
# results out of root folder are sanitized,
# and conform to default index list sequence.
GET https://localhost:9443/../
[Options]
insecure: true
HTTP 200
[Asserts]
```
<!DOCTYPE html>
<html>
<head>
<title>Index.html Title</title>
</head>
<body>
Index.html
</body>
</html>```
# Configure Caddy with custsom index "index.txt"
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
debug
}
localhost {
root {{indexed_root}}
file_server {
index index.txt
}
}
```
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
body == "index.txt"
# Configure with a root not containing index files
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
debug
}
localhost {
root {{unindexed_root}}
file_server
}
```
GET https://localhost:9443
[Options]
insecure: true
HTTP 404
+132
View File
@@ -0,0 +1,132 @@
# Configure Caddy with forward_auth directive
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
forward_auth localhost:9080 {
uri /auth
}
respond "Protected content"
}
http://localhost:9080 {
handle /auth {
respond 200
}
}
```
# forward_auth allows request when auth endpoint returns 2xx
GET https://localhost:9443
[Options]
delay: 500ms
insecure: true
HTTP 200
[Asserts]
body == "Protected content"
# Configure Caddy with forward_auth rejecting
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
forward_auth localhost:9080 {
uri /auth
}
respond "Protected content"
}
http://localhost:9080 {
handle /auth {
respond 401
}
}
```
# forward_auth blocks request when auth endpoint returns 4xx
GET https://localhost:9443
[Options]
delay: 500ms
insecure: true
HTTP 401
# Configure Caddy with forward_auth copying headers
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
forward_auth localhost:9080 {
uri /auth
copy_headers X-User-ID X-User-Email
}
respond "User: {header.X-User-ID}, Email: {header.X-User-Email}"
}
http://localhost:9080 {
handle /auth {
header X-User-ID "user123"
header X-User-Email "user@example.com"
respond 200
}
}
```
# forward_auth copies specified headers from auth response
GET https://localhost:9443
[Options]
delay: 500ms
insecure: true
HTTP 200
[Asserts]
body == "User: user123, Email: user@example.com"
# Configure Caddy with forward_auth and custom headers
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
forward_auth localhost:9080 {
uri /auth
header_up X-Original-URL {uri}
}
respond "OK"
}
http://localhost:9080 {
handle /auth {
respond "{header.X-Original-URL}"
}
}
```
# forward_auth can send custom headers to auth endpoint
GET https://localhost:9443/test/path
[Options]
delay: 500ms
insecure: true
HTTP 200
[Asserts]
body == "OK"
+22
View File
@@ -0,0 +1,22 @@
# Configure Caddy
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
debug
}
localhost {
header "X-Custom-Header" "Custom-Value"
}
```
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
header "X-Custom-Header" == "Custom-Value"
@@ -0,0 +1,190 @@
# Configure Caddy with request_header directive
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
request_header X-Custom-Header "CustomValue"
respond "{header.X-Custom-Header}"
}
```
# request_header adds headers to request
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
body == "CustomValue"
# Configure Caddy with request_header removing headers
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
request_header -User-Agent
respond "UA: {header.User-Agent}"
}
```
# request_header can remove headers
GET https://localhost:9443
User-Agent: TestAgent/1.0
[Options]
insecure: true
HTTP 200
[Asserts]
body == "UA: "
# Configure Caddy with request_header replacing headers
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
request_header Host "example.com"
respond "Host: {host}"
}
```
# request_header can replace Host header
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
body == "Host: example.com"
# Configure Caddy with request_header using placeholders
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
request_header X-Original-Path {path}
respond "Path: {header.X-Original-Path}"
}
```
# request_header can use placeholders
GET https://localhost:9443/test/path
[Options]
insecure: true
HTTP 200
[Asserts]
body == "Path: /test/path"
# Configure Caddy with conditional request_header
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
@api path /api/*
request_header @api X-API "true"
respond "API: {header.X-API}"
}
```
# request_header applies conditionally based on matcher
GET https://localhost:9443/api/test
[Options]
insecure: true
HTTP 200
[Asserts]
body == "API: true"
# request_header doesn't apply when matcher doesn't match
GET https://localhost:9443/other
[Options]
insecure: true
HTTP 200
[Asserts]
body == "API: "
# Configure Caddy with multiple request_header operations
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
request_header X-First "1"
request_header X-Second "2"
request_header X-Third "3"
respond "{header.X-First},{header.X-Second},{header.X-Third}"
}
```
# multiple request_header directives are applied
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
body == "1,2,3"
# Configure Caddy with request_header and reverse_proxy
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
debug
}
localhost {
request_header X-Custom-Header "Value"
reverse_proxy localhost:9450
}
http://localhost:9450 {
respond "{header.X-Custom-Header}"
}
```
# request_header adds header before reverse_proxy
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
body == "Value"
+36
View File
@@ -0,0 +1,36 @@
# Configure Caddy
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
log
request_body {
max_size 2B
}
reverse_proxy localhost:8000 # to fake body reading
handle_errors 4xx {
respond "OK"
}
}
http://localhost:8000 {
respond "Failed"
}
```
GET https://localhost:9443
[Options]
delay: 1s
insecure: true
```
Hello
```
HTTP 413
`OK`
# TODO: how to test{read,write}_timeout?
+66
View File
@@ -0,0 +1,66 @@
# Configure Caddy
POST http://localhost:2019/load
User-Agent: hurl/ci
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
rewrite /from /to
respond {uri}
}
```
# simple scenario: rewriting /from to /to produces expected result of seeing /to
GET https://localhost:9443/from
[Options]
insecure: true
HTTP 200
[Asserts]
body == "/to"
# unmatched path is passed through unchanged
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
body == "/"
# having a query parameter does not trip the rewrite and retains the query
GET https://localhost:9443/from?query_param=value
[Options]
insecure: true
HTTP 200
[Asserts]
body == "/to?query_param=value"
# Configure Caddy
POST http://localhost:2019/load
User-Agent: hurl/ci
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
rewrite /from /to?a=b
respond {uri}
}
```
# a rewrite with query parameters affects the parameters
GET https://localhost:9443/from?query_param=value
[Options]
insecure: true
HTTP 200
[Asserts]
body == "/to?a=b"
+171
View File
@@ -0,0 +1,171 @@
# Configure Caddy with route directive
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
route /api/* {
uri strip_prefix /api
respond "API: {uri}"
}
respond "Not API"
}
```
# route groups handlers and maintains order
GET https://localhost:9443/api/users
[Options]
insecure: true
HTTP 200
[Asserts]
body == "API: /users"
# route doesn't match non-matching paths
GET https://localhost:9443/other
[Options]
insecure: true
HTTP 200
[Asserts]
body == "Not API"
# Configure Caddy with nested routes
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
route /api/* {
uri strip_prefix /api
route /v1/* {
uri strip_prefix /v1
respond "API v1: {uri}"
}
respond "API: {uri}"
}
}
```
# nested routes process sequentially
GET https://localhost:9443/api/v1/users
[Options]
insecure: true
HTTP 200
[Asserts]
body == "API v1: /users"
# outer route processes when inner doesn't match
GET https://localhost:9443/api/users
[Options]
insecure: true
HTTP 200
[Asserts]
body == "API: /users"
# Configure Caddy with route and terminal handlers
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
route {
header X-First "1"
respond "Response"
header X-Second "2"
}
}
```
# route stops at terminal handler (respond)
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
header "X-First" == "1"
header "X-Second" not exists
# Configure Caddy with route preserving handler order
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
route {
vars step1 "done"
vars step2 "done"
vars step3 "done"
respond "{vars.step1},{vars.step2},{vars.step3}"
}
}
```
# route preserves exact handler order
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
body == "done,done,done"
# Configure Caddy with route and matchers
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
route {
@api path /api/*
vars @api type "api"
vars type "other"
respond "{vars.type}"
}
}
```
# route applies matchers in sequence
GET https://localhost:9443/api/test
[Options]
insecure: true
HTTP 200
[Asserts]
body == "other"
# route continues when matcher doesn't match
GET https://localhost:9443/test
[Options]
insecure: true
HTTP 200
[Asserts]
body == "other"
@@ -0,0 +1,105 @@
# Configure Caddy
POST http://localhost:2019/load
User-Agent: hurl/ci
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
log
respond "Hello, World!"
}
```
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
`Hello, World!`
GET https://localhost:9443/foo
[Options]
insecure: true
HTTP 200
[Asserts]
`Hello, World!`
# Configure Caddy
POST http://localhost:2019/load
User-Agent: hurl/ci
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
respond "New text!"
}
```
GET https://localhost:9443
[Options]
insecure: true
HTTP/2 200
[Asserts]
`New text!`
GET https://localhost:9443/foo
[Options]
insecure: true
HTTP/2 200
[Asserts]
`New text!`
GET https://localhost:9443/foo
[Options]
insecure: true
HTTP/2 200
[Asserts]
body != "Hello, World!"
# Configure Caddy
# The body is a placeholder
POST http://localhost:2019/load
User-Agent: hurl/ci
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
log
respond {http.request.body}
}
```
# handler responds with the "application/json" if the response body is valid JSON
POST https://localhost:9443
[Options]
insecure: true
```json
{
"greeting": "Hello, world!"
}
```
HTTP/2 200
[Asserts]
header "Content-Type" == "application/json"
```json
{
"greeting": "Hello, world!"
}
```
+191
View File
@@ -0,0 +1,191 @@
# Configure Caddy with uri strip_prefix
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
uri strip_prefix /api
respond {uri}
}
```
# strip_prefix removes the prefix from the URI
GET https://localhost:9443/api/users
[Options]
insecure: true
HTTP 200
[Asserts]
body == "/users"
# URI without prefix is unchanged
GET https://localhost:9443/users
[Options]
insecure: true
HTTP 200
[Asserts]
body == "/users"
# Configure Caddy with uri strip_suffix
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
uri strip_suffix .php
respond {uri}
}
```
# strip_suffix removes the suffix from the URI
GET https://localhost:9443/index.php
[Options]
insecure: true
HTTP 200
[Asserts]
body == "/index"
# URI without suffix is unchanged
GET https://localhost:9443/index.html
[Options]
insecure: true
HTTP 200
[Asserts]
body == "/index.html"
# Configure Caddy with uri replace
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
uri replace old new
respond {uri}
}
```
# replace substitutes all occurrences
GET https://localhost:9443/old/path/old
[Options]
insecure: true
HTTP 200
[Asserts]
body == "/new/path/new"
# Configure Caddy with uri path_regexp
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
uri path_regexp /([0-9]+) /$1/id
respond {uri}
}
```
# path_regexp replaces using regular expressions
GET https://localhost:9443/123
[Options]
insecure: true
HTTP 200
[Asserts]
body == "/123/id"
# Configure Caddy with uri query operations
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
uri query +foo bar
respond {query}
}
```
# query operations add parameters
GET https://localhost:9443/?existing=value
[Options]
insecure: true
HTTP 200
[Asserts]
body == "existing=value&foo=bar"
# Configure Caddy with uri query delete
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
uri query -sensitive
respond {query}
}
```
# query operations delete parameters
GET https://localhost:9443/?keep=this&sensitive=secret
[Options]
insecure: true
HTTP 200
[Asserts]
body == "keep=this"
# Configure Caddy with uri query rename
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
uri query old>new
respond {query}
}
```
# query operations rename parameters
GET https://localhost:9443/?old=value
[Options]
insecure: true
HTTP 200
[Asserts]
body == "new=value"
+125
View File
@@ -0,0 +1,125 @@
# Configure Caddy with vars directive
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
vars my_var "custom_value"
vars another_var "another_value"
respond "{vars.my_var} {vars.another_var}"
}
```
# Variables are accessible in placeholders
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
body == "custom_value another_value"
# Configure Caddy with vars using placeholders
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
vars request_path {path}
vars request_method {method}
respond "Path: {vars.request_path}, Method: {vars.request_method}"
}
```
# Variables can be set from request placeholders
GET https://localhost:9443/test/path
[Options]
insecure: true
HTTP 200
[Asserts]
body == "Path: /test/path, Method: GET"
# POST method is captured correctly
POST https://localhost:9443/another
[Options]
insecure: true
HTTP 200
[Asserts]
body == "Path: /another, Method: POST"
# Configure Caddy with vars in route
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
route /api/* {
vars api_version "v1"
respond "API {vars.api_version}"
}
respond "Not API"
}
```
# Variables are scoped to their route
GET https://localhost:9443/api/users
[Options]
insecure: true
HTTP 200
[Asserts]
body == "API v1"
# Outside the route, variables are not set
GET https://localhost:9443/other
[Options]
insecure: true
HTTP 200
[Asserts]
body == "Not API"
# Configure Caddy with vars overwriting
POST http://localhost:2019/load
Content-Type: text/caddyfile
```
{
skip_install_trust
http_port 9080
https_port 9443
local_certs
}
localhost {
# without `route`, middlewares are sorted an unstable sort
route {
vars my_var "2"
vars my_var "1"
}
respond "{vars.my_var}"
}
```
# Later vars directives overwrite earlier ones
GET https://localhost:9443
[Options]
insecure: true
HTTP 200
[Asserts]
body == "1"
+2
View File
@@ -0,0 +1,2 @@
indexed_root=caddytest/spec/http/file_server/assets/indexed
unindexed_root=caddytest/spec/http/file_server/assets/unindexed
+70 -11
View File
@@ -172,9 +172,19 @@ func cmdStart(fl Flags) (int, error) {
func cmdRun(fl Flags) (int, error) {
caddy.TrapSignals()
logger := caddy.Log()
// set up buffered logging for early startup
// so that we can hold onto logs until after
// the config is loaded (or fails to load)
// so that we can write the logs to the user's
// configured output. we must be sure to flush
// on any error before the config is loaded.
logger, defaultLogger, logBuffer := caddy.BufferedLog()
undoMaxProcs := setResourceLimits(logger)
defer undoMaxProcs()
// release the local reference to the undo function so it can be GC'd;
// the deferred call above has already captured the actual function value.
undoMaxProcs = nil //nolint:ineffassign,wastedassign
configFlag := fl.String("config")
configAdapterFlag := fl.String("adapter")
@@ -187,6 +197,7 @@ func cmdRun(fl Flags) (int, error) {
// load all additional envs as soon as possible
err := handleEnvFileFlag(fl)
if err != nil {
logBuffer.FlushTo(defaultLogger)
return caddy.ExitCodeFailedStartup, err
}
@@ -204,6 +215,7 @@ func cmdRun(fl Flags) (int, error) {
logger.Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
resumeFlag = false
} else if err != nil {
logBuffer.FlushTo(defaultLogger)
return caddy.ExitCodeFailedStartup, err
} else {
if configFlag == "" {
@@ -219,9 +231,11 @@ func cmdRun(fl Flags) (int, error) {
}
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
var configFile string
var adapterUsed string
if !resumeFlag {
config, configFile, err = LoadConfig(configFlag, configAdapterFlag)
config, configFile, adapterUsed, err = LoadConfig(configFlag, configAdapterFlag)
if err != nil {
logBuffer.FlushTo(defaultLogger)
return caddy.ExitCodeFailedStartup, err
}
}
@@ -236,11 +250,35 @@ func cmdRun(fl Flags) (int, error) {
}
}
// If we have a source config file (we're running via 'caddy run --config ...'),
// record it so SIGUSR1 can reload from the same file. Also provide a callback
// that knows how to load/adapt that source when requested by the main process.
if configFile != "" {
caddy.SetLastConfig(configFile, adapterUsed, func(file, adapter string) error {
cfg, _, _, err := LoadConfig(file, adapter)
if err != nil {
return err
}
return caddy.Load(cfg, true)
})
}
// run the initial config
err = caddy.Load(config, true)
if err != nil {
logBuffer.FlushTo(defaultLogger)
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
}
// release the reference to the config so it can be GC'd
config = nil //nolint:ineffassign,wastedassign
// at this stage the config will have replaced the
// default logger to the configured one, so we can
// log normally, now that the config is running.
// also clear our ref to the buffer so it can get GC'd
logger = caddy.Log()
defaultLogger = nil //nolint:ineffassign,wastedassign
logBuffer = nil //nolint:wastedassign,ineffassign
logger.Info("serving initial configuration")
// if we are to report to another process the successful start
@@ -256,18 +294,22 @@ func cmdRun(fl Flags) (int, error) {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("dialing confirmation address: %v", err)
}
defer conn.Close()
_, err = conn.Write(confirmationBytes)
if err != nil {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("writing confirmation bytes to %s: %v", pingbackFlag, err)
}
// close (non-defer because we `select {}` below)
// and release references so they can be GC'd
conn.Close()
confirmationBytes = nil //nolint:ineffassign,wastedassign
conn = nil //nolint:wastedassign,ineffassign
}
// if enabled, reload config file automatically on changes
// (this better only be used in dev!)
if watchFlag {
go watchConfigFile(configFile, configAdapterFlag)
go watchConfigFile(configFile, adapterUsed)
}
// warn if the environment does not provide enough information about the disk
@@ -289,6 +331,9 @@ func cmdRun(fl Flags) (int, error) {
}
}
// release the last local logger reference
logger = nil //nolint:wastedassign,ineffassign
select {}
}
@@ -319,7 +364,7 @@ func cmdReload(fl Flags) (int, error) {
forceFlag := fl.Bool("force")
// get the config in caddy's native format
config, configFile, err := LoadConfig(configFlag, configAdapterFlag)
config, configFile, adapterUsed, err := LoadConfig(configFlag, configAdapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
@@ -337,6 +382,10 @@ func cmdReload(fl Flags) (int, error) {
if forceFlag {
headers.Set("Cache-Control", "must-revalidate")
}
// Provide the source file/adapter to the running process so it can
// preserve its last-config knowledge if this reload came from the same source.
headers.Set("Caddy-Config-Source-File", configFile)
headers.Set("Caddy-Config-Source-Adapter", adapterUsed)
resp, err := AdminAPIRequest(adminAddr, http.MethodPost, "/load", headers, bytes.NewReader(config))
if err != nil {
@@ -441,16 +490,20 @@ func cmdEnviron(fl Flags) (int, error) {
}
func cmdAdaptConfig(fl Flags) (int, error) {
inputFlag := fl.String("config")
configFlag := fl.String("config")
adapterFlag := fl.String("adapter")
prettyFlag := fl.Bool("pretty")
validateFlag := fl.Bool("validate")
var err error
inputFlag, err = configFileWithRespectToDefault(caddy.Log(), inputFlag)
configFlag, err = configFileWithRespectToDefault(caddy.Log(), configFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
if configFlag == "" {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)")
}
// load all additional envs as soon as possible
err = handleEnvFileFlag(fl)
@@ -469,13 +522,19 @@ func cmdAdaptConfig(fl Flags) (int, error) {
fmt.Errorf("unrecognized config adapter: %s", adapterFlag)
}
input, err := os.ReadFile(inputFlag)
var input []byte
// read from stdin if the file name is "-"
if configFlag == "-" {
input, err = io.ReadAll(os.Stdin)
} else {
input, err = os.ReadFile(configFlag)
}
if err != nil {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("reading input file: %v", err)
}
opts := map[string]any{"filename": inputFlag}
opts := map[string]any{"filename": configFlag}
adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts)
if err != nil {
@@ -541,7 +600,7 @@ func cmdValidateConfig(fl Flags) (int, error) {
fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)")
}
input, _, err := LoadConfig(configFlag, adapterFlag)
input, _, _, err := LoadConfig(configFlag, adapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
@@ -756,7 +815,7 @@ func DetermineAdminAPIAddress(address string, config []byte, configFile, configA
loadedConfig := config
if len(loadedConfig) == 0 {
// get the config in caddy's native format
loadedConfig, loadedConfigFile, err = LoadConfig(configFile, configAdapter)
loadedConfig, loadedConfigFile, _, err = LoadConfig(configFile, configAdapter)
if err != nil {
return "", err
}
+3 -1
View File
@@ -293,6 +293,8 @@ zero exit status will be returned.
If --envfile is specified, an environment file with environment variables
in the KEY=VALUE format will be loaded into the Caddy process.
If you wish to use stdin instead of a regular file, use - as the path.
`,
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("config", "c", "", "Configuration file to adapt (required)")
@@ -390,7 +392,7 @@ lines will be prefixed with '-' and '+' where they differ. Note that
unchanged lines are prefixed with two spaces for alignment, and that this
is not a valid patch format.
If you wish you use stdin instead of a regular file, use - as the path.
If you wish to use stdin instead of a regular file, use - as the path.
When reading from stdin, the --overwrite flag has no effect: the result
is always printed to stdout.
`,
+18 -13
View File
@@ -100,7 +100,12 @@ func handlePingbackConn(conn net.Conn, expect []byte) error {
// there is no config available. It prints any warnings to stderr,
// and returns the resulting JSON config bytes along with
// the name of the loaded config file (if any).
func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
// The return values are:
// - config bytes (nil if no config)
// - config file used ("" if none)
// - adapter used ("" if none)
// - error, if any
func LoadConfig(configFile, adapterName string) ([]byte, string, string, error) {
return loadConfigWithLogger(caddy.Log(), configFile, adapterName)
}
@@ -138,7 +143,7 @@ func isCaddyfile(configFile, adapterName string) (bool, error) {
return false, nil
}
func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([]byte, string, error) {
func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([]byte, string, string, error) {
// if no logger is provided, use a nop logger
// just so we don't have to check for nil
if logger == nil {
@@ -147,7 +152,7 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
// specifying an adapter without a config file is ambiguous
if adapterName != "" && configFile == "" {
return nil, "", fmt.Errorf("cannot adapt config without config file (use --config)")
return nil, "", "", fmt.Errorf("cannot adapt config without config file (use --config)")
}
// load initial config and adapter
@@ -158,13 +163,13 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
if configFile == "-" {
config, err = io.ReadAll(os.Stdin)
if err != nil {
return nil, "", fmt.Errorf("reading config from stdin: %v", err)
return nil, "", "", fmt.Errorf("reading config from stdin: %v", err)
}
logger.Info("using config from stdin")
} else {
config, err = os.ReadFile(configFile)
if err != nil {
return nil, "", fmt.Errorf("reading config from file: %v", err)
return nil, "", "", fmt.Errorf("reading config from file: %v", err)
}
logger.Info("using config from file", zap.String("file", configFile))
}
@@ -179,7 +184,7 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
cfgAdapter = nil
} else if err != nil {
// default Caddyfile exists, but error reading it
return nil, "", fmt.Errorf("reading default Caddyfile: %v", err)
return nil, "", "", fmt.Errorf("reading default Caddyfile: %v", err)
} else {
// success reading default Caddyfile
configFile = "Caddyfile"
@@ -191,14 +196,14 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
if yes, err := isCaddyfile(configFile, adapterName); yes {
adapterName = "caddyfile"
} else if err != nil {
return nil, "", err
return nil, "", "", err
}
// load config adapter
if adapterName != "" {
cfgAdapter = caddyconfig.GetAdapter(adapterName)
if cfgAdapter == nil {
return nil, "", fmt.Errorf("unrecognized config adapter: %s", adapterName)
return nil, "", "", fmt.Errorf("unrecognized config adapter: %s", adapterName)
}
}
@@ -208,7 +213,7 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
"filename": configFile,
})
if err != nil {
return nil, "", fmt.Errorf("adapting config using %s: %v", adapterName, err)
return nil, "", "", fmt.Errorf("adapting config using %s: %v", adapterName, err)
}
logger.Info("adapted config to JSON", zap.String("adapter", adapterName))
for _, warn := range warnings {
@@ -226,11 +231,11 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
// validate that the config is at least valid JSON
err = json.Unmarshal(config, new(any))
if err != nil {
return nil, "", fmt.Errorf("config is not valid JSON: %v; did you mean to use a config adapter (the --adapter flag)?", err)
return nil, "", "", fmt.Errorf("config is not valid JSON: %v; did you mean to use a config adapter (the --adapter flag)?", err)
}
}
return config, configFile, nil
return config, configFile, adapterName, nil
}
// watchConfigFile watches the config file at filename for changes
@@ -256,7 +261,7 @@ func watchConfigFile(filename, adapterName string) {
}
// get current config
lastCfg, _, err := loadConfigWithLogger(nil, filename, adapterName)
lastCfg, _, _, err := loadConfigWithLogger(nil, filename, adapterName)
if err != nil {
logger().Error("unable to load latest config", zap.Error(err))
return
@@ -268,7 +273,7 @@ func watchConfigFile(filename, adapterName string) {
//nolint:staticcheck
for range time.Tick(1 * time.Second) {
// get current config
newCfg, _, err := loadConfigWithLogger(nil, filename, adapterName)
newCfg, _, _, err := loadConfigWithLogger(nil, filename, adapterName)
if err != nil {
logger().Error("unable to load latest config", zap.Error(err))
return
+3 -3
View File
@@ -62,7 +62,7 @@ func splitModule(arg string) (module, version string, err error) {
err = fmt.Errorf("module name is required")
}
return
return module, version, err
}
func cmdAddPackage(fl Flags) (int, error) {
@@ -217,7 +217,7 @@ func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
bi, ok := debug.ReadBuildInfo()
if !ok {
err = fmt.Errorf("no build info")
return
return standard, nonstandard, unknown, err
}
for _, modID := range caddy.Modules() {
@@ -260,7 +260,7 @@ func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
nonstandard = append(nonstandard, caddyModGoMod)
}
}
return
return standard, nonstandard, unknown, err
}
func listModules(path string) error {
+1 -1
View File
@@ -36,7 +36,7 @@ type storVal struct {
// determineStorage returns the top-level storage module from the given config.
// It may return nil even if no error.
func determineStorage(configFile string, configAdapter string) (*storVal, error) {
cfg, _, err := LoadConfig(configFile, configAdapter)
cfg, _, _, err := LoadConfig(configFile, configAdapter)
if err != nil {
return nil, err
}
+103 -83
View File
@@ -1,84 +1,116 @@
module github.com/caddyserver/caddy/v2
go 1.24
go 1.25
require (
github.com/BurntSushi/toml v1.5.0
github.com/KimMachineGun/automemlimit v0.7.1
github.com/DeRuina/timberjack v1.3.9
github.com/KimMachineGun/automemlimit v0.7.5
github.com/Masterminds/sprig/v3 v3.3.0
github.com/alecthomas/chroma/v2 v2.19.0
github.com/alecthomas/chroma/v2 v2.20.0
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
github.com/caddyserver/certmagic v0.23.0
github.com/caddyserver/certmagic v0.25.0
github.com/caddyserver/zerossl v0.1.3
github.com/cloudflare/circl v1.6.1
github.com/dustin/go-humanize v1.0.1
github.com/go-chi/chi/v5 v5.2.2
github.com/google/cel-go v0.24.1
github.com/go-chi/chi/v5 v5.2.3
github.com/google/cel-go v0.26.1
github.com/google/uuid v1.6.0
github.com/klauspost/compress v1.18.0
github.com/klauspost/compress v1.18.1
github.com/klauspost/cpuid/v2 v2.3.0
github.com/mholt/acmez/v3 v3.1.2
github.com/prometheus/client_golang v1.19.1
github.com/quic-go/quic-go v0.54.0
github.com/smallstep/certificates v0.26.1
github.com/smallstep/nosql v0.6.1
github.com/mholt/acmez/v3 v3.1.4
github.com/prometheus/client_golang v1.23.2
github.com/quic-go/quic-go v0.56.0
github.com/smallstep/certificates v0.28.4
github.com/smallstep/nosql v0.7.0
github.com/smallstep/truststore v0.13.0
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.7
github.com/stretchr/testify v1.10.0
github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53
github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark v1.7.13
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0
go.opentelemetry.io/otel v1.31.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0
go.opentelemetry.io/otel/sdk v1.31.0
go.opentelemetry.io/contrib/exporters/autoexport v0.63.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
go.opentelemetry.io/contrib/propagators/autoprop v0.63.0
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/sdk v1.38.0
go.uber.org/automaxprocs v1.6.0
go.uber.org/zap v1.27.0
go.uber.org/zap/exp v0.3.0
golang.org/x/crypto v0.40.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810
golang.org/x/net v0.42.0
golang.org/x/sync v0.16.0
golang.org/x/term v0.33.0
golang.org/x/time v0.12.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
golang.org/x/crypto v0.43.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20250927194341-2beaa59a3c99
golang.org/x/net v0.46.0
golang.org/x/sync v0.17.0
golang.org/x/term v0.36.0
golang.org/x/time v0.14.0
gopkg.in/yaml.v3 v3.0.1
)
require (
cel.dev/expr v0.19.1 // indirect
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/ccoveille/go-safecast v1.6.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect
github.com/google/go-tpm v0.9.0 // indirect
github.com/google/go-tpm v0.9.6 // indirect
github.com/google/go-tspi v0.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/otlptranslator v0.0.2 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/smallstep/cli-utils v0.12.1 // indirect
github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca // indirect
github.com/smallstep/linkedca v0.23.0 // indirect
github.com/smallstep/pkcs7 v0.2.1 // indirect
github.com/smallstep/scep v0.0.0-20240926084937-8cf1ca453101 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect
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.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
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.38.0 // indirect
go.opentelemetry.io/contrib/propagators/b3 v1.38.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect
go.opentelemetry.io/contrib/propagators/ot v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect
go.opentelemetry.io/otel/log v0.14.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
google.golang.org/api v0.254.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
)
require (
@@ -87,72 +119,60 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0
github.com/chzyer/readline v1.5.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.2.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-kit/kit v0.13.0 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.3 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.3 // indirect
github.com/libdns/libdns v1.0.0-beta.1
github.com/libdns/libdns v1.1.1
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/miekg/dns v1.1.68 // indirect
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.1-0.20240628150027-b718e7ce4964
github.com/pires/go-proxyproto v0.8.1
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.5.0
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rosedblabs/wal v1.3.6
github.com/rs/xid v1.5.0 // indirect
github.com/prometheus/client_model v0.6.2
github.com/prometheus/common v0.67.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/slackhq/nebula v1.6.1 // indirect
github.com/slackhq/nebula v1.9.7 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/urfave/cli v1.22.14 // indirect
go.etcd.io/bbolt v1.3.9 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.step.sm/cli-utils v0.9.0 // indirect
go.step.sm/crypto v0.45.0
go.step.sm/linkedca v0.20.1 // indirect
github.com/urfave/cli v1.22.17 // indirect
go.etcd.io/bbolt v1.3.10 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.step.sm/crypto v0.74.0
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/sys v0.34.0
golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/grpc v1.67.1 // indirect
google.golang.org/protobuf v1.35.1 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/sys v0.37.0
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
howett.net/plist v1.0.0 // indirect
)
+291 -498
View File
File diff suppressed because it is too large Load Diff
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
import (
"sync"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// LogBufferCore is a zapcore.Core that buffers log entries in memory.
type LogBufferCore struct {
mu sync.Mutex
entries []zapcore.Entry
fields [][]zapcore.Field
level zapcore.LevelEnabler
}
type LogBufferCoreInterface interface {
zapcore.Core
FlushTo(*zap.Logger)
}
func NewLogBufferCore(level zapcore.LevelEnabler) *LogBufferCore {
return &LogBufferCore{
level: level,
}
}
func (c *LogBufferCore) Enabled(lvl zapcore.Level) bool {
return c.level.Enabled(lvl)
}
func (c *LogBufferCore) With(fields []zapcore.Field) zapcore.Core {
return c
}
func (c *LogBufferCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if c.Enabled(entry.Level) {
return ce.AddCore(entry, c)
}
return ce
}
func (c *LogBufferCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
c.mu.Lock()
defer c.mu.Unlock()
c.entries = append(c.entries, entry)
c.fields = append(c.fields, fields)
return nil
}
func (c *LogBufferCore) Sync() error { return nil }
// FlushTo flushes buffered logs to the given zap.Logger.
func (c *LogBufferCore) FlushTo(logger *zap.Logger) {
c.mu.Lock()
defer c.mu.Unlock()
for idx, entry := range c.entries {
logger.WithOptions().Check(entry.Level, entry.Message).Write(c.fields[idx]...)
}
c.entries = nil
c.fields = nil
}
var (
_ zapcore.Core = (*LogBufferCore)(nil)
_ LogBufferCoreInterface = (*LogBufferCore)(nil)
)
+3 -3
View File
@@ -261,14 +261,14 @@ func (fcpc *fakeClosePacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err e
if atomic.LoadInt32(&fcpc.closed) == 1 {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
if err = fcpc.SetReadDeadline(time.Time{}); err != nil {
return
return n, addr, err
}
}
}
return
return n, addr, err
}
return
return n, addr, err
}
// Close won't close the underlying socket unless there is no more reference, then listenerPool will close it.
+87 -5
View File
@@ -31,13 +31,17 @@ import (
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"github.com/quic-go/quic-go/qlog"
h3qlog "github.com/quic-go/quic-go/http3/qlog"
"go.uber.org/zap"
"golang.org/x/time/rate"
"github.com/caddyserver/caddy/v2/internal"
)
// listenFdsStart is the first file descriptor number for systemd socket activation.
// File descriptors 0, 1, 2 are reserved for stdin, stdout, stderr.
const listenFdsStart = 3
// NetworkAddress represents one or more network addresses.
// It contains the individual components for a parsed network
// address of the form accepted by ParseNetworkAddress().
@@ -305,6 +309,64 @@ func IsFdNetwork(netw string) bool {
return strings.HasPrefix(netw, "fd")
}
// getFdByName returns the file descriptor number for the given
// socket name from systemd's LISTEN_FDNAMES environment variable.
// Socket names are provided by systemd via socket activation.
//
// The name can optionally include an index to handle multiple sockets
// with the same name: "web:0" for first, "web:1" for second, etc.
// If no index is specified, defaults to index 0 (first occurrence).
func getFdByName(nameWithIndex string) (int, error) {
if nameWithIndex == "" {
return 0, fmt.Errorf("socket name cannot be empty")
}
fdNamesStr := os.Getenv("LISTEN_FDNAMES")
if fdNamesStr == "" {
return 0, fmt.Errorf("LISTEN_FDNAMES environment variable not set")
}
// Parse name and optional index
parts := strings.Split(nameWithIndex, ":")
if len(parts) > 2 {
return 0, fmt.Errorf("invalid socket name format '%s': too many colons", nameWithIndex)
}
name := parts[0]
targetIndex := 0
if len(parts) > 1 {
var err error
targetIndex, err = strconv.Atoi(parts[1])
if err != nil {
return 0, fmt.Errorf("invalid socket index '%s': %v", parts[1], err)
}
if targetIndex < 0 {
return 0, fmt.Errorf("socket index cannot be negative: %d", targetIndex)
}
}
// Parse the socket names
names := strings.Split(fdNamesStr, ":")
// Find the Nth occurrence of the requested name
matchCount := 0
for i, fdName := range names {
if fdName == name {
if matchCount == targetIndex {
return listenFdsStart + i, nil
}
matchCount++
}
}
if matchCount == 0 {
return 0, fmt.Errorf("socket name '%s' not found in LISTEN_FDNAMES", name)
}
return 0, fmt.Errorf("socket name '%s' found %d times, but index %d requested", name, matchCount, targetIndex)
}
// ParseNetworkAddress parses addr into its individual
// components. The input string is expected to be of
// the form "network/host:port-range" where any part is
@@ -336,9 +398,27 @@ func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort ui
}, err
}
if IsFdNetwork(network) {
fdAddr := host
// Handle named socket activation (fdname/name, fdgramname/name)
if strings.HasPrefix(network, "fdname") || strings.HasPrefix(network, "fdgramname") {
fdNum, err := getFdByName(host)
if err != nil {
return NetworkAddress{}, fmt.Errorf("named socket activation: %v", err)
}
fdAddr = strconv.Itoa(fdNum)
// Normalize network to standard fd/fdgram
if strings.HasPrefix(network, "fdname") {
network = "fd"
} else {
network = "fdgram"
}
}
return NetworkAddress{
Network: network,
Host: host,
Host: fdAddr,
}, nil
}
var start, end uint64
@@ -382,7 +462,7 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
a = afterSlash
if IsUnixNetwork(network) || IsFdNetwork(network) {
host = a
return
return network, host, port, err
}
}
@@ -402,7 +482,7 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
err = errors.Join(firstErr, err)
}
return
return network, host, port, err
}
// JoinNetworkAddress combines network, host, and port into a single
@@ -430,6 +510,7 @@ func JoinNetworkAddress(network, host, port string) string {
// address instead.
//
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
// NOTE: user should close the returned listener twice, once to stop accepting new connections, the second time to free up the packet conn.
func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config) (http3.QUICListener, error) {
lnKey := listenerKey("quic"+na.Network, na.JoinHostPort(portOffset))
@@ -466,7 +547,7 @@ func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config
http3.ConfigureTLSConfig(quicTlsConfig),
&quic.Config{
Allow0RTT: true,
Tracer: qlog.DefaultConnectionTracer,
Tracer: h3qlog.DefaultConnectionTracer,
},
)
if err != nil {
@@ -626,6 +707,7 @@ func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (*quic.Conn, error)
func (fcql *fakeCloseQuicListener) Close() error {
if atomic.CompareAndSwapInt32(&fcql.closed, 0, 1) {
fcql.contextCancel()
} else if atomic.CompareAndSwapInt32(&fcql.closed, 1, 2) {
_, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
}
return nil
+284
View File
@@ -15,6 +15,7 @@
package caddy
import (
"os"
"reflect"
"testing"
@@ -652,3 +653,286 @@ func TestSplitUnixSocketPermissionsBits(t *testing.T) {
}
}
}
// TestGetFdByName tests the getFdByName function for systemd socket activation.
func TestGetFdByName(t *testing.T) {
// Save original environment
originalFdNames := os.Getenv("LISTEN_FDNAMES")
// Restore environment after test
defer func() {
if originalFdNames != "" {
os.Setenv("LISTEN_FDNAMES", originalFdNames)
} else {
os.Unsetenv("LISTEN_FDNAMES")
}
}()
tests := []struct {
name string
fdNames string
socketName string
expectedFd int
expectError bool
}{
{
name: "simple http socket",
fdNames: "http",
socketName: "http",
expectedFd: 3,
},
{
name: "multiple different sockets - first",
fdNames: "http:https:dns",
socketName: "http",
expectedFd: 3,
},
{
name: "multiple different sockets - second",
fdNames: "http:https:dns",
socketName: "https",
expectedFd: 4,
},
{
name: "multiple different sockets - third",
fdNames: "http:https:dns",
socketName: "dns",
expectedFd: 5,
},
{
name: "duplicate names - first occurrence (no index)",
fdNames: "web:web:api",
socketName: "web",
expectedFd: 3,
},
{
name: "duplicate names - first occurrence (explicit index 0)",
fdNames: "web:web:api",
socketName: "web:0",
expectedFd: 3,
},
{
name: "duplicate names - second occurrence (index 1)",
fdNames: "web:web:api",
socketName: "web:1",
expectedFd: 4,
},
{
name: "complex duplicates - first api",
fdNames: "web:api:web:api:dns",
socketName: "api:0",
expectedFd: 4,
},
{
name: "complex duplicates - second api",
fdNames: "web:api:web:api:dns",
socketName: "api:1",
expectedFd: 6,
},
{
name: "complex duplicates - first web",
fdNames: "web:api:web:api:dns",
socketName: "web:0",
expectedFd: 3,
},
{
name: "complex duplicates - second web",
fdNames: "web:api:web:api:dns",
socketName: "web:1",
expectedFd: 5,
},
{
name: "socket not found",
fdNames: "http:https",
socketName: "missing",
expectError: true,
},
{
name: "empty socket name",
fdNames: "http",
socketName: "",
expectError: true,
},
{
name: "missing LISTEN_FDNAMES",
fdNames: "",
socketName: "http",
expectError: true,
},
{
name: "index out of range",
fdNames: "web:web",
socketName: "web:2",
expectError: true,
},
{
name: "negative index",
fdNames: "web",
socketName: "web:-1",
expectError: true,
},
{
name: "invalid index format",
fdNames: "web",
socketName: "web:abc",
expectError: true,
},
{
name: "too many colons",
fdNames: "web",
socketName: "web:0:extra",
expectError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Set up environment
if tc.fdNames != "" {
os.Setenv("LISTEN_FDNAMES", tc.fdNames)
} else {
os.Unsetenv("LISTEN_FDNAMES")
}
// Test the function
fd, err := getFdByName(tc.socketName)
if tc.expectError {
if err == nil {
t.Errorf("Expected error but got none")
}
} else {
if err != nil {
t.Errorf("Expected no error but got: %v", err)
}
if fd != tc.expectedFd {
t.Errorf("Expected FD %d but got %d", tc.expectedFd, fd)
}
}
})
}
}
// TestParseNetworkAddressFdName tests parsing of fdname and fdgramname addresses.
func TestParseNetworkAddressFdName(t *testing.T) {
// Save and restore environment
originalFdNames := os.Getenv("LISTEN_FDNAMES")
defer func() {
if originalFdNames != "" {
os.Setenv("LISTEN_FDNAMES", originalFdNames)
} else {
os.Unsetenv("LISTEN_FDNAMES")
}
}()
// Set up test environment
os.Setenv("LISTEN_FDNAMES", "http:https:dns")
tests := []struct {
input string
expectAddr NetworkAddress
expectErr bool
}{
{
input: "fdname/http",
expectAddr: NetworkAddress{
Network: "fd",
Host: "3",
},
},
{
input: "fdname/https",
expectAddr: NetworkAddress{
Network: "fd",
Host: "4",
},
},
{
input: "fdname/dns",
expectAddr: NetworkAddress{
Network: "fd",
Host: "5",
},
},
{
input: "fdname/http:0",
expectAddr: NetworkAddress{
Network: "fd",
Host: "3",
},
},
{
input: "fdname/https:0",
expectAddr: NetworkAddress{
Network: "fd",
Host: "4",
},
},
{
input: "fdgramname/http",
expectAddr: NetworkAddress{
Network: "fdgram",
Host: "3",
},
},
{
input: "fdgramname/https",
expectAddr: NetworkAddress{
Network: "fdgram",
Host: "4",
},
},
{
input: "fdgramname/http:0",
expectAddr: NetworkAddress{
Network: "fdgram",
Host: "3",
},
},
{
input: "fdname/nonexistent",
expectErr: true,
},
{
input: "fdgramname/nonexistent",
expectErr: true,
},
{
input: "fdname/http:99",
expectErr: true,
},
{
input: "fdname/invalid:abc",
expectErr: true,
},
// Test that old fd/N syntax still works
{
input: "fd/7",
expectAddr: NetworkAddress{
Network: "fd",
Host: "7",
},
},
{
input: "fdgram/8",
expectAddr: NetworkAddress{
Network: "fdgram",
Host: "8",
},
},
}
for i, tc := range tests {
actualAddr, err := ParseNetworkAddress(tc.input)
if tc.expectErr && err == nil {
t.Errorf("Test %d (%s): Expected error but got none", i, tc.input)
}
if !tc.expectErr && err != nil {
t.Errorf("Test %d (%s): Expected no error but got: %v", i, tc.input, err)
}
if !tc.expectErr && !reflect.DeepEqual(tc.expectAddr, actualAddr) {
t.Errorf("Test %d (%s): Expected %+v but got %+v", i, tc.input, tc.expectAddr, actualAddr)
}
}
}
+24
View File
@@ -28,6 +28,8 @@ import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/term"
"github.com/caddyserver/caddy/v2/internal"
)
func init() {
@@ -190,6 +192,13 @@ func (logging *Logging) setupNewDefault(ctx Context) error {
)
}
// if we had a buffered core, flush its contents ASAP
// before we try to log anything else, so the order of
// logs is preserved
if oldBufferCore, ok := oldDefault.logger.Core().(*internal.LogBufferCore); ok {
oldBufferCore.FlushTo(newDefault.logger)
}
return nil
}
@@ -773,6 +782,21 @@ func Log() *zap.Logger {
return defaultLogger.logger
}
// BufferedLog sets the default logger to one that buffers
// logs before a config is loaded.
// Returns the buffered logger, the original default logger
// (for flushing on errors), and the buffer core so that the
// caller can flush the logs after the config is loaded or
// fails to load.
func BufferedLog() (*zap.Logger, *zap.Logger, *internal.LogBufferCore) {
defaultLoggerMu.Lock()
defer defaultLoggerMu.Unlock()
origLogger := defaultLogger.logger
bufferCore := internal.NewLogBufferCore(zap.InfoLevel)
defaultLogger.logger = zap.New(bufferCore)
return defaultLogger.logger, origLogger, bufferCore
}
var (
coloringEnabled = os.Getenv("NO_COLOR") == "" && os.Getenv("TERM") != "xterm-mono"
defaultLogger, _ = newDefaultProductionLog()
+3 -1
View File
@@ -345,9 +345,11 @@ func StrictUnmarshalJSON(data []byte, v any) error {
return dec.Decode(v)
}
var JSONRawMessageType = reflect.TypeFor[json.RawMessage]()
// isJSONRawMessage returns true if the type is encoding/json.RawMessage.
func isJSONRawMessage(typ reflect.Type) bool {
return typ.PkgPath() == "encoding/json" && typ.Name() == "RawMessage"
return typ == JSONRawMessageType
}
// isModuleMapType returns true if the type is map[string]json.RawMessage.
+106 -76
View File
@@ -28,7 +28,6 @@ import (
"go.uber.org/zap"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyevents"
@@ -151,6 +150,11 @@ type App struct {
logger *zap.Logger
tlsApp *caddytls.TLS
// stopped indicates whether the app has stopped
// It can only happen if it has started successfully in the first place.
// Otherwise, Cleanup will call Stop to clean up resources.
stopped bool
// used temporarily between phases 1 and 2 of auto HTTPS
allCertDomains map[string]struct{}
}
@@ -166,13 +170,15 @@ func (App) CaddyModule() caddy.ModuleInfo {
// Provision sets up the app.
func (app *App) Provision(ctx caddy.Context) error {
// store some references
app.logger = ctx.Logger()
app.ctx = ctx
// provision TLS and events apps
tlsAppIface, err := ctx.App("tls")
if err != nil {
return fmt.Errorf("getting tls app: %v", err)
}
app.tlsApp = tlsAppIface.(*caddytls.TLS)
app.ctx = ctx
app.logger = ctx.Logger()
eventsAppIface, err := ctx.App("events")
if err != nil {
@@ -192,6 +198,8 @@ func (app *App) Provision(ctx caddy.Context) error {
if app.Metrics != nil {
app.Metrics.init = sync.Once{}
app.Metrics.httpMetrics = &httpMetrics{}
// Scan config for allowed hosts to prevent cardinality explosion
app.Metrics.scanConfigForHosts(app)
}
// prepare each server
oldContext := ctx.Context
@@ -231,15 +239,6 @@ func (app *App) Provision(ctx caddy.Context) error {
for _, srvProtocol := range srv.Protocols {
srvProtocolsUnique[srvProtocol] = struct{}{}
}
_, h1ok := srvProtocolsUnique["h1"]
_, h2ok := srvProtocolsUnique["h2"]
_, h2cok := srvProtocolsUnique["h2c"]
// the Go standard library does not let us serve only HTTP/2 using
// http.Server; we would probably need to write our own server
if !h1ok && (h2ok || h2cok) {
return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName)
}
if srv.ListenProtocols != nil {
if len(srv.ListenProtocols) != len(srv.Listen) {
@@ -273,19 +272,6 @@ func (app *App) Provision(ctx caddy.Context) error {
}
}
lnProtocolsIncludeUnique := map[string]struct{}{}
for _, lnProtocol := range lnProtocolsInclude {
lnProtocolsIncludeUnique[lnProtocol] = struct{}{}
}
_, h1ok := lnProtocolsIncludeUnique["h1"]
_, h2ok := lnProtocolsIncludeUnique["h2"]
_, h2cok := lnProtocolsIncludeUnique["h2c"]
// check if any listener protocols contain h2 or h2c without h1
if !h1ok && (h2ok || h2cok) {
return fmt.Errorf("server %s, listener %d: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName, i)
}
srv.ListenProtocols[i] = lnProtocolsInclude
}
}
@@ -443,6 +429,25 @@ func (app *App) Validate() error {
return nil
}
func removeTLSALPN(srv *Server, target string) {
for _, cp := range srv.TLSConnPolicies {
// the TLSConfig was already provisioned, so... manually remove it
for i, np := range cp.TLSConfig.NextProtos {
if np == target {
cp.TLSConfig.NextProtos = append(cp.TLSConfig.NextProtos[:i], cp.TLSConfig.NextProtos[i+1:]...)
break
}
}
// remove it from the parent connection policy too, just to keep things tidy
for i, alpn := range cp.ALPN {
if alpn == target {
cp.ALPN = append(cp.ALPN[:i], cp.ALPN[i+1:]...)
break
}
}
}
}
// Start runs the app. It finishes automatic HTTPS if enabled,
// including management of certificates.
func (app *App) Start() error {
@@ -461,32 +466,44 @@ func (app *App) Start() error {
MaxHeaderBytes: srv.MaxHeaderBytes,
Handler: srv,
ErrorLog: serverLogger,
Protocols: new(http.Protocols),
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
return context.WithValue(ctx, ConnCtxKey, c)
if nc, ok := c.(interface{ tlsNetConn() net.Conn }); ok {
getTlsConStateFunc := sync.OnceValue(func() *tls.ConnectionState {
tlsConnState := nc.tlsNetConn().(connectionStater).ConnectionState()
return &tlsConnState
})
ctx = context.WithValue(ctx, tlsConnectionStateFuncCtxKey, getTlsConStateFunc)
}
return ctx
},
}
h2server := new(http2.Server)
// disable HTTP/2, which we enabled by default during provisioning
if !srv.protocol("h2") {
srv.server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
for _, cp := range srv.TLSConnPolicies {
// the TLSConfig was already provisioned, so... manually remove it
for i, np := range cp.TLSConfig.NextProtos {
if np == "h2" {
cp.TLSConfig.NextProtos = append(cp.TLSConfig.NextProtos[:i], cp.TLSConfig.NextProtos[i+1:]...)
break
}
}
// remove it from the parent connection policy too, just to keep things tidy
for i, alpn := range cp.ALPN {
if alpn == "h2" {
cp.ALPN = append(cp.ALPN[:i], cp.ALPN[i+1:]...)
break
}
}
}
} else {
removeTLSALPN(srv, "h2")
}
if !srv.protocol("h1") {
removeTLSALPN(srv, "http/1.1")
}
// configure the http versions the server will serve
if srv.protocol("h1") {
srv.server.Protocols.SetHTTP1(true)
}
if srv.protocol("h2") || srv.protocol("h2c") {
// skip setting h2 because if NextProtos is present, it's list of alpn versions will take precedence.
// it will always be present because http2.ConfigureServer will populate that field
// enabling h2c because some listener wrapper will wrap the connection that is no longer *tls.Conn
// However, we need to handle the case that if the connection is h2c but h2c is not enabled. We identify
// this type of connection by checking if it's behind a TLS listener wrapper or if it implements tls.ConnectionState.
srv.server.Protocols.SetUnencryptedHTTP2(true)
// when h2c is enabled but h2 disabled, we already removed h2 from NextProtos
// the handshake will never succeed with h2
// http2.ConfigureServer will enable the server to handle both h2 and h2c
h2server := new(http2.Server)
//nolint:errcheck
http2.ConfigureServer(srv.server, h2server)
}
@@ -496,11 +513,6 @@ func (app *App) Start() error {
tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
srv.configureServer(srv.server)
// enable H2C if configured
if srv.protocol("h2c") {
srv.server.Handler = h2c.NewHandler(srv, h2server)
}
for lnIndex, lnAddr := range srv.Listen {
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
if err != nil {
@@ -533,8 +545,10 @@ func (app *App) Start() error {
// create the listener for this socket
lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{
KeepAliveConfig: net.KeepAliveConfig{
Enable: srv.KeepAliveInterval != 0,
Enable: srv.KeepAliveInterval >= 0,
Interval: time.Duration(srv.KeepAliveInterval),
Idle: time.Duration(srv.KeepAliveIdle),
Count: srv.KeepAliveCount,
},
})
if err != nil {
@@ -565,15 +579,13 @@ func (app *App) Start() error {
ln = srv.listenerWrappers[i].WrapListener(ln)
}
// handle http2 if use tls listener wrapper
if h2ok {
http2lnWrapper := &http2Listener{
Listener: ln,
server: srv.server,
h2server: h2server,
}
srv.h2listeners = append(srv.h2listeners, http2lnWrapper)
ln = http2lnWrapper
// check if the connection is h2c
ln = &http2Listener{
useTLS: useTLS,
useH1: h1ok,
useH2: h2ok || h2cok,
Listener: ln,
logger: app.logger,
}
// if binding to port 0, the OS chooses a port for us;
@@ -591,11 +603,8 @@ func (app *App) Start() error {
srv.listeners = append(srv.listeners, ln)
// enable HTTP/1 if configured
if h1ok {
//nolint:errcheck
go srv.server.Serve(ln)
}
//nolint:errcheck
go srv.server.Serve(ln)
}
if h2ok && !useTLS {
@@ -708,6 +717,11 @@ func (app *App) Stop() error {
defer finishedShutdown.Done()
startedShutdown.Done()
// possible if server failed to Start
if server.server == nil {
return
}
if err := server.server.Shutdown(ctx); err != nil {
app.logger.Error("server shutdown",
zap.Error(err),
@@ -722,31 +736,36 @@ func (app *App) Stop() error {
return
}
// closing quic listeners won't affect accepted connections now
// so like stdlib, close listeners first, but keep the net.PacketConns open
for _, h3ln := range server.quicListeners {
if err := h3ln.Close(); err != nil {
app.logger.Error("http3 listener close",
zap.Error(err))
}
}
if err := server.h3server.Shutdown(ctx); err != nil {
app.logger.Error("HTTP/3 server shutdown",
zap.Error(err),
zap.Strings("addresses", server.Listen))
}
}
stopH2Listener := func(server *Server) {
defer finishedShutdown.Done()
startedShutdown.Done()
for i, s := range server.h2listeners {
if err := s.Shutdown(ctx); err != nil {
app.logger.Error("http2 listener shutdown",
zap.Error(err),
zap.Int("index", i))
// close the underlying net.PacketConns now
// see the comment for ListenQUIC
for _, h3ln := range server.quicListeners {
if err := h3ln.Close(); err != nil {
app.logger.Error("http3 listener close socket",
zap.Error(err))
}
}
}
for _, server := range app.Servers {
startedShutdown.Add(3)
finishedShutdown.Add(3)
startedShutdown.Add(2)
finishedShutdown.Add(2)
go stopServer(server)
go stopH3Server(server)
go stopH2Listener(server)
}
// block until all the goroutines have been run by the scheduler;
@@ -773,9 +792,20 @@ func (app *App) Stop() error {
}
}
app.stopped = true
return nil
}
// Cleanup will close remaining listeners if they still remain
// because some of the servers fail to start.
// It simply calls Stop because Stop won't be called when Start fails.
func (app *App) Cleanup() error {
if app.stopped {
return nil
}
return app.Stop()
}
func (app *App) httpPort() int {
if app.HTTPPort == 0 {
return DefaultHTTPPort
+16
View File
@@ -265,6 +265,22 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
}
}
// if all servers have auto_https disabled and no domains need certs,
// skip the rest of the TLS automation setup to avoid creating
// unnecessary PKI infrastructure and automation policies
allServersDisabled := true
for _, srv := range app.Servers {
if srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled {
allServersDisabled = false
break
}
}
if allServersDisabled && len(uniqueDomainsForCerts) == 0 {
logger.Debug("all servers have automatic HTTPS disabled and no domains need certificates, skipping TLS automation setup")
return nil
}
// we now have a list of all the unique names for which we need certs
var internal, tailscale []string
uniqueDomainsLoop:
+188
View File
@@ -0,0 +1,188 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyauth
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"fmt"
"strconv"
"strings"
"golang.org/x/crypto/argon2"
"github.com/caddyserver/caddy/v2"
)
func init() {
caddy.RegisterModule(Argon2idHash{})
}
const (
argon2idName = "argon2id"
defaultArgon2idTime = 1
defaultArgon2idMemory = 46 * 1024
defaultArgon2idThreads = 1
defaultArgon2idKeylen = 32
defaultSaltLength = 16
)
// Argon2idHash implements the Argon2id password hashing.
type Argon2idHash struct {
salt []byte
time uint32
memory uint32
threads uint8
keyLen uint32
}
// CaddyModule returns the Caddy module information.
func (Argon2idHash) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.authentication.hashes.argon2id",
New: func() caddy.Module { return new(Argon2idHash) },
}
}
// Compare checks if the plaintext password matches the given Argon2id hash.
func (Argon2idHash) Compare(hashed, plaintext []byte) (bool, error) {
argHash, storedKey, err := DecodeHash(hashed)
if err != nil {
return false, err
}
computedKey := argon2.IDKey(
plaintext,
argHash.salt,
argHash.time,
argHash.memory,
argHash.threads,
argHash.keyLen,
)
return subtle.ConstantTimeCompare(storedKey, computedKey) == 1, nil
}
// Hash generates an Argon2id hash of the given plaintext using the configured parameters and salt.
func (b Argon2idHash) Hash(plaintext []byte) ([]byte, error) {
if b.salt == nil {
s, err := generateSalt(defaultSaltLength)
if err != nil {
return nil, err
}
b.salt = s
}
key := argon2.IDKey(
plaintext,
b.salt,
b.time,
b.memory,
b.threads,
b.keyLen,
)
hash := fmt.Sprintf(
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
argon2.Version,
b.memory,
b.time,
b.threads,
base64.RawStdEncoding.EncodeToString(b.salt),
base64.RawStdEncoding.EncodeToString(key),
)
return []byte(hash), nil
}
// DecodeHash parses an Argon2id PHC string into an Argon2idHash struct and returns the struct along with the derived key.
func DecodeHash(hash []byte) (*Argon2idHash, []byte, error) {
parts := strings.Split(string(hash), "$")
if len(parts) != 6 {
return nil, nil, fmt.Errorf("invalid hash format")
}
if parts[1] != argon2idName {
return nil, nil, fmt.Errorf("unsupported variant: %s", parts[1])
}
version, err := strconv.Atoi(strings.TrimPrefix(parts[2], "v="))
if err != nil {
return nil, nil, fmt.Errorf("invalid version: %w", err)
}
if version != argon2.Version {
return nil, nil, fmt.Errorf("incompatible version: %d", version)
}
params := strings.Split(parts[3], ",")
if len(params) != 3 {
return nil, nil, fmt.Errorf("invalid parameters")
}
mem, err := strconv.ParseUint(strings.TrimPrefix(params[0], "m="), 10, 32)
if err != nil {
return nil, nil, fmt.Errorf("invalid memory parameter: %w", err)
}
iter, err := strconv.ParseUint(strings.TrimPrefix(params[1], "t="), 10, 32)
if err != nil {
return nil, nil, fmt.Errorf("invalid iterations parameter: %w", err)
}
threads, err := strconv.ParseUint(strings.TrimPrefix(params[2], "p="), 10, 8)
if err != nil {
return nil, nil, fmt.Errorf("invalid parallelism parameter: %w", err)
}
salt, err := base64.RawStdEncoding.Strict().DecodeString(parts[4])
if err != nil {
return nil, nil, fmt.Errorf("decode salt: %w", err)
}
key, err := base64.RawStdEncoding.Strict().DecodeString(parts[5])
if err != nil {
return nil, nil, fmt.Errorf("decode key: %w", err)
}
return &Argon2idHash{
salt: salt,
time: uint32(iter),
memory: uint32(mem),
threads: uint8(threads),
keyLen: uint32(len(key)),
}, key, nil
}
// FakeHash returns a constant fake hash for timing attacks mitigation.
func (Argon2idHash) FakeHash() []byte {
// hashed with the following command:
// caddy hash-password --plaintext "antitiming" --algorithm "argon2id"
return []byte("$argon2id$v=19$m=47104,t=1,p=1$P2nzckEdTZ3bxCiBCkRTyA$xQL3Z32eo5jKl7u5tcIsnEKObYiyNZQQf5/4sAau6Pg")
}
// Interface guards
var (
_ Comparer = (*Argon2idHash)(nil)
_ Hasher = (*Argon2idHash)(nil)
)
func generateSalt(length int) ([]byte, error) {
salt := make([]byte, length)
if _, err := rand.Read(salt); err != nil {
return nil, fmt.Errorf("failed to generate salt: %w", err)
}
return salt, nil
}
@@ -15,6 +15,8 @@
package caddyauth
import (
"errors"
"golang.org/x/crypto/bcrypt"
"github.com/caddyserver/caddy/v2"
@@ -24,8 +26,18 @@ func init() {
caddy.RegisterModule(BcryptHash{})
}
// defaultBcryptCost cost 14 strikes a solid balance between security, usability, and hardware performance
const (
bcryptName = "bcrypt"
defaultBcryptCost = 14
)
// BcryptHash implements the bcrypt hash.
type BcryptHash struct{}
type BcryptHash struct {
// cost is the bcrypt hashing difficulty factor (work factor).
// Higher values increase computation time and security.
cost int
}
// CaddyModule returns the Caddy module information.
func (BcryptHash) CaddyModule() caddy.ModuleInfo {
@@ -38,7 +50,7 @@ func (BcryptHash) CaddyModule() caddy.ModuleInfo {
// Compare compares passwords.
func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) {
err := bcrypt.CompareHashAndPassword(hashed, plaintext)
if err == bcrypt.ErrMismatchedHashAndPassword {
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
return false, nil
}
if err != nil {
@@ -48,8 +60,13 @@ func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) {
}
// Hash hashes plaintext using a random salt.
func (BcryptHash) Hash(plaintext []byte) ([]byte, error) {
return bcrypt.GenerateFromPassword(plaintext, 14)
func (b BcryptHash) Hash(plaintext []byte) ([]byte, error) {
cost := b.cost
if cost < bcrypt.MinCost || cost > bcrypt.MaxCost {
cost = defaultBcryptCost
}
return bcrypt.GenerateFromPassword(plaintext, cost)
}
// FakeHash returns a fake hash.
+2 -1
View File
@@ -60,7 +60,8 @@ func (Authentication) CaddyModule() caddy.ModuleInfo {
}
}
// Provision sets up a.
// Provision sets up an Authentication module by initializing its logger,
// loading and registering all configured authentication providers.
func (a *Authentication) Provision(ctx caddy.Context) error {
a.logger = ctx.Logger()
a.Providers = make(map[string]Authenticator)
+4 -2
View File
@@ -51,7 +51,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
var hashName string
switch len(args) {
case 0:
hashName = "bcrypt"
hashName = bcryptName
case 1:
hashName = args[0]
case 2:
@@ -62,8 +62,10 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
}
switch hashName {
case "bcrypt":
case bcryptName:
cmp = BcryptHash{}
case argon2idName:
cmp = Argon2idHash{}
default:
return nil, h.Errf("unrecognized hash algorithm: %s", hashName)
}
+69 -8
View File
@@ -32,21 +32,55 @@ import (
func init() {
caddycmd.RegisterCommand(caddycmd.Command{
Name: "hash-password",
Usage: "[--plaintext <password>] [--algorithm <name>]",
Usage: "[--plaintext <password>] [--algorithm <argon2id|bcrypt>] [--bcrypt-cost <difficulty>] [--argon2id-time <iterations>] [--argon2id-memory <KiB>] [--argon2id-threads <n>] [--argon2id-keylen <bytes>]",
Short: "Hashes a password and writes base64",
Long: `
Convenient way to hash a plaintext password. The resulting
hash is written to stdout as a base64 string.
--plaintext, when omitted, will be read from stdin. If
Caddy is attached to a controlling tty, the plaintext will
not be echoed.
--plaintext
The password to hash. If omitted, it will be read from stdin.
If Caddy is attached to a controlling TTY, the input will not be echoed.
--algorithm currently only supports 'bcrypt', and is the default.
--algorithm
Selects the hashing algorithm. Valid options are:
* 'argon2id' (recommended for modern security)
* 'bcrypt' (legacy, slower, configurable cost)
bcrypt-specific parameters:
--bcrypt-cost
Sets the bcrypt hashing difficulty. Higher values increase security by
making the hash computation slower and more CPU-intensive.
Must be within the valid range [bcrypt.MinCost, bcrypt.MaxCost].
If omitted or invalid, the default cost is used.
Argon2id-specific parameters:
--argon2id-time
Number of iterations to perform. Increasing this makes
hashing slower and more resistant to brute-force attacks.
--argon2id-memory
Amount of memory to use during hashing.
Larger values increase resistance to GPU/ASIC attacks.
--argon2id-threads
Number of CPU threads to use. Increase for faster hashing
on multi-core systems.
--argon2id-keylen
Length of the resulting hash in bytes. Longer keys increase
security but slightly increase storage size.
`,
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm")
cmd.Flags().StringP("algorithm", "a", bcryptName, "Name of the hash algorithm")
cmd.Flags().Int("bcrypt-cost", defaultBcryptCost, "Bcrypt hashing cost (only used with 'bcrypt' algorithm)")
cmd.Flags().Uint32("argon2id-time", defaultArgon2idTime, "Number of iterations for Argon2id hashing. Increasing this makes the hash slower and more resistant to brute-force attacks.")
cmd.Flags().Uint32("argon2id-memory", defaultArgon2idMemory, "Memory to use in KiB for Argon2id hashing. Larger values increase resistance to GPU/ASIC attacks.")
cmd.Flags().Uint8("argon2id-threads", defaultArgon2idThreads, "Number of CPU threads to use for Argon2id hashing. Increase for faster hashing on multi-core systems.")
cmd.Flags().Uint32("argon2id-keylen", defaultArgon2idKeylen, "Length of the resulting Argon2id hash in bytes. Longer hashes increase security but slightly increase storage size.")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
},
})
@@ -57,6 +91,7 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
algorithm := fs.String("algorithm")
plaintext := []byte(fs.String("plaintext"))
bcryptCost := fs.Int("bcrypt-cost")
if len(plaintext) == 0 {
fd := int(os.Stdin.Fd())
@@ -107,8 +142,34 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
var hash []byte
var hashString string
switch algorithm {
case "bcrypt":
hash, err = BcryptHash{}.Hash(plaintext)
case bcryptName:
hash, err = BcryptHash{cost: bcryptCost}.Hash(plaintext)
hashString = string(hash)
case argon2idName:
time, err := fs.GetUint32("argon2id-time")
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id time parameter: %w", err)
}
memory, err := fs.GetUint32("argon2id-memory")
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id memory parameter: %w", err)
}
threads, err := fs.GetUint8("argon2id-threads")
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id threads parameter: %w", err)
}
keyLen, err := fs.GetUint32("argon2id-keylen")
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id keylen parameter: %w", err)
}
hash, _ = Argon2idHash{
time: time,
memory: memory,
threads: threads,
keyLen: keyLen,
}.Hash(plaintext)
hashString = string(hash)
default:
return caddy.ExitCodeFailedStartup, fmt.Errorf("unrecognized hash algorithm: %s", algorithm)
+1 -1
View File
@@ -665,7 +665,7 @@ func celMatcherJSONMacroExpander(funcName string) parser.MacroExpander {
// map literals containing heterogeneous values, in this case string and list
// of string.
func CELValueToMapStrList(data ref.Val) (map[string][]string, error) {
mapStrType := reflect.TypeOf(map[string]any{})
mapStrType := reflect.TypeFor[map[string]any]()
mapStrRaw, err := data.ConvertToNative(mapStrType)
if err != nil {
return nil, err
+1 -1
View File
@@ -535,7 +535,7 @@ func BenchmarkMatchExpressionMatch(b *testing.B) {
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
tc.expression.MatchWithError(req)
}
})
+23 -5
View File
@@ -50,7 +50,7 @@ type Encode struct {
// Only encode responses that are at least this many bytes long.
MinLength int `json:"minimum_length,omitempty"`
// Only encode responses that match against this ResponseMmatcher.
// Only encode responses that match against this ResponseMatcher.
// The default is a collection of text-based Content-Type headers.
Matcher *caddyhttp.ResponseMatcher `json:"match,omitempty"`
@@ -92,6 +92,7 @@ func (enc *Encode) Provision(ctx caddy.Context) error {
"application/font*",
"application/geo+json*",
"application/graphql+json*",
"application/graphql-response+json*",
"application/javascript*",
"application/json*",
"application/ld+json*",
@@ -176,7 +177,17 @@ func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh
break
}
}
return next.ServeHTTP(w, r)
err := next.ServeHTTP(w, r)
// If there was an error, disable encoding completely
// This prevents corruption when handle_errors processes the response
if err != nil {
if ew, ok := w.(*responseWriter); ok {
ew.disabled = true
}
}
return err
}
func (enc *Encode) addEncoding(e Encoding) error {
@@ -232,6 +243,7 @@ type responseWriter struct {
statusCode int
wroteHeader bool
isConnect bool
disabled bool // disable encoding (for error responses)
}
// WriteHeader stores the status to write when the time comes
@@ -424,7 +436,14 @@ func (rw *responseWriter) Unwrap() http.ResponseWriter {
// init should be called before we write a response, if rw.buf has contents.
func (rw *responseWriter) init() {
// Don't initialize encoder for error responses
// This prevents response corruption when handle_errors is used
if rw.disabled {
return
}
hdr := rw.Header()
if hdr.Get("Content-Encoding") == "" && isEncodeAllowed(hdr) &&
rw.config.Match(rw) {
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
@@ -452,8 +471,7 @@ func (rw *responseWriter) init() {
func hasVaryValue(hdr http.Header, target string) bool {
for _, vary := range hdr.Values("Vary") {
vals := strings.Split(vary, ",")
for _, val := range vals {
for val := range strings.SplitSeq(vary, ",") {
if strings.EqualFold(strings.TrimSpace(val), target) {
return true
}
@@ -478,7 +496,7 @@ func AcceptedEncodings(r *http.Request, preferredOrder []string) []string {
prefs := []encodingPreference{}
for _, accepted := range strings.Split(acceptEncHeader, ",") {
for accepted := range strings.SplitSeq(acceptEncHeader, ",") {
parts := strings.Split(accepted, ";")
encName := strings.ToLower(strings.TrimSpace(parts[0]))
+1 -1
View File
@@ -9,7 +9,7 @@ import (
func BenchmarkOpenResponseWriter(b *testing.B) {
enc := new(Encode)
for n := 0; n < b.N; n++ {
for b.Loop() {
enc.openResponseWriter("test", nil, false)
}
}
+10 -1
View File
@@ -167,6 +167,8 @@ type FileServer struct {
// If set, file Etags will be read from sidecar files
// with any of these suffixes, instead of generating
// our own Etag.
// Keep in mind that the Etag values in the files have to be quoted as per RFC7232.
// See https://datatracker.ietf.org/doc/html/rfc7232#section-2.3 for a few examples.
EtagFileExtensions []string `json:"etag_file_extensions,omitempty"`
fsmap caddy.FileSystems
@@ -455,7 +457,14 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
}
defer file.Close()
respHeader.Set("Content-Encoding", ae)
respHeader.Del("Accept-Ranges")
// stdlib won't set Content-Length for non-range requests if Content-Encoding is set.
// see: https://github.com/caddyserver/caddy/issues/7040
// Setting the Range header manually will result in 206 Partial Content.
// see: https://github.com/caddyserver/caddy/issues/7250
if r.Header.Get("Range") == "" {
respHeader.Set("Content-Length", strconv.FormatInt(compressedInfo.Size(), 10))
}
// try to get the etag from pre computed files if an etag suffix list was provided
if etag == "" && fsrv.EtagFileExtensions != nil {
+1 -1
View File
@@ -159,7 +159,7 @@ func (ops *HeaderOps) Provision(_ caddy.Context) error {
return nil
}
// containsCaddyPlaceholders checks if the string contains Caddy placeholder syntax {key}
// containsPlaceholders checks if the string contains Caddy placeholder syntax {key}
func containsPlaceholders(s string) bool {
openIdx := strings.Index(s, "{")
if openIdx == -1 {
+107 -78
View File
@@ -1,102 +1,131 @@
package caddyhttp
import (
"context"
"crypto/tls"
weakrand "math/rand"
"io"
"net"
"net/http"
"sync/atomic"
"time"
"go.uber.org/zap"
"golang.org/x/net/http2"
)
// http2Listener wraps the listener to solve the following problems:
// 1. server h2 natively without using h2c hack when listener handles tls connection but
// don't return *tls.Conn
// 2. graceful shutdown. the shutdown logic is copied from stdlib http.Server, it's an extra maintenance burden but
// whatever, the shutdown logic maybe extracted to be used with h2c graceful shutdown. http2.Server supports graceful shutdown
// sending GO_AWAY frame to connected clients, but doesn't track connection status. It requires explicit call of http2.ConfigureServer
type http2Listener struct {
cnt uint64
net.Listener
server *http.Server
h2server *http2.Server
}
type connectionStateConn interface {
net.Conn
type connectionStater interface {
ConnectionState() tls.ConnectionState
}
// http2Listener wraps the listener to solve the following problems:
// 1. prevent genuine h2c connections from succeeding if h2c is not enabled
// and the connection doesn't implment connectionStater or the resulting NegotiatedProtocol
// isn't http2.
// This does allow a connection to pass as tls enabled even if it's not, listener wrappers
// can do this.
// 2. After wrapping the connection doesn't implement connectionStater, emit a warning so that listener
// wrapper authors will hopefully implement it.
// 3. check if the connection matches a specific http version. h2/h2c has a distinct preface.
type http2Listener struct {
useTLS bool
useH1 bool
useH2 bool
net.Listener
logger *zap.Logger
}
func (h *http2Listener) Accept() (net.Conn, error) {
for {
conn, err := h.Listener.Accept()
if err != nil {
return nil, err
}
conn, err := h.Listener.Accept()
if err != nil {
return nil, err
}
if csc, ok := conn.(connectionStateConn); ok {
// *tls.Conn will return empty string because it's only populated after handshake is complete
if csc.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS {
go h.serveHttp2(csc)
continue
}
}
// *tls.Conn doesn't need to be wrapped because we already removed unwanted alpns
// and handshake won't succeed without mutually supported alpns
if tlsConn, ok := conn.(*tls.Conn); ok {
return tlsConn, nil
}
_, isConnectionStater := conn.(connectionStater)
// emit a warning
if h.useTLS && !isConnectionStater {
h.logger.Warn("tls is enabled, but listener wrapper returns a connection that doesn't implement connectionStater")
} else if !h.useTLS && isConnectionStater {
h.logger.Warn("tls is disabled, but listener wrapper returns a connection that implements connectionStater")
}
// if both h1 and h2 are enabled, we don't need to check the preface
if h.useH1 && h.useH2 {
if isConnectionStater {
return tlsStateConn{conn}, nil
}
return conn, nil
}
}
func (h *http2Listener) serveHttp2(csc connectionStateConn) {
atomic.AddUint64(&h.cnt, 1)
h.runHook(csc, http.StateNew)
defer func() {
csc.Close()
atomic.AddUint64(&h.cnt, ^uint64(0))
h.runHook(csc, http.StateClosed)
}()
h.h2server.ServeConn(csc, &http2.ServeConnOpts{
Context: h.server.ConnContext(context.Background(), csc),
BaseConfig: h.server,
Handler: h.server.Handler,
})
}
const shutdownPollIntervalMax = 500 * time.Millisecond
func (h *http2Listener) Shutdown(ctx context.Context) error {
pollIntervalBase := time.Millisecond
nextPollInterval := func() time.Duration {
// Add 10% jitter.
//nolint:gosec
interval := pollIntervalBase + time.Duration(weakrand.Intn(int(pollIntervalBase/10)))
// Double and clamp for next time.
pollIntervalBase *= 2
if pollIntervalBase > shutdownPollIntervalMax {
pollIntervalBase = shutdownPollIntervalMax
}
return interval
// impossible both are false, either useH1 or useH2 must be true,
// or else the listener wouldn't be created
h2Conn := &http2Conn{
h2Expected: h.useH2,
logger: h.logger,
Conn: conn,
}
if isConnectionStater {
return tlsStateConn{http2StateConn{h2Conn}}, nil
}
return h2Conn, nil
}
timer := time.NewTimer(nextPollInterval())
defer timer.Stop()
for {
if atomic.LoadUint64(&h.cnt) == 0 {
return nil
// tlsStateConn wraps a net.Conn that implements connectionStater to hide that method
// we can call netConn to get the original net.Conn and get the tls connection state
// golang 1.25 will call that method, and it breaks h2 with connections other than *tls.Conn
type tlsStateConn struct {
net.Conn
}
func (conn tlsStateConn) tlsNetConn() net.Conn {
return conn.Conn
}
type http2StateConn struct {
*http2Conn
}
func (conn http2StateConn) ConnectionState() tls.ConnectionState {
return conn.Conn.(connectionStater).ConnectionState()
}
type http2Conn struct {
// current index where the preface should match,
// no matching is done if idx is >= len(http2.ClientPreface)
idx int
// whether the connection is expected to be h2/h2c
h2Expected bool
// log if one such connection is detected
logger *zap.Logger
net.Conn
}
func (c *http2Conn) Read(p []byte) (int, error) {
if c.idx >= len(http2.ClientPreface) {
return c.Conn.Read(p)
}
n, err := c.Conn.Read(p)
for i := range n {
// first mismatch
if p[i] != http2.ClientPreface[c.idx] {
// close the connection if h2 is expected
if c.h2Expected {
c.logger.Debug("h1 connection detected, but h1 is not enabled")
_ = c.Conn.Close()
return 0, io.EOF
}
// no need to continue matching anymore
c.idx = len(http2.ClientPreface)
return n, err
}
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
timer.Reset(nextPollInterval())
c.idx++
// matching complete
if c.idx == len(http2.ClientPreface) && !c.h2Expected {
c.logger.Debug("h2/h2c connection detected, but h2/h2c is not enabled")
_ = c.Conn.Close()
return 0, io.EOF
}
}
}
func (h *http2Listener) runHook(conn net.Conn, state http.ConnState) {
if h.server.ConnState != nil {
h.server.ConnState(conn, state)
}
return n, err
}
+28 -2
View File
@@ -17,6 +17,7 @@ package intercept
import (
"bytes"
"fmt"
"io"
"net/http"
"strconv"
"strings"
@@ -175,10 +176,35 @@ func (ir Intercept) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
c.Write(zap.Int("handler", rec.handlerIndex))
}
// pass the request through the response handler routes
return rec.handler.Routes.Compile(next).ServeHTTP(w, r)
// response recorder doesn't create a new copy of the original headers, they're
// present in the original response writer
// create a new recorder to see if any response body from the new handler is present,
// if not, use the already buffered response body
recorder := caddyhttp.NewResponseRecorder(w, nil, nil)
if err := rec.handler.Routes.Compile(emptyHandler).ServeHTTP(recorder, r); err != nil {
return err
}
// no new response status and the status is not 0
if recorder.Status() == 0 && rec.Status() != 0 {
w.WriteHeader(rec.Status())
}
// no new response body and there is some in the original response
// TODO: what if the new response doesn't have a body by design?
// see: https://github.com/caddyserver/caddy/pull/6232#issue-2235224400
if recorder.Size() == 0 && buf.Len() > 0 {
_, err := io.Copy(w, buf)
return err
}
return nil
}
// this handler does nothing because everything we need is already buffered
var emptyHandler caddyhttp.Handler = caddyhttp.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) error {
return nil
})
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
//
// intercept [<matcher>] {
+2 -3
View File
@@ -20,7 +20,6 @@ import (
"net"
"net/http"
"net/netip"
"reflect"
"strings"
"github.com/google/cel-go/cel"
@@ -109,7 +108,7 @@ func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
[]*cel.Type{cel.ListType(cel.StringType)},
// function to convert a constant list of strings to a MatchPath instance.
func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{})
refStringList := stringSliceType
strList, err := data.ConvertToNative(refStringList)
if err != nil {
return nil, err
@@ -222,7 +221,7 @@ func (MatchClientIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
[]*cel.Type{cel.ListType(cel.StringType)},
// function to convert a constant list of strings to a MatchPath instance.
func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{})
refStringList := stringSliceType
strList, err := data.ConvertToNative(refStringList)
if err != nil {
return nil, err
+2 -2
View File
@@ -209,7 +209,7 @@ func errLogValues(err error) (status int, msg string, fields func() []zapcore.Fi
zap.String("err_trace", handlerErr.Trace),
}
}
return
return status, msg, fields
}
fields = func() []zapcore.Field {
return []zapcore.Field{
@@ -218,7 +218,7 @@ func errLogValues(err error) (status int, msg string, fields func() []zapcore.Fi
}
status = http.StatusInternalServerError
msg = err.Error()
return
return status, msg, fields
}
// ExtraLogFields is a list of extra fields to log with every request.
+6 -7
View File
@@ -23,7 +23,6 @@ import (
"net/textproto"
"net/url"
"path"
"reflect"
"regexp"
"runtime"
"slices"
@@ -373,7 +372,7 @@ func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"host_match_request_list",
[]*cel.Type{cel.ListType(cel.StringType)},
func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{})
refStringList := stringSliceType
strList, err := data.ConvertToNative(refStringList)
if err != nil {
return nil, err
@@ -654,7 +653,7 @@ func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) {
[]*cel.Type{cel.ListType(cel.StringType)},
// function to convert a constant list of strings to a MatchPath instance.
func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{})
refStringList := stringSliceType
strList, err := data.ConvertToNative(refStringList)
if err != nil {
return nil, err
@@ -733,7 +732,7 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"path_regexp_request_string_string",
[]*cel.Type{cel.StringType, cel.StringType},
func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{})
refStringList := stringSliceType
params, err := data.ConvertToNative(refStringList)
if err != nil {
return nil, err
@@ -802,7 +801,7 @@ func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) {
"method_request_list",
[]*cel.Type{cel.ListType(cel.StringType)},
func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{})
refStringList := stringSliceType
strList, err := data.ConvertToNative(refStringList)
if err != nil {
return nil, err
@@ -1173,7 +1172,7 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"header_regexp_request_string_string",
[]*cel.Type{cel.StringType, cel.StringType},
func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{})
refStringList := stringSliceType
params, err := data.ConvertToNative(refStringList)
if err != nil {
return nil, err
@@ -1196,7 +1195,7 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"header_regexp_request_string_string_string",
[]*cel.Type{cel.StringType, cel.StringType, cel.StringType},
func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{})
refStringList := stringSliceType
params, err := data.ConvertToNative(refStringList)
if err != nil {
return nil, err
+4 -9
View File
@@ -947,7 +947,7 @@ func BenchmarkHeaderREMatcher(b *testing.B) {
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx)
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
for run := 0; run < b.N; run++ {
for b.Loop() {
match.MatchWithError(req)
}
}
@@ -992,8 +992,6 @@ func TestVarREMatcher(t *testing.T) {
expect: true,
},
} {
i := i // capture range value
tc := tc // capture range value
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()
// compile the regexp and validate its name
@@ -1180,8 +1178,7 @@ func BenchmarkLargeHostMatcher(b *testing.B) {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
matcher.MatchWithError(req)
}
}
@@ -1194,8 +1191,7 @@ func BenchmarkHostMatcherWithoutPlaceholder(b *testing.B) {
match := MatchHost{"localhost"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
match.MatchWithError(req)
}
}
@@ -1212,8 +1208,7 @@ func BenchmarkHostMatcherWithPlaceholder(b *testing.B) {
req = req.WithContext(ctx)
match := MatchHost{"{env.GO_BENCHMARK_DOMAIN}"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
match.MatchWithError(req)
}
}
+117 -4
View File
@@ -17,14 +17,60 @@ import (
// Metrics configures metrics observations.
// EXPERIMENTAL and subject to change or removal.
//
// Example configuration:
//
// {
// "apps": {
// "http": {
// "metrics": {
// "per_host": true,
// "allow_catch_all_hosts": false
// },
// "servers": {
// "srv0": {
// "routes": [{
// "match": [{"host": ["example.com", "www.example.com"]}],
// "handle": [{"handler": "static_response", "body": "Hello"}]
// }]
// }
// }
// }
// }
// }
//
// In this configuration:
// - Requests to example.com and www.example.com get individual host labels
// - All other hosts (e.g., attacker.com) are aggregated under "_other" label
// - This prevents unlimited cardinality from arbitrary Host headers
type Metrics struct {
// Enable per-host metrics. Enabling this option may
// incur high-memory consumption, depending on the number of hosts
// managed by Caddy.
//
// CARDINALITY PROTECTION: To prevent unbounded cardinality attacks,
// only explicitly configured hosts (via host matchers) are allowed
// by default. Other hosts are aggregated under the "_other" label.
// See AllowCatchAllHosts to change this behavior.
PerHost bool `json:"per_host,omitempty"`
init sync.Once
httpMetrics *httpMetrics `json:"-"`
// Allow metrics for catch-all hosts (hosts without explicit configuration).
// When false (default), only hosts explicitly configured via host matchers
// will get individual metrics labels. All other hosts will be aggregated
// under the "_other" label to prevent cardinality explosion.
//
// This is automatically enabled for HTTPS servers (since certificates provide
// some protection against unbounded cardinality), but disabled for HTTP servers
// by default to prevent cardinality attacks from arbitrary Host headers.
//
// Set to true to allow all hosts to get individual metrics (NOT RECOMMENDED
// for production environments exposed to the internet).
AllowCatchAllHosts bool `json:"allow_catch_all_hosts,omitempty"`
init sync.Once
httpMetrics *httpMetrics
allowedHosts map[string]struct{}
hasHTTPSServer bool
}
type httpMetrics struct {
@@ -101,6 +147,63 @@ func initHTTPMetrics(ctx caddy.Context, metrics *Metrics) {
}, httpLabels)
}
// scanConfigForHosts scans the HTTP app configuration to build a set of allowed hosts
// for metrics collection, similar to how auto-HTTPS scans for domain names.
func (m *Metrics) scanConfigForHosts(app *App) {
if !m.PerHost {
return
}
m.allowedHosts = make(map[string]struct{})
m.hasHTTPSServer = false
for _, srv := range app.Servers {
// Check if this server has TLS enabled
serverHasTLS := len(srv.TLSConnPolicies) > 0
if serverHasTLS {
m.hasHTTPSServer = true
}
// Collect hosts from route matchers
for _, route := range srv.Routes {
for _, matcherSet := range route.MatcherSets {
for _, matcher := range matcherSet {
if hm, ok := matcher.(*MatchHost); ok {
for _, host := range *hm {
// Only allow non-fuzzy hosts to prevent unbounded cardinality
if !hm.fuzzy(host) {
m.allowedHosts[strings.ToLower(host)] = struct{}{}
}
}
}
}
}
}
}
}
// shouldAllowHostMetrics determines if metrics should be collected for the given host.
// This implements the cardinality protection by only allowing metrics for:
// 1. Explicitly configured hosts
// 2. Catch-all requests on HTTPS servers (if AllowCatchAllHosts is true or auto-enabled)
// 3. Catch-all requests on HTTP servers only if explicitly allowed
func (m *Metrics) shouldAllowHostMetrics(host string, isHTTPS bool) bool {
if !m.PerHost {
return true // host won't be used in labels anyway
}
normalizedHost := strings.ToLower(host)
// Always allow explicitly configured hosts
if _, exists := m.allowedHosts[normalizedHost]; exists {
return true
}
// For catch-all requests (not in allowed hosts)
allowCatchAll := m.AllowCatchAllHosts || (isHTTPS && m.hasHTTPSServer)
return allowCatchAll
}
// serverNameFromContext extracts the current server name from the context.
// Returns "UNKNOWN" if none is available (should probably never happen).
func serverNameFromContext(ctx context.Context) string {
@@ -133,9 +236,19 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
// of a panic
statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""}
// Determine if this is an HTTPS request
isHTTPS := r.TLS != nil
if h.metrics.PerHost {
labels["host"] = strings.ToLower(r.Host)
statusLabels["host"] = strings.ToLower(r.Host)
// Apply cardinality protection for host metrics
if h.metrics.shouldAllowHostMetrics(r.Host, isHTTPS) {
labels["host"] = strings.ToLower(r.Host)
statusLabels["host"] = strings.ToLower(r.Host)
} else {
// Use a catch-all label for unallowed hosts to prevent cardinality explosion
labels["host"] = "_other"
statusLabels["host"] = "_other"
}
}
inFlight := h.metrics.httpMetrics.requestInFlight.With(labels)
+112 -3
View File
@@ -2,6 +2,7 @@ package caddyhttp
import (
"context"
"crypto/tls"
"errors"
"net/http"
"net/http/httptest"
@@ -206,9 +207,11 @@ func TestMetricsInstrumentedHandler(t *testing.T) {
func TestMetricsInstrumentedHandlerPerHost(t *testing.T) {
ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
metrics := &Metrics{
PerHost: true,
init: sync.Once{},
httpMetrics: &httpMetrics{},
PerHost: true,
AllowCatchAllHosts: true, // Allow all hosts for testing
init: sync.Once{},
httpMetrics: &httpMetrics{},
allowedHosts: make(map[string]struct{}),
}
handlerErr := errors.New("oh noes")
response := []byte("hello world!")
@@ -379,6 +382,112 @@ func TestMetricsInstrumentedHandlerPerHost(t *testing.T) {
}
}
func TestMetricsCardinalityProtection(t *testing.T) {
ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
// Test 1: Without AllowCatchAllHosts, arbitrary hosts should be mapped to "_other"
metrics := &Metrics{
PerHost: true,
AllowCatchAllHosts: false, // Default - should map unknown hosts to "_other"
init: sync.Once{},
httpMetrics: &httpMetrics{},
allowedHosts: make(map[string]struct{}),
}
// Add one allowed host
metrics.allowedHosts["allowed.com"] = struct{}{}
mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
w.Write([]byte("hello"))
return nil
})
ih := newMetricsInstrumentedHandler(ctx, "test", mh, metrics)
// Test request to allowed host
r1 := httptest.NewRequest("GET", "http://allowed.com/", nil)
r1.Host = "allowed.com"
w1 := httptest.NewRecorder()
ih.ServeHTTP(w1, r1, HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return nil }))
// Test request to unknown host (should be mapped to "_other")
r2 := httptest.NewRequest("GET", "http://attacker.com/", nil)
r2.Host = "attacker.com"
w2 := httptest.NewRecorder()
ih.ServeHTTP(w2, r2, HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return nil }))
// Test request to another unknown host (should also be mapped to "_other")
r3 := httptest.NewRequest("GET", "http://evil.com/", nil)
r3.Host = "evil.com"
w3 := httptest.NewRecorder()
ih.ServeHTTP(w3, r3, HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return nil }))
// Check that metrics contain:
// - One entry for "allowed.com"
// - One entry for "_other" (aggregating attacker.com and evil.com)
expected := `
# HELP caddy_http_requests_total Counter of HTTP(S) requests made.
# TYPE caddy_http_requests_total counter
caddy_http_requests_total{handler="test",host="_other",server="UNKNOWN"} 2
caddy_http_requests_total{handler="test",host="allowed.com",server="UNKNOWN"} 1
`
if err := testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expected),
"caddy_http_requests_total",
); err != nil {
t.Errorf("Cardinality protection test failed: %s", err)
}
}
func TestMetricsHTTPSCatchAll(t *testing.T) {
ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
// Test that HTTPS requests allow catch-all even when AllowCatchAllHosts is false
metrics := &Metrics{
PerHost: true,
AllowCatchAllHosts: false,
hasHTTPSServer: true, // Simulate having HTTPS servers
init: sync.Once{},
httpMetrics: &httpMetrics{},
allowedHosts: make(map[string]struct{}), // Empty - no explicitly allowed hosts
}
mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
w.Write([]byte("hello"))
return nil
})
ih := newMetricsInstrumentedHandler(ctx, "test", mh, metrics)
// Test HTTPS request (should be allowed even though not in allowedHosts)
r1 := httptest.NewRequest("GET", "https://unknown.com/", nil)
r1.Host = "unknown.com"
r1.TLS = &tls.ConnectionState{} // Mark as TLS/HTTPS
w1 := httptest.NewRecorder()
ih.ServeHTTP(w1, r1, HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return nil }))
// Test HTTP request (should be mapped to "_other")
r2 := httptest.NewRequest("GET", "http://unknown.com/", nil)
r2.Host = "unknown.com"
// No TLS field = HTTP request
w2 := httptest.NewRecorder()
ih.ServeHTTP(w2, r2, HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return nil }))
// Check that HTTPS request gets real host, HTTP gets "_other"
expected := `
# HELP caddy_http_requests_total Counter of HTTP(S) requests made.
# TYPE caddy_http_requests_total counter
caddy_http_requests_total{handler="test",host="_other",server="UNKNOWN"} 1
caddy_http_requests_total{handler="test",host="unknown.com",server="UNKNOWN"} 1
`
if err := testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expected),
"caddy_http_requests_total",
); err != nil {
t.Errorf("HTTPS catch-all test failed: %s", err)
}
}
type middlewareHandlerFunc func(http.ResponseWriter, *http.Request, Handler) error
func (f middlewareHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request, h Handler) error {
+2 -2
View File
@@ -41,7 +41,7 @@ func parseLinkHeader(header string) []linkResource {
return resources
}
for _, link := range strings.Split(header, comma) {
for link := range strings.SplitSeq(header, comma) {
l := linkResource{params: make(map[string]string)}
li, ri := strings.Index(link, "<"), strings.Index(link, ">")
@@ -51,7 +51,7 @@ func parseLinkHeader(header string) []linkResource {
l.uri = strings.TrimSpace(link[li+1 : ri])
for _, param := range strings.Split(strings.TrimSpace(link[ri+1:]), semicolon) {
for param := range strings.SplitSeq(strings.TrimSpace(link[ri+1:]), semicolon) {
before, after, isCut := strings.Cut(strings.TrimSpace(param), equal)
key := strings.TrimSpace(before)
if key == "" {
+8 -2
View File
@@ -172,8 +172,12 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
// current URI, including any internal rewrites
case "http.request.uri":
return req.URL.RequestURI(), true
case "http.request.uri_escaped":
return url.QueryEscape(req.URL.RequestURI()), true
case "http.request.uri.path":
return req.URL.Path, true
case "http.request.uri.path_escaped":
return url.QueryEscape(req.URL.Path), true
case "http.request.uri.path.file":
_, file := path.Split(req.URL.Path)
return file, true
@@ -186,6 +190,8 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
return path.Ext(req.URL.Path), true
case "http.request.uri.query":
return req.URL.RawQuery, true
case "http.request.uri.query_escaped":
return url.QueryEscape(req.URL.RawQuery), true
case "http.request.uri.prefixed_query":
if req.URL.RawQuery == "" {
return "", true
@@ -283,7 +289,7 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
return prefix.String(), true
}
// hostname labels
// hostname labels (case insensitive, so normalize to lowercase)
if strings.HasPrefix(key, reqHostLabelsReplPrefix) {
idxStr := key[len(reqHostLabelsReplPrefix):]
idx, err := strconv.Atoi(idxStr)
@@ -298,7 +304,7 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
if idx >= len(hostLabels) {
return "", true
}
return hostLabels[len(hostLabels)-idx-1], true
return strings.ToLower(hostLabels[len(hostLabels)-idx-1]), true
}
// path parts
+37 -1
View File
@@ -28,7 +28,7 @@ import (
)
func TestHTTPVarReplacement(t *testing.T) {
req, _ := http.NewRequest(http.MethodGet, "/foo/bar.tar.gz", nil)
req, _ := http.NewRequest(http.MethodGet, "/foo/bar.tar.gz?a=1&b=2", nil)
repl := caddy.NewReplacer()
localAddr, _ := net.ResolveTCPAddr("tcp", "192.168.159.1:80")
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
@@ -142,6 +142,22 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
get: "http.request.host.labels.2",
expect: "",
},
{
get: "http.request.uri",
expect: "/foo/bar.tar.gz?a=1&b=2",
},
{
get: "http.request.uri_escaped",
expect: "%2Ffoo%2Fbar.tar.gz%3Fa%3D1%26b%3D2",
},
{
get: "http.request.uri.path",
expect: "/foo/bar.tar.gz",
},
{
get: "http.request.uri.path_escaped",
expect: "%2Ffoo%2Fbar.tar.gz",
},
{
get: "http.request.uri.path.file",
expect: "bar.tar.gz",
@@ -155,6 +171,26 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
get: "http.request.uri.path.file.ext",
expect: ".gz",
},
{
get: "http.request.uri.query",
expect: "a=1&b=2",
},
{
get: "http.request.uri.query_escaped",
expect: "a%3D1%26b%3D2",
},
{
get: "http.request.uri.query.a",
expect: "1",
},
{
get: "http.request.uri.query.b",
expect: "2",
},
{
get: "http.request.uri.prefixed_query",
expect: "?a=1&b=2",
},
{
get: "http.request.tls.cipher_suite",
expect: "TLS_AES_256_GCM_SHA384",
+1 -1
View File
@@ -116,7 +116,7 @@ func (ew errorWrapper) Read(p []byte) (n int, err error) {
if errors.As(err, &mbe) {
err = caddyhttp.Error(http.StatusRequestEntityTooLarge, err)
}
return
return n, err
}
// Interface guard
+4 -4
View File
@@ -75,8 +75,8 @@ For proxying:
cmd.Flags().BoolP("insecure", "", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING TLS CERTIFICATES!)")
cmd.Flags().BoolP("disable-redirects", "r", false, "Disable HTTP->HTTPS redirects")
cmd.Flags().BoolP("internal-certs", "i", false, "Use internal CA for issuing certs")
cmd.Flags().StringSliceP("header-up", "H", []string{}, "Set a request header to send to the upstream (format: \"Field: value\")")
cmd.Flags().StringSliceP("header-down", "d", []string{}, "Set a response header to send back to the client (format: \"Field: value\")")
cmd.Flags().StringArrayP("header-up", "H", []string{}, "Set a request header to send to the upstream (format: \"Field: value\")")
cmd.Flags().StringArrayP("header-down", "d", []string{}, "Set a response header to send back to the client (format: \"Field: value\")")
cmd.Flags().BoolP("access-log", "", false, "Enable the access log")
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdReverseProxy)
@@ -182,7 +182,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
}
// set up header_up
headerUp, err := fs.GetStringSlice("header-up")
headerUp, err := fs.GetStringArray("header-up")
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err)
}
@@ -204,7 +204,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
}
// set up header_down
headerDown, err := fs.GetStringSlice("header-down")
headerDown, err := fs.GetStringArray("header-down")
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err)
}
@@ -154,13 +154,13 @@ func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error)
err = writer.writeBeginRequest(uint16(Responder), 0)
if err != nil {
return
return r, err
}
writer.recType = Params
err = writer.writePairs(p)
if err != nil {
return
return r, err
}
writer.recType = Stdin
@@ -176,7 +176,7 @@ func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error)
}
r = &streamReader{c: c}
return
return r, err
}
// clientCloser is a io.ReadCloser. It wraps a io.Reader with a Closer
@@ -213,7 +213,7 @@ func (f clientCloser) Close() error {
func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) {
r, err := c.Do(p, req)
if err != nil {
return
return resp, err
}
rb := bufio.NewReader(r)
@@ -223,7 +223,7 @@ func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Respons
// Parse the response headers.
mimeHeader, err := tp.ReadMIMEHeader()
if err != nil && err != io.EOF {
return
return resp, err
}
resp.Header = http.Header(mimeHeader)
@@ -231,7 +231,7 @@ func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Respons
statusNumber, statusInfo, statusIsCut := strings.Cut(resp.Header.Get("Status"), " ")
resp.StatusCode, err = strconv.Atoi(statusNumber)
if err != nil {
return
return resp, err
}
if statusIsCut {
resp.Status = statusInfo
@@ -260,7 +260,7 @@ func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Respons
}
resp.Body = closer
return
return resp, err
}
// Get issues a GET request to the fcgi responder.
@@ -329,7 +329,7 @@ func (c *client) PostFile(p map[string]string, data url.Values, file map[string]
for _, v0 := range val {
err = writer.WriteField(key, v0)
if err != nil {
return
return resp, err
}
}
}
@@ -347,13 +347,13 @@ func (c *client) PostFile(p map[string]string, data url.Values, file map[string]
}
_, err = io.Copy(part, fd)
if err != nil {
return
return resp, err
}
}
err = writer.Close()
if err != nil {
return
return resp, err
}
return c.Post(p, "POST", bodyType, buf, int64(buf.Len()))

Some files were not shown because too many files have changed in this diff Show More