Compare commits

...

127 commits

Author SHA1 Message Date
Matthew Holt
1f8dab572c caddytls: Don't publish ECH configs if other records don't exist
Publishing a DNS record for a name that doesn't have any could make wildcards ineffective, which would be surprising for site owners and could lead to downtime.
2025-03-12 16:33:14 -06:00
Steffen Busch
2ac09fdb20
requestbody: Fix ContentLength calculation after body replacement (#6896) 2025-03-12 22:18:02 +00:00
Adrien Pensart
dccf3d8982
requestbody: Add set option to replace request body (#5795)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-03-12 19:38:51 +00:00
Matthew Holt
af2d33afbb
headers: Allow nil HeaderOps (fix #6893) 2025-03-11 08:52:15 -06:00
Matthew Holt
39262f8663 caddytls: Minor fixes for ECH 2025-03-11 08:12:48 -06:00
jjiang-stripe
49f9af9a4a
caddytls: Fix TrustedCACerts backwards compatibility (#6889)
* add failing test

* fix ca pool provisioning

* remove unused param
2025-03-10 12:50:47 -06:00
Matthew Holt
d57ab215a2 caddytls: Pointer receiver (fix #6885) 2025-03-08 14:19:06 -07:00
Steffen Busch
f4432a306a
caddyfile: add error handling for unrecognized subdirective/options in various modules (#6884) 2025-03-08 23:45:05 +03:00
WeidiDeng
220cd1c2bc
reverseproxy: more comments about buffering and add new tests (#6778)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-03-07 11:22:43 -07:00
Matthew Holt
1975408d89 chore: Remove unnecessary explicit type parameters 2025-03-07 11:18:00 -07:00
Matthew Holt
4ebcfed9c9 caddytls: Reorder provisioning steps (fix #6877)
Also add a quick check to allow users to load their own certs for ECH (outer) domains.
2025-03-07 11:18:00 -07:00
Kévin Dunglas
d2a2311bfd
ci: fix Go matrix (#6846)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-03-07 10:40:51 -07:00
Matthew Holt
adbe7f87e6
caddytls: Only make DNS solver if not already set (fix #6880) 2025-03-07 09:46:43 -07:00
Matthew Holt
19876208c7
cmd: Promote undo maxProcs func to caller 2025-03-06 16:47:02 -07:00
Matthew Holt
a686f7c346
cmd: Only set memory/CPU limits on run (fix #6879) 2025-03-06 15:11:38 -07:00
Matthew Holt
84364ffcd0
caddypki: Remove lifetime check at Caddyfile parse (fix #6878)
The same check is done at provision time of the ACME server, and that is the correct place to do it.
2025-03-06 11:40:03 -07:00
Matthew Holt
1641e76fd7
go.mod: Upgrade dependencies 2025-03-06 09:52:02 -07:00
Matthew Holt
bc3d497739
caddytls: Fix broken refactor
Not sure how that happened...
2025-03-06 08:54:40 -07:00
Matthew Holt
a807fe0659
caddytls: Enhance ECH documentation 2025-03-06 08:52:52 -07:00
Matthew Holt
3207769232 Update min go version in readme 2025-03-06 06:51:21 -07:00
sashaphmn
481bc80d6e
readme: update Twitter name and link (#6874) 2025-03-06 08:21:30 +00:00
dependabot[bot]
3644ee31ca
build(deps): bump github.com/cloudflare/circl from 1.3.3 to 1.3.7 (#6876)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.3.3 to 1.3.7.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.3.3...v1.3.7)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-05 17:11:11 -07:00
Matt Holt
d7764dfdbb
caddytls: Encrypted ClientHello (ECH) (#6862)
* caddytls: Initial commit of Encrypted ClientHello (ECH)

* WIP Caddyfile

* Fill out Caddyfile support

* Enhance godoc comments

* Augment, don't overwrite, HTTPS records

* WIP

* WIP: publication history

* Fix republication logic

* Apply global DNS module to ACME challenges

This allows DNS challenges to be enabled without locally-configured DNS modules

* Ignore false positive from prealloc linter

* ci: Use only latest Go version (1.24 currently)

We no longer support older Go versions, for security benefits.

* Remove old commented code

Static ECH keys for now

* Implement SendAsRetry
2025-03-05 17:04:10 -07:00
dependabot[bot]
eacd7720e9
build(deps): bump github.com/go-jose/go-jose/v3 from 3.0.3 to 3.0.4 (#6871)
Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v3.0.3...v3.0.4)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-03 14:02:16 -07:00
Mohammed Al Sahaf
02e348f911
chore: upgrade cobra (#6868)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-03-03 13:49:17 -07:00
Matthew Holt
ca37c0b05f Fix typo in TLS group x25519mlkem768 2025-03-03 10:26:42 -07:00
baruchyahalom
8861eae223
caddytest: Support configuration defaults override (#6850) 2025-03-03 14:35:54 +00:00
Marten Seemann
fd4de7e0ae
chore: update quic-go to v0.50.0 (#6854) 2025-02-20 12:45:52 +03:00
Ns2Kracy
0d7c63920d
go.mod: remove glog dependency (#6838)
Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-02-17 15:08:39 +00:00
Mohammed Al Sahaf
6a8d4f1d60
chore: ci: upgrade Go version to 1.24 (#6839)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-02-17 07:58:20 -07:00
Gaurav Dhameeja
d7621fdbe6
tests: tests for error handling & metrics in admin endpoints (#6805)
* feat/tests: tests for error handling & metrics in admin endpoints

- TestAdminHandlerErrorHandling - Tests the handler.handleError()
   functionality by directly verifying error response formatting
- TestAdminHandlerBuiltinRouteErrors - Tests the error
   handling pathway by using real admin server routes and verifying
   both error responses and prometheus metrics increments
- provisionAdminRouters: add unit tests for admin handler registration and routing for admin.api
- TestAllowedOriginsUnixSocket: checks unix socket with default origins are added
- TestReplaceRemoteAdminServer: test for replaceRemoteAdminServer with certificate validation, custom origins and cleanup

* test: added test for manage manageIdentity

---------

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-02-12 11:39:47 +00:00
Matthew Holt
172136a0a0
caddytls: Support post-quantum key exchange mechanism X25519MLKEM768
Also bump minimum Go version to 1.24.
2025-02-11 22:43:54 -07:00
WeidiDeng
22563a70eb
file_server: use the UTC timezone for modified time (#6830)
* use UTC timezone for modified time

* use http.ParseTime to handle If-Modified-Since

* use time.Compare to simplify comparison

* take the directory's modtime into consideration when calculating lastModified

* update comments about If-Modified-Since's handling
2025-02-10 08:39:43 -07:00
Matthew Holt
9b74a53e51
Revert "logging: Always set fields func; fix #6829"
This reverts commit 932dac157a.

Somehow the code I was looking at changed when I committed, without realizing it. This has already been fixed in #6777.
2025-02-07 06:23:43 -07:00
Matthew Holt
932dac157a logging: Always set fields func; fix #6829 2025-02-07 06:18:37 -07:00
Mahdi Mohammadi
96c5c554c1
admin: fix index validation for PUT requests (#6824) 2025-02-04 08:57:32 -07:00
Mohammed Al Sahaf
9283770f68
reverseproxy: ignore duplicate collector registration error (#6820)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-02-04 10:55:30 +03:00
dependabot[bot]
9996d6a70b
build(deps): bump github.com/golang/glog from 1.2.2 to 1.2.4 (#6814)
Bumps [github.com/golang/glog](https://github.com/golang/glog) from 1.2.2 to 1.2.4.
- [Release notes](https://github.com/golang/glog/releases)
- [Commits](https://github.com/golang/glog/compare/v1.2.2...v1.2.4)

---
updated-dependencies:
- dependency-name: github.com/golang/glog
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-28 17:25:11 -07:00
Sander Bruens
cfc3af6749
fix: update broken link to Ardan Labs (#6800) 2025-01-28 21:19:02 +00:00
Mohammed Al Sahaf
904a0fa368
reverse_proxy: re-add healthy upstreams metric (#6806)
* reverse_proxy: re-add healthy upstreams metric

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

* lint

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

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-01-27 14:30:54 -07:00
vnxme
d7872c3bfa
caddytls: Refactor sni matcher (#6812) 2025-01-27 11:42:09 -07:00
Kévin Dunglas
066d770409
cmd: automatically set GOMEMLIMIT (#6809)
* feat: automatically set GOMEMLIMIT

* add system support

* comments

* add logs
2025-01-27 09:32:24 -07:00
Matthew Holt
1115158616 caddyhttp: ResponseRecorder sets stream regardless of 1xx
Fixes a panic where rr.stream is not true when it should be in the event of 1xx, because the buf is nil
2025-01-27 08:18:37 -07:00
vnxme
7b8f3505e3
caddytls: Fix sni_regexp matcher to obtain layer4 contexts (#6804)
* caddytls: Fix sni_regexp matcher

* caddytls: Refactor sni_regexp matcher
2025-01-25 07:45:41 -07:00
Mohammed Al Sahaf
30743c361a
chore: don't use deprecated archives.format_overrides.format (#6807)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2025-01-24 16:37:16 -07:00
Marten Seemann
8d748bee71
chore: update quic-go to v0.49.0 (#6803) 2025-01-23 23:07:19 -05:00
Matthew Holt
99073eaa33
go.mod: Upgrade CertMagic to v0.21.7
Fixes rare edge case panics related to ARI
2025-01-17 06:54:58 -07:00
Matthew Holt
e7da3b267b
reverseproxy: Via header (#6275) 2025-01-17 06:49:01 -07:00
Omar Ramadan
9e0e5a4b4c
logging: Fix crash if logging error is not HandlerError (#6777)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-01-16 10:06:29 -07:00
Matthew Holt
2c4295ee48
caddytls: Initial support for ACME profiles
Still very experimental; only deployed to LE staging so far.
2025-01-09 13:57:00 -07:00
Kévin Dunglas
1f35a8a402
fastcgi: improve parsePHPFastCGI docs (#6779) 2025-01-09 11:54:44 -07:00
Matthew Holt
0e570e0cc7
go.mod: UPgrade CertMagic to 0.21.6 (fix ARI handshake maintenance) 2025-01-08 07:43:27 -07:00
Arsh
e48b75843b
header: match subdirective for response matching (#6765) 2025-01-07 22:48:06 -07:00
Matt Holt
1f927d6b07
log: Only chmod if permission bits differ; make log dir (#6761)
* log: Only chmod if permission bits differ

Follow-up to #6314 and https://caddy.community/t/caddy-2-9-0-breaking-change/27576/11

* Fix test

* Refactor FileWriter

* Ooooh octal... right...
2025-01-07 21:51:03 -07:00
Hyeonggeun Oh
50778b5542
fix: disable h3 for unix domain socket (#6769) 2025-01-07 17:21:57 -07:00
WeidiDeng
1bd567d7ad
reverseproxy: buffer requests for fastcgi by default (#6759)
* buffer requests for fastcgi by default

* fix import cycle

* fix the return value of bufferedBody

* more comments about fastcgi buffering

---------

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2025-01-02 11:18:25 -07:00
Matthew Holt
34cff4af7d
core: Only initiate exit once (should fix #6707) 2024-12-31 13:08:58 -07:00
Matthew Holt
3f3f8b3d52
go.mod: Upgrade CertMagic to v0.21.5 2024-12-30 10:51:55 -07:00
Mohammed Al Sahaf
f2c17d1f3f
testing: sort force-automated hosts (#6756) 2024-12-30 05:38:29 -05:00
Francis Lavoie
afa778ae05
httpcaddyfile: Implement experimental force_automate option (#6712) 2024-12-24 08:58:40 -07:00
WeidiDeng
5ba1e06fd6
encode: try to use sendfile when compression is not used (#6749)
* try to use sendfile when encode is enabled

* change variable name

* add comments

* remove connect check since it's done in Write method
2024-12-20 21:37:16 +00:00
Francis Lavoie
c216cf551d
caddyhttp: Allow matching Transfer-Encoding, add to access logs (#6629)
* caddyhttp: Allow matching Transfer-Encoding

* Log transfer_encoding on the request

---------

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2024-12-20 11:16:34 -07:00
Matthew Holt
ed1c594cdb go.mod: Upgrade ACMEz to v3; and upgrade CertMagic 2024-12-19 12:17:07 -07:00
Matthew Holt
66c80caf23 cmd: Disable go1.23 tlskyber=1 experiment
Compilation with go 1.24 fails with it, since the experiment has ended. We'll reintroduce post quantum soon.
2024-12-19 12:17:07 -07:00
Matt Holt
47391e4ec7
Update SECURITY.md
Clarify version support
2024-12-18 21:36:22 -07:00
WeidiDeng
6790c0e38a
fastcgi: check for CONTENT_LENGTH when sending requests (#6661)
* fastcgi: check for CONTENT_LENGTH when sending requests

* order imports

* use strconv.ParseUint instead of strconv.ParseInt

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>

---------

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
2024-12-18 00:22:12 +00:00
WeidiDeng
c864b82ae1
reverseproxy: Set Content-Length when body is fully buffered (#6638) 2024-12-17 23:36:13 +00:00
Aaron Paterson
e76405d550
core: Change ListenerFunc signature (#6651)
* right side in tls ln

* remove ParseNetworkAddressFromHostPort

* ignore placeholder port

* remove println

* update test cases (!!!)

* []

* comment

* Trim

* err

* Update addresses.go

* revert na.JoinHostPort

* Update listeners.go
2024-12-12 15:34:50 -07:00
bt90
328fb614f0
reverseproxy: Only handle websocket protocol (#6740) 2024-12-11 11:17:05 -07:00
WeidiDeng
bcaa8aaf11
encode: write status immediate for success response for CONNECT requests (#6738)
* encode: write status immediate for success response for CONNECT requests

* fix compile

* fix test

* fix lint

* treat first write and flush for encode response writer to CONNECT request as success if status is not set explicitly
2024-12-11 11:15:01 -07:00
Kévin Dunglas
d0e209e1da
encode: good defaults (#6737)
* feat: good default for encode

* fix tests and add a new one
2024-12-10 16:48:30 -07:00
Kévin Dunglas
290cfea08f
fileserver: add a test for precompressed defaults (#6743) 2024-12-10 16:48:02 -07:00
Kévin Dunglas
5c2617ebf9
fileserver: good default for precompressed (#6736) 2024-12-10 08:31:43 -07:00
José Padilla
57ae9c3107
chore: fix some typo in HTTPLoader comment (#6735) 2024-12-07 11:30:20 +00:00
WeidiDeng
9c0c71e577
reverseproxy: Rewrite requests and responses for websocket over http2 (#6567)
* reverse proxy: rewrite requests and responses for websocket over http2

* delete protocol pseudo-header

* modify cloned requests

* set request variable to track if it's a h2 websocket

* use request bodu

* rewrite request body

* use WebSocket instead of Websocket in the headers

* use logger check for zap loggers

* fix lint
2024-12-06 13:23:27 -07:00
WeidiDeng
a1751adb40
chore: bump golang.org/x/net to v0.32.0 (#6728) 2024-12-05 09:28:07 -05:00
Francis Lavoie
d0123bd760
fileserver: Fix policy Validate() oversight (#6727) 2024-12-04 14:01:58 -05:00
Aziz Rmadi
fb72793269
cmd: Reject multiple configs for fmt command (#6717) 2024-12-04 05:43:52 -07:00
Kévin Dunglas
efd9251ad3
fileserver: Add first_exist_fallback strategy for try_files (#6699)
* feat: add first_exist_or_fallback strategy for try_files

* fix tests

* linter
2024-12-03 05:44:49 -07:00
Francis Lavoie
b116dcea3d
caddyhttp: Add {?query} placeholder (#6714)
* caddyhttp: Add `{prefixed_query}` placeholder

* fastcgi: Preserve query during canonical redirect

* Use orig_uri instead for the redirect, shorter Caddyfile shortcut
2024-12-02 08:06:38 -05:00
Mohammed Al Sahaf
16d5b22349
ci: prevent jobs running on PRs from forks (#6720)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2024-11-30 17:05:09 +03:00
Matthew Holt
22b9d51268
go.mod: Upgrade quic-go to 0.48.2 2024-11-26 10:47:55 -07:00
Mohammed Al Sahaf
1d106fa14d
metrics: add go and process collectors (#6704)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2024-11-23 02:38:59 +03:00
Rishita Shaw
8c3dd3de70
requestbody: Type-based error handling for MaxBytesError (#6701)
* fix: handle "request body too large" error using type assertion

* fix: address overlooked nil check for MaxBytesError

* fix: replace type assertion with errors.As() for MaxBytesError
2024-11-22 19:45:58 +00:00
Kévin Dunglas
eddbccd298
fastcgi: remove dir redirection when useless in php_fastcgi (#6698)
* perf: remove dir redirection when useless in php_fastcgi

* fix test

* review

* fix

* fix

* simplify

* simplify again

* restore test

* add test
2024-11-21 10:38:31 -07:00
Matthew Holt
197c564f20
caddyhttp: Set default ReadHeaderTimeout (1 min)
Ref. #6663
2024-11-19 11:24:12 -07:00
santhoshkumar
b3ce260389
cmd: ignore missing keys during storage export (#6697) 2024-11-18 14:40:51 +00:00
WeidiDeng
6028ff27fa
chore: make FastAbs comment more easy to understand (#6692) 2024-11-14 20:49:42 -07:00
Francis Lavoie
37f0c4bfae
chore: Add provides to .deb releases (#6691)
Fixes https://github.com/caddyserver/dist/issues/91
2024-11-14 14:15:02 -07:00
Francis Lavoie
315715e90f
core: Implement FastAbs to avoid repeated os.Getwd calls (#6687)
* core: Implement FastAbs to avoid repeated os.Getwd calls

* Lint

* Rename files
2024-11-13 03:55:51 -05:00
Matthew Holt
238f1108e6
reverseproxy: Revert #4952 - don't ignore context cancellation in stream mode
i.e. Revert commit f5dce84a70

Two years ago, the patch in #4952 was a seemingly necessary way to fix an issue (sort of an edge case), but it broke other more common use cases (see #6666).

Now, as of #6669, it seems like the original issue can no longer be replicated, so we are reverting that patch, because it was incorrect anyway.

If it turns out the original issue returns, a more proper patch may be in #6669 (even if used as a baseline for a future fix). A potential future fix could be an opt-in setting.
2024-11-12 13:43:34 -07:00
Nikolai K
b183aec83c
httpcaddyfile: Implement log sampling config (#6682)
* Allow log sampling configuration from Caddyfile

* Add log sampling adapt tests
2024-11-11 16:42:50 -07:00
Sucipto
825fe48e06
reverseproxy: Allow 0 as weights for weighted_round_robin (#6681)
* Allow 0 as weights

Change positive to non-negative

* reverseproxy: allow 0 as weighted round robin value

* test: add more wrr select test

---------

Co-authored-by: peanutduck <peanutduck@yahoo.com>
2024-11-07 17:58:31 -05:00
Mohammed Al Sahaf
b285763969
ci: use commit sha in goreleaser-check (#6677)
* ci: use commit sha in goreleaser-check

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

* fix output assignment

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

* run only on non-fork or caddyserver repo, and use branch name

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

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2024-11-05 16:07:25 -05:00
Matthew Holt
da88ec152c go.mod: Update certmagic 2024-11-05 11:03:54 -07:00
Francis Lavoie
5c8dc34418
caddytls: Allow disabling storage cleaning, avoids writing two files (#6593) 2024-11-05 10:47:41 -07:00
Francis Lavoie
5823eccf99
rewrite: Don't add / in Caddyfile, do it after replacer (#6662) 2024-11-05 10:15:31 -07:00
Atakan Yenel
cc23ad6402
fileserver: Add file_limit option for browse (to be experimental) (#6648)
* Add file_limit option for file_server browse

* Move file_limit inside browse.

* add file_server_file_limit caddyfile adapt test.
2024-11-05 09:35:32 -07:00
Mohammed Al Sahaf
91e34139a1
go.mod: upgrade only some otel deps (#6676)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2024-11-04 19:10:05 -05:00
Francis Lavoie
09b2cbcf4d
caddyhttp: Add MatchWithError to replace SetVar hack (#6596)
* caddyhttp: Add `MatchWithError` to replace SetVar hack

* Error in IP matchers on TLS handshake not complete

* Use MatchWithError everywhere possible

* Move implementations to MatchWithError versions

* Looser interface checking to allow fallback

* CEL factories can return RequestMatcherWithError

* Clarifying comment since it's subtle that an err is returned

* Return 425 Too Early status in IP matchers

* Keep AnyMatch signature the same for now

* Apparently Deprecated can't be all-uppercase to get IDE linting

* Linter
2024-11-04 23:18:50 +00:00
Matthew Holt
a3481f871b Fix tests 2024-11-04 16:04:10 -07:00
Francis Lavoie
05cfb121ec
forwardauth: Skip copying missing response headers (#6608) 2024-11-04 14:58:53 -07:00
Matthew Holt
00f948c605
go.mod: Update dependencies 2024-11-04 14:53:10 -07:00
Andreas Kohn
1d156527ea
events: Use WithLazy to prevent eager serialization of the event data (#6671) 2024-11-01 11:28:50 -06:00
Matthew Holt
350ad38f63
fileserver: Fix Caddyfile parsing
Reported at https://github.com/mholt/caddy-sqlite-fs/issues/3
2024-10-31 10:37:37 -06:00
Francis Lavoie
b129ed6be8
httpcaddyfile: Fixes for prefer_wildcard mode (#6636)
* httpcaddyfile: Fixes for prefer_wildcard mode

The wildcard hosts need to be collected first, then considered after, because there's no guarantee that all non-wildcards will appear after all wildcards when looping. Also we should not add a domain to Skip if it doesn't qualify for TLS anyway.

* Alternate solution by avoiding adding APs altogether if covered by wildcard
2024-10-30 17:09:12 +00:00
Lucas VerÍssimo Botelho
d398898b35
cmd: Allow add-package to select version of package (#6665)
* feat: allow versioning of packages

* docs: remove xcaddy issue reference
2024-10-30 16:48:36 +00:00
Kévin Dunglas
eaaa2e5872
chore: compile without nosql's support for Postgres and MySQL (#6655)
* chore: compile without nosql's support for Postgres and MySQL

* Update cross-build.yml

* Update cross-build.yml

* Update README.md
2024-10-22 14:53:14 -06:00
Francis Lavoie
4457afc170
chore: Bump quic-go to 0.48.1, fixing a panic (#6654) 2024-10-22 19:29:46 +00:00
Francis Lavoie
fbf0f4c425
reverseproxy: Sync changes from stdlib for 1xx handling (#6656)
* reverseproxy: Sync changes from stdlib for 1xx handling

Sourced from 960654be0c

* Use clear()

3bc28402fa
2024-10-22 12:10:46 -06:00
Matthew Holt
5e6024c48d
reverseproxy: Fix log message
Fixes regression from #6560
2024-10-21 12:19:04 -06:00
Yifan Yang
669fc41e63
tracing: Add spanID field to access logs and http.vars.span_id placeholder (#6646)
* logging: Add spanID field to access logs when tracing is enabled

Signed-off-by: YifanYang6 <yifanyang6@link.cuhk.edu.cn>

* tracing: add `http.vars.span_id` placeholder when tracing is enabled

Signed-off-by: YifanYang6 <yifanyang6@link.cuhk.edu.cn>

---------

Signed-off-by: YifanYang6 <yifanyang6@link.cuhk.edu.cn>
2024-10-21 11:06:55 -06:00
Aaron Paterson
0182fb87fa
core: addresses.go funcs renames (#6622)
* right side in tls ln

* remove ParseNetworkAddressFromHostPort

* ignore placeholder port

* remove println

* update test cases (!!!)

* []

* comment

* Trim

* Update addresses.go
2024-10-21 08:02:29 -06:00
David Wood
1391e8ed9a
chore: fix some function names in comment (#6650) 2024-10-21 07:39:58 +00:00
Logan Fleur
9753c44510
fileserver: fix try_policy when instantiating file matcher from CEL (#6624)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2024-10-20 14:08:30 +00:00
Kévin Dunglas
b443190b66
sigtrap: always ignore SIGPIPE (#6645) 2024-10-19 07:33:50 -06:00
Mohammed Al Sahaf
388c7e898c
metrics: move metrics up, outside servers (#6606)
* metrics: move `metrics`  up, outside `servers`

This change moves the metrics configuration from per-server level to a single config knob within the `http` app. Enabling `metrics` in any of the configured servers inside `http` enables metrics for all servers.

Fix #6604

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

* normalize domain name

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2024-10-18 09:54:21 -06:00
WeidiDeng
c6f2979986
caddyhttp: Close http3 server gracefully (#6213)
* close http3 server gracefully

* update server field

* update from upstream

---------

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2024-10-15 21:28:20 -04:00
Marten Seemann
a211c656f1
chore: update quic-go to v0.48.0 (#6627) 2024-10-15 09:38:10 -04:00
WeidiDeng
48ce47f1d4
reverseproxy: Use correct cases for websocket related headers (#6621)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2024-10-11 09:02:23 +00:00
Francis Lavoie
ef4e0224a8
caddyfile: Fix comma edgecase in address parsing (#6616) 2024-10-10 20:26:59 +00:00
Mohammed Al Sahaf
c8a76d003f
docs: expand proxy protocol docs (#6620) 2024-10-10 16:21:26 -04:00
Mohammed Al Sahaf
dd5decabe7
tests: fix caddyfile adapt warnings (#6619)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2024-10-10 13:38:49 -06:00
Francis Lavoie
d7564d632f
caddytls: Drop rate_limit and burst, has been deprecated (#6611) 2024-10-07 17:39:47 -04:00
Matthew Holt
88fd5f3491
caddyhttp: Use internal issuer for IPs when no APs configured
This fixes a regression in 2.8 where IP addresses
would be considered qualifying for public certs
by auto-HTTPS. The default issuers do not issue
IP certs at this time, so if no APs are explicitly
configured, we assign them to the internal
issuer. We have to add a couple lines of code because
CertMagic can no longer consider IPs as not
qualifying for public certs, since there are public CAs
that issue IP certs. This edge case is specific to Caddy's
auto-HTTPS.

Without this patch, Caddy will try using Let's Encrypt
or ZeroSSL's ACME endpoint to get IP certs, neither
of which support that.
2024-10-04 10:23:30 -06:00
Matthew Holt
2ae58ac13e
go.mod: Upgrade some dependencies 2024-10-02 16:00:48 -06:00
Mohammed Al Sahaf
01be1b54a8
ci: install xcaddy to fix release flow (#6602) 2024-10-02 16:12:29 +00:00
134 changed files with 6021 additions and 1229 deletions

10
.github/SECURITY.md vendored
View file

@ -5,11 +5,11 @@ The Caddy project would like to make sure that it stays on top of all practicall
## Supported Versions ## Supported Versions
| Version | Supported | | Version | Supported |
| ------- | ------------------ | | -------- | ----------|
| 2.x | ✔️ | | 2.latest | ✔️ |
| 1.x | :x: | | 1.x | :x: |
| < 1.x | :x: | | < 1.x | :x: |
## Acceptable Scope ## Acceptable Scope

View file

@ -12,6 +12,10 @@ on:
- master - master
- 2.* - 2.*
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
jobs: jobs:
test: test:
strategy: strategy:
@ -23,17 +27,13 @@ jobs:
- mac - mac
- windows - windows
go: go:
- '1.22' - '1.24'
- '1.23'
include: include:
# Set the minimum Go patch version for the given Go minor # Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }} # Usable via ${{ matrix.GO_SEMVER }}
- go: '1.22' - go: '1.24'
GO_SEMVER: '~1.22.3' GO_SEMVER: '~1.24.1'
- go: '1.23'
GO_SEMVER: '~1.23.0'
# Set some variables per OS, usable via ${{ matrix.VAR }} # 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) # 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)
@ -99,7 +99,7 @@ jobs:
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
run: | run: |
go build -tags nobadger -trimpath -ldflags="-w -s" -v go build -tags nobadger,nomysql,nopgx -trimpath -ldflags="-w -s" -v
- name: Smoke test Caddy - name: Smoke test Caddy
working-directory: ./cmd/caddy working-directory: ./cmd/caddy
@ -122,7 +122,7 @@ jobs:
# continue-on-error: true # continue-on-error: true
run: | run: |
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out # (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
go test -tags nobadger -v -coverprofile="cover-profile.out" -short -race ./... go test -tags nobadger,nomysql,nopgx -v -coverprofile="cover-profile.out" -short -race ./...
# echo "status=$?" >> $GITHUB_OUTPUT # echo "status=$?" >> $GITHUB_OUTPUT
# Relevant step if we reinvestigate publishing test/coverage reports # Relevant step if we reinvestigate publishing test/coverage reports
@ -143,7 +143,7 @@ jobs:
s390x-test: s390x-test:
name: test (s390x on IBM Z) name: test (s390x on IBM Z)
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
steps: steps:
- name: Checkout code - name: Checkout code
@ -170,7 +170,7 @@ jobs:
retries=3 retries=3
exit_code=0 exit_code=0
while ((retries > 0)); do while ((retries > 0)); do
CGO_ENABLED=0 go test -p 1 -tags nobadger -v ./... CGO_ENABLED=0 go test -p 1 -tags nobadger,nomysql,nopgx -v ./...
exit_code=$? exit_code=$?
if ((exit_code == 0)); then if ((exit_code == 0)); then
break break
@ -194,6 +194,7 @@ jobs:
goreleaser-check: goreleaser-check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -202,3 +203,18 @@ jobs:
with: with:
version: latest version: latest
args: check args: check
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: "~1.24"
check-latest: true
- name: Install xcaddy
run: |
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy version
- uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: build --single-target --snapshot
env:
TAG: ${{ github.head_ref || github.ref_name }}

View file

@ -10,6 +10,10 @@ on:
- master - master
- 2.* - 2.*
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
jobs: jobs:
build: build:
strategy: strategy:
@ -27,17 +31,13 @@ jobs:
- 'darwin' - 'darwin'
- 'netbsd' - 'netbsd'
go: go:
- '1.22' - '1.24'
- '1.23'
include: include:
# Set the minimum Go patch version for the given Go minor # Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }} # Usable via ${{ matrix.GO_SEMVER }}
- go: '1.22' - go: '1.24'
GO_SEMVER: '~1.22.3' GO_SEMVER: '~1.24.1'
- go: '1.23'
GO_SEMVER: '~1.23.0'
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true continue-on-error: true
@ -70,4 +70,4 @@ jobs:
continue-on-error: true continue-on-error: true
working-directory: ./cmd/caddy working-directory: ./cmd/caddy
run: | run: |
GOOS=$GOOS GOARCH=$GOARCH go build -tags nobadger -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null

View file

@ -13,6 +13,10 @@ on:
permissions: permissions:
contents: read contents: read
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
jobs: jobs:
# From https://github.com/golangci/golangci-lint-action # From https://github.com/golangci/golangci-lint-action
golangci: golangci:
@ -43,7 +47,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '~1.23' go-version: '~1.24'
check-latest: true check-latest: true
- name: golangci-lint - name: golangci-lint
@ -63,5 +67,5 @@ jobs:
- name: govulncheck - name: govulncheck
uses: golang/govulncheck-action@v1 uses: golang/govulncheck-action@v1
with: with:
go-version-input: '~1.23.0' go-version-input: '~1.24.1'
check-latest: true check-latest: true

View file

@ -5,6 +5,10 @@ on:
tags: tags:
- 'v*.*.*' - 'v*.*.*'
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
jobs: jobs:
release: release:
name: Release name: Release
@ -13,13 +17,13 @@ jobs:
os: os:
- ubuntu-latest - ubuntu-latest
go: go:
- '1.23' - '1.24'
include: include:
# Set the minimum Go patch version for the given Go minor # Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }} # Usable via ${{ matrix.GO_SEMVER }}
- go: '1.23' - go: '1.24'
GO_SEMVER: '~1.23.0' GO_SEMVER: '~1.24.1'
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233 # https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
@ -104,6 +108,10 @@ jobs:
uses: anchore/sbom-action/download-syft@main uses: anchore/sbom-action/download-syft@main
- name: Syft version - name: Syft version
run: syft version run: syft version
- name: Install xcaddy
run: |
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy version
# GoReleaser will take care of publishing those artifacts into the release # GoReleaser will take care of publishing those artifacts into the release
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v6

View file

@ -13,8 +13,7 @@ before:
- cp cmd/caddy/main.go caddy-build/main.go - cp cmd/caddy/main.go caddy-build/main.go
- /bin/sh -c 'cd ./caddy-build && go mod init caddy' - /bin/sh -c 'cd ./caddy-build && go mod init caddy'
# prepare syso files for windows embedding # prepare syso files for windows embedding
- go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest - /bin/sh -c 'for a in amd64 arm arm64; do XCADDY_SKIP_BUILD=1 GOOS=windows GOARCH=$a xcaddy build {{.Env.TAG}}; done'
- /bin/sh -c 'for a in amd64 arm arm64; do XCADDY_SKIP_BUILD=1 GOOS=windows GOARCH=$a $GOPATH/bin/xcaddy build {{.Env.TAG}}; done'
- /bin/sh -c 'mv /tmp/buildenv_*/*.syso caddy-build' - /bin/sh -c 'mv /tmp/buildenv_*/*.syso caddy-build'
# GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env # GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env
# so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate # so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate
@ -84,6 +83,8 @@ builds:
- -s -w - -s -w
tags: tags:
- nobadger - nobadger
- nomysql
- nopgx
signs: signs:
- cmd: cosign - cmd: cosign
@ -110,7 +111,7 @@ archives:
- id: default - id: default
format_overrides: format_overrides:
- goos: windows - goos: windows
format: zip formats: zip
name_template: >- name_template: >-
{{ .ProjectName }}_ {{ .ProjectName }}_
{{- .Version }}_ {{- .Version }}_
@ -191,6 +192,9 @@ nfpms:
preremove: ./caddy-dist/scripts/preremove.sh preremove: ./caddy-dist/scripts/preremove.sh
postremove: ./caddy-dist/scripts/postremove.sh postremove: ./caddy-dist/scripts/postremove.sh
provides:
- httpd
release: release:
github: github:
owner: caddyserver owner: caddyserver

View file

@ -16,7 +16,7 @@
<a href="https://github.com/caddyserver/caddy/actions/workflows/ci.yml"><img src="https://github.com/caddyserver/caddy/actions/workflows/ci.yml/badge.svg"></a> <a href="https://github.com/caddyserver/caddy/actions/workflows/ci.yml"><img src="https://github.com/caddyserver/caddy/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a> <a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
<br> <br>
<a href="https://twitter.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/badge/twitter-@caddyserver-55acee.svg" alt="@caddyserver on Twitter"></a> <a href="https://x.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/twitter/follow/caddyserver" alt="@caddyserver on Twitter"></a>
<a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a> <a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
<br> <br>
<a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a> <a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
@ -67,6 +67,7 @@
- Fully-managed local CA for internal names & IPs - Fully-managed local CA for internal names & IPs
- Can coordinate with other Caddy instances in a cluster - Can coordinate with other Caddy instances in a cluster
- Multi-issuer fallback - Multi-issuer fallback
- Encrypted ClientHello (ECH) support
- **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues - **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues
- **Production-ready** after serving trillions of requests and managing millions of TLS certificates - **Production-ready** after serving trillions of requests and managing millions of TLS certificates
- **Scales to hundreds of thousands of sites** as proven in production - **Scales to hundreds of thousands of sites** as proven in production
@ -87,7 +88,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
Requirements: Requirements:
- [Go 1.22.3 or newer](https://golang.org/dl/) - [Go 1.24.0 or newer](https://golang.org/dl/)
### For development ### For development
@ -131,7 +132,7 @@ $ xcaddy build
4. Initialize a Go module: `go mod init caddy` 4. Initialize a Go module: `go mod init caddy`
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name. 5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name.
6. (Optional) Add plugins by adding their import: `_ "import/path/here"` 6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
7. Compile: `go build` 7. Compile: `go build -tags=nobadger,nomysql,nopgx`
@ -176,7 +177,7 @@ The docs are also open source. You can contribute to them here: https://github.c
## Getting help ## Getting help
- We advise companies using Caddy to secure a support contract through [Ardan Labs](https://www.ardanlabs.com/my/contact-us?dd=caddy) before help is needed. - We advise companies using Caddy to secure a support contract through [Ardan Labs](https://www.ardanlabs.com) before help is needed.
- A [sponsorship](https://github.com/sponsors/mholt) goes a long way! We can offer private help to sponsors. If Caddy is benefitting your company, please consider a sponsorship. This not only helps fund full-time work to ensure the longevity of the project, it provides your company the resources, support, and discounts you need; along with being a great look for your company to your customers and potential customers! - A [sponsorship](https://github.com/sponsors/mholt) goes a long way! We can offer private help to sponsors. If Caddy is benefitting your company, please consider a sponsorship. This not only helps fund full-time work to ensure the longevity of the project, it provides your company the resources, support, and discounts you need; along with being a great look for your company to your customers and potential customers!
@ -192,8 +193,8 @@ Matthew Holt began developing Caddy in 2014 while studying computer science at B
**The name "Caddy" is trademarked.** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". Caddy is a registered trademark of Stack Holdings GmbH. **The name "Caddy" is trademarked.** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". Caddy is a registered trademark of Stack Holdings GmbH.
- _Project on Twitter: [@caddyserver](https://twitter.com/caddyserver)_ - _Project on X: [@caddyserver](https://x.com/caddyserver)_
- _Author on Twitter: [@mholt6](https://twitter.com/mholt6)_ - _Author on X: [@mholt6](https://x.com/mholt6)_
Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company. Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company.

View file

@ -214,7 +214,7 @@ type AdminPermissions struct {
// newAdminHandler reads admin's config and returns an http.Handler suitable // newAdminHandler reads admin's config and returns an http.Handler suitable
// for use in an admin endpoint server, which will be listening on listenAddr. // for use in an admin endpoint server, which will be listening on listenAddr.
func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, ctx Context) adminHandler { func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Context) adminHandler {
muxWrap := adminHandler{mux: http.NewServeMux()} muxWrap := adminHandler{mux: http.NewServeMux()}
// secure the local or remote endpoint respectively // secure the local or remote endpoint respectively
@ -1139,7 +1139,7 @@ traverseLoop:
return fmt.Errorf("[%s] invalid array index '%s': %v", return fmt.Errorf("[%s] invalid array index '%s': %v",
path, idxStr, err) path, idxStr, err)
} }
if idx < 0 || idx >= len(arr) { if idx < 0 || (method != http.MethodPut && idx >= len(arr)) || idx > len(arr) {
return fmt.Errorf("[%s] array index out of bounds: %s", path, idxStr) return fmt.Errorf("[%s] array index out of bounds: %s", path, idxStr)
} }
} }

View file

@ -15,12 +15,19 @@
package caddy package caddy
import ( import (
"context"
"crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest"
"reflect" "reflect"
"sync" "sync"
"testing" "testing"
"github.com/caddyserver/certmagic"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
) )
var testCfg = []byte(`{ var testCfg = []byte(`{
@ -203,3 +210,730 @@ func BenchmarkLoad(b *testing.B) {
Load(testCfg, true) Load(testCfg, true)
} }
} }
func TestAdminHandlerErrorHandling(t *testing.T) {
initAdminMetrics()
handler := adminHandler{
mux: http.NewServeMux(),
}
handler.mux.Handle("/error", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := fmt.Errorf("test error")
handler.handleError(w, r, err)
}))
req := httptest.NewRequest(http.MethodGet, "/error", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code == http.StatusOK {
t.Error("expected error response, got success")
}
var apiErr APIError
if err := json.NewDecoder(rr.Body).Decode(&apiErr); err != nil {
t.Fatalf("decoding response: %v", err)
}
if apiErr.Message != "test error" {
t.Errorf("expected error message 'test error', got '%s'", apiErr.Message)
}
}
func initAdminMetrics() {
if adminMetrics.requestErrors != nil {
prometheus.Unregister(adminMetrics.requestErrors)
}
if adminMetrics.requestCount != nil {
prometheus.Unregister(adminMetrics.requestCount)
}
adminMetrics.requestErrors = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "caddy",
Subsystem: "admin_http",
Name: "request_errors_total",
Help: "Number of errors that occurred handling admin endpoint requests",
}, []string{"handler", "path", "method"})
adminMetrics.requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "caddy",
Subsystem: "admin_http",
Name: "requests_total",
Help: "Count of requests to the admin endpoint",
}, []string{"handler", "path", "code", "method"}) // Added code and method labels
prometheus.MustRegister(adminMetrics.requestErrors)
prometheus.MustRegister(adminMetrics.requestCount)
}
func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
initAdminMetrics()
cfg := &Config{
Admin: &AdminConfig{
Listen: "localhost:2019",
},
}
err := replaceLocalAdminServer(cfg, Context{})
if err != nil {
t.Fatalf("setting up admin server: %v", err)
}
defer func() {
stopAdminServer(localAdminServer)
}()
tests := []struct {
name string
path string
method string
expectedStatus int
}{
{
name: "stop endpoint wrong method",
path: "/stop",
method: http.MethodGet,
expectedStatus: http.StatusMethodNotAllowed,
},
{
name: "config endpoint wrong content-type",
path: "/config/",
method: http.MethodPost,
expectedStatus: http.StatusBadRequest,
},
{
name: "config ID missing ID",
path: "/id/",
method: http.MethodGet,
expectedStatus: http.StatusBadRequest,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := httptest.NewRequest(test.method, fmt.Sprintf("http://localhost:2019%s", test.path), nil)
rr := httptest.NewRecorder()
localAdminServer.Handler.ServeHTTP(rr, req)
if rr.Code != test.expectedStatus {
t.Errorf("expected status %d but got %d", test.expectedStatus, rr.Code)
}
metricValue := testGetMetricValue(map[string]string{
"path": test.path,
"handler": "admin",
"method": test.method,
})
if metricValue != 1 {
t.Errorf("expected error metric to be incremented once, got %v", metricValue)
}
})
}
}
func testGetMetricValue(labels map[string]string) float64 {
promLabels := prometheus.Labels{}
for k, v := range labels {
promLabels[k] = v
}
metric, err := adminMetrics.requestErrors.GetMetricWith(promLabels)
if err != nil {
return 0
}
pb := &dto.Metric{}
metric.Write(pb)
return pb.GetCounter().GetValue()
}
type mockRouter struct {
routes []AdminRoute
}
func (m mockRouter) Routes() []AdminRoute {
return m.routes
}
type mockModule struct {
mockRouter
}
func (m *mockModule) CaddyModule() ModuleInfo {
return ModuleInfo{
ID: "admin.api.mock",
New: func() Module {
mm := &mockModule{
mockRouter: mockRouter{
routes: m.routes,
},
}
return mm
},
}
}
func TestNewAdminHandlerRouterRegistration(t *testing.T) {
originalModules := make(map[string]ModuleInfo)
for k, v := range modules {
originalModules[k] = v
}
defer func() {
modules = originalModules
}()
mockRoute := AdminRoute{
Pattern: "/mock",
Handler: AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(http.StatusOK)
return nil
}),
}
mock := &mockModule{
mockRouter: mockRouter{
routes: []AdminRoute{mockRoute},
},
}
RegisterModule(mock)
addr, err := ParseNetworkAddress("localhost:2019")
if err != nil {
t.Fatalf("Failed to parse address: %v", err)
}
admin := &AdminConfig{
EnforceOrigin: false,
}
handler := admin.newAdminHandler(addr, false, Context{})
req := httptest.NewRequest("GET", "/mock", nil)
req.Host = "localhost:2019"
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Expected status code %d but got %d", http.StatusOK, rr.Code)
t.Logf("Response body: %s", rr.Body.String())
}
if len(admin.routers) != 1 {
t.Errorf("Expected 1 router to be stored, got %d", len(admin.routers))
}
}
type mockProvisionableRouter struct {
mockRouter
provisionErr error
provisioned bool
}
func (m *mockProvisionableRouter) Provision(Context) error {
m.provisioned = true
return m.provisionErr
}
type mockProvisionableModule struct {
*mockProvisionableRouter
}
func (m *mockProvisionableModule) CaddyModule() ModuleInfo {
return ModuleInfo{
ID: "admin.api.mock_provision",
New: func() Module {
mm := &mockProvisionableModule{
mockProvisionableRouter: &mockProvisionableRouter{
mockRouter: m.mockRouter,
provisionErr: m.provisionErr,
},
}
return mm
},
}
}
func TestAdminRouterProvisioning(t *testing.T) {
tests := []struct {
name string
provisionErr error
wantErr bool
routersAfter int // expected number of routers after provisioning
}{
{
name: "successful provisioning",
provisionErr: nil,
wantErr: false,
routersAfter: 0,
},
{
name: "provisioning error",
provisionErr: fmt.Errorf("provision failed"),
wantErr: true,
routersAfter: 1,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
originalModules := make(map[string]ModuleInfo)
for k, v := range modules {
originalModules[k] = v
}
defer func() {
modules = originalModules
}()
mockRoute := AdminRoute{
Pattern: "/mock",
Handler: AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
return nil
}),
}
// Create provisionable module
mock := &mockProvisionableModule{
mockProvisionableRouter: &mockProvisionableRouter{
mockRouter: mockRouter{
routes: []AdminRoute{mockRoute},
},
provisionErr: test.provisionErr,
},
}
RegisterModule(mock)
admin := &AdminConfig{}
addr, err := ParseNetworkAddress("localhost:2019")
if err != nil {
t.Fatalf("Failed to parse address: %v", err)
}
_ = admin.newAdminHandler(addr, false, Context{})
err = admin.provisionAdminRouters(Context{})
if test.wantErr {
if err == nil {
t.Error("Expected error but got nil")
}
} else {
if err != nil {
t.Errorf("Expected no error but got: %v", err)
}
}
if len(admin.routers) != test.routersAfter {
t.Errorf("Expected %d routers after provisioning, got %d", test.routersAfter, len(admin.routers))
}
})
}
}
func TestAllowedOriginsUnixSocket(t *testing.T) {
tests := []struct {
name string
addr NetworkAddress
origins []string
expectOrigins []string
}{
{
name: "unix socket with default origins",
addr: NetworkAddress{
Network: "unix",
Host: "/tmp/caddy.sock",
},
origins: nil, // default origins
expectOrigins: []string{
"", // empty host as per RFC 2616
"127.0.0.1",
"::1",
},
},
{
name: "unix socket with custom origins",
addr: NetworkAddress{
Network: "unix",
Host: "/tmp/caddy.sock",
},
origins: []string{"example.com"},
expectOrigins: []string{
"example.com",
},
},
{
name: "tcp socket on localhost gets all loopback addresses",
addr: NetworkAddress{
Network: "tcp",
Host: "localhost",
StartPort: 2019,
EndPort: 2019,
},
origins: nil,
expectOrigins: []string{
"localhost:2019",
"[::1]:2019",
"127.0.0.1:2019",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
admin := AdminConfig{
Origins: test.origins,
}
got := admin.allowedOrigins(test.addr)
var gotOrigins []string
for _, u := range got {
gotOrigins = append(gotOrigins, u.Host)
}
if len(gotOrigins) != len(test.expectOrigins) {
t.Errorf("Expected %d origins but got %d", len(test.expectOrigins), len(gotOrigins))
return
}
expectMap := make(map[string]struct{})
for _, origin := range test.expectOrigins {
expectMap[origin] = struct{}{}
}
gotMap := make(map[string]struct{})
for _, origin := range gotOrigins {
gotMap[origin] = struct{}{}
}
if !reflect.DeepEqual(expectMap, gotMap) {
t.Errorf("Origins mismatch.\nExpected: %v\nGot: %v", test.expectOrigins, gotOrigins)
}
})
}
}
func TestReplaceRemoteAdminServer(t *testing.T) {
const testCert = `MIIDCTCCAfGgAwIBAgIUXsqJ1mY8pKlHQtI3HJ23x2eZPqwwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDEwMTAwMDAwMFoXDTI0MDEw
MTAwMDAwMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA4O4S6BSoYcoxvRqI+h7yPOjF6KjntjzVVm9M+uHK4lzX
F1L3pSxJ2nDD4wZEV3FJ5yFOHVFqkG2vXG3BIczOlYG7UeNmKbQnKc5kZj3HGUrS
VGEktA4OJbeZhhWP15gcXN5eDM2eH3g9BFXVX6AURxLiUXzhNBUEZuj/OEyH9yEF
/qPCE+EjzVvWxvBXwgz/io4r4yok/Vq/bxJ6FlV6R7DX5oJSXyO0VEHZPi9DIyNU
kK3F/r4U1sWiJGWOs8i3YQWZ2ejh1C0aLFZpPcCGGgMNpoF31gyYP6ZuPDUyCXsE
g36UUw1JHNtIXYcLhnXuqj4A8TybTDpgXLqvwA9DBQIDAQABo1MwUTAdBgNVHQ4E
FgQUc13z30pFC63rr/HGKOE7E82vjXwwHwYDVR0jBBgwFoAUc13z30pFC63rr/HG
KOE7E82vjXwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAHO3j
oeiUXXJ7xD4P8Wj5t9d+E8lE1Xv1Dk3Z+EdG5+dan+RcToE42JJp9zB7FIh5Qz8g
W77LAjqh5oyqz3A2VJcyVgfE3uJP1R1mJM7JfGHf84QH4TZF2Q1RZY4SZs0VQ6+q
5wSlIZ4NXDy4Q4XkIJBGS61wT8IzYFXYBpx4PCP1Qj0PIE4sevEGwjsBIgxK307o
BxF8AWe6N6e4YZmQLGjQ+SeH0iwZb6vpkHyAY8Kj2hvK+cq2P7vU3VGi0t3r1F8L
IvrXHCvO2BMNJ/1UK1M4YNX8LYJqQhg9hEsIROe1OE/m3VhxIYMJI+qZXk9yHfgJ
vq+SH04xKhtFudVBAQ==`
tests := []struct {
name string
cfg *Config
wantErr bool
}{
{
name: "nil config",
cfg: nil,
wantErr: false,
},
{
name: "nil admin config",
cfg: &Config{
Admin: nil,
},
wantErr: false,
},
{
name: "nil remote config",
cfg: &Config{
Admin: &AdminConfig{},
},
wantErr: false,
},
{
name: "invalid listen address",
cfg: &Config{
Admin: &AdminConfig{
Remote: &RemoteAdmin{
Listen: "invalid:address",
},
},
},
wantErr: true,
},
{
name: "valid config",
cfg: &Config{
Admin: &AdminConfig{
Identity: &IdentityConfig{},
Remote: &RemoteAdmin{
Listen: "localhost:2021",
AccessControl: []*AdminAccess{
{
PublicKeys: []string{testCert},
Permissions: []AdminPermissions{{Methods: []string{"GET"}, Paths: []string{"/test"}}},
},
},
},
},
},
wantErr: false,
},
{
name: "invalid certificate",
cfg: &Config{
Admin: &AdminConfig{
Identity: &IdentityConfig{},
Remote: &RemoteAdmin{
Listen: "localhost:2021",
AccessControl: []*AdminAccess{
{
PublicKeys: []string{"invalid-cert-data"},
Permissions: []AdminPermissions{{Methods: []string{"GET"}, Paths: []string{"/test"}}},
},
},
},
},
},
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := Context{
Context: context.Background(),
cfg: test.cfg,
}
if test.cfg != nil {
test.cfg.storage = &certmagic.FileStorage{Path: t.TempDir()}
}
if test.cfg != nil && test.cfg.Admin != nil && test.cfg.Admin.Identity != nil {
identityCertCache = certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) {
return &certmagic.Config{}, nil
},
})
}
err := replaceRemoteAdminServer(ctx, test.cfg)
if test.wantErr {
if err == nil {
t.Error("Expected error but got nil")
}
} else {
if err != nil {
t.Errorf("Expected no error but got: %v", err)
}
}
// Clean up
if remoteAdminServer != nil {
_ = stopAdminServer(remoteAdminServer)
}
})
}
}
type mockIssuer struct {
configSet *certmagic.Config
}
func (m *mockIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
return &certmagic.IssuedCertificate{
Certificate: []byte(csr.Raw),
}, nil
}
func (m *mockIssuer) SetConfig(cfg *certmagic.Config) {
m.configSet = cfg
}
func (m *mockIssuer) IssuerKey() string {
return "mock"
}
type mockIssuerModule struct {
*mockIssuer
}
func (m *mockIssuerModule) CaddyModule() ModuleInfo {
return ModuleInfo{
ID: "tls.issuance.acme",
New: func() Module {
return &mockIssuerModule{mockIssuer: new(mockIssuer)}
},
}
}
func TestManageIdentity(t *testing.T) {
originalModules := make(map[string]ModuleInfo)
for k, v := range modules {
originalModules[k] = v
}
defer func() {
modules = originalModules
}()
RegisterModule(&mockIssuerModule{})
certPEM := []byte(`-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgIIE31FZVaPXTUwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMTI5MTMyNzQzWhcNMTQwNTI5MDAwMDAw
WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN
TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp
bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3lcub2pUwkjC
5GJQA2ZZfJJi6d1QHhEmkX9VxKYGp6gagZuRqJWy9TXP6++1ZzQQxqZLD0TkuxZ9
8i9Nz00000CCBjCCAQQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMGgG
CCsGAQUFBwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3BraS5nb29nbGUuY29t
L0dJQUcyLmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVudHMxLmdvb2dsZS5j
b20vb2NzcDAdBgNVHQ4EFgQUiJxtimAuTfwb+aUtBn5UYKreKvMwDAYDVR0TAQH/
BAIwADAfBgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqBLzAXBgNVHREEEDAO
ggxtYWlsLmdvb2dsZTANBgkqhkiG9w0BAQUFAAOCAQEAMP6IWgNGZE8wP9TjFjSZ
3mmW3A1eIr0CuPwNZ2LJ5ZD1i70ojzcj4I9IdP5yPg9CAEV4hNASbM1LzfC7GmJE
tPzW5tRmpKVWZGRgTgZI8Hp/xZXMwLh9ZmXV4kESFAGj5G5FNvJyUV7R5Eh+7OZX
7G4jJ4ZGJh+5jzN9HdJJHQHGYNIYOzC7+HH9UMwCjX9vhQ4RjwFZJThS2Yb+y7pb
9yxTJZoXC6J0H5JpnZb7kZEJ+Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END CERTIFICATE-----`)
keyPEM := []byte(`-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
...
-----END PRIVATE KEY-----`)
testStorage := certmagic.FileStorage{Path: t.TempDir()}
err := testStorage.Store(context.Background(), "localhost/localhost.crt", certPEM)
if err != nil {
t.Fatal(err)
}
err = testStorage.Store(context.Background(), "localhost/localhost.key", keyPEM)
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
cfg *Config
wantErr bool
checkState func(*testing.T, *Config)
}{
{
name: "nil config",
cfg: nil,
},
{
name: "nil admin config",
cfg: &Config{
Admin: nil,
},
},
{
name: "nil identity config",
cfg: &Config{
Admin: &AdminConfig{},
},
},
{
name: "default issuer when none specified",
cfg: &Config{
Admin: &AdminConfig{
Identity: &IdentityConfig{
Identifiers: []string{"localhost"},
},
},
storage: &testStorage,
},
checkState: func(t *testing.T, cfg *Config) {
if len(cfg.Admin.Identity.issuers) == 0 {
t.Error("Expected at least 1 issuer to be configured")
return
}
if _, ok := cfg.Admin.Identity.issuers[0].(*mockIssuerModule); !ok {
t.Error("Expected mock issuer to be configured")
}
},
},
{
name: "custom issuer",
cfg: &Config{
Admin: &AdminConfig{
Identity: &IdentityConfig{
Identifiers: []string{"localhost"},
IssuersRaw: []json.RawMessage{
json.RawMessage(`{"module": "acme"}`),
},
},
},
storage: &certmagic.FileStorage{Path: "testdata"},
},
checkState: func(t *testing.T, cfg *Config) {
if len(cfg.Admin.Identity.issuers) != 1 {
t.Fatalf("Expected 1 issuer, got %d", len(cfg.Admin.Identity.issuers))
}
mockIss, ok := cfg.Admin.Identity.issuers[0].(*mockIssuerModule)
if !ok {
t.Fatal("Expected mock issuer")
}
if mockIss.configSet == nil {
t.Error("Issuer config was not set")
}
},
},
{
name: "invalid issuer module",
cfg: &Config{
Admin: &AdminConfig{
Identity: &IdentityConfig{
Identifiers: []string{"localhost"},
IssuersRaw: []json.RawMessage{
json.RawMessage(`{"module": "doesnt_exist"}`),
},
},
},
},
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if identityCertCache != nil {
// Reset the cert cache before each test
identityCertCache.Stop()
identityCertCache = nil
}
ctx := Context{
Context: context.Background(),
cfg: test.cfg,
moduleInstances: make(map[string][]Module),
}
err := manageIdentity(ctx, test.cfg)
if test.wantErr {
if err == nil {
t.Error("Expected error but got nil")
}
return
}
if err != nil {
t.Fatalf("Expected no error but got: %v", err)
}
if test.checkState != nil {
test.checkState(t, test.cfg)
}
})
}
}

View file

@ -725,8 +725,10 @@ func Validate(cfg *Config) error {
// Errors are logged along the way, and an appropriate exit // Errors are logged along the way, and an appropriate exit
// code is emitted. // code is emitted.
func exitProcess(ctx context.Context, logger *zap.Logger) { func exitProcess(ctx context.Context, logger *zap.Logger) {
// let the rest of the program know we're quitting // let the rest of the program know we're quitting; only do it once
atomic.StoreInt32(exiting, 1) if !atomic.CompareAndSwapInt32(exiting, 0, 1) {
return
}
// give the OS or service/process manager our 2 weeks' notice: we quit // give the OS or service/process manager our 2 weeks' notice: we quit
if err := notify.Stopping(); err != nil { if err := notify.Stopping(); err != nil {

View file

@ -264,8 +264,13 @@ func (p *parser) addresses() error {
return p.Errf("Site addresses cannot contain a comma ',': '%s' - put a space after the comma to separate site addresses", value) return p.Errf("Site addresses cannot contain a comma ',': '%s' - put a space after the comma to separate site addresses", value)
} }
token.Text = value // After the above, a comma surrounded by spaces would result
p.block.Keys = append(p.block.Keys, token) // in an empty token which we should ignore
if value != "" {
// Add the token as a site address
token.Text = value
p.block.Keys = append(p.block.Keys, token)
}
} }
// Advance token and possibly break out of loop or return error // Advance token and possibly break out of loop or return error
@ -418,7 +423,7 @@ func (p *parser) doImport(nesting int) error {
// make path relative to the file of the _token_ being processed rather // make path relative to the file of the _token_ being processed rather
// than current working directory (issue #867) and then use glob to get // than current working directory (issue #867) and then use glob to get
// list of matching filenames // list of matching filenames
absFile, err := filepath.Abs(p.Dispenser.File()) absFile, err := caddy.FastAbs(p.Dispenser.File())
if err != nil { if err != nil {
return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.File(), err) return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.File(), err)
} }
@ -617,7 +622,7 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
// Tack the file path onto these tokens so errors show the imported file's name // Tack the file path onto these tokens so errors show the imported file's name
// (we use full, absolute path to avoid bugs: issue #1892) // (we use full, absolute path to avoid bugs: issue #1892)
filename, err := filepath.Abs(importFile) filename, err := caddy.FastAbs(importFile)
if err != nil { if err != nil {
return nil, p.Errf("Failed to get absolute path of file: %s: %v", importFile, err) return nil, p.Errf("Failed to get absolute path of file: %s: %v", importFile, err)
} }

View file

@ -555,6 +555,10 @@ func TestParseAll(t *testing.T) {
{"localhost:1234", "http://host2"}, {"localhost:1234", "http://host2"},
}}, }},
{`foo.example.com , example.com`, false, [][]string{
{"foo.example.com", "example.com"},
}},
{`localhost:1234, http://host2,`, true, [][]string{}}, {`localhost:1234, http://host2,`, true, [][]string{}},
{`http://host1.com, http://host2.com { {`http://host1.com, http://host2.com {
@ -614,8 +618,8 @@ func TestParseAll(t *testing.T) {
} }
for j, block := range blocks { for j, block := range blocks {
if len(block.Keys) != len(test.keys[j]) { if len(block.Keys) != len(test.keys[j]) {
t.Errorf("Test %d: Expected %d keys in block %d, got %d", t.Errorf("Test %d: Expected %d keys in block %d, got %d: %v",
i, len(test.keys[j]), j, len(block.Keys)) i, len(test.keys[j]), j, len(block.Keys), block.Keys)
continue continue
} }
for k, addr := range block.GetKeysText() { for k, addr := range block.GetKeysText() {

View file

@ -31,7 +31,7 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// mapAddressToServerBlocks returns a map of listener address to list of server // mapAddressToProtocolToServerBlocks returns a map of listener address to list of server
// blocks that will be served on that address. To do this, each server block is // blocks that will be served on that address. To do this, each server block is
// expanded so that each one is considered individually, although keys of a // expanded so that each one is considered individually, although keys of a
// server block that share the same address stay grouped together so the config // server block that share the same address stay grouped together so the config
@ -329,8 +329,12 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad
// use a map to prevent duplication // use a map to prevent duplication
listeners := map[string]map[string]struct{}{} listeners := map[string]map[string]struct{}{}
for _, lnCfgVal := range lnCfgVals { for _, lnCfgVal := range lnCfgVals {
for _, lnHost := range lnCfgVal.addresses { for _, lnAddr := range lnCfgVal.addresses {
networkAddr, err := caddy.ParseNetworkAddressFromHostPort(lnHost, lnPort) lnNetw, lnHost, _, err := caddy.SplitNetworkAddress(lnAddr)
if err != nil {
return nil, fmt.Errorf("splitting listener address: %v", err)
}
networkAddr, err := caddy.ParseNetworkAddress(caddy.JoinNetworkAddress(lnNetw, lnHost, lnPort))
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing network address: %v", err) return nil, fmt.Errorf("parsing network address: %v", err)
} }

View file

@ -24,7 +24,7 @@ import (
"time" "time"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/mholt/acmez/v2/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@ -84,7 +84,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// parseTLS parses the tls directive. Syntax: // parseTLS parses the tls directive. Syntax:
// //
// tls [<email>|internal]|[<cert_file> <key_file>] { // tls [<email>|internal|force_automate]|[<cert_file> <key_file>] {
// protocols <min> [<max>] // protocols <min> [<max>]
// ciphers <cipher_suites...> // ciphers <cipher_suites...>
// curves <curves...> // curves <curves...>
@ -99,7 +99,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// ca <acme_ca_endpoint> // ca <acme_ca_endpoint>
// ca_root <pem_file> // ca_root <pem_file>
// key_type [ed25519|p256|p384|rsa2048|rsa4096] // key_type [ed25519|p256|p384|rsa2048|rsa4096]
// dns <provider_name> [...] // dns [<provider_name> [...]] (required, though, if DNS is not configured as global option)
// propagation_delay <duration> // propagation_delay <duration>
// propagation_timeout <duration> // propagation_timeout <duration>
// resolvers <dns_servers...> // resolvers <dns_servers...>
@ -107,6 +107,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// dns_challenge_override_domain <domain> // dns_challenge_override_domain <domain>
// on_demand // on_demand
// reuse_private_keys // reuse_private_keys
// force_automate
// eab <key_id> <mac_key> // eab <key_id> <mac_key>
// issuer <module_name> [...] // issuer <module_name> [...]
// get_certificate <module_name> [...] // get_certificate <module_name> [...]
@ -126,6 +127,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
var certManagers []certmagic.Manager var certManagers []certmagic.Manager
var onDemand bool var onDemand bool
var reusePrivateKeys bool var reusePrivateKeys bool
var forceAutomate bool
firstLine := h.RemainingArgs() firstLine := h.RemainingArgs()
switch len(firstLine) { switch len(firstLine) {
@ -133,8 +135,10 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
case 1: case 1:
if firstLine[0] == "internal" { if firstLine[0] == "internal" {
internalIssuer = new(caddytls.InternalIssuer) internalIssuer = new(caddytls.InternalIssuer)
} else if firstLine[0] == "force_automate" {
forceAutomate = true
} else if !strings.Contains(firstLine[0], "@") { } else if !strings.Contains(firstLine[0], "@") {
return nil, h.Err("single argument must either be 'internal' or an email address") return nil, h.Err("single argument must either be 'internal', 'force_automate', or an email address")
} else { } else {
acmeIssuer = &caddytls.ACMEIssuer{ acmeIssuer = &caddytls.ACMEIssuer{
Email: firstLine[0], Email: firstLine[0],
@ -308,10 +312,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
certManagers = append(certManagers, certManager) certManagers = append(certManagers, certManager)
case "dns": case "dns":
if !h.NextArg() {
return nil, h.ArgErr()
}
provName := h.Val()
if acmeIssuer == nil { if acmeIssuer == nil {
acmeIssuer = new(caddytls.ACMEIssuer) acmeIssuer = new(caddytls.ACMEIssuer)
} }
@ -321,12 +321,19 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
if acmeIssuer.Challenges.DNS == nil { if acmeIssuer.Challenges.DNS == nil {
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig) acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
} }
modID := "dns.providers." + provName // DNS provider configuration optional, since it may be configured globally via the TLS app with global options
unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID) if h.NextArg() {
if err != nil { provName := h.Val()
return nil, err modID := "dns.providers." + provName
unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
if err != nil {
return nil, err
}
acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, h.warnings)
} else if h.Option("dns") == nil {
// if DNS is omitted locally, it needs to be configured globally
return nil, h.ArgErr()
} }
acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, h.warnings)
case "resolvers": case "resolvers":
args := h.RemainingArgs() args := h.RemainingArgs()
@ -569,6 +576,15 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
}) })
} }
// if enabled, the names in the site addresses will be
// added to the automation policies
if forceAutomate {
configVals = append(configVals, ConfigValue{
Class: "tls.force_automate",
Value: true,
})
}
// custom certificate selection // custom certificate selection
if len(certSelector.AnyTag) > 0 { if len(certSelector.AnyTag) > 0 {
cp.CertSelection = &certSelector cp.CertSelection = &certSelector
@ -981,6 +997,50 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
} }
cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings) cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings)
case "sampling":
d := h.Dispenser.NewFromNextSegment()
for d.NextArg() {
// consume any tokens on the same line, if any.
}
sampling := &caddy.LogSampling{}
for nesting := d.Nesting(); d.NextBlock(nesting); {
subdir := d.Val()
switch subdir {
case "interval":
if !d.NextArg() {
return nil, d.ArgErr()
}
interval, err := time.ParseDuration(d.Val() + "ns")
if err != nil {
return nil, d.Errf("failed to parse interval: %v", err)
}
sampling.Interval = interval
case "first":
if !d.NextArg() {
return nil, d.ArgErr()
}
first, err := strconv.Atoi(d.Val())
if err != nil {
return nil, d.Errf("failed to parse first: %v", err)
}
sampling.First = first
case "thereafter":
if !d.NextArg() {
return nil, d.ArgErr()
}
thereafter, err := strconv.Atoi(d.Val())
if err != nil {
return nil, d.Errf("failed to parse thereafter: %v", err)
}
sampling.Thereafter = thereafter
default:
return nil, d.Errf("unrecognized subdirective: %s", subdir)
}
}
cl.Sampling = sampling
case "core": case "core":
if !h.NextArg() { if !h.NextArg() {
return nil, h.ArgErr() return nil, h.ArgErr()

View file

@ -62,6 +62,20 @@ func TestLogDirectiveSyntax(t *testing.T) {
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"core":{"module":"mock"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`, output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"core":{"module":"mock"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`,
expectError: false, expectError: false,
}, },
{
input: `:8080 {
log {
sampling {
interval 2
first 3
thereafter 4
}
}
}
`,
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"sampling":{"interval":2,"first":3,"thereafter":4},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`,
expectError: false,
},
} { } {
adapter := caddyfile.Adapter{ adapter := caddyfile.Adapter{

View file

@ -15,6 +15,7 @@
package httpcaddyfile package httpcaddyfile
import ( import (
"cmp"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
@ -186,12 +187,25 @@ func (st ServerType) Setup(
return nil, warnings, err return nil, warnings, err
} }
// hoist the metrics config from per-server to global
metrics, _ := options["metrics"].(*caddyhttp.Metrics)
for _, s := range servers {
if s.Metrics != nil {
metrics = cmp.Or(metrics, &caddyhttp.Metrics{})
metrics = &caddyhttp.Metrics{
PerHost: metrics.PerHost || s.Metrics.PerHost,
}
s.Metrics = nil // we don't need it anymore
}
}
// now that each server is configured, make the HTTP app // now that each server is configured, make the HTTP app
httpApp := caddyhttp.App{ httpApp := caddyhttp.App{
HTTPPort: tryInt(options["http_port"], &warnings), HTTPPort: tryInt(options["http_port"], &warnings),
HTTPSPort: tryInt(options["https_port"], &warnings), HTTPSPort: tryInt(options["https_port"], &warnings),
GracePeriod: tryDuration(options["grace_period"], &warnings), GracePeriod: tryDuration(options["grace_period"], &warnings),
ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings), ShutdownDelay: tryDuration(options["shutdown_delay"], &warnings),
Metrics: metrics,
Servers: servers, Servers: servers,
} }
@ -336,7 +350,7 @@ func (st ServerType) Setup(
// avoid duplicates by sorting + compacting // avoid duplicates by sorting + compacting
sort.Strings(defaultLog.Exclude) sort.Strings(defaultLog.Exclude)
defaultLog.Exclude = slices.Compact[[]string, string](defaultLog.Exclude) defaultLog.Exclude = slices.Compact(defaultLog.Exclude)
} }
} }
// we may have not actually added anything, so remove if empty // we may have not actually added anything, so remove if empty
@ -692,6 +706,16 @@ func (st *ServerType) serversFromPairings(
return specificity(iLongestHost) > specificity(jLongestHost) return specificity(iLongestHost) > specificity(jLongestHost)
}) })
// collect all hosts that have a wildcard in them
wildcardHosts := []string{}
for _, sblock := range p.serverBlocks {
for _, addr := range sblock.parsedKeys {
if strings.HasPrefix(addr.Host, "*.") {
wildcardHosts = append(wildcardHosts, addr.Host[2:])
}
}
}
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled
@ -739,6 +763,14 @@ func (st *ServerType) serversFromPairings(
} }
} }
// collect hosts that are forced to be automated
forceAutomatedNames := make(map[string]struct{})
if _, ok := sblock.pile["tls.force_automate"]; ok {
for _, host := range hosts {
forceAutomatedNames[host] = struct{}{}
}
}
// tls: connection policies // tls: connection policies
if cpVals, ok := sblock.pile["tls.connection_policy"]; ok { if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
// tls connection policies // tls connection policies
@ -770,20 +802,13 @@ func (st *ServerType) serversFromPairings(
} }
// only append this policy if it actually changes something // only append this policy if it actually changes something
if !cp.SettingsEmpty() { if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) {
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp) srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
hasCatchAllTLSConnPolicy = len(hosts) == 0 hasCatchAllTLSConnPolicy = len(hosts) == 0
} }
} }
} }
wildcardHosts := []string{}
for _, addr := range sblock.parsedKeys {
if strings.HasPrefix(addr.Host, "*.") {
wildcardHosts = append(wildcardHosts, addr.Host[2:])
}
}
for _, addr := range sblock.parsedKeys { for _, addr := range sblock.parsedKeys {
// if server only uses HTTP port, auto-HTTPS will not apply // if server only uses HTTP port, auto-HTTPS will not apply
if listenersUseAnyPortOtherThan(srv.Listen, httpPort) { if listenersUseAnyPortOtherThan(srv.Listen, httpPort) {
@ -799,18 +824,6 @@ func (st *ServerType) serversFromPairings(
} }
} }
// If prefer wildcard is enabled, then we add hosts that are
// already covered by the wildcard to the skip list
if srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard && addr.Scheme == "https" {
baseDomain := addr.Host
if idx := strings.Index(baseDomain, "."); idx != -1 {
baseDomain = baseDomain[idx+1:]
}
if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host)
}
}
// If TLS is specified as directive, it will also result in 1 or more connection policy being created // If TLS is specified as directive, it will also result in 1 or more connection policy being created
// Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without // Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without
// specifying prefix "https://" // specifying prefix "https://"
@ -827,6 +840,19 @@ func (st *ServerType) serversFromPairings(
(addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) { (addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) {
addressQualifiesForTLS = true addressQualifiesForTLS = true
} }
// If prefer wildcard is enabled, then we add hosts that are
// already covered by the wildcard to the skip list
if addressQualifiesForTLS && srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard {
baseDomain := addr.Host
if idx := strings.Index(baseDomain, "."); idx != -1 {
baseDomain = baseDomain[idx+1:]
}
if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
srv.AutoHTTPS.SkipCerts = append(srv.AutoHTTPS.SkipCerts, addr.Host)
}
}
// predict whether auto-HTTPS will add the conn policy for us; if so, we // predict whether auto-HTTPS will add the conn policy for us; if so, we
// may not need to add one for this server // may not need to add one for this server
autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy && autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy &&
@ -1095,6 +1121,12 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
return nil, fmt.Errorf("two policies with same match criteria have conflicting default SNI: %s vs. %s", return nil, fmt.Errorf("two policies with same match criteria have conflicting default SNI: %s vs. %s",
cps[i].DefaultSNI, cps[j].DefaultSNI) cps[i].DefaultSNI, cps[j].DefaultSNI)
} }
if cps[i].FallbackSNI != "" &&
cps[j].FallbackSNI != "" &&
cps[i].FallbackSNI != cps[j].FallbackSNI {
return nil, fmt.Errorf("two policies with same match criteria have conflicting fallback SNI: %s vs. %s",
cps[i].FallbackSNI, cps[j].FallbackSNI)
}
if cps[i].ProtocolMin != "" && if cps[i].ProtocolMin != "" &&
cps[j].ProtocolMin != "" && cps[j].ProtocolMin != "" &&
cps[i].ProtocolMin != cps[j].ProtocolMin { cps[i].ProtocolMin != cps[j].ProtocolMin {
@ -1135,6 +1167,9 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
if cps[i].DefaultSNI == "" && cps[j].DefaultSNI != "" { if cps[i].DefaultSNI == "" && cps[j].DefaultSNI != "" {
cps[i].DefaultSNI = cps[j].DefaultSNI cps[i].DefaultSNI = cps[j].DefaultSNI
} }
if cps[i].FallbackSNI == "" && cps[j].FallbackSNI != "" {
cps[i].FallbackSNI = cps[j].FallbackSNI
}
if cps[i].ProtocolMin == "" && cps[j].ProtocolMin != "" { if cps[i].ProtocolMin == "" && cps[j].ProtocolMin != "" {
cps[i].ProtocolMin = cps[j].ProtocolMin cps[i].ProtocolMin = cps[j].ProtocolMin
} }
@ -1448,9 +1483,9 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod
// iterate each pairing of host and path matchers and // iterate each pairing of host and path matchers and
// put them into a map for JSON encoding // put them into a map for JSON encoding
var matcherSets []map[string]caddyhttp.RequestMatcher var matcherSets []map[string]caddyhttp.RequestMatcherWithError
for _, mp := range matcherPairs { for _, mp := range matcherPairs {
matcherSet := make(map[string]caddyhttp.RequestMatcher) matcherSet := make(map[string]caddyhttp.RequestMatcherWithError)
if len(mp.hostm) > 0 { if len(mp.hostm) > 0 {
matcherSet["host"] = mp.hostm matcherSet["host"] = mp.hostm
} }
@ -1509,12 +1544,17 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
if err != nil { if err != nil {
return err return err
} }
rm, ok := unm.(caddyhttp.RequestMatcher)
if !ok { if rm, ok := unm.(caddyhttp.RequestMatcherWithError); ok {
return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
return nil
} }
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil) // nolint:staticcheck
return nil if rm, ok := unm.(caddyhttp.RequestMatcher); ok {
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
return nil
}
return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
} }
// if the next token is quoted, we can assume it's not a matcher name // if the next token is quoted, we can assume it's not a matcher name
@ -1558,7 +1598,7 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
return nil return nil
} }
func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.ModuleMap, error) { func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcherWithError) (caddy.ModuleMap, error) {
msEncoded := make(caddy.ModuleMap) msEncoded := make(caddy.ModuleMap)
for matcherName, val := range matchers { for matcherName, val := range matchers {
jsonBytes, err := json.Marshal(val) jsonBytes, err := json.Marshal(val)
@ -1638,6 +1678,18 @@ func listenersUseAnyPortOtherThan(addresses []string, otherPort string) bool {
return false return false
} }
func mapContains[K comparable, V any](m map[K]V, keys []K) bool {
if len(m) == 0 || len(keys) == 0 {
return false
}
for _, key := range keys {
if _, ok := m[key]; ok {
return true
}
}
return false
}
// specificity returns len(s) minus any wildcards (*) and // specificity returns len(s) minus any wildcards (*) and
// placeholders ({...}). Basically, it's a length count // placeholders ({...}). Basically, it's a length count
// that penalizes the use of wildcards and placeholders. // that penalizes the use of wildcards and placeholders.

View file

@ -19,11 +19,13 @@ import (
"strconv" "strconv"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/mholt/acmez/v2/acme" "github.com/libdns/libdns"
"github.com/mholt/acmez/v3/acme"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/caddyserver/caddy/v2/modules/caddytls"
) )
@ -38,12 +40,13 @@ func init() {
RegisterGlobalOption("fallback_sni", parseOptSingleString) RegisterGlobalOption("fallback_sni", parseOptSingleString)
RegisterGlobalOption("order", parseOptOrder) RegisterGlobalOption("order", parseOptOrder)
RegisterGlobalOption("storage", parseOptStorage) RegisterGlobalOption("storage", parseOptStorage)
RegisterGlobalOption("storage_clean_interval", parseOptDuration) RegisterGlobalOption("storage_check", parseStorageCheck)
RegisterGlobalOption("storage_clean_interval", parseStorageCleanInterval)
RegisterGlobalOption("renew_interval", parseOptDuration) RegisterGlobalOption("renew_interval", parseOptDuration)
RegisterGlobalOption("ocsp_interval", parseOptDuration) RegisterGlobalOption("ocsp_interval", parseOptDuration)
RegisterGlobalOption("acme_ca", parseOptSingleString) RegisterGlobalOption("acme_ca", parseOptSingleString)
RegisterGlobalOption("acme_ca_root", parseOptSingleString) RegisterGlobalOption("acme_ca_root", parseOptSingleString)
RegisterGlobalOption("acme_dns", parseOptACMEDNS) RegisterGlobalOption("acme_dns", parseOptDNS)
RegisterGlobalOption("acme_eab", parseOptACMEEAB) RegisterGlobalOption("acme_eab", parseOptACMEEAB)
RegisterGlobalOption("cert_issuer", parseOptCertIssuer) RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
RegisterGlobalOption("skip_install_trust", parseOptTrue) RegisterGlobalOption("skip_install_trust", parseOptTrue)
@ -53,12 +56,15 @@ func init() {
RegisterGlobalOption("local_certs", parseOptTrue) RegisterGlobalOption("local_certs", parseOptTrue)
RegisterGlobalOption("key_type", parseOptSingleString) RegisterGlobalOption("key_type", parseOptSingleString)
RegisterGlobalOption("auto_https", parseOptAutoHTTPS) RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
RegisterGlobalOption("metrics", parseMetricsOptions)
RegisterGlobalOption("servers", parseServerOptions) RegisterGlobalOption("servers", parseServerOptions)
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions) RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
RegisterGlobalOption("cert_lifetime", parseOptDuration) RegisterGlobalOption("cert_lifetime", parseOptDuration)
RegisterGlobalOption("log", parseLogOptions) RegisterGlobalOption("log", parseLogOptions)
RegisterGlobalOption("preferred_chains", parseOptPreferredChains) RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
RegisterGlobalOption("persist_config", parseOptPersistConfig) RegisterGlobalOption("persist_config", parseOptPersistConfig)
RegisterGlobalOption("dns", parseOptDNS)
RegisterGlobalOption("ech", parseOptECH)
} }
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil } func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
@ -187,6 +193,40 @@ func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
return storage, nil return storage, nil
} }
func parseStorageCheck(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
if !d.Next() {
return "", d.ArgErr()
}
val := d.Val()
if d.Next() {
return "", d.ArgErr()
}
if val != "off" {
return "", d.Errf("storage_check must be 'off'")
}
return val, nil
}
func parseStorageCleanInterval(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
if !d.Next() {
return "", d.ArgErr()
}
val := d.Val()
if d.Next() {
return "", d.ArgErr()
}
if val == "off" {
return false, nil
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("failed to parse storage_clean_interval, must be a duration or 'off' %w", err)
}
return caddy.Duration(dur), nil
}
func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) { func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
if !d.Next() { // consume option name if !d.Next() { // consume option name
return nil, d.ArgErr() return nil, d.ArgErr()
@ -201,25 +241,6 @@ func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
return caddy.Duration(dur), nil return caddy.Duration(dur), nil
} }
func parseOptACMEDNS(d *caddyfile.Dispenser, _ any) (any, error) {
if !d.Next() { // consume option name
return nil, d.ArgErr()
}
if !d.Next() { // get DNS module name
return nil, d.ArgErr()
}
modID := "dns.providers." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
prov, ok := unm.(certmagic.DNSProvider)
if !ok {
return nil, d.Errf("module %s (%T) is not a certmagic.DNSProvider", modID, unm)
}
return prov, nil
}
func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) { func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
eab := new(acme.EAB) eab := new(acme.EAB)
d.Next() // consume option name d.Next() // consume option name
@ -394,36 +415,10 @@ func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", modName, nil) ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", modName, nil)
case "interval": case "interval":
if !d.NextArg() { return nil, d.Errf("the on_demand_tls 'interval' option is no longer supported, remove it from your config")
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, err
}
if ond == nil {
ond = new(caddytls.OnDemandConfig)
}
if ond.RateLimit == nil {
ond.RateLimit = new(caddytls.RateLimit)
}
ond.RateLimit.Interval = caddy.Duration(dur)
case "burst": case "burst":
if !d.NextArg() { return nil, d.Errf("the on_demand_tls 'burst' option is no longer supported, remove it from your config")
return nil, d.ArgErr()
}
burst, err := strconv.Atoi(d.Val())
if err != nil {
return nil, err
}
if ond == nil {
ond = new(caddytls.OnDemandConfig)
}
if ond.RateLimit == nil {
ond.RateLimit = new(caddytls.RateLimit)
}
ond.RateLimit.Burst = burst
default: default:
return nil, d.Errf("unrecognized parameter '%s'", d.Val()) return nil, d.Errf("unrecognized parameter '%s'", d.Val())
@ -472,6 +467,24 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
return val, nil return val, nil
} }
func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) {
d.Next() // consume option name
metrics := new(caddyhttp.Metrics)
for d.NextBlock(0) {
switch d.Val() {
case "per_host":
metrics.PerHost = true
default:
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
}
}
return metrics, nil
}
func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) {
return unmarshalCaddyfileMetricsOptions(d)
}
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) { func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
return unmarshalCaddyfileServerOptions(d) return unmarshalCaddyfileServerOptions(d)
} }
@ -541,3 +554,68 @@ func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() d.Next()
return caddytls.ParseCaddyfilePreferredChainsOptions(d) return caddytls.ParseCaddyfilePreferredChainsOptions(d)
} }
func parseOptDNS(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
if !d.Next() { // get DNS module name
return nil, d.ArgErr()
}
modID := "dns.providers." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
switch unm.(type) {
case libdns.RecordGetter,
libdns.RecordSetter,
libdns.RecordAppender,
libdns.RecordDeleter:
default:
return nil, d.Errf("module %s (%T) is not a libdns provider", modID, unm)
}
return unm, nil
}
func parseOptECH(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
ech := new(caddytls.ECH)
publicNames := d.RemainingArgs()
for _, publicName := range publicNames {
ech.Configs = append(ech.Configs, caddytls.ECHConfiguration{
PublicName: publicName,
})
}
if len(ech.Configs) == 0 {
return nil, d.ArgErr()
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "dns":
if !d.Next() {
return nil, d.ArgErr()
}
providerName := d.Val()
modID := "dns.providers." + providerName
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
ech.Publication = append(ech.Publication, &caddytls.ECHPublication{
Configs: publicNames,
PublishersRaw: caddy.ModuleMap{
"dns": caddyconfig.JSON(caddytls.ECHDNSPublisher{
ProviderRaw: caddyconfig.JSONModuleObject(unm, "name", providerName, nil),
}, nil),
},
})
default:
return nil, d.Errf("ech: unrecognized subdirective '%s'", d.Val())
}
}
return ech, nil
}

View file

@ -240,11 +240,14 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
} }
case "metrics": case "metrics":
caddy.Log().Warn("The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead.")
serverOpts.Metrics = new(caddyhttp.Metrics) serverOpts.Metrics = new(caddyhttp.Metrics)
for nesting := d.Nesting(); d.NextBlock(nesting); { for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() { switch d.Val() {
case "per_host": case "per_host":
serverOpts.Metrics.PerHost = true serverOpts.Metrics.PerHost = true
default:
return nil, d.Errf("unrecognized metrics option '%s'", d.Val())
} }
} }

View file

@ -52,19 +52,27 @@ func NewShorthandReplacer() ShorthandReplacer {
// be used in the Caddyfile, and the right is the replacement. // be used in the Caddyfile, and the right is the replacement.
func placeholderShorthands() []string { func placeholderShorthands() []string {
return []string{ return []string{
"{dir}", "{http.request.uri.path.dir}",
"{file}", "{http.request.uri.path.file}",
"{host}", "{http.request.host}", "{host}", "{http.request.host}",
"{hostport}", "{http.request.hostport}", "{hostport}", "{http.request.hostport}",
"{port}", "{http.request.port}", "{port}", "{http.request.port}",
"{orig_method}", "{http.request.orig_method}",
"{orig_uri}", "{http.request.orig_uri}",
"{orig_path}", "{http.request.orig_uri.path}",
"{orig_dir}", "{http.request.orig_uri.path.dir}",
"{orig_file}", "{http.request.orig_uri.path.file}",
"{orig_query}", "{http.request.orig_uri.query}",
"{orig_?query}", "{http.request.orig_uri.prefixed_query}",
"{method}", "{http.request.method}", "{method}", "{http.request.method}",
"{uri}", "{http.request.uri}",
"{path}", "{http.request.uri.path}", "{path}", "{http.request.uri.path}",
"{dir}", "{http.request.uri.path.dir}",
"{file}", "{http.request.uri.path.file}",
"{query}", "{http.request.uri.query}", "{query}", "{http.request.uri.query}",
"{?query}", "{http.request.uri.prefixed_query}",
"{remote}", "{http.request.remote}", "{remote}", "{http.request.remote}",
"{remote_host}", "{http.request.remote.host}", "{remote_host}", "{http.request.remote.host}",
"{remote_port}", "{http.request.remote.port}", "{remote_port}", "{http.request.remote.port}",
"{scheme}", "{http.request.scheme}", "{scheme}", "{http.request.scheme}",
"{uri}", "{http.request.uri}",
"{uuid}", "{http.request.uuid}", "{uuid}", "{http.request.uuid}",
"{tls_cipher}", "{http.request.tls.cipher_suite}", "{tls_cipher}", "{http.request.tls.cipher_suite}",
"{tls_version}", "{http.request.tls.version}", "{tls_version}", "{http.request.tls.version}",

View file

@ -25,7 +25,7 @@ import (
"strings" "strings"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/mholt/acmez/v2/acme" "github.com/mholt/acmez/v3/acme"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
@ -92,6 +92,28 @@ func (st ServerType) buildTLSApp(
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP) tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
} }
// collect all hosts that have a wildcard in them, and arent HTTP
wildcardHosts := []string{}
// hosts that have been explicitly marked to be automated,
// even if covered by another wildcard
forcedAutomatedNames := make(map[string]struct{})
for _, p := range pairings {
var addresses []string
for _, addressWithProtocols := range p.addressesWithProtocols {
addresses = append(addresses, addressWithProtocols.address)
}
if !listenersUseAnyPortOtherThan(addresses, httpPort) {
continue
}
for _, sblock := range p.serverBlocks {
for _, addr := range sblock.parsedKeys {
if strings.HasPrefix(addr.Host, "*.") {
wildcardHosts = append(wildcardHosts, addr.Host[2:])
}
}
}
}
for _, p := range pairings { for _, p := range pairings {
// avoid setting up TLS automation policies for a server that is HTTP-only // avoid setting up TLS automation policies for a server that is HTTP-only
var addresses []string var addresses []string
@ -115,6 +137,12 @@ func (st ServerType) buildTLSApp(
return nil, warnings, err return nil, warnings, err
} }
// make a plain copy so we can compare whether we made any changes
apCopy, err := newBaseAutomationPolicy(options, warnings, true)
if err != nil {
return nil, warnings, err
}
sblockHosts := sblock.hostsFromKeys(false) sblockHosts := sblock.hostsFromKeys(false)
if len(sblockHosts) == 0 && catchAllAP != nil { if len(sblockHosts) == 0 && catchAllAP != nil {
ap = catchAllAP ap = catchAllAP
@ -125,6 +153,13 @@ func (st ServerType) buildTLSApp(
ap.OnDemand = true ap.OnDemand = true
} }
// collect hosts that are forced to be automated
if _, ok := sblock.pile["tls.force_automate"]; ok {
for _, host := range sblockHosts {
forcedAutomatedNames[host] = struct{}{}
}
}
// reuse private keys tls // reuse private keys tls
if _, ok := sblock.pile["tls.reuse_private_keys"]; ok { if _, ok := sblock.pile["tls.reuse_private_keys"]; ok {
ap.ReusePrivateKeys = true ap.ReusePrivateKeys = true
@ -217,9 +252,21 @@ func (st ServerType) buildTLSApp(
catchAllAP = ap catchAllAP = ap
} }
hostsNotHTTP := sblock.hostsFromKeysNotHTTP(httpPort)
sort.Strings(hostsNotHTTP) // solely for deterministic test results
// if the we prefer wildcards and the AP is unchanged,
// then we can skip this AP because it should be covered
// by an AP with a wildcard
if slices.Contains(autoHTTPS, "prefer_wildcard") {
if hostsCoveredByWildcard(hostsNotHTTP, wildcardHosts) &&
reflect.DeepEqual(ap, apCopy) {
continue
}
}
// associate our new automation policy with this server block's hosts // associate our new automation policy with this server block's hosts
ap.SubjectsRaw = sblock.hostsFromKeysNotHTTP(httpPort) ap.SubjectsRaw = hostsNotHTTP
sort.Strings(ap.SubjectsRaw) // solely for deterministic test results
// if a combination of public and internal names were given // if a combination of public and internal names were given
// for this same server block and no issuer was specified, we // for this same server block and no issuer was specified, we
@ -258,6 +305,7 @@ func (st ServerType) buildTLSApp(
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)} ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
} }
} }
if tlsApp.Automation == nil { if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig) tlsApp.Automation = new(caddytls.AutomationConfig)
} }
@ -311,6 +359,40 @@ func (st ServerType) buildTLSApp(
tlsApp.Automation.OnDemand = onDemand tlsApp.Automation.OnDemand = onDemand
} }
// set up "global" (to the TLS app) DNS provider config
if globalDNS, ok := options["dns"]; ok && globalDNS != nil {
tlsApp.DNSRaw = caddyconfig.JSONModuleObject(globalDNS, "name", globalDNS.(caddy.Module).CaddyModule().ID.Name(), nil)
}
// set up ECH from Caddyfile options
if ech, ok := options["ech"].(*caddytls.ECH); ok {
tlsApp.EncryptedClientHello = ech
// outer server names will need certificates, so make sure they're included
// in an automation policy for them that applies any global options
ap, err := newBaseAutomationPolicy(options, warnings, true)
if err != nil {
return nil, warnings, err
}
for _, cfg := range ech.Configs {
ap.SubjectsRaw = append(ap.SubjectsRaw, cfg.PublicName)
}
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap)
}
// if the storage clean interval is a boolean, then it's "off" to disable cleaning
if sc, ok := options["storage_check"].(string); ok && sc == "off" {
tlsApp.DisableStorageCheck = true
}
// if the storage clean interval is a boolean, then it's "off" to disable cleaning
if sci, ok := options["storage_clean_interval"].(bool); ok && !sci {
tlsApp.DisableStorageClean = true
}
// set the storage clean interval if configured // set the storage clean interval if configured
if storageCleanInterval, ok := options["storage_clean_interval"].(caddy.Duration); ok { if storageCleanInterval, ok := options["storage_clean_interval"].(caddy.Duration); ok {
if tlsApp.Automation == nil { if tlsApp.Automation == nil {
@ -359,6 +441,13 @@ func (st ServerType) buildTLSApp(
} }
} }
} }
for name := range forcedAutomatedNames {
if slices.Contains(al, name) {
continue
}
al = append(al, name)
}
slices.Sort(al) // to stabilize the adapt output
if len(al) > 0 { if len(al) > 0 {
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings) tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
} }
@ -418,10 +507,7 @@ func (st ServerType) buildTLSApp(
} }
// consolidate automation policies that are the exact same // consolidate automation policies that are the exact same
tlsApp.Automation.Policies = consolidateAutomationPolicies( tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
tlsApp.Automation.Policies,
slices.Contains(autoHTTPS, "prefer_wildcard"),
)
// ensure automation policies don't overlap subjects (this should be // ensure automation policies don't overlap subjects (this should be
// an error at provision-time as well, but catch it in the adapt phase // an error at provision-time as well, but catch it in the adapt phase
@ -491,7 +577,8 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil { if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil {
acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference) acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference)
} }
if globalHTTPPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.HTTP == nil || acmeIssuer.Challenges.HTTP.AlternatePort == 0) { // only configure alt HTTP and TLS-ALPN ports if the DNS challenge is not enabled (wouldn't hurt, but isn't necessary since the DNS challenge is exclusive of others)
if globalHTTPPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.HTTP == nil || acmeIssuer.Challenges.HTTP.AlternatePort == 0) {
if acmeIssuer.Challenges == nil { if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig) acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
} }
@ -500,7 +587,7 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
} }
acmeIssuer.Challenges.HTTP.AlternatePort = globalHTTPPort.(int) acmeIssuer.Challenges.HTTP.AlternatePort = globalHTTPPort.(int)
} }
if globalHTTPSPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.TLSALPN == nil || acmeIssuer.Challenges.TLSALPN.AlternatePort == 0) { if globalHTTPSPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.TLSALPN == nil || acmeIssuer.Challenges.TLSALPN.AlternatePort == 0) {
if acmeIssuer.Challenges == nil { if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig) acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
} }
@ -567,7 +654,7 @@ func newBaseAutomationPolicy(
// consolidateAutomationPolicies combines automation policies that are the same, // consolidateAutomationPolicies combines automation policies that are the same,
// for a cleaner overall output. // for a cleaner overall output.
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy, preferWildcard bool) []*caddytls.AutomationPolicy { func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {
// sort from most specific to least specific; we depend on this ordering // sort from most specific to least specific; we depend on this ordering
sort.SliceStable(aps, func(i, j int) bool { sort.SliceStable(aps, func(i, j int) bool {
if automationPolicyIsSubset(aps[i], aps[j]) { if automationPolicyIsSubset(aps[i], aps[j]) {
@ -652,31 +739,6 @@ outer:
j-- j--
} }
} }
if preferWildcard {
// remove subjects from i if they're covered by a wildcard in j
iSubjs := aps[i].SubjectsRaw
for iSubj := 0; iSubj < len(iSubjs); iSubj++ {
for jSubj := range aps[j].SubjectsRaw {
if !strings.HasPrefix(aps[j].SubjectsRaw[jSubj], "*.") {
continue
}
if certmagic.MatchWildcard(aps[i].SubjectsRaw[iSubj], aps[j].SubjectsRaw[jSubj]) {
iSubjs = slices.Delete(iSubjs, iSubj, iSubj+1)
iSubj--
break
}
}
}
aps[i].SubjectsRaw = iSubjs
// remove i if it has no subjects left
if len(aps[i].SubjectsRaw) == 0 {
aps = slices.Delete(aps, i, i+1)
i--
continue outer
}
}
} }
} }
@ -748,3 +810,20 @@ func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
func isTailscaleDomain(name string) bool { func isTailscaleDomain(name string) bool {
return strings.HasSuffix(strings.ToLower(name), ".ts.net") return strings.HasSuffix(strings.ToLower(name), ".ts.net")
} }
func hostsCoveredByWildcard(hosts []string, wildcards []string) bool {
if len(hosts) == 0 || len(wildcards) == 0 {
return false
}
for _, host := range hosts {
for _, wildcard := range wildcards {
if strings.HasPrefix(host, "*.") {
continue
}
if certmagic.MatchWildcard(host, "*."+wildcard) {
return true
}
}
}
return false
}

View file

@ -35,7 +35,7 @@ func init() {
// If the response is not a JSON config, a config adapter must be specified // If the response is not a JSON config, a config adapter must be specified
// either in the loader config (`adapter`), or in the Content-Type HTTP header // either in the loader config (`adapter`), or in the Content-Type HTTP header
// returned in the HTTP response from the server. The Content-Type header is // returned in the HTTP response from the server. The Content-Type header is
// read just like the admin API's `/load` endpoint. Uf you don't have control // read just like the admin API's `/load` endpoint. If you don't have control
// over the HTTP server (but can still trust its response), you can override // over the HTTP server (but can still trust its response), you can override
// the Content-Type header by setting the `adapter` property in this config. // the Content-Type header by setting the `adapter` property in this config.
type HTTPLoader struct { type HTTPLoader struct {

View file

@ -31,8 +31,8 @@ import (
_ "github.com/caddyserver/caddy/v2/modules/standard" _ "github.com/caddyserver/caddy/v2/modules/standard"
) )
// Defaults store any configuration required to make the tests run // Config store any configuration required to make the tests run
type Defaults struct { type Config struct {
// Port we expect caddy to listening on // Port we expect caddy to listening on
AdminPort int AdminPort int
// Certificates we expect to be loaded before attempting to run the tests // Certificates we expect to be loaded before attempting to run the tests
@ -44,7 +44,7 @@ type Defaults struct {
} }
// Default testing values // Default testing values
var Default = Defaults{ var Default = Config{
AdminPort: 2999, // different from what a real server also running on a developer's machine might be AdminPort: 2999, // different from what a real server also running on a developer's machine might be
Certificates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"}, Certificates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
TestRequestTimeout: 5 * time.Second, TestRequestTimeout: 5 * time.Second,
@ -61,6 +61,7 @@ type Tester struct {
Client *http.Client Client *http.Client
configLoaded bool configLoaded bool
t testing.TB t testing.TB
config Config
} }
// NewTester will create a new testing client with an attached cookie jar // NewTester will create a new testing client with an attached cookie jar
@ -78,9 +79,29 @@ func NewTester(t testing.TB) *Tester {
}, },
configLoaded: false, configLoaded: false,
t: t, t: t,
config: Default,
} }
} }
// WithDefaultOverrides this will override the default test configuration with the provided values.
func (tc *Tester) WithDefaultOverrides(overrides Config) *Tester {
if overrides.AdminPort != 0 {
tc.config.AdminPort = overrides.AdminPort
}
if len(overrides.Certificates) > 0 {
tc.config.Certificates = overrides.Certificates
}
if overrides.TestRequestTimeout != 0 {
tc.config.TestRequestTimeout = overrides.TestRequestTimeout
tc.Client.Timeout = overrides.TestRequestTimeout
}
if overrides.LoadRequestTimeout != 0 {
tc.config.LoadRequestTimeout = overrides.LoadRequestTimeout
}
return tc
}
type configLoadError struct { type configLoadError struct {
Response string Response string
} }
@ -113,7 +134,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
return nil return nil
} }
err := validateTestPrerequisites(tc.t) err := validateTestPrerequisites(tc)
if err != nil { if err != nil {
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err) tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
return nil return nil
@ -121,7 +142,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
tc.t.Cleanup(func() { tc.t.Cleanup(func() {
if tc.t.Failed() && tc.configLoaded { if tc.t.Failed() && tc.configLoaded {
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort)) res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
if err != nil { if err != nil {
tc.t.Log("unable to read the current config") tc.t.Log("unable to read the current config")
return return
@ -151,10 +172,10 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
tc.t.Logf("After: %s", rawConfig) tc.t.Logf("After: %s", rawConfig)
} }
client := &http.Client{ client := &http.Client{
Timeout: Default.LoadRequestTimeout, Timeout: tc.config.LoadRequestTimeout,
} }
start := time.Now() start := time.Now()
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig)) req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", tc.config.AdminPort), strings.NewReader(rawConfig))
if err != nil { if err != nil {
tc.t.Errorf("failed to create request. %s", err) tc.t.Errorf("failed to create request. %s", err)
return err return err
@ -205,11 +226,11 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
} }
client := &http.Client{ client := &http.Client{
Timeout: Default.LoadRequestTimeout, Timeout: tc.config.LoadRequestTimeout,
} }
fetchConfig := func(client *http.Client) any { fetchConfig := func(client *http.Client) any {
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort)) resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
if err != nil { if err != nil {
return nil return nil
} }
@ -237,30 +258,30 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
} }
const initConfig = `{ const initConfig = `{
admin localhost:2999 admin localhost:%d
} }
` `
// validateTestPrerequisites ensures the certificates are available in the // validateTestPrerequisites ensures the certificates are available in the
// designated path and Caddy sub-process is running. // designated path and Caddy sub-process is running.
func validateTestPrerequisites(t testing.TB) error { func validateTestPrerequisites(tc *Tester) error {
// check certificates are found // check certificates are found
for _, certName := range Default.Certificates { for _, certName := range tc.config.Certificates {
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) { if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("caddy integration test certificates (%s) not found", certName) return fmt.Errorf("caddy integration test certificates (%s) not found", certName)
} }
} }
if isCaddyAdminRunning() != nil { if isCaddyAdminRunning(tc) != nil {
// setup the init config file, and set the cleanup afterwards // setup the init config file, and set the cleanup afterwards
f, err := os.CreateTemp("", "") f, err := os.CreateTemp("", "")
if err != nil { if err != nil {
return err return err
} }
t.Cleanup(func() { tc.t.Cleanup(func() {
os.Remove(f.Name()) os.Remove(f.Name())
}) })
if _, err := f.WriteString(initConfig); err != nil { if _, err := f.WriteString(fmt.Sprintf(initConfig, tc.config.AdminPort)); err != nil {
return err return err
} }
@ -271,23 +292,23 @@ func validateTestPrerequisites(t testing.TB) error {
}() }()
// wait for caddy to start serving the initial config // wait for caddy to start serving the initial config
for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- { for retries := 10; retries > 0 && isCaddyAdminRunning(tc) != nil; retries-- {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
} }
// one more time to return the error // one more time to return the error
return isCaddyAdminRunning() return isCaddyAdminRunning(tc)
} }
func isCaddyAdminRunning() error { func isCaddyAdminRunning(tc *Tester) error {
// assert that caddy is running // assert that caddy is running
client := &http.Client{ client := &http.Client{
Timeout: Default.LoadRequestTimeout, Timeout: tc.config.LoadRequestTimeout,
} }
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort)) resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
if err != nil { if err != nil {
return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", Default.AdminPort) return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", tc.config.AdminPort)
} }
resp.Body.Close() resp.Body.Close()

View file

@ -6,6 +6,7 @@ import (
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"log/slog"
"net" "net"
"net/http" "net/http"
"strings" "strings"
@ -13,10 +14,11 @@ import (
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v2" "github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v2/acme" "github.com/mholt/acmez/v3/acme"
smallstepacme "github.com/smallstep/certificates/acme" smallstepacme "github.com/smallstep/certificates/acme"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
) )
const acmeChallengePort = 9081 const acmeChallengePort = 9081
@ -48,7 +50,7 @@ func TestACMEServerWithDefaults(t *testing.T) {
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client,
Logger: logger, Logger: slog.New(zapslog.NewHandler(logger.Core())),
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
@ -117,7 +119,7 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) {
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client,
Logger: logger, Logger: slog.New(zapslog.NewHandler(logger.Core())),
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},

View file

@ -5,13 +5,15 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"log/slog"
"strings" "strings"
"testing" "testing"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v2" "github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v2/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
) )
func TestACMEServerDirectory(t *testing.T) { func TestACMEServerDirectory(t *testing.T) {
@ -76,7 +78,7 @@ func TestACMEServerAllowPolicy(t *testing.T) {
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client,
Logger: logger, Logger: slog.New(zapslog.NewHandler(logger.Core())),
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
@ -165,7 +167,7 @@ func TestACMEServerDenyPolicy(t *testing.T) {
Client: &acme.Client{ Client: &acme.Client{
Directory: "https://acme.localhost:9443/acme/local/directory", Directory: "https://acme.localhost:9443/acme/local/directory",
HTTPClient: tester.Client, HTTPClient: tester.Client,
Logger: logger, Logger: slog.New(zapslog.NewHandler(logger.Core())),
}, },
ChallengeSolvers: map[string]acmez.Solver{ ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger}, acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},

View file

@ -5,15 +5,15 @@
root_cn "Internal Root Cert" root_cn "Internal Root Cert"
intermediate_cn "Internal Intermediate Cert" intermediate_cn "Internal Intermediate Cert"
} }
} }
} }
acme.example.com { acme.example.com {
acme_server { acme_server {
ca internal ca internal
sign_with_root sign_with_root
} }
} }
---------- ----------
{ {
"apps": { "apps": {

View file

@ -74,6 +74,9 @@ foo.example.com {
} }
], ],
"automatic_https": { "automatic_https": {
"skip_certificates": [
"foo.example.com"
],
"prefer_wildcard": true "prefer_wildcard": true
} }
} }

View file

@ -0,0 +1,268 @@
{
auto_https prefer_wildcard
}
# Covers two domains
*.one.example.com {
tls {
dns mock
}
respond "one fallback"
}
# Is covered, should not get its own AP
foo.one.example.com {
respond "foo one"
}
# This one has its own tls config so it doesn't get covered (escape hatch)
bar.one.example.com {
respond "bar one"
tls bar@bar.com
}
# Covers nothing but AP gets consolidated with the first
*.two.example.com {
tls {
dns mock
}
respond "two fallback"
}
# Is HTTP so it should not cover
http://*.three.example.com {
respond "three fallback"
}
# Has no wildcard coverage so it gets an AP
foo.three.example.com {
respond "foo three"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"foo.three.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "foo three",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"foo.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "foo one",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"bar.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "bar one",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "one fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.two.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "two fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"automatic_https": {
"skip_certificates": [
"foo.one.example.com",
"bar.one.example.com"
],
"prefer_wildcard": true
}
},
"srv1": {
"listen": [
":80"
],
"routes": [
{
"match": [
{
"host": [
"*.three.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "three fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"automatic_https": {
"prefer_wildcard": true
}
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"foo.three.example.com"
]
},
{
"subjects": [
"bar.one.example.com"
],
"issuers": [
{
"email": "bar@bar.com",
"module": "acme"
},
{
"ca": "https://acme.zerossl.com/v2/DV90",
"email": "bar@bar.com",
"module": "acme"
}
]
},
{
"subjects": [
"*.one.example.com",
"*.two.example.com"
],
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"name": "mock"
}
}
},
"module": "acme"
}
]
}
]
}
}
}
}

View file

@ -21,6 +21,8 @@ encode {
zstd zstd
gzip 5 gzip 5
} }
encode
---------- ----------
{ {
"apps": { "apps": {
@ -76,6 +78,17 @@ encode {
"zstd", "zstd",
"gzip" "gzip"
] ]
},
{
"encodings": {
"gzip": {},
"zstd": {}
},
"handler": "encode",
"prefer": [
"zstd",
"gzip"
]
} }
] ]
} }

View file

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

View file

@ -3,6 +3,10 @@
file_server { file_server {
precompressed zstd br gzip precompressed zstd br gzip
} }
file_server {
precompressed
}
---------- ----------
{ {
"apps": { "apps": {
@ -30,6 +34,22 @@ file_server {
"br", "br",
"gzip" "gzip"
] ]
},
{
"handler": "file_server",
"hide": [
"./Caddyfile"
],
"precompressed": {
"br": {},
"gzip": {},
"zstd": {}
},
"precompressed_order": [
"br",
"zstd",
"gzip"
]
} }
] ]
} }

View file

@ -1,6 +1,6 @@
app.example.com { app.example.com {
forward_auth authelia:9091 { forward_auth authelia:9091 {
uri /api/verify?rd=https://authelia.example.com uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
} }
@ -39,6 +39,13 @@ app.example.com {
] ]
}, },
"routes": [ "routes": [
{
"handle": [
{
"handler": "vars"
}
]
},
{ {
"handle": [ "handle": [
{ {
@ -47,19 +54,104 @@ app.example.com {
"set": { "set": {
"Remote-Email": [ "Remote-Email": [
"{http.reverse_proxy.header.Remote-Email}" "{http.reverse_proxy.header.Remote-Email}"
], ]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-Email}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"Remote-Groups": [ "Remote-Groups": [
"{http.reverse_proxy.header.Remote-Groups}" "{http.reverse_proxy.header.Remote-Groups}"
], ]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-Groups}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"Remote-Name": [ "Remote-Name": [
"{http.reverse_proxy.header.Remote-Name}" "{http.reverse_proxy.header.Remote-Name}"
], ]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-Name}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"Remote-User": [ "Remote-User": [
"{http.reverse_proxy.header.Remote-User}" "{http.reverse_proxy.header.Remote-User}"
] ]
} }
} }
} }
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-User}": [
""
]
}
}
]
}
] ]
} }
] ]
@ -80,7 +172,7 @@ app.example.com {
}, },
"rewrite": { "rewrite": {
"method": "GET", "method": "GET",
"uri": "/api/verify?rd=https://authelia.example.com" "uri": "/api/authz/forward-auth"
}, },
"upstreams": [ "upstreams": [
{ {

View file

@ -28,6 +28,13 @@ forward_auth localhost:9000 {
] ]
}, },
"routes": [ "routes": [
{
"handle": [
{
"handler": "vars"
}
]
},
{ {
"handle": [ "handle": [
{ {
@ -36,22 +43,131 @@ forward_auth localhost:9000 {
"set": { "set": {
"1": [ "1": [
"{http.reverse_proxy.header.A}" "{http.reverse_proxy.header.A}"
], ]
"3": [ }
"{http.reverse_proxy.header.C}" }
], }
"5": [ ],
"{http.reverse_proxy.header.E}" "match": [
], {
"not": [
{
"vars": {
"{http.reverse_proxy.header.A}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"B": [ "B": [
"{http.reverse_proxy.header.B}" "{http.reverse_proxy.header.B}"
], ]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.B}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"3": [
"{http.reverse_proxy.header.C}"
]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.C}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"D": [ "D": [
"{http.reverse_proxy.header.D}" "{http.reverse_proxy.header.D}"
] ]
} }
} }
} }
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.D}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"5": [
"{http.reverse_proxy.header.E}"
]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.E}": [
""
]
}
}
]
}
] ]
} }
] ]

View file

@ -9,6 +9,8 @@
storage file_system { storage file_system {
root /data root /data
} }
storage_check off
storage_clean_interval off
acme_ca https://example.com acme_ca https://example.com
acme_ca_root /path/to/ca.crt acme_ca_root /path/to/ca.crt
ocsp_stapling off ocsp_stapling off
@ -17,8 +19,6 @@
admin off admin off
on_demand_tls { on_demand_tls {
ask https://example.com ask https://example.com
interval 30s
burst 20
} }
local_certs local_certs
key_type ed25519 key_type ed25519
@ -72,14 +72,12 @@
"permission": { "permission": {
"endpoint": "https://example.com", "endpoint": "https://example.com",
"module": "http" "module": "http"
},
"rate_limit": {
"interval": 30000000000,
"burst": 20
} }
} }
}, },
"disable_ocsp_stapling": true "disable_ocsp_stapling": true,
"disable_storage_check": true,
"disable_storage_clean": true
} }
} }
} }

View file

@ -17,8 +17,6 @@
admin off admin off
on_demand_tls { on_demand_tls {
ask https://example.com ask https://example.com
interval 30s
burst 20
} }
storage_clean_interval 7d storage_clean_interval 7d
renew_interval 1d renew_interval 1d
@ -89,10 +87,6 @@
"permission": { "permission": {
"endpoint": "https://example.com", "endpoint": "https://example.com",
"module": "http" "module": "http"
},
"rate_limit": {
"interval": 30000000000,
"burst": 20
} }
}, },
"ocsp_interval": 172800000000000, "ocsp_interval": 172800000000000,

View file

@ -16,8 +16,6 @@
} }
on_demand_tls { on_demand_tls {
ask https://example.com ask https://example.com
interval 30s
burst 20
} }
local_certs local_certs
key_type ed25519 key_type ed25519
@ -74,10 +72,6 @@
"permission": { "permission": {
"endpoint": "https://example.com", "endpoint": "https://example.com",
"module": "http" "module": "http"
},
"rate_limit": {
"interval": 30000000000,
"burst": 20
} }
} }
} }

View file

@ -0,0 +1,23 @@
{
log {
sampling {
interval 300
first 50
thereafter 40
}
}
}
----------
{
"logging": {
"logs": {
"default": {
"sampling": {
"interval": 300,
"first": 50,
"thereafter": 40
}
}
}
}
}

View file

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

View file

@ -1,11 +1,12 @@
example.com { example.com {
respond <<EOF respond <<EOF
<html> <html>
<head><title>Foo</title> <head><title>Foo</title>
<body>Foo</body> <body>Foo</body>
</html> </html>
EOF 200 EOF 200
} }
---------- ----------
{ {
"apps": { "apps": {

View file

@ -0,0 +1,45 @@
:80 {
log {
sampling {
interval 300
first 50
thereafter 40
}
}
}
----------
{
"logging": {
"logs": {
"default": {
"exclude": [
"http.log.access.log0"
]
},
"log0": {
"sampling": {
"interval": 300,
"first": 50,
"thereafter": 40
},
"include": [
"http.log.access.log0"
]
}
}
},
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"logs": {
"default_logger_name": "log0"
}
}
}
}
}
}

View file

@ -1,23 +1,23 @@
example.com example.com
map {host} {my_placeholder} {magic_number} { map {host} {my_placeholder} {magic_number} {
# Should output boolean "true" and an integer # Should output boolean "true" and an integer
example.com true 3 example.com true 3
# Should output a string and null # Should output a string and null
foo.example.com "string value" foo.example.com "string value"
# Should output two strings (quoted int) # Should output two strings (quoted int)
(.*)\.example.com "${1} subdomain" "5" (.*)\.example.com "${1} subdomain" "5"
# Should output null and a string (quoted int) # Should output null and a string (quoted int)
~.*\.net$ - `7` ~.*\.net$ - `7`
# Should output a float and the string "false" # Should output a float and the string "false"
~.*\.xyz$ 123.456 "false" ~.*\.xyz$ 123.456 "false"
# Should output two strings, second being escaped quote # Should output two strings, second being escaped quote
default "unknown domain" \""" default "unknown domain" \"""
} }
vars foo bar vars foo bar
@ -27,6 +27,7 @@ vars {
ghi 2.3 ghi 2.3
jkl "mn op" jkl "mn op"
} }
---------- ----------
{ {
"apps": { "apps": {

View file

@ -0,0 +1,39 @@
{
metrics
servers :80 {
metrics {
per_host
}
}
}
:80 {
respond "Hello"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"handle": [
{
"body": "Hello",
"handler": "static_response"
}
]
}
]
}
},
"metrics": {
"per_host": true
}
}
}
}

View file

@ -26,11 +26,11 @@
} }
] ]
} }
], ]
"metrics": {
"per_host": true
}
} }
},
"metrics": {
"per_host": true
} }
} }
} }

View file

@ -8,7 +8,7 @@ route {
} }
not path */ not path */
} }
redir @canonicalPath {http.request.orig_uri.path}/ 308 redir @canonicalPath {orig_path}/{orig_?query} 308
# If the requested file does not exist, try index files # If the requested file does not exist, try index files
@indexFiles { @indexFiles {
@ -17,7 +17,7 @@ route {
split_path .php split_path .php
} }
} }
rewrite @indexFiles {http.matchers.file.relative} rewrite @indexFiles {file_match.relative}
# Proxy PHP files to the FastCGI responder # Proxy PHP files to the FastCGI responder
@phpFiles { @phpFiles {
@ -50,7 +50,7 @@ route {
"handler": "static_response", "handler": "static_response",
"headers": { "headers": {
"Location": [ "Location": [
"{http.request.orig_uri.path}/" "{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"
] ]
}, },
"status_code": 308 "status_code": 308

View file

@ -42,7 +42,7 @@
"handler": "static_response", "handler": "static_response",
"headers": { "headers": {
"Location": [ "Location": [
"{http.request.orig_uri.path}/" "{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"
] ]
}, },
"status_code": 308 "status_code": 308
@ -58,6 +58,7 @@
"{http.request.uri.path}/index.php", "{http.request.uri.path}/index.php",
"index.php" "index.php"
], ],
"try_policy": "first_exist_fallback",
"split_path": [ "split_path": [
".php" ".php"
] ]

View file

@ -33,7 +33,7 @@ php_fastcgi @test localhost:9000
"handler": "static_response", "handler": "static_response",
"headers": { "headers": {
"Location": [ "Location": [
"{http.request.orig_uri.path}/" "{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"
] ]
}, },
"status_code": 308 "status_code": 308
@ -73,7 +73,8 @@ php_fastcgi @test localhost:9000
"{http.request.uri.path}", "{http.request.uri.path}",
"{http.request.uri.path}/index.php", "{http.request.uri.path}/index.php",
"index.php" "index.php"
] ],
"try_policy": "first_exist_fallback"
} }
} }
] ]

View file

@ -43,7 +43,7 @@ php_fastcgi localhost:9000 {
"handler": "static_response", "handler": "static_response",
"headers": { "headers": {
"Location": [ "Location": [
"{http.request.orig_uri.path}/" "{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"
] ]
}, },
"status_code": 308 "status_code": 308
@ -59,6 +59,7 @@ php_fastcgi localhost:9000 {
"{http.request.uri.path}/index.php5", "{http.request.uri.path}/index.php5",
"index.php5" "index.php5"
], ],
"try_policy": "first_exist_fallback",
"split_path": [ "split_path": [
".php", ".php",
".php5" ".php5"

View file

@ -46,7 +46,7 @@ php_fastcgi localhost:9000 {
"handler": "static_response", "handler": "static_response",
"headers": { "headers": {
"Location": [ "Location": [
"{http.request.orig_uri.path}/" "{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"
] ]
}, },
"status_code": 308 "status_code": 308

View file

@ -0,0 +1,95 @@
:8884
php_fastcgi localhost:9000 {
# some php_fastcgi-specific subdirectives
split .php .php5
env VAR1 value1
env VAR2 value2
root /var/www
try_files {path} index.php
dial_timeout 3s
read_timeout 10s
write_timeout 20s
# passed through to reverse_proxy (directive order doesn't matter!)
lb_policy random
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"match": [
{
"file": {
"try_files": [
"{http.request.uri.path}",
"index.php"
],
"try_policy": "first_exist_fallback",
"split_path": [
".php",
".php5"
]
}
}
],
"handle": [
{
"handler": "rewrite",
"uri": "{http.matchers.file.relative}"
}
]
},
{
"match": [
{
"path": [
"*.php",
"*.php5"
]
}
],
"handle": [
{
"handler": "reverse_proxy",
"load_balancing": {
"selection_policy": {
"policy": "random"
}
},
"transport": {
"dial_timeout": 3000000000,
"env": {
"VAR1": "value1",
"VAR2": "value2"
},
"protocol": "fastcgi",
"read_timeout": 10000000000,
"root": "/var/www",
"split_path": [
".php",
".php5"
],
"write_timeout": 20000000000
},
"upstreams": [
{
"dial": "localhost:9000"
}
]
}
]
}
]
}
}
}
}
}

View file

@ -0,0 +1,180 @@
automated1.example.com {
tls force_automate
respond "Automated!"
}
automated2.example.com {
tls force_automate
respond "Automated!"
}
shadowed.example.com {
respond "Shadowed!"
}
*.example.com {
tls cert.pem key.pem
respond "Wildcard!"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"automated1.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Automated!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"automated2.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Automated!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"shadowed.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Shadowed!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Wildcard!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"tls_connection_policies": [
{
"match": {
"sni": [
"automated1.example.com"
]
}
},
{
"match": {
"sni": [
"automated2.example.com"
]
}
},
{
"match": {
"sni": [
"*.example.com"
]
},
"certificate_selection": {
"any_tag": [
"cert0"
]
}
},
{}
]
}
}
},
"tls": {
"certificates": {
"automate": [
"automated1.example.com",
"automated2.example.com"
],
"load_files": [
{
"certificate": "cert.pem",
"key": "key.pem",
"tags": [
"cert0"
]
}
]
}
}
}
}

View file

@ -0,0 +1,102 @@
subdomain.example.com {
respond "Subdomain!"
}
*.example.com {
tls cert.pem key.pem
respond "Wildcard!"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"subdomain.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Subdomain!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Wildcard!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"tls_connection_policies": [
{
"match": {
"sni": [
"*.example.com"
]
},
"certificate_selection": {
"any_tag": [
"cert0"
]
}
},
{}
]
}
}
},
"tls": {
"certificates": {
"load_files": [
{
"certificate": "cert.pem",
"key": "key.pem",
"tags": [
"cert0"
]
}
]
}
}
}
}

View file

@ -34,7 +34,7 @@ func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil return nil
} }
// AppendsRecords appends DNS records to the zone. // AppendRecords appends DNS records to the zone.
func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) { func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
return nil, nil return nil, nil
} }

View file

@ -1,8 +1,3 @@
// The below line is required to enable post-quantum key agreement in Go 1.23
// by default without insisting on setting a minimum version of 1.23 in go.mod.
// See https://github.com/caddyserver/caddy/issues/6540#issuecomment-2313094905
//go:debug tlskyber=1
// Copyright 2015 Matthew Holt and The Caddy Authors // Copyright 2015 Matthew Holt and The Caddy Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");

View file

@ -171,6 +171,10 @@ func cmdStart(fl Flags) (int, error) {
func cmdRun(fl Flags) (int, error) { func cmdRun(fl Flags) (int, error) {
caddy.TrapSignals() caddy.TrapSignals()
logger := caddy.Log()
undoMaxProcs := setResourceLimits(logger)
defer undoMaxProcs()
configFlag := fl.String("config") configFlag := fl.String("config")
configAdapterFlag := fl.String("adapter") configAdapterFlag := fl.String("adapter")
resumeFlag := fl.Bool("resume") resumeFlag := fl.Bool("resume")
@ -196,18 +200,18 @@ func cmdRun(fl Flags) (int, error) {
config, err = os.ReadFile(caddy.ConfigAutosavePath) config, err = os.ReadFile(caddy.ConfigAutosavePath)
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) {
// not a bad error; just can't resume if autosave file doesn't exist // not a bad error; just can't resume if autosave file doesn't exist
caddy.Log().Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath)) logger.Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
resumeFlag = false resumeFlag = false
} else if err != nil { } else if err != nil {
return caddy.ExitCodeFailedStartup, err return caddy.ExitCodeFailedStartup, err
} else { } else {
if configFlag == "" { if configFlag == "" {
caddy.Log().Info("resuming from last configuration", logger.Info("resuming from last configuration",
zap.String("autosave_file", caddy.ConfigAutosavePath)) zap.String("autosave_file", caddy.ConfigAutosavePath))
} else { } else {
// if they also specified a config file, user should be aware that we're not // if they also specified a config file, user should be aware that we're not
// using it (doing so could lead to data/config loss by overwriting!) // using it (doing so could lead to data/config loss by overwriting!)
caddy.Log().Warn("--config and --resume flags were used together; ignoring --config and resuming from last configuration", logger.Warn("--config and --resume flags were used together; ignoring --config and resuming from last configuration",
zap.String("autosave_file", caddy.ConfigAutosavePath)) zap.String("autosave_file", caddy.ConfigAutosavePath))
} }
} }
@ -225,7 +229,7 @@ func cmdRun(fl Flags) (int, error) {
if pidfileFlag != "" { if pidfileFlag != "" {
err := caddy.PIDFile(pidfileFlag) err := caddy.PIDFile(pidfileFlag)
if err != nil { if err != nil {
caddy.Log().Error("unable to write PID file", logger.Error("unable to write PID file",
zap.String("pidfile", pidfileFlag), zap.String("pidfile", pidfileFlag),
zap.Error(err)) zap.Error(err))
} }
@ -236,7 +240,7 @@ func cmdRun(fl Flags) (int, error) {
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err) return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
} }
caddy.Log().Info("serving initial configuration") logger.Info("serving initial configuration")
// if we are to report to another process the successful start // if we are to report to another process the successful start
// of the server, do so now by echoing back contents of stdin // of the server, do so now by echoing back contents of stdin
@ -272,15 +276,15 @@ func cmdRun(fl Flags) (int, error) {
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
if os.Getenv("HOME") == "" && os.Getenv("USERPROFILE") == "" && !hasXDG { if os.Getenv("HOME") == "" && os.Getenv("USERPROFILE") == "" && !hasXDG {
caddy.Log().Warn("neither HOME nor USERPROFILE environment variables are set - please fix; some assets might be stored in ./caddy") logger.Warn("neither HOME nor USERPROFILE environment variables are set - please fix; some assets might be stored in ./caddy")
} }
case "plan9": case "plan9":
if os.Getenv("home") == "" && !hasXDG { if os.Getenv("home") == "" && !hasXDG {
caddy.Log().Warn("$home environment variable is empty - please fix; some assets might be stored in ./caddy") logger.Warn("$home environment variable is empty - please fix; some assets might be stored in ./caddy")
} }
default: default:
if os.Getenv("HOME") == "" && !hasXDG { if os.Getenv("HOME") == "" && !hasXDG {
caddy.Log().Warn("$HOME environment variable is empty - please fix; some assets might be stored in ./caddy") logger.Warn("$HOME environment variable is empty - please fix; some assets might be stored in ./caddy")
} }
} }
@ -560,10 +564,15 @@ func cmdValidateConfig(fl Flags) (int, error) {
func cmdFmt(fl Flags) (int, error) { func cmdFmt(fl Flags) (int, error) {
configFile := fl.Arg(0) configFile := fl.Arg(0)
if configFile == "" { configFlag := fl.String("config")
configFile = "Caddyfile" if (len(fl.Args()) > 1) || (configFlag != "" && configFile != "") {
return caddy.ExitCodeFailedStartup, fmt.Errorf("fmt does not support multiple files %s %s", configFlag, strings.Join(fl.Args(), " "))
}
if configFile == "" && configFlag == "" {
configFile = "Caddyfile"
} else if configFile == "" {
configFile = configFlag
} }
// as a special case, read from stdin if the file name is "-" // as a special case, read from stdin if the file name is "-"
if configFile == "-" { if configFile == "-" {
input, err := io.ReadAll(os.Stdin) input, err := io.ReadAll(os.Stdin)

View file

@ -388,6 +388,7 @@ When reading from stdin, the --overwrite flag has no effect: the result
is always printed to stdout. is always printed to stdout.
`, `,
CobraFunc: func(cmd *cobra.Command) { CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("config", "c", "", "Configuration file")
cmd.Flags().BoolP("overwrite", "w", false, "Overwrite the input file with the results") cmd.Flags().BoolP("overwrite", "w", false, "Overwrite the input file with the results")
cmd.Flags().BoolP("diff", "d", false, "Print the differences between the input file and the formatted output") cmd.Flags().BoolP("diff", "d", false, "Print the differences between the input file and the formatted output")
cmd.RunE = WrapCommandFuncForCobra(cmdFmt) cmd.RunE = WrapCommandFuncForCobra(cmdFmt)
@ -409,12 +410,13 @@ latest versions. EXPERIMENTAL: May be changed or removed.
RegisterCommand(Command{ RegisterCommand(Command{
Name: "add-package", Name: "add-package",
Usage: "<packages...>", Usage: "<package[@version]...>",
Short: "Adds Caddy packages (EXPERIMENTAL)", Short: "Adds Caddy packages (EXPERIMENTAL)",
Long: ` Long: `
Downloads an updated Caddy binary with the specified packages (module/plugin) Downloads an updated Caddy binary with the specified packages (module/plugin)
added. Retains existing packages. Returns an error if the any of packages are added, with an optional version specified (e.g., "package@version"). Retains
already included. EXPERIMENTAL: May be changed or removed. existing packages. Returns an error if any of the specified packages are already
included. EXPERIMENTAL: May be changed or removed.
`, `,
CobraFunc: func(cmd *cobra.Command) { CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it")
@ -439,7 +441,7 @@ EXPERIMENTAL: May be changed or removed.
}) })
defaultFactory.Use(func(rootCmd *cobra.Command) { defaultFactory.Use(func(rootCmd *cobra.Command) {
RegisterCommand(Command{ rootCmd.AddCommand(caddyCmdToCobra(Command{
Name: "manpage", Name: "manpage",
Usage: "--directory <path>", Usage: "--directory <path>",
Short: "Generates the manual pages for Caddy commands", Short: "Generates the manual pages for Caddy commands",
@ -469,7 +471,7 @@ argument of --directory. If the directory does not exist, it will be created.
return caddy.ExitCodeSuccess, nil return caddy.ExitCodeSuccess, nil
}) })
}, },
}) }))
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md // source: https://github.com/spf13/cobra/blob/main/shell_completions.md
rootCmd.AddCommand(&cobra.Command{ rootCmd.AddCommand(&cobra.Command{

View file

@ -24,6 +24,7 @@ import (
"io" "io"
"io/fs" "io/fs"
"log" "log"
"log/slog"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@ -33,10 +34,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/KimMachineGun/automemlimit/memlimit"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"go.uber.org/automaxprocs/maxprocs" "go.uber.org/automaxprocs/maxprocs"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
@ -66,12 +69,6 @@ func Main() {
os.Exit(caddy.ExitCodeFailedStartup) os.Exit(caddy.ExitCodeFailedStartup)
} }
undo, err := maxprocs.Set()
defer undo()
if err != nil {
caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err))
}
if err := defaultFactory.Build().Execute(); err != nil { if err := defaultFactory.Build().Execute(); err != nil {
var exitError *exitError var exitError *exitError
if errors.As(err, &exitError) { if errors.As(err, &exitError) {
@ -467,6 +464,31 @@ func printEnvironment() {
} }
} }
func setResourceLimits(logger *zap.Logger) func() {
// Configure the maximum number of CPUs to use to match the Linux container quota (if any)
// See https://pkg.go.dev/runtime#GOMAXPROCS
undo, err := maxprocs.Set(maxprocs.Logger(logger.Sugar().Infof))
if err != nil {
logger.Warn("failed to set GOMAXPROCS", zap.Error(err))
}
// Configure the maximum memory to use to match the Linux container quota (if any) or system memory
// See https://pkg.go.dev/runtime/debug#SetMemoryLimit
_, _ = memlimit.SetGoMemLimitWithOpts(
memlimit.WithLogger(
slog.New(zapslog.NewHandler(logger.Core())),
),
memlimit.WithProvider(
memlimit.ApplyFallback(
memlimit.FromCgroup,
memlimit.FromSystem,
),
),
)
return undo
}
// StringSlice is a flag.Value that enables repeated use of a string flag. // StringSlice is a flag.Value that enables repeated use of a string flag.
type StringSlice []string type StringSlice []string

View file

@ -46,6 +46,25 @@ func cmdUpgrade(fl Flags) (int, error) {
return upgradeBuild(pluginPkgs, fl) return upgradeBuild(pluginPkgs, fl)
} }
func splitModule(arg string) (module, version string, err error) {
const versionSplit = "@"
// accommodate module paths that have @ in them, but we can only tolerate that if there's also
// a version, otherwise we don't know if it's a version separator or part of the file path
lastVersionSplit := strings.LastIndex(arg, versionSplit)
if lastVersionSplit < 0 {
module = arg
} else {
module, version = arg[:lastVersionSplit], arg[lastVersionSplit+1:]
}
if module == "" {
err = fmt.Errorf("module name is required")
}
return
}
func cmdAddPackage(fl Flags) (int, error) { func cmdAddPackage(fl Flags) (int, error) {
if len(fl.Args()) == 0 { if len(fl.Args()) == 0 {
return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified") return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified")
@ -60,10 +79,15 @@ func cmdAddPackage(fl Flags) (int, error) {
} }
for _, arg := range fl.Args() { for _, arg := range fl.Args() {
if _, ok := pluginPkgs[arg]; ok { module, version, err := splitModule(arg)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
}
// only allow a version to be specified if it's different from the existing version
if _, ok := pluginPkgs[module]; ok && !(version != "" && pluginPkgs[module].Version != version) {
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added") return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added")
} }
pluginPkgs[arg] = struct{}{} pluginPkgs[module] = pluginPackage{Version: version, Path: module}
} }
return upgradeBuild(pluginPkgs, fl) return upgradeBuild(pluginPkgs, fl)
@ -83,7 +107,11 @@ func cmdRemovePackage(fl Flags) (int, error) {
} }
for _, arg := range fl.Args() { for _, arg := range fl.Args() {
if _, ok := pluginPkgs[arg]; !ok { module, _, err := splitModule(arg)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
}
if _, ok := pluginPkgs[module]; !ok {
// package does not exist // package does not exist
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added") return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added")
} }
@ -93,7 +121,7 @@ func cmdRemovePackage(fl Flags) (int, error) {
return upgradeBuild(pluginPkgs, fl) return upgradeBuild(pluginPkgs, fl)
} }
func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) { func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) {
l := caddy.Log() l := caddy.Log()
thisExecPath, err := os.Executable() thisExecPath, err := os.Executable()
@ -120,8 +148,8 @@ func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) {
"os": {runtime.GOOS}, "os": {runtime.GOOS},
"arch": {runtime.GOARCH}, "arch": {runtime.GOARCH},
} }
for pkg := range pluginPkgs { for _, pkgInfo := range pluginPkgs {
qs.Add("p", pkg) qs.Add("p", pkgInfo.String())
} }
// initiate the build // initiate the build
@ -276,14 +304,14 @@ func downloadBuild(qs url.Values) (*http.Response, error) {
return resp, nil return resp, nil
} }
func getPluginPackages(modules []moduleInfo) (map[string]struct{}, error) { func getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, error) {
pluginPkgs := make(map[string]struct{}) pluginPkgs := make(map[string]pluginPackage)
for _, mod := range modules { for _, mod := range modules {
if mod.goModule.Replace != nil { if mod.goModule.Replace != nil {
return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s", return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s",
mod.goModule.Path, mod.goModule.Replace.Path) mod.goModule.Path, mod.goModule.Replace.Path)
} }
pluginPkgs[mod.goModule.Path] = struct{}{} pluginPkgs[mod.goModule.Path] = pluginPackage{Version: mod.goModule.Version, Path: mod.goModule.Path}
} }
return pluginPkgs, nil return pluginPkgs, nil
} }
@ -312,3 +340,15 @@ func writeCaddyBinary(path string, body *io.ReadCloser, fileInfo os.FileInfo) er
} }
const downloadPath = "https://caddyserver.com/api/download" const downloadPath = "https://caddyserver.com/api/download"
type pluginPackage struct {
Version string
Path string
}
func (p pluginPackage) String() string {
if p.Version == "" {
return p.Path
}
return p.Path + "@" + p.Version
}

View file

@ -21,6 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
@ -190,12 +191,20 @@ func cmdExportStorage(fl Flags) (int, error) {
for _, k := range keys { for _, k := range keys {
info, err := stor.Stat(ctx, k) info, err := stor.Stat(ctx, k)
if err != nil { if err != nil {
if errors.Is(err, fs.ErrNotExist) {
caddy.Log().Warn(fmt.Sprintf("key: %s removed while export is in-progress", k))
continue
}
return caddy.ExitCodeFailedQuit, err return caddy.ExitCodeFailedQuit, err
} }
if info.IsTerminal { if info.IsTerminal {
v, err := stor.Load(ctx, k) v, err := stor.Load(ctx, k)
if err != nil { if err != nil {
if errors.Is(err, fs.ErrNotExist) {
caddy.Log().Warn(fmt.Sprintf("key: %s removed while export is in-progress", k))
continue
}
return caddy.ExitCodeFailedQuit, err return caddy.ExitCodeFailedQuit, err
} }

View file

@ -110,6 +110,8 @@ func (ctx *Context) GetMetricsRegistry() *prometheus.Registry {
func (ctx *Context) initMetrics() { func (ctx *Context) initMetrics() {
ctx.metricsRegistry.MustRegister( ctx.metricsRegistry.MustRegister(
collectors.NewBuildInfoCollector(), collectors.NewBuildInfoCollector(),
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
collectors.NewGoCollector(),
adminMetrics.requestCount, adminMetrics.requestCount,
adminMetrics.requestErrors, adminMetrics.requestErrors,
globalMetrics.configSuccess, globalMetrics.configSuccess,
@ -383,6 +385,17 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
return nil, fmt.Errorf("module value cannot be null") return nil, fmt.Errorf("module value cannot be null")
} }
// if this is an app module, keep a reference to it,
// since submodules may need to reference it during
// provisioning (even though the parent app module
// may not be fully provisioned yet; this is the case
// with the tls app's automation policies, which may
// refer to the tls app to check if a global DNS
// module has been configured for DNS challenges)
if appModule, ok := val.(App); ok {
ctx.cfg.apps[id] = appModule
}
ctx.ancestry = append(ctx.ancestry, val) ctx.ancestry = append(ctx.ancestry, val)
if prov, ok := val.(Provisioner); ok { if prov, ok := val.(Provisioner); ok {
@ -469,7 +482,6 @@ func (ctx Context) App(name string) (any, error) {
if appRaw != nil { if appRaw != nil {
ctx.cfg.AppsRaw[name] = nil // allow GC to deallocate ctx.cfg.AppsRaw[name] = nil // allow GC to deallocate
} }
ctx.cfg.apps[name] = modVal.(App)
return modVal, nil return modVal, nil
} }
@ -555,12 +567,8 @@ func (ctx Context) Slogger() *slog.Logger {
if mod == nil { if mod == nil {
return slog.New(zapslog.NewHandler(Log().Core(), nil)) return slog.New(zapslog.NewHandler(Log().Core(), nil))
} }
return slog.New(zapslog.NewHandler(ctx.cfg.Logging.Logger(mod).Core(),
return slog.New(zapslog.NewHandler( zapslog.WithName(string(mod.CaddyModule().ID)),
ctx.cfg.Logging.Logger(mod).Core(),
&zapslog.HandlerOptions{
LoggerName: string(mod.CaddyModule().ID),
},
)) ))
} }

39
filepath.go Normal file
View file

@ -0,0 +1,39 @@
// 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.
//go:build !windows
package caddy
import (
"os"
"path/filepath"
)
// FastAbs is an optimized version of filepath.Abs for Unix systems,
// since we don't expect the working directory to ever change once
// Caddy is running. Avoid the os.Getwd() syscall overhead.
// It's overall the same as stdlib's implementation, the difference
// being cached working directory.
func FastAbs(path string) (string, error) {
if filepath.IsAbs(path) {
return filepath.Clean(path), nil
}
if wderr != nil {
return "", wderr
}
return filepath.Join(wd, path), nil
}
var wd, wderr = os.Getwd()

27
filepath_windows.go Normal file
View file

@ -0,0 +1,27 @@
// 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 caddy
import (
"path/filepath"
)
// FastAbs can't be optimized on Windows because there
// are special file paths that require the use of syscall.FullPath
// to handle correctly.
// Just call stdlib's implementation which uses that function.
func FastAbs(path string) (string, error) {
return filepath.Abs(path)
}

View file

@ -1,3 +1,17 @@
// 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 caddy package caddy
import "io/fs" import "io/fs"

121
go.mod
View file

@ -1,111 +1,112 @@
module github.com/caddyserver/caddy/v2 module github.com/caddyserver/caddy/v2
go 1.22.3 go 1.24
toolchain go1.23.0
require ( require (
github.com/BurntSushi/toml v1.3.2 github.com/BurntSushi/toml v1.4.0
github.com/Masterminds/sprig/v3 v3.2.3 github.com/KimMachineGun/automemlimit v0.7.1
github.com/alecthomas/chroma/v2 v2.13.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/alecthomas/chroma/v2 v2.15.0
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
github.com/caddyserver/certmagic v0.21.3 github.com/caddyserver/certmagic v0.22.0
github.com/caddyserver/zerossl v0.1.3 github.com/caddyserver/zerossl v0.1.3
github.com/cloudflare/circl v1.6.0
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/chi/v5 v5.2.1
github.com/google/cel-go v0.21.0 github.com/google/cel-go v0.24.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/klauspost/compress v1.17.8 github.com/klauspost/compress v1.18.0
github.com/klauspost/cpuid/v2 v2.2.7 github.com/klauspost/cpuid/v2 v2.2.10
github.com/mholt/acmez/v2 v2.0.1 github.com/mholt/acmez/v3 v3.1.0
github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_golang v1.19.1
github.com/quic-go/quic-go v0.47.0 github.com/quic-go/quic-go v0.50.0
github.com/smallstep/certificates v0.26.1 github.com/smallstep/certificates v0.26.1
github.com/smallstep/nosql v0.6.1 github.com/smallstep/nosql v0.6.1
github.com/smallstep/truststore v0.13.0 github.com/smallstep/truststore v0.13.0
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.10.0
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53
github.com/yuin/goldmark v1.7.1 github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 go.opentelemetry.io/contrib/propagators/autoprop v0.42.0
go.opentelemetry.io/otel v1.24.0 go.opentelemetry.io/otel v1.31.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0
go.opentelemetry.io/otel/sdk v1.21.0 go.opentelemetry.io/otel/sdk v1.31.0
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.6.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
go.uber.org/zap/exp v0.2.0 go.uber.org/zap/exp v0.3.0
golang.org/x/crypto v0.26.0 golang.org/x/crypto v0.36.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810
golang.org/x/net v0.28.0 golang.org/x/net v0.37.0
golang.org/x/sync v0.8.0 golang.org/x/sync v0.12.0
golang.org/x/term v0.23.0 golang.org/x/term v0.30.0
golang.org/x/time v0.5.0 golang.org/x/time v0.11.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
cel.dev/expr v0.19.1 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fxamacker/cbor/v2 v2.6.0 // indirect github.com/fxamacker/cbor/v2 v2.6.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-kit/log v0.2.1 // indirect github.com/go-kit/log v0.2.1 // indirect
github.com/golang/glog v1.2.0 // indirect
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // 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.0 // indirect
github.com/google/go-tspi v0.3.0 // indirect github.com/google/go-tspi v0.3.0 // indirect
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/onsi/ginkgo/v2 v2.13.2 // indirect github.com/onsi/ginkgo/v2 v2.13.2 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // 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/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.17.0 // 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/b3 v1.17.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger 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.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.5.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect
) )
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 github.com/cespare/xxhash/v2 v2.3.0
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgraph-io/ristretto v0.2.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-kit/kit v0.13.0 // indirect github.com/go-kit/kit v0.13.0 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // 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.7.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/huandu/xstrings v1.3.3 // indirect github.com/huandu/xstrings v1.5.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgconn v1.14.3 // indirect
@ -115,43 +116,43 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.3 // indirect github.com/jackc/pgx/v4 v4.18.3 // indirect
github.com/libdns/libdns v0.2.2 // indirect github.com/libdns/libdns v0.2.3
github.com/manifoldco/promptui v0.9.0 // indirect github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/dns v1.1.59 // indirect github.com/miekg/dns v1.1.63 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // 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.7.1-0.20240628150027-b718e7ce4964
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/client_model v0.5.0
github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect
github.com/rs/xid v1.5.0 // indirect github.com/rs/xid v1.5.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/slackhq/nebula v1.6.1 // indirect github.com/slackhq/nebula v1.6.1 // indirect
github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/urfave/cli v1.22.14 // indirect github.com/urfave/cli v1.22.14 // indirect
go.etcd.io/bbolt v1.3.9 // indirect go.etcd.io/bbolt v1.3.9 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 go.opentelemetry.io/otel/trace v1.31.0
go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.step.sm/cli-utils v0.9.0 // indirect go.step.sm/cli-utils v0.9.0 // indirect
go.step.sm/crypto v0.45.0 go.step.sm/crypto v0.45.0
go.step.sm/linkedca v0.20.1 // indirect go.step.sm/linkedca v0.20.1 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.17.0 // indirect golang.org/x/mod v0.24.0 // indirect
golang.org/x/sys v0.23.0 golang.org/x/sys v0.31.0
golang.org/x/text v0.17.0 // indirect golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/tools v0.31.0 // indirect
google.golang.org/grpc v1.63.2 // indirect google.golang.org/grpc v1.67.1 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/protobuf v1.35.1 // indirect
howett.net/plist v1.0.0 // indirect howett.net/plist v1.0.0 // indirect
) )

263
go.sum
View file

@ -1,3 +1,5 @@
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
@ -7,15 +9,17 @@ cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
@ -26,24 +30,27 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/KimMachineGun/automemlimit v0.7.1 h1:QcG/0iCOLChjfUweIMC3YL5Xy9C3VBeNmCZHrZfJMBw=
github.com/KimMachineGun/automemlimit v0.7.1/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
@ -86,17 +93,16 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= github.com/caddyserver/certmagic v0.22.0 h1:hi2skv2jouUw9uQUEyYSTTmqPZPHgf61dOANSIVCLOw=
github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= github.com/caddyserver/certmagic v0.22.0/go.mod h1:Vc0msarAPhOagbDc/SU6M2zbzdwVuZ0lkTh2EqtH4vs=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
@ -107,6 +113,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -117,8 +125,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -129,15 +137,15 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
@ -146,16 +154,18 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
@ -167,8 +177,8 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
@ -181,8 +191,6 @@ github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPh
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
@ -198,8 +206,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= github.com/google/cel-go v0.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI=
github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
@ -222,7 +230,6 @@ github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0Z
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
@ -235,16 +242,13 @@ github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoF
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
@ -301,11 +305,10 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -324,8 +327,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -344,19 +347,17 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= github.com/mholt/acmez/v3 v3.1.0 h1:RlOx2SSZ8dIAM5GfkMe8TdaxjjkiHTGorlMUt8GeMzg=
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= github.com/mholt/acmez/v3 v3.1.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -368,6 +369,8 @@ github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
@ -394,11 +397,11 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y= github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E= github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
@ -412,8 +415,9 @@ github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n
github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
@ -466,16 +470,15 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
@ -493,8 +496,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ= github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
@ -508,14 +511,14 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
@ -526,8 +529,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w= go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w=
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/go.mod h1:Mv/tWNtZn+NbALDb2XcItP0OM3lWWZjAfSroINxfW+Y= go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/go.mod h1:Mv/tWNtZn+NbALDb2XcItP0OM3lWWZjAfSroINxfW+Y=
go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c= go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c=
@ -538,20 +541,20 @@ go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWE
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA= go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA=
go.opentelemetry.io/contrib/propagators/ot v1.17.0 h1:ufo2Vsz8l76eI47jFjuVyjyB3Ae2DmfiCV/o6Vc8ii0= go.opentelemetry.io/contrib/propagators/ot v1.17.0 h1:ufo2Vsz8l76eI47jFjuVyjyB3Ae2DmfiCV/o6Vc8ii0=
go.opentelemetry.io/contrib/propagators/ot v1.17.0/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk= go.opentelemetry.io/contrib/propagators/ot v1.17.0/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8=
go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc= go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc=
@ -562,12 +565,12 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
@ -579,8 +582,8 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -596,12 +599,11 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw= golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810 h1:V5+zy0jmgNYmK1uW/sPpBw8ioFvalrhaUrYWmu1Fpe4=
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
@ -613,8 +615,8 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -629,17 +631,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -648,8 +649,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -667,7 +668,6 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -675,22 +675,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -698,16 +696,15 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -723,8 +720,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -746,18 +743,18 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -771,8 +768,6 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -30,7 +30,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func reuseUnixSocket(network, addr string) (any, error) { func reuseUnixSocket(_, _ string) (any, error) {
return nil, nil return nil, nil
} }

View file

@ -139,7 +139,7 @@ func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net
} }
// check to see if plugin provides listener // check to see if plugin provides listener
if ln, err := getListenerFromPlugin(ctx, na.Network, na.JoinHostPort(portOffset), config); ln != nil || err != nil { if ln, err := getListenerFromPlugin(ctx, na.Network, na.Host, na.port(), portOffset, config); ln != nil || err != nil {
return ln, err return ln, err
} }
@ -305,25 +305,6 @@ func IsFdNetwork(netw string) bool {
return strings.HasPrefix(netw, "fd") return strings.HasPrefix(netw, "fd")
} }
// normally we would simply append the port,
// but if host is IPv6, we need to ensure it
// is enclosed in [ ]; net.JoinHostPort does
// this for us, but host might also have a
// network type in front (e.g. "tcp/") leading
// to "[tcp/::1]" which causes parsing failures
// later; what we need is "tcp/[::1]", so we have
// to split the network and host, then re-combine
func ParseNetworkAddressFromHostPort(host, port string) (NetworkAddress, error) {
network, addr, ok := strings.Cut(host, "/")
if !ok {
addr = network
network = ""
}
addr = strings.Trim(addr, "[]") // IPv6
networkAddr := JoinNetworkAddress(network, addr, port)
return ParseNetworkAddress(networkAddr)
}
// ParseNetworkAddress parses addr into its individual // ParseNetworkAddress parses addr into its individual
// components. The input string is expected to be of // components. The input string is expected to be of
// the form "network/host:port-range" where any part is // the form "network/host:port-range" where any part is
@ -399,25 +380,28 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
if slashFound { if slashFound {
network = strings.ToLower(strings.TrimSpace(beforeSlash)) network = strings.ToLower(strings.TrimSpace(beforeSlash))
a = afterSlash a = afterSlash
if IsUnixNetwork(network) || IsFdNetwork(network) {
host = a
return
}
} }
if IsUnixNetwork(network) || IsFdNetwork(network) {
host = a
return
}
host, port, err = net.SplitHostPort(a) host, port, err = net.SplitHostPort(a)
if err == nil || a == "" { firstErr := err
return
} if err != nil {
// in general, if there was an error, it was likely "missing port", // in general, if there was an error, it was likely "missing port",
// so try adding a bogus port to take advantage of standard library's // so try removing square brackets around an IPv6 host, adding a bogus
// robust parser, then strip the artificial port before returning // port to take advantage of standard library's robust parser, then
// (don't overwrite original error though; might still be relevant) // strip the artificial port.
var err2 error host, _, err = net.SplitHostPort(net.JoinHostPort(strings.Trim(a, "[]"), "0"))
host, port, err2 = net.SplitHostPort(a + ":0")
if err2 == nil {
err = nil
port = "" port = ""
} }
if err != nil {
err = errors.Join(firstErr, err)
}
return return
} }
@ -674,11 +658,11 @@ var unixSocketsMu sync.Mutex
// getListenerFromPlugin returns a listener on the given network and address // getListenerFromPlugin returns a listener on the given network and address
// if a plugin has registered the network name. It may return (nil, nil) if // if a plugin has registered the network name. It may return (nil, nil) if
// no plugin can provide a listener. // no plugin can provide a listener.
func getListenerFromPlugin(ctx context.Context, network, addr string, config net.ListenConfig) (any, error) { func getListenerFromPlugin(ctx context.Context, network, host, port string, portOffset uint, config net.ListenConfig) (any, error) {
// get listener from plugin if network type is registered // get listener from plugin if network type is registered
if getListener, ok := networkTypes[network]; ok { if getListener, ok := networkTypes[network]; ok {
Log().Debug("getting listener from plugin", zap.String("network", network)) Log().Debug("getting listener from plugin", zap.String("network", network))
return getListener(ctx, network, addr, config) return getListener(ctx, network, host, port, portOffset, config)
} }
return nil, nil return nil, nil
@ -692,7 +676,7 @@ func listenerKey(network, addr string) string {
// The listeners must be capable of overlapping: with Caddy, new configs are loaded // The listeners must be capable of overlapping: with Caddy, new configs are loaded
// before old ones are unloaded, so listeners may overlap briefly if the configs // before old ones are unloaded, so listeners may overlap briefly if the configs
// both need the same listener. EXPERIMENTAL and subject to change. // both need the same listener. EXPERIMENTAL and subject to change.
type ListenerFunc func(ctx context.Context, network, addr string, cfg net.ListenConfig) (any, error) type ListenerFunc func(ctx context.Context, network, host, portRange string, portOffset uint, cfg net.ListenConfig) (any, error)
var networkTypes = map[string]ListenerFunc{} var networkTypes = map[string]ListenerFunc{}

View file

@ -31,7 +31,7 @@ func TestSplitNetworkAddress(t *testing.T) {
}{ }{
{ {
input: "", input: "",
expectErr: true, expectHost: "",
}, },
{ {
input: "foo", input: "foo",
@ -42,7 +42,7 @@ func TestSplitNetworkAddress(t *testing.T) {
}, },
{ {
input: "::", input: "::",
expectErr: true, expectHost: "::",
}, },
{ {
input: "[::]", input: "[::]",
@ -77,7 +77,7 @@ func TestSplitNetworkAddress(t *testing.T) {
{ {
input: "udp/", input: "udp/",
expectNetwork: "udp", expectNetwork: "udp",
expectErr: true, expectHost: "",
}, },
{ {
input: "unix//foo/bar", input: "unix//foo/bar",
@ -185,7 +185,8 @@ func TestParseNetworkAddress(t *testing.T) {
}{ }{
{ {
input: "", input: "",
expectErr: true, expectAddr: NetworkAddress{
},
}, },
{ {
input: ":", input: ":",
@ -311,7 +312,8 @@ func TestParseNetworkAddressWithDefaults(t *testing.T) {
}{ }{
{ {
input: "", input: "",
expectErr: true, expectAddr: NetworkAddress{
},
}, },
{ {
input: ":", input: ":",

View file

@ -262,7 +262,7 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
return nil, false return nil, false
}) })
logger = logger.With(zap.Any("data", e.Data)) logger = logger.WithLazy(zap.Any("data", e.Data))
logger.Debug("event") logger.Debug("event")

View file

@ -15,6 +15,7 @@
package caddyhttp package caddyhttp
import ( import (
"cmp"
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
@ -142,6 +143,10 @@ type App struct {
// affect functionality. // affect functionality.
Servers map[string]*Server `json:"servers,omitempty"` Servers map[string]*Server `json:"servers,omitempty"`
// If set, metrics observations will be enabled.
// This setting is EXPERIMENTAL and subject to change.
Metrics *Metrics `json:"metrics,omitempty"`
ctx caddy.Context ctx caddy.Context
logger *zap.Logger logger *zap.Logger
tlsApp *caddytls.TLS tlsApp *caddytls.TLS
@ -184,6 +189,10 @@ func (app *App) Provision(ctx caddy.Context) error {
return err return err
} }
if app.Metrics != nil {
app.Metrics.init = sync.Once{}
app.Metrics.httpMetrics = &httpMetrics{}
}
// prepare each server // prepare each server
oldContext := ctx.Context oldContext := ctx.Context
for srvName, srv := range app.Servers { for srvName, srv := range app.Servers {
@ -196,6 +205,15 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.errorLogger = app.logger.Named("log.error") srv.errorLogger = app.logger.Named("log.error")
srv.shutdownAtMu = new(sync.RWMutex) srv.shutdownAtMu = new(sync.RWMutex)
if srv.Metrics != nil {
srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead")
app.Metrics = cmp.Or(app.Metrics, &Metrics{
init: sync.Once{},
httpMetrics: &httpMetrics{},
})
app.Metrics.PerHost = app.Metrics.PerHost || srv.Metrics.PerHost
}
// only enable access logs if configured // only enable access logs if configured
if srv.Logs != nil { if srv.Logs != nil {
srv.accessLogger = app.logger.Named("log.access") srv.accessLogger = app.logger.Named("log.access")
@ -342,16 +360,11 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...) srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
} }
} }
// pre-compile the primary handler chain, and be sure to wrap it in our // pre-compile the primary handler chain, and be sure to wrap it in our
// route handler so that important security checks are done, etc. // route handler so that important security checks are done, etc.
primaryRoute := emptyHandler primaryRoute := emptyHandler
if srv.Routes != nil { if srv.Routes != nil {
if srv.Metrics != nil { err := srv.Routes.ProvisionHandlers(ctx, app.Metrics)
srv.Metrics.init = sync.Once{}
srv.Metrics.httpMetrics = &httpMetrics{}
}
err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics)
if err != nil { if err != nil {
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err) return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
} }
@ -370,7 +383,7 @@ func (app *App) Provision(ctx caddy.Context) error {
// provision the named routes (they get compiled at runtime) // provision the named routes (they get compiled at runtime)
for name, route := range srv.NamedRoutes { for name, route := range srv.NamedRoutes {
err := route.Provision(ctx, srv.Metrics) err := route.Provision(ctx, app.Metrics)
if err != nil { if err != nil {
return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err) return fmt.Errorf("server %s: setting up named route '%s' handlers: %v", name, srvName, err)
} }
@ -388,6 +401,9 @@ func (app *App) Provision(ctx caddy.Context) error {
if srv.IdleTimeout == 0 { if srv.IdleTimeout == 0 {
srv.IdleTimeout = defaultIdleTimeout srv.IdleTimeout = defaultIdleTimeout
} }
if srv.ReadHeaderTimeout == 0 {
srv.ReadHeaderTimeout = defaultReadHeaderTimeout // see #6663
}
} }
ctx.Context = oldContext ctx.Context = oldContext
return nil return nil
@ -513,21 +529,6 @@ func (app *App) Start() error {
// enable TLS if there is a policy and if this is not the HTTP port // enable TLS if there is a policy and if this is not the HTTP port
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort() useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
// enable HTTP/3 if configured
if h3ok && useTLS {
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
return err
}
}
if h3ok && !useTLS {
// Can only serve h3 with TLS enabled
app.logger.Warn("HTTP/3 skipped because it requires TLS",
zap.String("network", listenAddr.Network),
zap.String("addr", hostport))
}
if h1ok || h2ok && useTLS || h2cok { if h1ok || h2ok && useTLS || h2cok {
// create the listener for this socket // create the listener for this socket
lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)}) lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
@ -598,6 +599,33 @@ func (app *App) Start() error {
zap.String("network", listenAddr.Network), zap.String("network", listenAddr.Network),
zap.String("addr", hostport)) zap.String("addr", hostport))
} }
if h3ok {
// Can't serve HTTP/3 on the same socket as HTTP/1 and 2 because it uses
// a different transport mechanism... which is fine, but the OS doesn't
// differentiate between a SOCK_STREAM file and a SOCK_DGRAM file; they
// are still one file on the system. So even though "unixpacket" and
// "unixgram" are different network types just as "tcp" and "udp" are,
// the OS will not let us use the same file as both STREAM and DGRAM.
if listenAddr.IsUnixNetwork() {
app.logger.Warn("HTTP/3 disabled because Unix can't multiplex STREAM and DGRAM on same socket",
zap.String("file", hostport))
continue
}
if useTLS {
// enable HTTP/3 if configured
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
return err
}
} else {
// Can only serve h3 with TLS enabled
app.logger.Warn("HTTP/3 skipped because it requires TLS",
zap.String("network", listenAddr.Network),
zap.String("addr", hostport))
}
}
} }
} }
@ -689,16 +717,7 @@ func (app *App) Stop() error {
return return
} }
// First close h3server then close listeners unlike stdlib for several reasons: if err := server.h3server.Shutdown(ctx); err != nil {
// 1, udp has only a single socket, once closed, no more data can be read and
// written. In contrast, closing tcp listeners won't affect established connections.
// This have something to do with graceful shutdown when upstream implements it.
// 2, h3server will only close listeners it's registered (quic listeners). Closing
// listener first and these listeners maybe unregistered thus won't be closed. caddy
// distinguishes quic-listener and underlying datagram sockets.
// TODO: CloseGracefully, once implemented upstream (see https://github.com/quic-go/quic-go/issues/2103)
if err := server.h3server.Close(); err != nil {
app.logger.Error("HTTP/3 server shutdown", app.logger.Error("HTTP/3 server shutdown",
zap.Error(err), zap.Error(err),
zap.Strings("addresses", server.Listen)) zap.Strings("addresses", server.Listen))
@ -766,11 +785,20 @@ func (app *App) httpsPort() int {
return app.HTTPSPort return app.HTTPSPort
} }
// defaultIdleTimeout is the default HTTP server timeout const (
// for closing idle connections; useful to avoid resource // defaultIdleTimeout is the default HTTP server timeout
// exhaustion behind hungry CDNs, for example (we've had // for closing idle connections; useful to avoid resource
// several complaints without this). // exhaustion behind hungry CDNs, for example (we've had
const defaultIdleTimeout = caddy.Duration(5 * time.Minute) // several complaints without this).
defaultIdleTimeout = caddy.Duration(5 * time.Minute)
// defaultReadHeaderTimeout is the default timeout for
// reading HTTP headers from clients. Headers are generally
// small, often less than 1 KB, so it shouldn't take a
// long time even on legitimately slow connections or
// busy servers to read it.
defaultReadHeaderTimeout = caddy.Duration(time.Minute)
)
// Interface guards // Interface guards
var ( var (

View file

@ -163,6 +163,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
} }
} }
// trim the list of domains covered by wildcards, if configured
if srv.AutoHTTPS.PreferWildcard { if srv.AutoHTTPS.PreferWildcard {
wildcards := make(map[string]struct{}) wildcards := make(map[string]struct{})
for d := range serverDomainSet { for d := range serverDomainSet {
@ -184,6 +185,17 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
} }
} }
// build the list of domains that could be used with ECH (if enabled)
// so the TLS app can know to publish ECH configs for them; we do this
// after trimming domains covered by wildcards because, presumably,
// if the user wants to use wildcard certs, they also want to use the
// wildcard for ECH, rather than individual subdomains
echDomains := make([]string, 0, len(serverDomainSet))
for d := range serverDomainSet {
echDomains = append(echDomains, d)
}
app.tlsApp.RegisterServerNames(echDomains)
// nothing more to do here if there are no domains that qualify for // nothing more to do here if there are no domains that qualify for
// automatic HTTPS and there are no explicit TLS connection policies: // automatic HTTPS and there are no explicit TLS connection policies:
// if there is at least one domain but no TLS conn policy (F&&T), we'll // if there is at least one domain but no TLS conn policy (F&&T), we'll
@ -320,11 +332,21 @@ uniqueDomainsLoop:
} }
} }
// if no automation policy exists for the name yet, we // if no automation policy exists for the name yet, we will associate it with an implicit one;
// will associate it with an implicit one // we handle tailscale domains specially, and we also separate out identifiers that need the
// internal issuer (self-signed certs); certmagic does not consider public IP addresses to be
// disqualified for public certs, because there are public CAs that will issue certs for IPs.
// However, with auto-HTTPS, many times there is no issuer explicitly defined, and the default
// issuers do not (currently, as of 2024) issue IP certificates; so assign all IP subjects to
// the internal issuer when there are no explicit automation policies
shouldUseInternal := func(ident string) bool {
usingDefaultIssuersAndIsIP := certmagic.SubjectIsIP(ident) &&
(app.tlsApp == nil || app.tlsApp.Automation == nil || len(app.tlsApp.Automation.Policies) == 0)
return !certmagic.SubjectQualifiesForPublicCert(d) || usingDefaultIssuersAndIsIP
}
if isTailscaleDomain(d) { if isTailscaleDomain(d) {
tailscale = append(tailscale, d) tailscale = append(tailscale, d)
} else if !certmagic.SubjectQualifiesForPublicCert(d) { } else if shouldUseInternal(d) {
internal = append(internal, d) internal = append(internal, d)
} }
} }

View file

@ -36,10 +36,26 @@ func init() {
// RequestMatcher is a type that can match to a request. // RequestMatcher is a type that can match to a request.
// A route matcher MUST NOT modify the request, with the // A route matcher MUST NOT modify the request, with the
// only exception being its context. // only exception being its context.
//
// Deprecated: Matchers should now implement RequestMatcherWithError.
// You may remove any interface guards for RequestMatcher
// but keep your Match() methods for backwards compatibility.
type RequestMatcher interface { type RequestMatcher interface {
Match(*http.Request) bool Match(*http.Request) bool
} }
// RequestMatcherWithError is like RequestMatcher but can return an error.
// An error during matching will abort the request middleware chain and
// invoke the error middleware chain.
//
// This will eventually replace RequestMatcher. Matcher modules
// should implement both interfaces, and once all modules have
// been updated to use RequestMatcherWithError, the RequestMatcher
// interface may eventually be dropped.
type RequestMatcherWithError interface {
MatchWithError(*http.Request) (bool, error)
}
// Handler is like http.Handler except ServeHTTP may return an error. // Handler is like http.Handler except ServeHTTP may return an error.
// //
// If any handler encounters an error, it should be returned for proper // If any handler encounters an error, it should be returned for proper

View file

@ -202,17 +202,25 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchExpression) Match(r *http.Request) bool { func (m MatchExpression) Match(r *http.Request) bool {
match, err := m.MatchWithError(r)
if err != nil {
SetVar(r.Context(), MatcherErrorVarKey, err)
}
return match
}
// MatchWithError returns true if r matches m.
func (m MatchExpression) MatchWithError(r *http.Request) (bool, error) {
celReq := celHTTPRequest{r} celReq := celHTTPRequest{r}
out, _, err := m.prg.Eval(celReq) out, _, err := m.prg.Eval(celReq)
if err != nil { if err != nil {
m.log.Error("evaluating expression", zap.Error(err)) m.log.Error("evaluating expression", zap.Error(err))
SetVar(r.Context(), MatcherErrorVarKey, err) return false, err
return false
} }
if outBool, ok := out.Value().(bool); ok { if outBool, ok := out.Value().(bool); ok {
return outBool return outBool, nil
} }
return false return false, nil
} }
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
@ -380,7 +388,7 @@ type CELLibraryProducer interface {
// limited set of function signatures. For strong type validation you may need // limited set of function signatures. For strong type validation you may need
// to provide a custom macro which does a more detailed analysis of the CEL // to provide a custom macro which does a more detailed analysis of the CEL
// literal provided to the macro as an argument. // literal provided to the macro as an argument.
func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac CELMatcherFactory) (cel.Library, error) { func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fac any) (cel.Library, error) {
requestType := cel.ObjectType("http.Request") requestType := cel.ObjectType("http.Request")
var macro parser.Macro var macro parser.Macro
switch len(matcherDataTypes) { switch len(matcherDataTypes) {
@ -424,7 +432,11 @@ func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fa
} }
// CELMatcherFactory converts a constant CEL value into a RequestMatcher. // CELMatcherFactory converts a constant CEL value into a RequestMatcher.
type CELMatcherFactory func(data ref.Val) (RequestMatcher, error) // Deprecated: Use CELMatcherWithErrorFactory instead.
type CELMatcherFactory = func(data ref.Val) (RequestMatcher, error)
// CELMatcherWithErrorFactory converts a constant CEL value into a RequestMatcherWithError.
type CELMatcherWithErrorFactory = func(data ref.Val) (RequestMatcherWithError, error)
// matcherCELLibrary is a simplistic configurable cel.Library implementation. // matcherCELLibrary is a simplistic configurable cel.Library implementation.
type matcherCELLibrary struct { type matcherCELLibrary struct {
@ -452,7 +464,7 @@ func (lib *matcherCELLibrary) ProgramOptions() []cel.ProgramOption {
// that takes a single argument, and optimizes the implementation to precompile // that takes a single argument, and optimizes the implementation to precompile
// the matcher and return a function that references the precompiled and // the matcher and return a function that references the precompiled and
// provisioned matcher. // provisioned matcher.
func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.InterpretableDecorator { func CELMatcherDecorator(funcName string, fac any) interpreter.InterpretableDecorator {
return func(i interpreter.Interpretable) (interpreter.Interpretable, error) { return func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
call, ok := i.(interpreter.InterpretableCall) call, ok := i.(interpreter.InterpretableCall)
if !ok { if !ok {
@ -481,35 +493,92 @@ func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.Int
// and matcher provisioning should be handled at dynamically. // and matcher provisioning should be handled at dynamically.
return i, nil return i, nil
} }
matcher, err := fac(matcherData.Value())
if err != nil { if factory, ok := fac.(CELMatcherWithErrorFactory); ok {
return nil, err matcher, err := factory(matcherData.Value())
if err != nil {
return nil, err
}
return interpreter.NewCall(
i.ID(), funcName, funcName+"_opt",
[]interpreter.Interpretable{reqAttr},
func(args ...ref.Val) ref.Val {
// The request value, guaranteed to be of type celHTTPRequest
celReq := args[0]
// If needed this call could be changed to convert the value
// to a *http.Request using CEL's ConvertToNative method.
httpReq := celReq.Value().(celHTTPRequest)
match, err := matcher.MatchWithError(httpReq.Request)
if err != nil {
return types.WrapErr(err)
}
return types.Bool(match)
},
), nil
} }
return interpreter.NewCall(
i.ID(), funcName, funcName+"_opt", if factory, ok := fac.(CELMatcherFactory); ok {
[]interpreter.Interpretable{reqAttr}, matcher, err := factory(matcherData.Value())
func(args ...ref.Val) ref.Val { if err != nil {
// The request value, guaranteed to be of type celHTTPRequest return nil, err
celReq := args[0] }
// If needed this call could be changed to convert the value return interpreter.NewCall(
// to a *http.Request using CEL's ConvertToNative method. i.ID(), funcName, funcName+"_opt",
httpReq := celReq.Value().(celHTTPRequest) []interpreter.Interpretable{reqAttr},
return types.Bool(matcher.Match(httpReq.Request)) func(args ...ref.Val) ref.Val {
}, // The request value, guaranteed to be of type celHTTPRequest
), nil celReq := args[0]
// If needed this call could be changed to convert the value
// to a *http.Request using CEL's ConvertToNative method.
httpReq := celReq.Value().(celHTTPRequest)
if m, ok := matcher.(RequestMatcherWithError); ok {
match, err := m.MatchWithError(httpReq.Request)
if err != nil {
return types.WrapErr(err)
}
return types.Bool(match)
}
return types.Bool(matcher.Match(httpReq.Request))
},
), nil
}
return nil, fmt.Errorf("invalid matcher factory, must be CELMatcherFactory or CELMatcherWithErrorFactory: %T", fac)
} }
} }
// CELMatcherRuntimeFunction creates a function binding for when the input to the matcher // CELMatcherRuntimeFunction creates a function binding for when the input to the matcher
// is dynamically resolved rather than a set of static constant values. // is dynamically resolved rather than a set of static constant values.
func CELMatcherRuntimeFunction(funcName string, fac CELMatcherFactory) functions.BinaryOp { func CELMatcherRuntimeFunction(funcName string, fac any) functions.BinaryOp {
return func(celReq, matcherData ref.Val) ref.Val { return func(celReq, matcherData ref.Val) ref.Val {
matcher, err := fac(matcherData) if factory, ok := fac.(CELMatcherWithErrorFactory); ok {
if err != nil { matcher, err := factory(matcherData)
return types.WrapErr(err) if err != nil {
return types.WrapErr(err)
}
httpReq := celReq.Value().(celHTTPRequest)
match, err := matcher.MatchWithError(httpReq.Request)
if err != nil {
return types.WrapErr(err)
}
return types.Bool(match)
} }
httpReq := celReq.Value().(celHTTPRequest) if factory, ok := fac.(CELMatcherFactory); ok {
return types.Bool(matcher.Match(httpReq.Request)) matcher, err := factory(matcherData)
if err != nil {
return types.WrapErr(err)
}
httpReq := celReq.Value().(celHTTPRequest)
if m, ok := matcher.(RequestMatcherWithError); ok {
match, err := m.MatchWithError(httpReq.Request)
if err != nil {
return types.WrapErr(err)
}
return types.Bool(match)
}
return types.Bool(matcher.Match(httpReq.Request))
}
return types.NewErr("CELMatcherRuntimeFunction invalid matcher factory: %T", fac)
} }
} }
@ -733,9 +802,9 @@ const MatcherNameCtxKey = "matcher_name"
// Interface guards // Interface guards
var ( var (
_ caddy.Provisioner = (*MatchExpression)(nil) _ caddy.Provisioner = (*MatchExpression)(nil)
_ RequestMatcher = (*MatchExpression)(nil) _ RequestMatcherWithError = (*MatchExpression)(nil)
_ caddyfile.Unmarshaler = (*MatchExpression)(nil) _ caddyfile.Unmarshaler = (*MatchExpression)(nil)
_ json.Marshaler = (*MatchExpression)(nil) _ json.Marshaler = (*MatchExpression)(nil)
_ json.Unmarshaler = (*MatchExpression)(nil) _ json.Unmarshaler = (*MatchExpression)(nil)
) )

View file

@ -489,7 +489,11 @@ func TestMatchExpressionMatch(t *testing.T) {
} }
} }
if tc.expression.Match(req) != tc.wantResult { matches, err := tc.expression.MatchWithError(req)
if err != nil {
t.Errorf("MatchExpression.Match() error = %v", err)
}
if matches != tc.wantResult {
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr) t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
} }
}) })
@ -532,7 +536,7 @@ func BenchmarkMatchExpressionMatch(b *testing.B) {
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
tc.expression.Match(req) tc.expression.MatchWithError(req)
} }
}) })
} }

View file

@ -57,21 +57,7 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next() // consume directive name d.Next() // consume directive name
prefer := []string{} prefer := []string{}
for _, arg := range d.RemainingArgs() { remainingArgs := d.RemainingArgs()
mod, err := caddy.GetModule("http.encoders." + arg)
if err != nil {
return d.Errf("finding encoder module '%s': %v", mod, err)
}
encoding, ok := mod.New().(Encoding)
if !ok {
return d.Errf("module %s is not an HTTP encoding", mod)
}
if enc.EncodingsRaw == nil {
enc.EncodingsRaw = make(caddy.ModuleMap)
}
enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil)
prefer = append(prefer, arg)
}
responseMatchers := make(map[string]caddyhttp.ResponseMatcher) responseMatchers := make(map[string]caddyhttp.ResponseMatcher)
for d.NextBlock(0) { for d.NextBlock(0) {
@ -111,6 +97,26 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
} }
} }
if len(prefer) == 0 && len(remainingArgs) == 0 {
remainingArgs = []string{"zstd", "gzip"}
}
for _, arg := range remainingArgs {
mod, err := caddy.GetModule("http.encoders." + arg)
if err != nil {
return d.Errf("finding encoder module '%s': %v", mod, err)
}
encoding, ok := mod.New().(Encoding)
if !ok {
return d.Errf("module %s is not an HTTP encoding", mod)
}
if enc.EncodingsRaw == nil {
enc.EncodingsRaw = make(caddy.ModuleMap)
}
enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil)
prefer = append(prefer, arg)
}
// use the order in which the encoders were defined. // use the order in which the encoders were defined.
enc.Prefer = prefer enc.Prefer = prefer

View file

@ -156,7 +156,7 @@ func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh
if _, ok := enc.writerPools[encName]; !ok { if _, ok := enc.writerPools[encName]; !ok {
continue // encoding not offered continue // encoding not offered
} }
w = enc.openResponseWriter(encName, w) w = enc.openResponseWriter(encName, w, r.Method == http.MethodConnect)
defer w.(*responseWriter).Close() defer w.(*responseWriter).Close()
// to comply with RFC 9110 section 8.8.3(.3), we modify the Etag when encoding // to comply with RFC 9110 section 8.8.3(.3), we modify the Etag when encoding
@ -201,14 +201,14 @@ func (enc *Encode) addEncoding(e Encoding) error {
// openResponseWriter creates a new response writer that may (or may not) // openResponseWriter creates a new response writer that may (or may not)
// encode the response with encodingName. The returned response writer MUST // encode the response with encodingName. The returned response writer MUST
// be closed after the handler completes. // be closed after the handler completes.
func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter) *responseWriter { func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter, isConnect bool) *responseWriter {
var rw responseWriter var rw responseWriter
return enc.initResponseWriter(&rw, encodingName, w) return enc.initResponseWriter(&rw, encodingName, w, isConnect)
} }
// initResponseWriter initializes the responseWriter instance // initResponseWriter initializes the responseWriter instance
// allocated in openResponseWriter, enabling mid-stack inlining. // allocated in openResponseWriter, enabling mid-stack inlining.
func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter { func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter, isConnect bool) *responseWriter {
if rww, ok := wrappedRW.(*caddyhttp.ResponseWriterWrapper); ok { if rww, ok := wrappedRW.(*caddyhttp.ResponseWriterWrapper); ok {
rw.ResponseWriter = rww rw.ResponseWriter = rww
} else { } else {
@ -216,6 +216,7 @@ func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, w
} }
rw.encodingName = encodingName rw.encodingName = encodingName
rw.config = enc rw.config = enc
rw.isConnect = isConnect
return rw return rw
} }
@ -230,6 +231,7 @@ type responseWriter struct {
config *Encode config *Encode
statusCode int statusCode int
wroteHeader bool wroteHeader bool
isConnect bool
} }
// WriteHeader stores the status to write when the time comes // WriteHeader stores the status to write when the time comes
@ -245,6 +247,14 @@ func (rw *responseWriter) WriteHeader(status int) {
rw.Header().Add("Vary", "Accept-Encoding") rw.Header().Add("Vary", "Accept-Encoding")
} }
// write status immediately if status is 2xx and the request is CONNECT
// since it means the response is successful.
// see: https://github.com/caddyserver/caddy/issues/6733#issuecomment-2525058845
if rw.isConnect && 200 <= status && status <= 299 {
rw.ResponseWriter.WriteHeader(status)
rw.wroteHeader = true
}
// write status immediately when status code is informational // write status immediately when status code is informational
// see: https://caddy.community/t/disappear-103-early-hints-response-with-encode-enable-caddy-v2-7-6/23081/5 // see: https://caddy.community/t/disappear-103-early-hints-response-with-encode-enable-caddy-v2-7-6/23081/5
if 100 <= status && status <= 199 { if 100 <= status && status <= 199 {
@ -260,6 +270,12 @@ func (enc *Encode) Match(rw *responseWriter) bool {
// FlushError is an alternative Flush returning an error. It delays the actual Flush of the underlying // FlushError is an alternative Flush returning an error. It delays the actual Flush of the underlying
// ResponseWriterWrapper until headers were written. // ResponseWriterWrapper until headers were written.
func (rw *responseWriter) FlushError() error { func (rw *responseWriter) FlushError() error {
// WriteHeader wasn't called and is a CONNECT request, treat it as a success.
// otherwise, wait until header is written.
if rw.isConnect && !rw.wroteHeader && rw.statusCode == 0 {
rw.WriteHeader(http.StatusOK)
}
if !rw.wroteHeader { if !rw.wroteHeader {
// flushing the underlying ResponseWriter will write header and status code, // flushing the underlying ResponseWriter will write header and status code,
// but we need to delay that until we can determine if we must encode and // but we need to delay that until we can determine if we must encode and
@ -288,6 +304,12 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
return 0, nil return 0, nil
} }
// WriteHeader wasn't called and is a CONNECT request, treat it as a success.
// otherwise, determine if the response should be compressed.
if rw.isConnect && !rw.wroteHeader && rw.statusCode == 0 {
rw.WriteHeader(http.StatusOK)
}
// sniff content-type and determine content-length // sniff content-type and determine content-length
if !rw.wroteHeader && rw.config.MinLength > 0 { if !rw.wroteHeader && rw.config.MinLength > 0 {
var gtMinLength bool var gtMinLength bool
@ -325,6 +347,49 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
} }
} }
// used to mask ReadFrom method
type writerOnly struct {
io.Writer
}
// copied from stdlib
const sniffLen = 512
// ReadFrom will try to use sendfile to copy from the reader to the response writer.
// It's only used if the response writer implements io.ReaderFrom and the data can't be compressed.
// It's based on stdlin http1.1 response writer implementation.
// https://github.com/golang/go/blob/f4e3ec3dbe3b8e04a058d266adf8e048bab563f2/src/net/http/server.go#L586
func (rw *responseWriter) ReadFrom(r io.Reader) (int64, error) {
rf, ok := rw.ResponseWriter.(io.ReaderFrom)
// sendfile can't be used anyway
if !ok {
// mask ReadFrom to avoid infinite recursion
return io.Copy(writerOnly{rw}, r)
}
var ns int64
// try to sniff the content type and determine if the response should be compressed
if !rw.wroteHeader && rw.config.MinLength > 0 {
var (
err error
buf [sniffLen]byte
)
// mask ReadFrom to let Write determine if the response should be compressed
ns, err = io.CopyBuffer(writerOnly{rw}, io.LimitReader(r, sniffLen), buf[:])
if err != nil || ns < sniffLen {
return ns, err
}
}
// the response will be compressed, no sendfile support
if rw.w != nil {
nr, err := io.Copy(rw.w, r)
return nr + ns, err
}
nr, err := rf.ReadFrom(r)
return nr + ns, err
}
// Close writes any remaining buffered response and // Close writes any remaining buffered response and
// deallocates any active resources. // deallocates any active resources.
func (rw *responseWriter) Close() error { func (rw *responseWriter) Close() error {

View file

@ -9,7 +9,7 @@ import (
func BenchmarkOpenResponseWriter(b *testing.B) { func BenchmarkOpenResponseWriter(b *testing.B) {
enc := new(Encode) enc := new(Encode)
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
enc.openResponseWriter("test", nil) enc.openResponseWriter("test", nil, false)
} }
} }

View file

@ -66,8 +66,15 @@ type Browse struct {
// - `sort size` will sort by size in ascending order // - `sort size` will sort by size in ascending order
// The first option must be `sort_by` and the second option must be `order` (if exists). // The first option must be `sort_by` and the second option must be `order` (if exists).
SortOptions []string `json:"sort,omitempty"` SortOptions []string `json:"sort,omitempty"`
// FileLimit limits the number of up to n DirEntry values in directory order.
FileLimit int `json:"file_limit,omitempty"`
} }
const (
defaultDirEntryLimit = 10000
)
func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
if c := fsrv.logger.Check(zapcore.DebugLevel, "browse enabled; listing directory contents"); c != nil { if c := fsrv.logger.Check(zapcore.DebugLevel, "browse enabled; listing directory contents"); c != nil {
c.Write(zap.String("path", dirPath), zap.String("root", root)) c.Write(zap.String("path", dirPath), zap.String("root", root))
@ -123,9 +130,9 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
// speed up browser/client experience and caching by supporting If-Modified-Since // speed up browser/client experience and caching by supporting If-Modified-Since
if ifModSinceStr := r.Header.Get("If-Modified-Since"); ifModSinceStr != "" { if ifModSinceStr := r.Header.Get("If-Modified-Since"); ifModSinceStr != "" {
ifModSince, err := time.ParseInLocation(http.TimeFormat, ifModSinceStr, time.Local) // basically a copy of stdlib file server's handling of If-Modified-Since
lastModTrunc := listing.lastModified.Truncate(time.Second) ifModSince, err := http.ParseTime(ifModSinceStr)
if err == nil && (lastModTrunc.Equal(ifModSince) || lastModTrunc.Before(ifModSince)) { if err == nil && listing.lastModified.Truncate(time.Second).Compare(ifModSince) <= 0 {
w.WriteHeader(http.StatusNotModified) w.WriteHeader(http.StatusNotModified)
return nil return nil
} }
@ -206,7 +213,16 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
} }
func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs.FS, dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (*browseTemplateContext, error) { func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs.FS, dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (*browseTemplateContext, error) {
files, err := dir.ReadDir(10000) // TODO: this limit should probably be configurable // modTime for the directory itself
stat, err := dir.Stat()
if err != nil {
return nil, err
}
dirLimit := defaultDirEntryLimit
if fsrv.Browse.FileLimit != 0 {
dirLimit = fsrv.Browse.FileLimit
}
files, err := dir.ReadDir(dirLimit)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return nil, err return nil, err
} }
@ -214,7 +230,7 @@ func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs
// user can presumably browse "up" to parent folder if path is longer than "/" // user can presumably browse "up" to parent folder if path is longer than "/"
canGoUp := len(urlPath) > 1 canGoUp := len(urlPath) > 1
return fsrv.directoryListing(ctx, fileSystem, files, canGoUp, root, urlPath, repl), nil return fsrv.directoryListing(ctx, fileSystem, stat.ModTime(), files, canGoUp, root, urlPath, repl), nil
} }
// browseApplyQueryParams applies query parameters to the listing. // browseApplyQueryParams applies query parameters to the listing.

View file

@ -35,15 +35,16 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS, entries []fs.DirEntry, canGoUp bool, root, urlPath string, repl *caddy.Replacer) *browseTemplateContext { func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS, parentModTime time.Time, entries []fs.DirEntry, canGoUp bool, root, urlPath string, repl *caddy.Replacer) *browseTemplateContext {
filesToHide := fsrv.transformHidePaths(repl) filesToHide := fsrv.transformHidePaths(repl)
name, _ := url.PathUnescape(urlPath) name, _ := url.PathUnescape(urlPath)
tplCtx := &browseTemplateContext{ tplCtx := &browseTemplateContext{
Name: path.Base(name), Name: path.Base(name),
Path: urlPath, Path: urlPath,
CanGoUp: canGoUp, CanGoUp: canGoUp,
lastModified: parentModTime,
} }
for _, entry := range entries { for _, entry := range entries {
@ -131,6 +132,10 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
}) })
} }
// this time is used for the Last-Modified header and comparing If-Modified-Since from client
// both are expected to be in UTC, so we convert to UTC here
// see: https://github.com/caddyserver/caddy/issues/6828
tplCtx.lastModified = tplCtx.lastModified.UTC()
return tplCtx return tplCtx
} }

View file

@ -16,6 +16,7 @@ package fileserver
import ( import (
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@ -78,7 +79,7 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.ArgErr() return d.ArgErr()
} }
for d.NextBlock(0) { for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() { switch d.Val() {
case "fs": case "fs":
if !d.NextArg() { if !d.NextArg() {
@ -129,15 +130,29 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.Errf("unknown sort option '%s'", dVal) return d.Errf("unknown sort option '%s'", dVal)
} }
} }
case "file_limit":
fileLimit := d.RemainingArgs()
if len(fileLimit) != 1 {
return d.Err("file_limit should have an integer value")
}
val, _ := strconv.Atoi(fileLimit[0])
if fsrv.Browse.FileLimit != 0 {
return d.Err("file_limit is already enabled")
}
fsrv.Browse.FileLimit = val
default: default:
return d.Errf("unknown subdirective '%s'", d.Val()) return d.Errf("unknown subdirective '%s'", d.Val())
} }
} }
case "precompressed": case "precompressed":
var order []string fsrv.PrecompressedOrder = d.RemainingArgs()
for d.NextArg() { if len(fsrv.PrecompressedOrder) == 0 {
modID := "http.precompressed." + d.Val() fsrv.PrecompressedOrder = []string{"br", "zstd", "gzip"}
}
for _, format := range fsrv.PrecompressedOrder {
modID := "http.precompressed." + format
mod, err := caddy.GetModule(modID) mod, err := caddy.GetModule(modID)
if err != nil { if err != nil {
return d.Errf("getting module named '%s': %v", modID, err) return d.Errf("getting module named '%s': %v", modID, err)
@ -150,10 +165,8 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if fsrv.PrecompressedRaw == nil { if fsrv.PrecompressedRaw == nil {
fsrv.PrecompressedRaw = make(caddy.ModuleMap) fsrv.PrecompressedRaw = make(caddy.ModuleMap)
} }
fsrv.PrecompressedRaw[d.Val()] = caddyconfig.JSON(precompress, nil) fsrv.PrecompressedRaw[format] = caddyconfig.JSON(precompress, nil)
order = append(order, d.Val())
} }
fsrv.PrecompressedOrder = order
case "status": case "status":
if !d.NextArg() { if !d.NextArg() {
@ -263,7 +276,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
tryPolicy = h.Val() tryPolicy = h.Val()
switch tryPolicy { switch tryPolicy {
case tryPolicyFirstExist, tryPolicyLargestSize, tryPolicySmallestSize, tryPolicyMostRecentlyMod: case tryPolicyFirstExist, tryPolicyFirstExistFallback, tryPolicyLargestSize, tryPolicySmallestSize, tryPolicyMostRecentlyMod:
default: default:
return nil, h.Errf("unrecognized try policy: %s", tryPolicy) return nil, h.Errf("unrecognized try policy: %s", tryPolicy)
} }

View file

@ -66,6 +66,7 @@ respond with a file listing.`,
cmd.Flags().BoolP("templates", "t", false, "Enable template rendering") cmd.Flags().BoolP("templates", "t", false, "Enable template rendering")
cmd.Flags().BoolP("access-log", "a", false, "Enable the access log") cmd.Flags().BoolP("access-log", "a", false, "Enable the access log")
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs") cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
cmd.Flags().IntP("file-limit", "f", defaultDirEntryLimit, "Max directories to read")
cmd.Flags().BoolP("no-compress", "", false, "Disable Zstandard and Gzip compression") cmd.Flags().BoolP("no-compress", "", false, "Disable Zstandard and Gzip compression")
cmd.Flags().StringSliceP("precompressed", "p", []string{}, "Specify precompression file extensions. Compression preference implied from flag order.") cmd.Flags().StringSliceP("precompressed", "p", []string{}, "Specify precompression file extensions. Compression preference implied from flag order.")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer) cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer)
@ -91,6 +92,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
browse := fs.Bool("browse") browse := fs.Bool("browse")
templates := fs.Bool("templates") templates := fs.Bool("templates")
accessLog := fs.Bool("access-log") accessLog := fs.Bool("access-log")
fileLimit := fs.Int("file-limit")
debug := fs.Bool("debug") debug := fs.Bool("debug")
revealSymlinks := fs.Bool("reveal-symlinks") revealSymlinks := fs.Bool("reveal-symlinks")
compress := !fs.Bool("no-compress") compress := !fs.Bool("no-compress")
@ -151,7 +153,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
} }
if browse { if browse {
handler.Browse = &Browse{RevealSymlinks: revealSymlinks} handler.Browse = &Browse{RevealSymlinks: revealSymlinks, FileLimit: fileLimit}
} }
handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil)) handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil))

View file

@ -90,6 +90,7 @@ type MatchFile struct {
// How to choose a file in TryFiles. Can be: // How to choose a file in TryFiles. Can be:
// //
// - first_exist // - first_exist
// - first_exist_fallback
// - smallest_size // - smallest_size
// - largest_size // - largest_size
// - most_recently_modified // - most_recently_modified
@ -173,7 +174,7 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) { func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) {
requestType := cel.ObjectType("http.Request") requestType := cel.ObjectType("http.Request")
matcherFactory := func(data ref.Val) (caddyhttp.RequestMatcher, error) { matcherFactory := func(data ref.Val) (caddyhttp.RequestMatcherWithError, error) {
values, err := caddyhttp.CELValueToMapStrList(data) values, err := caddyhttp.CELValueToMapStrList(data)
if err != nil { if err != nil {
return nil, err return nil, err
@ -191,7 +192,7 @@ func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) {
var try_policy string var try_policy string
if len(values["try_policy"]) > 0 { if len(values["try_policy"]) > 0 {
root = values["try_policy"][0] try_policy = values["try_policy"][0]
} }
m := MatchFile{ m := MatchFile{
@ -296,6 +297,7 @@ func (m MatchFile) Validate() error {
switch m.TryPolicy { switch m.TryPolicy {
case "", case "",
tryPolicyFirstExist, tryPolicyFirstExist,
tryPolicyFirstExistFallback,
tryPolicyLargestSize, tryPolicyLargestSize,
tryPolicySmallestSize, tryPolicySmallestSize,
tryPolicyMostRecentlyMod: tryPolicyMostRecentlyMod:
@ -313,12 +315,22 @@ func (m MatchFile) Validate() error {
// - http.matchers.file.type: file or directory // - http.matchers.file.type: file or directory
// - http.matchers.file.remainder: Portion remaining after splitting file path (if configured) // - http.matchers.file.remainder: Portion remaining after splitting file path (if configured)
func (m MatchFile) Match(r *http.Request) bool { func (m MatchFile) Match(r *http.Request) bool {
match, err := m.selectFile(r)
if err != nil {
// nolint:staticcheck
caddyhttp.SetVar(r.Context(), caddyhttp.MatcherErrorVarKey, err)
}
return match
}
// MatchWithError returns true if r matches m.
func (m MatchFile) MatchWithError(r *http.Request) (bool, error) {
return m.selectFile(r) return m.selectFile(r)
} }
// selectFile chooses a file according to m.TryPolicy by appending // selectFile chooses a file according to m.TryPolicy by appending
// the paths in m.TryFiles to m.Root, with placeholder replacements. // the paths in m.TryFiles to m.Root, with placeholder replacements.
func (m MatchFile) selectFile(r *http.Request) (matched bool) { func (m MatchFile) selectFile(r *http.Request) (bool, error) {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
root := filepath.Clean(repl.ReplaceAll(m.Root, ".")) root := filepath.Clean(repl.ReplaceAll(m.Root, "."))
@ -330,7 +342,7 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
if c := m.logger.Check(zapcore.ErrorLevel, "use of unregistered filesystem"); c != nil { if c := m.logger.Check(zapcore.ErrorLevel, "use of unregistered filesystem"); c != nil {
c.Write(zap.String("fs", fsName)) c.Write(zap.String("fs", fsName))
} }
return false return false, nil
} }
type matchCandidate struct { type matchCandidate struct {
fullpath, relative, splitRemainder string fullpath, relative, splitRemainder string
@ -405,13 +417,13 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
} }
// setPlaceholders creates the placeholders for the matched file // setPlaceholders creates the placeholders for the matched file
setPlaceholders := func(candidate matchCandidate, info fs.FileInfo) { setPlaceholders := func(candidate matchCandidate, isDir bool) {
repl.Set("http.matchers.file.relative", filepath.ToSlash(candidate.relative)) repl.Set("http.matchers.file.relative", filepath.ToSlash(candidate.relative))
repl.Set("http.matchers.file.absolute", filepath.ToSlash(candidate.fullpath)) repl.Set("http.matchers.file.absolute", filepath.ToSlash(candidate.fullpath))
repl.Set("http.matchers.file.remainder", filepath.ToSlash(candidate.splitRemainder)) repl.Set("http.matchers.file.remainder", filepath.ToSlash(candidate.splitRemainder))
fileType := "file" fileType := "file"
if info.IsDir() { if isDir {
fileType = "directory" fileType = "directory"
} }
repl.Set("http.matchers.file.type", fileType) repl.Set("http.matchers.file.type", fileType)
@ -419,17 +431,32 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
// match file according to the configured policy // match file according to the configured policy
switch m.TryPolicy { switch m.TryPolicy {
case "", tryPolicyFirstExist: case "", tryPolicyFirstExist, tryPolicyFirstExistFallback:
for _, pattern := range m.TryFiles { maxI := -1
if m.TryPolicy == tryPolicyFirstExistFallback {
maxI = len(m.TryFiles) - 1
}
for i, pattern := range m.TryFiles {
// If the pattern is a status code, emit an error,
// which short-circuits the middleware pipeline and
// writes an HTTP error response.
if err := parseErrorCode(pattern); err != nil { if err := parseErrorCode(pattern); err != nil {
caddyhttp.SetVar(r.Context(), caddyhttp.MatcherErrorVarKey, err) return false, err
return
} }
candidates := makeCandidates(pattern) candidates := makeCandidates(pattern)
for _, c := range candidates { for _, c := range candidates {
// Skip the IO if using fallback policy and it's the latest item
if i == maxI {
setPlaceholders(c, false)
return true, nil
}
if info, exists := m.strictFileExists(fileSystem, c.fullpath); exists { if info, exists := m.strictFileExists(fileSystem, c.fullpath); exists {
setPlaceholders(c, info) setPlaceholders(c, info.IsDir())
return true return true, nil
} }
} }
} }
@ -450,10 +477,10 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
} }
} }
if largestInfo == nil { if largestInfo == nil {
return false return false, nil
} }
setPlaceholders(largest, largestInfo) setPlaceholders(largest, largestInfo.IsDir())
return true return true, nil
case tryPolicySmallestSize: case tryPolicySmallestSize:
var smallestSize int64 var smallestSize int64
@ -471,10 +498,10 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
} }
} }
if smallestInfo == nil { if smallestInfo == nil {
return false return false, nil
} }
setPlaceholders(smallest, smallestInfo) setPlaceholders(smallest, smallestInfo.IsDir())
return true return true, nil
case tryPolicyMostRecentlyMod: case tryPolicyMostRecentlyMod:
var recent matchCandidate var recent matchCandidate
@ -491,13 +518,13 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
} }
} }
if recentInfo == nil { if recentInfo == nil {
return false return false, nil
} }
setPlaceholders(recent, recentInfo) setPlaceholders(recent, recentInfo.IsDir())
return true return true, nil
} }
return return false, nil
} }
// parseErrorCode checks if the input is a status // parseErrorCode checks if the input is a status
@ -695,15 +722,16 @@ var globSafeRepl = strings.NewReplacer(
) )
const ( const (
tryPolicyFirstExist = "first_exist" tryPolicyFirstExist = "first_exist"
tryPolicyLargestSize = "largest_size" tryPolicyFirstExistFallback = "first_exist_fallback"
tryPolicySmallestSize = "smallest_size" tryPolicyLargestSize = "largest_size"
tryPolicyMostRecentlyMod = "most_recently_modified" tryPolicySmallestSize = "smallest_size"
tryPolicyMostRecentlyMod = "most_recently_modified"
) )
// Interface guards // Interface guards
var ( var (
_ caddy.Validator = (*MatchFile)(nil) _ caddy.Validator = (*MatchFile)(nil)
_ caddyhttp.RequestMatcher = (*MatchFile)(nil) _ caddyhttp.RequestMatcherWithError = (*MatchFile)(nil)
_ caddyhttp.CELLibraryProducer = (*MatchFile)(nil) _ caddyhttp.CELLibraryProducer = (*MatchFile)(nil)
) )

View file

@ -130,7 +130,10 @@ func TestFileMatcher(t *testing.T) {
req := &http.Request{URL: u} req := &http.Request{URL: u}
repl := caddyhttp.NewTestReplacer(req) repl := caddyhttp.NewTestReplacer(req)
result := m.Match(req) result, err := m.MatchWithError(req)
if err != nil {
t.Errorf("Test %d: unexpected error: %v", i, err)
}
if result != tc.matched { if result != tc.matched {
t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result) t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result)
} }
@ -240,7 +243,10 @@ func TestPHPFileMatcher(t *testing.T) {
req := &http.Request{URL: u} req := &http.Request{URL: u}
repl := caddyhttp.NewTestReplacer(req) repl := caddyhttp.NewTestReplacer(req)
result := m.Match(req) result, err := m.MatchWithError(req)
if err != nil {
t.Errorf("Test %d: unexpected error: %v", i, err)
}
if result != tc.matched { if result != tc.matched {
t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result) t.Errorf("Test %d: expected match=%t, got %t", i, tc.matched, result)
} }
@ -289,6 +295,7 @@ var expressionTests = []struct {
wantErr bool wantErr bool
wantResult bool wantResult bool
clientCertificate []byte clientCertificate []byte
expectedPath string
}{ }{
{ {
name: "file error no args (MatchFile)", name: "file error no args (MatchFile)",
@ -354,6 +361,15 @@ var expressionTests = []struct {
urlTarget: "https://example.com/nopenope.txt", urlTarget: "https://example.com/nopenope.txt",
wantResult: false, wantResult: false,
}, },
{
name: "file match long pattern foo.txt with try_policy (MatchFile)",
expression: &caddyhttp.MatchExpression{
Expr: `file({"root": "./testdata", "try_policy": "largest_size", "try_files": ["foo.txt", "large.txt"]})`,
},
urlTarget: "https://example.com/",
wantResult: true,
expectedPath: "/large.txt",
},
} }
func TestMatchExpressionMatch(t *testing.T) { func TestMatchExpressionMatch(t *testing.T) {
@ -379,9 +395,24 @@ func TestMatchExpressionMatch(t *testing.T) {
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx) req = req.WithContext(ctx)
if tc.expression.Match(req) != tc.wantResult { matches, err := tc.expression.MatchWithError(req)
if err != nil {
t.Errorf("MatchExpression.Match() error = %v", err)
return
}
if matches != tc.wantResult {
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr) t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
} }
if tc.expectedPath != "" {
path, ok := repl.Get("http.matchers.file.relative")
if !ok {
t.Errorf("MatchExpression.Match() expected to return path '%s', but got none", tc.expectedPath)
}
if path != tc.expectedPath {
t.Errorf("MatchExpression.Match() expected to return path '%s', but got '%s'", tc.expectedPath, path)
}
}
}) })
} }
} }

View file

@ -204,7 +204,7 @@ func (fsrv *FileServer) Provision(ctx caddy.Context) error {
// absolute paths before the server starts for very slight performance improvement // absolute paths before the server starts for very slight performance improvement
for i, h := range fsrv.Hide { for i, h := range fsrv.Hide {
if !strings.Contains(h, "{") && strings.Contains(h, separator) { if !strings.Contains(h, "{") && strings.Contains(h, separator) {
if abs, err := filepath.Abs(h); err == nil { if abs, err := caddy.FastAbs(h); err == nil {
fsrv.Hide[i] = abs fsrv.Hide[i] = abs
} }
} }
@ -636,7 +636,7 @@ func (fsrv *FileServer) transformHidePaths(repl *caddy.Replacer) []string {
for i := range fsrv.Hide { for i := range fsrv.Hide {
hide[i] = repl.ReplaceAll(fsrv.Hide[i], "") hide[i] = repl.ReplaceAll(fsrv.Hide[i], "")
if strings.Contains(hide[i], separator) { if strings.Contains(hide[i], separator) {
abs, err := filepath.Abs(hide[i]) abs, err := caddy.FastAbs(hide[i])
if err == nil { if err == nil {
hide[i] = abs hide[i] = abs
} }
@ -655,7 +655,7 @@ func fileHidden(filename string, hide []string) bool {
} }
// all path comparisons use the complete absolute path if possible // all path comparisons use the complete absolute path if possible
filenameAbs, err := filepath.Abs(filename) filenameAbs, err := caddy.FastAbs(filename)
if err == nil { if err == nil {
filename = filenameAbs filename = filenameAbs
} }

View file

@ -0,0 +1,3 @@
This is a file with more content than the other files in this directory
such that tests using the largest_size policy pick this file, or the
smallest_size policy avoids this file.

View file

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

View file

@ -78,7 +78,7 @@ func (h Handler) Validate() error {
return err return err
} }
} }
if h.Response != nil { if h.Response != nil && h.Response.HeaderOps != nil {
err := h.Response.validate() err := h.Response.validate()
if err != nil { if err != nil {
return err return err
@ -133,6 +133,9 @@ type HeaderOps struct {
// Provision sets up the header operations. // Provision sets up the header operations.
func (ops *HeaderOps) Provision(_ caddy.Context) error { func (ops *HeaderOps) Provision(_ caddy.Context) error {
if ops == nil {
return nil // it's possible no ops are configured; fix #6893
}
for fieldName, replacements := range ops.Replace { for fieldName, replacements := range ops.Replace {
for i, r := range replacements { for i, r := range replacements {
if r.SearchRegexp == "" { if r.SearchRegexp == "" {
@ -200,9 +203,7 @@ func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) {
for _, fieldName := range ops.Delete { for _, fieldName := range ops.Delete {
fieldName = repl.ReplaceKnown(fieldName, "") fieldName = repl.ReplaceKnown(fieldName, "")
if fieldName == "*" { if fieldName == "*" {
for existingField := range hdr { clear(hdr)
delete(hdr, existingField)
}
} }
} }

View file

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

View file

@ -108,7 +108,7 @@ func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
// internal data type of the MatchPath value. // internal data type of the MatchPath value.
[]*cel.Type{cel.ListType(cel.StringType)}, []*cel.Type{cel.ListType(cel.StringType)},
// function to convert a constant list of strings to a MatchPath instance. // function to convert a constant list of strings to a MatchPath instance.
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
strList, err := data.ConvertToNative(refStringList) strList, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@ -145,9 +145,23 @@ func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchRemoteIP) Match(r *http.Request) bool { func (m MatchRemoteIP) Match(r *http.Request) bool {
if r.TLS != nil && !r.TLS.HandshakeComplete { match, err := m.MatchWithError(r)
return false // if handshake is not finished, we infer 0-RTT that has not verified remote IP; could be spoofed if err != nil {
SetVar(r.Context(), MatcherErrorVarKey, err)
} }
return match
}
// MatchWithError returns true if r matches m.
func (m MatchRemoteIP) MatchWithError(r *http.Request) (bool, error) {
// if handshake is not finished, we infer 0-RTT that has
// not verified remote IP; could be spoofed, so we throw
// HTTP 425 status to tell the client to try again after
// the handshake is complete
if r.TLS != nil && !r.TLS.HandshakeComplete {
return false, Error(http.StatusTooEarly, fmt.Errorf("TLS handshake not complete, remote IP cannot be verified"))
}
address := r.RemoteAddr address := r.RemoteAddr
clientIP, zoneID, err := parseIPZoneFromString(address) clientIP, zoneID, err := parseIPZoneFromString(address)
if err != nil { if err != nil {
@ -155,7 +169,7 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
c.Write(zap.Error(err)) c.Write(zap.Error(err))
} }
return false return false, nil
} }
matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones) matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
if !matches && !zoneFilter { if !matches && !zoneFilter {
@ -163,7 +177,7 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
c.Write(zap.String("zone", zoneID)) c.Write(zap.String("zone", zoneID))
} }
} }
return matches return matches, nil
} }
// CaddyModule returns the Caddy module information. // CaddyModule returns the Caddy module information.
@ -207,7 +221,7 @@ func (MatchClientIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
// internal data type of the MatchPath value. // internal data type of the MatchPath value.
[]*cel.Type{cel.ListType(cel.StringType)}, []*cel.Type{cel.ListType(cel.StringType)},
// function to convert a constant list of strings to a MatchPath instance. // function to convert a constant list of strings to a MatchPath instance.
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
strList, err := data.ConvertToNative(refStringList) strList, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@ -238,20 +252,34 @@ func (m *MatchClientIP) Provision(ctx caddy.Context) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchClientIP) Match(r *http.Request) bool { func (m MatchClientIP) Match(r *http.Request) bool {
if r.TLS != nil && !r.TLS.HandshakeComplete { match, err := m.MatchWithError(r)
return false // if handshake is not finished, we infer 0-RTT that has not verified remote IP; could be spoofed if err != nil {
SetVar(r.Context(), MatcherErrorVarKey, err)
} }
return match
}
// MatchWithError returns true if r matches m.
func (m MatchClientIP) MatchWithError(r *http.Request) (bool, error) {
// if handshake is not finished, we infer 0-RTT that has
// not verified remote IP; could be spoofed, so we throw
// HTTP 425 status to tell the client to try again after
// the handshake is complete
if r.TLS != nil && !r.TLS.HandshakeComplete {
return false, Error(http.StatusTooEarly, fmt.Errorf("TLS handshake not complete, remote IP cannot be verified"))
}
address := GetVar(r.Context(), ClientIPVarKey).(string) address := GetVar(r.Context(), ClientIPVarKey).(string)
clientIP, zoneID, err := parseIPZoneFromString(address) clientIP, zoneID, err := parseIPZoneFromString(address)
if err != nil { if err != nil {
m.logger.Error("getting client IP", zap.Error(err)) m.logger.Error("getting client IP", zap.Error(err))
return false return false, nil
} }
matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones) matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
if !matches && !zoneFilter { if !matches && !zoneFilter {
m.logger.Debug("zone ID from client IP did not match", zap.String("zone", zoneID)) m.logger.Debug("zone ID from client IP did not match", zap.String("zone", zoneID))
} }
return matches return matches, nil
} }
func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) { func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
@ -326,13 +354,13 @@ func matchIPByCidrZones(clientIP netip.Addr, zoneID string, cidrs []*netip.Prefi
// Interface guards // Interface guards
var ( var (
_ RequestMatcher = (*MatchRemoteIP)(nil) _ RequestMatcherWithError = (*MatchRemoteIP)(nil)
_ caddy.Provisioner = (*MatchRemoteIP)(nil) _ caddy.Provisioner = (*MatchRemoteIP)(nil)
_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil) _ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
_ CELLibraryProducer = (*MatchRemoteIP)(nil) _ CELLibraryProducer = (*MatchRemoteIP)(nil)
_ RequestMatcher = (*MatchClientIP)(nil) _ RequestMatcherWithError = (*MatchClientIP)(nil)
_ caddy.Provisioner = (*MatchClientIP)(nil) _ caddy.Provisioner = (*MatchClientIP)(nil)
_ caddyfile.Unmarshaler = (*MatchClientIP)(nil) _ caddyfile.Unmarshaler = (*MatchClientIP)(nil)
_ CELLibraryProducer = (*MatchClientIP)(nil) _ CELLibraryProducer = (*MatchClientIP)(nil)
) )

View file

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

View file

@ -51,6 +51,9 @@ func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error {
Header: r.Header, Header: r.Header,
ShouldLogCredentials: r.ShouldLogCredentials, ShouldLogCredentials: r.ShouldLogCredentials,
}) })
if r.TransferEncoding != nil {
enc.AddArray("transfer_encoding", LoggableStringArray(r.TransferEncoding))
}
if r.TLS != nil { if r.TLS != nil {
enc.AddObject("tls", LoggableTLSConnState(*r.TLS)) enc.AddObject("tls", LoggableTLSConnState(*r.TLS))
} }

View file

@ -296,6 +296,12 @@ func (m MatchHost) Provision(_ caddy.Context) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchHost) Match(r *http.Request) bool { func (m MatchHost) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchHost) MatchWithError(r *http.Request) (bool, error) {
reqHost, _, err := net.SplitHostPort(r.Host) reqHost, _, err := net.SplitHostPort(r.Host)
if err != nil { if err != nil {
// OK; probably didn't have a port // OK; probably didn't have a port
@ -315,7 +321,7 @@ func (m MatchHost) Match(r *http.Request) bool {
return m[i] >= reqHost return m[i] >= reqHost
}) })
if pos < len(m) && m[pos] == reqHost { if pos < len(m) && m[pos] == reqHost {
return true return true, nil
} }
} }
@ -346,13 +352,13 @@ outer:
continue outer continue outer
} }
} }
return true return true, nil
} else if strings.EqualFold(reqHost, host) { } else if strings.EqualFold(reqHost, host) {
return true return true, nil
} }
} }
return false return false, nil
} }
// CELLibrary produces options that expose this matcher for use in CEL // CELLibrary produces options that expose this matcher for use in CEL
@ -366,7 +372,7 @@ func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"host", "host",
"host_match_request_list", "host_match_request_list",
[]*cel.Type{cel.ListType(cel.StringType)}, []*cel.Type{cel.ListType(cel.StringType)},
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
strList, err := data.ConvertToNative(refStringList) strList, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@ -411,6 +417,12 @@ func (m MatchPath) Provision(_ caddy.Context) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchPath) Match(r *http.Request) bool { func (m MatchPath) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
// Even though RFC 9110 says that path matching is case-sensitive // Even though RFC 9110 says that path matching is case-sensitive
// (https://www.rfc-editor.org/rfc/rfc9110.html#section-4.2.3), // (https://www.rfc-editor.org/rfc/rfc9110.html#section-4.2.3),
// we do case-insensitive matching to mitigate security issues // we do case-insensitive matching to mitigate security issues
@ -436,7 +448,7 @@ func (m MatchPath) Match(r *http.Request) bool {
// special case: whole path is wildcard; this is unnecessary // special case: whole path is wildcard; this is unnecessary
// as it matches all requests, which is the same as no matcher // as it matches all requests, which is the same as no matcher
if matchPattern == "*" { if matchPattern == "*" {
return true return true, nil
} }
// Clean the path, merge doubled slashes, etc. // Clean the path, merge doubled slashes, etc.
@ -464,7 +476,7 @@ func (m MatchPath) Match(r *http.Request) bool {
if strings.Contains(matchPattern, "%") { if strings.Contains(matchPattern, "%") {
reqPathForPattern := CleanPath(r.URL.EscapedPath(), mergeSlashes) reqPathForPattern := CleanPath(r.URL.EscapedPath(), mergeSlashes)
if m.matchPatternWithEscapeSequence(reqPathForPattern, matchPattern) { if m.matchPatternWithEscapeSequence(reqPathForPattern, matchPattern) {
return true return true, nil
} }
// doing prefix/suffix/substring matches doesn't make sense // doing prefix/suffix/substring matches doesn't make sense
@ -483,7 +495,7 @@ func (m MatchPath) Match(r *http.Request) bool {
strings.HasPrefix(matchPattern, "*") && strings.HasPrefix(matchPattern, "*") &&
strings.HasSuffix(matchPattern, "*") { strings.HasSuffix(matchPattern, "*") {
if strings.Contains(reqPathForPattern, matchPattern[1:len(matchPattern)-1]) { if strings.Contains(reqPathForPattern, matchPattern[1:len(matchPattern)-1]) {
return true return true, nil
} }
continue continue
} }
@ -495,7 +507,7 @@ func (m MatchPath) Match(r *http.Request) bool {
// treat it as a fast suffix match // treat it as a fast suffix match
if strings.HasPrefix(matchPattern, "*") { if strings.HasPrefix(matchPattern, "*") {
if strings.HasSuffix(reqPathForPattern, matchPattern[1:]) { if strings.HasSuffix(reqPathForPattern, matchPattern[1:]) {
return true return true, nil
} }
continue continue
} }
@ -504,7 +516,7 @@ func (m MatchPath) Match(r *http.Request) bool {
// treat it as a fast prefix match // treat it as a fast prefix match
if strings.HasSuffix(matchPattern, "*") { if strings.HasSuffix(matchPattern, "*") {
if strings.HasPrefix(reqPathForPattern, matchPattern[:len(matchPattern)-1]) { if strings.HasPrefix(reqPathForPattern, matchPattern[:len(matchPattern)-1]) {
return true return true, nil
} }
continue continue
} }
@ -515,10 +527,10 @@ func (m MatchPath) Match(r *http.Request) bool {
// because we can't handle it anyway // because we can't handle it anyway
matches, _ := path.Match(matchPattern, reqPathForPattern) matches, _ := path.Match(matchPattern, reqPathForPattern)
if matches { if matches {
return true return true, nil
} }
} }
return false return false, nil
} }
func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool { func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) bool {
@ -642,7 +654,7 @@ func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) {
// internal data type of the MatchPath value. // internal data type of the MatchPath value.
[]*cel.Type{cel.ListType(cel.StringType)}, []*cel.Type{cel.ListType(cel.StringType)},
// function to convert a constant list of strings to a MatchPath instance. // function to convert a constant list of strings to a MatchPath instance.
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
strList, err := data.ConvertToNative(refStringList) strList, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@ -677,6 +689,12 @@ func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchPathRE) Match(r *http.Request) bool { func (m MatchPathRE) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchPathRE) MatchWithError(r *http.Request) (bool, error) {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
// Clean the path, merges doubled slashes, etc. // Clean the path, merges doubled slashes, etc.
@ -684,7 +702,7 @@ func (m MatchPathRE) Match(r *http.Request) bool {
// the path matcher. See #4407 // the path matcher. See #4407
cleanedPath := cleanPath(r.URL.Path) cleanedPath := cleanPath(r.URL.Path)
return m.MatchRegexp.Match(cleanedPath, repl) return m.MatchRegexp.Match(cleanedPath, repl), nil
} }
// CELLibrary produces options that expose this matcher for use in CEL // CELLibrary produces options that expose this matcher for use in CEL
@ -698,7 +716,7 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"path_regexp", "path_regexp",
"path_regexp_request_string", "path_regexp_request_string",
[]*cel.Type{cel.StringType}, []*cel.Type{cel.StringType},
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
pattern := data.(types.String) pattern := data.(types.String)
matcher := MatchPathRE{MatchRegexp{ matcher := MatchPathRE{MatchRegexp{
Name: ctx.Value(MatcherNameCtxKey).(string), Name: ctx.Value(MatcherNameCtxKey).(string),
@ -715,7 +733,7 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"path_regexp", "path_regexp",
"path_regexp_request_string_string", "path_regexp_request_string_string",
[]*cel.Type{cel.StringType, cel.StringType}, []*cel.Type{cel.StringType, cel.StringType},
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
params, err := data.ConvertToNative(refStringList) params, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@ -764,7 +782,13 @@ func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchMethod) Match(r *http.Request) bool { func (m MatchMethod) Match(r *http.Request) bool {
return slices.Contains(m, r.Method) match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchMethod) MatchWithError(r *http.Request) (bool, error) {
return slices.Contains(m, r.Method), nil
} }
// CELLibrary produces options that expose this matcher for use in CEL // CELLibrary produces options that expose this matcher for use in CEL
@ -778,7 +802,7 @@ func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) {
"method", "method",
"method_request_list", "method_request_list",
[]*cel.Type{cel.ListType(cel.StringType)}, []*cel.Type{cel.ListType(cel.StringType)},
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
strList, err := data.ConvertToNative(refStringList) strList, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@ -823,10 +847,17 @@ func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Match returns true if r matches m. An empty m matches an empty query string. // Match returns true if r matches m. An empty m matches an empty query string.
func (m MatchQuery) Match(r *http.Request) bool { func (m MatchQuery) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
// An empty m matches an empty query string.
func (m MatchQuery) MatchWithError(r *http.Request) (bool, error) {
// If no query keys are configured, this only // If no query keys are configured, this only
// matches an empty query string. // matches an empty query string.
if len(m) == 0 { if len(m) == 0 {
return len(r.URL.Query()) == 0 return len(r.URL.Query()) == 0, nil
} }
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
@ -843,7 +874,7 @@ func (m MatchQuery) Match(r *http.Request) bool {
// "Relying on parser alignment for security is doomed." Overall conclusion is that // "Relying on parser alignment for security is doomed." Overall conclusion is that
// splitting on & and rejecting ; in key=value pairs is safer than accepting raw ;. // splitting on & and rejecting ; in key=value pairs is safer than accepting raw ;.
// We regard the Go team's decision as sound and thus reject malformed query strings. // We regard the Go team's decision as sound and thus reject malformed query strings.
return false return false, nil
} }
// Count the amount of matched keys, to ensure we AND // Count the amount of matched keys, to ensure we AND
@ -854,7 +885,7 @@ func (m MatchQuery) Match(r *http.Request) bool {
param = repl.ReplaceAll(param, "") param = repl.ReplaceAll(param, "")
paramVal, found := parsed[param] paramVal, found := parsed[param]
if !found { if !found {
return false return false, nil
} }
for _, v := range vals { for _, v := range vals {
v = repl.ReplaceAll(v, "") v = repl.ReplaceAll(v, "")
@ -864,7 +895,7 @@ func (m MatchQuery) Match(r *http.Request) bool {
} }
} }
} }
return matchedKeys == len(m) return matchedKeys == len(m), nil
} }
// CELLibrary produces options that expose this matcher for use in CEL // CELLibrary produces options that expose this matcher for use in CEL
@ -878,7 +909,7 @@ func (MatchQuery) CELLibrary(_ caddy.Context) (cel.Library, error) {
"query", "query",
"query_matcher_request_map", "query_matcher_request_map",
[]*cel.Type{CELTypeJSON}, []*cel.Type{CELTypeJSON},
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
mapStrListStr, err := CELValueToMapStrList(data) mapStrListStr, err := CELValueToMapStrList(data)
if err != nil { if err != nil {
return nil, err return nil, err
@ -940,8 +971,14 @@ func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchHeader) Match(r *http.Request) bool { func (m MatchHeader) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchHeader) MatchWithError(r *http.Request) (bool, error) {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
return matchHeaders(r.Header, http.Header(m), r.Host, repl) return matchHeaders(r.Header, http.Header(m), r.Host, r.TransferEncoding, repl), nil
} }
// CELLibrary produces options that expose this matcher for use in CEL // CELLibrary produces options that expose this matcher for use in CEL
@ -956,7 +993,7 @@ func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) {
"header", "header",
"header_matcher_request_map", "header_matcher_request_map",
[]*cel.Type{CELTypeJSON}, []*cel.Type{CELTypeJSON},
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
mapStrListStr, err := CELValueToMapStrList(data) mapStrListStr, err := CELValueToMapStrList(data)
if err != nil { if err != nil {
return nil, err return nil, err
@ -967,22 +1004,26 @@ func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) {
} }
// getHeaderFieldVals returns the field values for the given fieldName from input. // getHeaderFieldVals returns the field values for the given fieldName from input.
// The host parameter should be obtained from the http.Request.Host field since // The host parameter should be obtained from the http.Request.Host field, and the
// net/http removes it from the header map. // transferEncoding from http.Request.TransferEncoding, since net/http removes them
func getHeaderFieldVals(input http.Header, fieldName, host string) []string { // from the header map.
func getHeaderFieldVals(input http.Header, fieldName, host string, transferEncoding []string) []string {
fieldName = textproto.CanonicalMIMEHeaderKey(fieldName) fieldName = textproto.CanonicalMIMEHeaderKey(fieldName)
if fieldName == "Host" && host != "" { if fieldName == "Host" && host != "" {
return []string{host} return []string{host}
} }
if fieldName == "Transfer-Encoding" && input[fieldName] == nil {
return transferEncoding
}
return input[fieldName] return input[fieldName]
} }
// matchHeaders returns true if input matches the criteria in against without regex. // matchHeaders returns true if input matches the criteria in against without regex.
// The host parameter should be obtained from the http.Request.Host field since // The host parameter should be obtained from the http.Request.Host field since
// net/http removes it from the header map. // net/http removes it from the header map.
func matchHeaders(input, against http.Header, host string, repl *caddy.Replacer) bool { func matchHeaders(input, against http.Header, host string, transferEncoding []string, repl *caddy.Replacer) bool {
for field, allowedFieldVals := range against { for field, allowedFieldVals := range against {
actualFieldVals := getHeaderFieldVals(input, field, host) actualFieldVals := getHeaderFieldVals(input, field, host, transferEncoding)
if allowedFieldVals != nil && len(allowedFieldVals) == 0 && actualFieldVals != nil { if allowedFieldVals != nil && len(allowedFieldVals) == 0 && actualFieldVals != nil {
// a non-nil but empty list of allowed values means // a non-nil but empty list of allowed values means
// match if the header field exists at all // match if the header field exists at all
@ -1075,8 +1116,14 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchHeaderRE) Match(r *http.Request) bool { func (m MatchHeaderRE) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchHeaderRE) MatchWithError(r *http.Request) (bool, error) {
for field, rm := range m { for field, rm := range m {
actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host) actualFieldVals := getHeaderFieldVals(r.Header, field, r.Host, r.TransferEncoding)
match := false match := false
fieldVal: fieldVal:
for _, actualFieldVal := range actualFieldVals { for _, actualFieldVal := range actualFieldVals {
@ -1087,10 +1134,10 @@ func (m MatchHeaderRE) Match(r *http.Request) bool {
} }
} }
if !match { if !match {
return false return false, nil
} }
} }
return true return true, nil
} }
// Provision compiles m's regular expressions. // Provision compiles m's regular expressions.
@ -1126,7 +1173,7 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"header_regexp", "header_regexp",
"header_regexp_request_string_string", "header_regexp_request_string_string",
[]*cel.Type{cel.StringType, cel.StringType}, []*cel.Type{cel.StringType, cel.StringType},
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
params, err := data.ConvertToNative(refStringList) params, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@ -1149,7 +1196,7 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
"header_regexp", "header_regexp",
"header_regexp_request_string_string_string", "header_regexp_request_string_string_string",
[]*cel.Type{cel.StringType, cel.StringType, cel.StringType}, []*cel.Type{cel.StringType, cel.StringType, cel.StringType},
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
refStringList := reflect.TypeOf([]string{}) refStringList := reflect.TypeOf([]string{})
params, err := data.ConvertToNative(refStringList) params, err := data.ConvertToNative(refStringList)
if err != nil { if err != nil {
@ -1187,31 +1234,37 @@ func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchProtocol) Match(r *http.Request) bool { func (m MatchProtocol) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchProtocol) MatchWithError(r *http.Request) (bool, error) {
switch string(m) { switch string(m) {
case "grpc": case "grpc":
return strings.HasPrefix(r.Header.Get("content-type"), "application/grpc") return strings.HasPrefix(r.Header.Get("content-type"), "application/grpc"), nil
case "https": case "https":
return r.TLS != nil return r.TLS != nil, nil
case "http": case "http":
return r.TLS == nil return r.TLS == nil, nil
case "http/1.0": case "http/1.0":
return r.ProtoMajor == 1 && r.ProtoMinor == 0 return r.ProtoMajor == 1 && r.ProtoMinor == 0, nil
case "http/1.0+": case "http/1.0+":
return r.ProtoAtLeast(1, 0) return r.ProtoAtLeast(1, 0), nil
case "http/1.1": case "http/1.1":
return r.ProtoMajor == 1 && r.ProtoMinor == 1 return r.ProtoMajor == 1 && r.ProtoMinor == 1, nil
case "http/1.1+": case "http/1.1+":
return r.ProtoAtLeast(1, 1) return r.ProtoAtLeast(1, 1), nil
case "http/2": case "http/2":
return r.ProtoMajor == 2 return r.ProtoMajor == 2, nil
case "http/2+": case "http/2+":
return r.ProtoAtLeast(2, 0) return r.ProtoAtLeast(2, 0), nil
case "http/3": case "http/3":
return r.ProtoMajor == 3 return r.ProtoMajor == 3, nil
case "http/3+": case "http/3+":
return r.ProtoAtLeast(3, 0) return r.ProtoAtLeast(3, 0), nil
} }
return false return false, nil
} }
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
@ -1238,7 +1291,7 @@ func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
"protocol", "protocol",
"protocol_request_string", "protocol_request_string",
[]*cel.Type{cel.StringType}, []*cel.Type{cel.StringType},
func(data ref.Val) (RequestMatcher, error) { func(data ref.Val) (RequestMatcherWithError, error) {
protocolStr, ok := data.(types.String) protocolStr, ok := data.(types.String)
if !ok { if !ok {
return nil, errors.New("protocol argument was not a string") return nil, errors.New("protocol argument was not a string")
@ -1258,16 +1311,22 @@ func (MatchTLS) CaddyModule() caddy.ModuleInfo {
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchTLS) Match(r *http.Request) bool { func (m MatchTLS) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m.
func (m MatchTLS) MatchWithError(r *http.Request) (bool, error) {
if r.TLS == nil { if r.TLS == nil {
return false return false, nil
} }
if m.HandshakeComplete != nil { if m.HandshakeComplete != nil {
if (!*m.HandshakeComplete && r.TLS.HandshakeComplete) || if (!*m.HandshakeComplete && r.TLS.HandshakeComplete) ||
(*m.HandshakeComplete && !r.TLS.HandshakeComplete) { (*m.HandshakeComplete && !r.TLS.HandshakeComplete) {
return false return false, nil
} }
} }
return true return true, nil
} }
// UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax: // UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax:
@ -1283,6 +1342,8 @@ func (m *MatchTLS) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
case "early_data": case "early_data":
var false bool var false bool
m.HandshakeComplete = &false m.HandshakeComplete = &false
default:
return d.Errf("unrecognized option '%s'", d.Val())
} }
} }
if d.NextArg() { if d.NextArg() {
@ -1337,7 +1398,15 @@ func (m *MatchNot) Provision(ctx caddy.Context) error {
for _, modMap := range matcherSets.([]map[string]any) { for _, modMap := range matcherSets.([]map[string]any) {
var ms MatcherSet var ms MatcherSet
for _, modIface := range modMap { for _, modIface := range modMap {
ms = append(ms, modIface.(RequestMatcher)) if mod, ok := modIface.(RequestMatcherWithError); ok {
ms = append(ms, mod)
continue
}
if mod, ok := modIface.(RequestMatcher); ok {
ms = append(ms, mod)
continue
}
return fmt.Errorf("module is not a request matcher: %T", modIface)
} }
m.MatcherSets = append(m.MatcherSets, ms) m.MatcherSets = append(m.MatcherSets, ms)
} }
@ -1348,12 +1417,24 @@ func (m *MatchNot) Provision(ctx caddy.Context) error {
// the embedded matchers, false is returned if any of its matcher // the embedded matchers, false is returned if any of its matcher
// sets return true. // sets return true.
func (m MatchNot) Match(r *http.Request) bool { func (m MatchNot) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return match
}
// MatchWithError returns true if r matches m. Since this matcher
// negates the embedded matchers, false is returned if any of its
// matcher sets return true.
func (m MatchNot) MatchWithError(r *http.Request) (bool, error) {
for _, ms := range m.MatcherSets { for _, ms := range m.MatcherSets {
if ms.Match(r) { matches, err := ms.MatchWithError(r)
return false if err != nil {
return false, err
}
if matches {
return false, nil
} }
} }
return true return true, nil
} }
// MatchRegexp is an embedable type for matching // MatchRegexp is an embedable type for matching
@ -1469,7 +1550,7 @@ func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested // ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested
// matcher set, and returns its raw module map value. // matcher set, and returns its raw module map value.
func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) { func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
matcherMap := make(map[string]RequestMatcher) matcherMap := make(map[string]any)
// in case there are multiple instances of the same matcher, concatenate // in case there are multiple instances of the same matcher, concatenate
// their tokens (we expect that UnmarshalCaddyfile should be able to // their tokens (we expect that UnmarshalCaddyfile should be able to
@ -1494,11 +1575,15 @@ func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, er
if err != nil { if err != nil {
return nil, err return nil, err
} }
rm, ok := unm.(RequestMatcher) if rm, ok := unm.(RequestMatcherWithError); ok {
if !ok { matcherMap[matcherName] = rm
return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) continue
} }
matcherMap[matcherName] = rm if rm, ok := unm.(RequestMatcher); ok {
matcherMap[matcherName] = rm
continue
}
return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
} }
// we should now have a functional matcher, but we also // we should now have a functional matcher, but we also
@ -1524,24 +1609,28 @@ const regexpPlaceholderPrefix = "http.regexp"
// holds an optional error emitted from a request matcher, // holds an optional error emitted from a request matcher,
// to short-circuit the handler chain, since matchers cannot // to short-circuit the handler chain, since matchers cannot
// return errors via the RequestMatcher interface. // return errors via the RequestMatcher interface.
//
// Deprecated: Matchers should implement RequestMatcherWithError
// which can return an error directly, instead of smuggling it
// through the vars map.
const MatcherErrorVarKey = "matchers.error" const MatcherErrorVarKey = "matchers.error"
// Interface guards // Interface guards
var ( var (
_ RequestMatcher = (*MatchHost)(nil) _ RequestMatcherWithError = (*MatchHost)(nil)
_ caddy.Provisioner = (*MatchHost)(nil) _ caddy.Provisioner = (*MatchHost)(nil)
_ RequestMatcher = (*MatchPath)(nil) _ RequestMatcherWithError = (*MatchPath)(nil)
_ RequestMatcher = (*MatchPathRE)(nil) _ RequestMatcherWithError = (*MatchPathRE)(nil)
_ caddy.Provisioner = (*MatchPathRE)(nil) _ caddy.Provisioner = (*MatchPathRE)(nil)
_ RequestMatcher = (*MatchMethod)(nil) _ RequestMatcherWithError = (*MatchMethod)(nil)
_ RequestMatcher = (*MatchQuery)(nil) _ RequestMatcherWithError = (*MatchQuery)(nil)
_ RequestMatcher = (*MatchHeader)(nil) _ RequestMatcherWithError = (*MatchHeader)(nil)
_ RequestMatcher = (*MatchHeaderRE)(nil) _ RequestMatcherWithError = (*MatchHeaderRE)(nil)
_ caddy.Provisioner = (*MatchHeaderRE)(nil) _ caddy.Provisioner = (*MatchHeaderRE)(nil)
_ RequestMatcher = (*MatchProtocol)(nil) _ RequestMatcherWithError = (*MatchProtocol)(nil)
_ RequestMatcher = (*MatchNot)(nil) _ RequestMatcherWithError = (*MatchNot)(nil)
_ caddy.Provisioner = (*MatchNot)(nil) _ caddy.Provisioner = (*MatchNot)(nil)
_ caddy.Provisioner = (*MatchRegexp)(nil) _ caddy.Provisioner = (*MatchRegexp)(nil)
_ caddyfile.Unmarshaler = (*MatchHost)(nil) _ caddyfile.Unmarshaler = (*MatchHost)(nil)
_ caddyfile.Unmarshaler = (*MatchPath)(nil) _ caddyfile.Unmarshaler = (*MatchPath)(nil)

View file

@ -158,7 +158,10 @@ func TestHostMatcher(t *testing.T) {
t.Errorf("Test %d %v: provisioning failed: %v", i, tc.match, err) t.Errorf("Test %d %v: provisioning failed: %v", i, tc.match, err)
} }
actual := tc.match.Match(req) actual, err := tc.match.MatchWithError(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
continue continue
@ -430,7 +433,10 @@ func TestPathMatcher(t *testing.T) {
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx) req = req.WithContext(ctx)
actual := tc.match.Match(req) actual, err := tc.match.MatchWithError(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
continue continue
@ -451,7 +457,10 @@ func TestPathMatcherWindows(t *testing.T) {
req = req.WithContext(ctx) req = req.WithContext(ctx)
match := MatchPath{"*.php"} match := MatchPath{"*.php"}
matched := match.Match(req) matched, err := match.MatchWithError(req)
if err != nil {
t.Errorf("Expected no error, but got: %v", err)
}
if !matched { if !matched {
t.Errorf("Expected to match; should ignore trailing dots and spaces") t.Errorf("Expected to match; should ignore trailing dots and spaces")
} }
@ -555,7 +564,10 @@ func TestPathREMatcher(t *testing.T) {
req = req.WithContext(ctx) req = req.WithContext(ctx)
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
actual := tc.match.Match(req) actual, err := tc.match.MatchWithError(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'", t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
i, tc.match.Pattern, tc.expect, actual, tc.input) i, tc.match.Pattern, tc.expect, actual, tc.input)
@ -691,7 +703,10 @@ func TestHeaderMatcher(t *testing.T) {
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx) req = req.WithContext(ctx)
actual := tc.match.Match(req) actual, err := tc.match.MatchWithError(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
continue continue
@ -818,7 +833,10 @@ func TestQueryMatcher(t *testing.T) {
repl.Set("http.vars.debug", "1") repl.Set("http.vars.debug", "1")
repl.Set("http.vars.key", "somekey") repl.Set("http.vars.key", "somekey")
req = req.WithContext(ctx) req = req.WithContext(ctx)
actual := tc.match.Match(req) actual, err := tc.match.MatchWithError(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input)
continue continue
@ -887,7 +905,10 @@ func TestHeaderREMatcher(t *testing.T) {
req = req.WithContext(ctx) req = req.WithContext(ctx)
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
actual := tc.match.Match(req) actual, err := tc.match.MatchWithError(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'", t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
i, tc.match, tc.expect, actual, tc.input) i, tc.match, tc.expect, actual, tc.input)
@ -927,7 +948,7 @@ func BenchmarkHeaderREMatcher(b *testing.B) {
req = req.WithContext(ctx) req = req.WithContext(ctx)
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
for run := 0; run < b.N; run++ { for run := 0; run < b.N; run++ {
match.Match(req) match.MatchWithError(req)
} }
} }
@ -998,7 +1019,10 @@ func TestVarREMatcher(t *testing.T) {
tc.input.ServeHTTP(httptest.NewRecorder(), req, emptyHandler) tc.input.ServeHTTP(httptest.NewRecorder(), req, emptyHandler)
actual := tc.match.Match(req) actual, err := tc.match.MatchWithError(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'", t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
i, tc.match, tc.expect, actual, tc.input) i, tc.match, tc.expect, actual, tc.input)
@ -1123,7 +1147,10 @@ func TestNotMatcher(t *testing.T) {
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
req = req.WithContext(ctx) req = req.WithContext(ctx)
actual := tc.match.Match(req) actual, err := tc.match.MatchWithError(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.match, err)
}
if actual != tc.expect { if actual != tc.expect {
t.Errorf("Test %d %+v: Expected %t, got %t for: host=%s path=%s'", i, tc.match, tc.expect, actual, tc.host, tc.path) t.Errorf("Test %d %+v: Expected %t, got %t for: host=%s path=%s'", i, tc.match, tc.expect, actual, tc.host, tc.path)
continue continue
@ -1155,7 +1182,7 @@ func BenchmarkLargeHostMatcher(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
matcher.Match(req) matcher.MatchWithError(req)
} }
} }
@ -1169,7 +1196,7 @@ func BenchmarkHostMatcherWithoutPlaceholder(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
match.Match(req) match.MatchWithError(req)
} }
} }
@ -1187,6 +1214,6 @@ func BenchmarkHostMatcherWithPlaceholder(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
match.Match(req) match.MatchWithError(req)
} }
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"net/http" "net/http"
"strings"
"sync" "sync"
"time" "time"
@ -133,8 +134,8 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""} statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""}
if h.metrics.PerHost { if h.metrics.PerHost {
labels["host"] = r.Host labels["host"] = strings.ToLower(r.Host)
statusLabels["host"] = r.Host statusLabels["host"] = strings.ToLower(r.Host)
} }
inFlight := h.metrics.httpMetrics.requestInFlight.With(labels) inFlight := h.metrics.httpMetrics.requestInFlight.With(labels)

View file

@ -25,7 +25,12 @@ import (
) )
// ListenerWrapper provides PROXY protocol support to Caddy by implementing // ListenerWrapper provides PROXY protocol support to Caddy by implementing
// the caddy.ListenerWrapper interface. It must be loaded before the `tls` listener. // the caddy.ListenerWrapper interface. If a connection is received via Unix
// socket, it's trusted. Otherwise, it's checked against the Allow/Deny lists,
// then it's handled by the FallbackPolicy.
//
// It must be loaded before the `tls` listener because the PROXY protocol
// encapsulates the TLS data.
// //
// Credit goes to https://github.com/mastercactapus/caddy2-proxyprotocol for having // Credit goes to https://github.com/mastercactapus/caddy2-proxyprotocol for having
// initially implemented this as a plugin. // initially implemented this as a plugin.
@ -45,8 +50,35 @@ type ListenerWrapper struct {
Deny []string `json:"deny,omitempty"` Deny []string `json:"deny,omitempty"`
deny []netip.Prefix deny []netip.Prefix
// Accepted values are: ignore, use, reject, require, skip // FallbackPolicy specifies the policy to use if the downstream
// default: ignore // IP address is not in the Allow list nor is in the Deny list.
//
// NOTE: The generated docs which describe the value of this
// field is wrong because of how this type unmarshals JSON in a
// custom way. The field expects a string, not a number.
//
// Accepted values are: IGNORE, USE, REJECT, REQUIRE, SKIP
//
// - IGNORE: address from PROXY header, but accept connection
//
// - USE: address from PROXY header
//
// - REJECT: connection when PROXY header is sent
// Note: even though the first read on the connection returns an error if
// a PROXY header is present, subsequent reads do not. It is the task of
// the code using the connection to handle that case properly.
//
// - REQUIRE: connection to send PROXY header, reject if not present
// Note: even though the first read on the connection returns an error if
// a PROXY header is not present, subsequent reads do not. It is the task
// of the code using the connection to handle that case properly.
//
// - SKIP: accepts a connection without requiring the PROXY header.
// Note: an example usage can be found in the SkipProxyHeaderForCIDR
// function.
//
// Default: IGNORE
//
// Policy definitions are here: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy // Policy definitions are here: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy
FallbackPolicy Policy `json:"fallback_policy,omitempty"` FallbackPolicy Policy `json:"fallback_policy,omitempty"`

View file

@ -186,6 +186,11 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
return path.Ext(req.URL.Path), true return path.Ext(req.URL.Path), true
case "http.request.uri.query": case "http.request.uri.query":
return req.URL.RawQuery, true return req.URL.RawQuery, true
case "http.request.uri.prefixed_query":
if req.URL.RawQuery == "" {
return "", true
}
return "?" + req.URL.RawQuery, true
case "http.request.duration": case "http.request.duration":
start := GetVar(req.Context(), "start_time").(time.Time) start := GetVar(req.Context(), "start_time").(time.Time)
return time.Since(start), true return time.Since(start), true
@ -239,6 +244,12 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
case "http.request.orig_uri.query": case "http.request.orig_uri.query":
or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request)
return or.URL.RawQuery, true return or.URL.RawQuery, true
case "http.request.orig_uri.prefixed_query":
or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request)
if or.URL.RawQuery == "" {
return "", true
}
return "?" + or.URL.RawQuery, true
} }
// remote IP range/prefix (e.g. keep top 24 bits of 1.2.3.4 => "1.2.3.0/24") // remote IP range/prefix (e.g. keep top 24 bits of 1.2.3.4 => "1.2.3.0/24")

View file

@ -68,6 +68,12 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
} }
rb.WriteTimeout = timeout rb.WriteTimeout = timeout
case "set":
var setStr string
if !h.AllArgs(&setStr) {
return nil, h.ArgErr()
}
rb.Set = setStr
default: default:
return nil, h.Errf("unrecognized request_body subdirective '%s'", h.Val()) return nil, h.Errf("unrecognized request_body subdirective '%s'", h.Val())
} }

View file

@ -15,8 +15,10 @@
package requestbody package requestbody
import ( import (
"errors"
"io" "io"
"net/http" "net/http"
"strings"
"time" "time"
"go.uber.org/zap" "go.uber.org/zap"
@ -42,6 +44,10 @@ type RequestBody struct {
// EXPERIMENTAL. Subject to change/removal. // EXPERIMENTAL. Subject to change/removal.
WriteTimeout time.Duration `json:"write_timeout,omitempty"` WriteTimeout time.Duration `json:"write_timeout,omitempty"`
// This field permit to replace body on the fly
// EXPERIMENTAL. Subject to change/removal.
Set string `json:"set,omitempty"`
logger *zap.Logger logger *zap.Logger
} }
@ -59,6 +65,18 @@ func (rb *RequestBody) Provision(ctx caddy.Context) error {
} }
func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
if rb.Set != "" {
if r.Body != nil {
err := r.Body.Close()
if err != nil {
return err
}
}
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
replacedBody := repl.ReplaceAll(rb.Set, "")
r.Body = io.NopCloser(strings.NewReader(replacedBody))
r.ContentLength = int64(len(replacedBody))
}
if r.Body == nil { if r.Body == nil {
return next.ServeHTTP(w, r) return next.ServeHTTP(w, r)
} }
@ -94,7 +112,8 @@ type errorWrapper struct {
func (ew errorWrapper) Read(p []byte) (n int, err error) { func (ew errorWrapper) Read(p []byte) (n int, err error) {
n, err = ew.ReadCloser.Read(p) n, err = ew.ReadCloser.Read(p)
if err != nil && err.Error() == "http: request body too large" { var mbe *http.MaxBytesError
if errors.As(err, &mbe) {
err = caddyhttp.Error(http.StatusRequestEntityTooLarge, err) err = caddyhttp.Error(http.StatusRequestEntityTooLarge, err)
} }
return return

View file

@ -41,7 +41,7 @@ func (rm ResponseMatcher) Match(statusCode int, hdr http.Header) bool {
if !rm.matchStatusCode(statusCode) { if !rm.matchStatusCode(statusCode) {
return false return false
} }
return matchHeaders(hdr, rm.Headers, "", nil) return matchHeaders(hdr, rm.Headers, "", []string{}, nil)
} }
func (rm ResponseMatcher) matchStatusCode(statusCode int) bool { func (rm ResponseMatcher) matchStatusCode(statusCode int) bool {

View file

@ -154,16 +154,16 @@ func (rr *responseRecorder) WriteHeader(statusCode int) {
// connections by manually setting headers and writing status 101 // connections by manually setting headers and writing status 101
rr.statusCode = statusCode rr.statusCode = statusCode
// decide whether we should buffer the response
if rr.shouldBuffer == nil {
rr.stream = true
} else {
rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header())
}
// 1xx responses aren't final; just informational // 1xx responses aren't final; just informational
if statusCode < 100 || statusCode > 199 { if statusCode < 100 || statusCode > 199 {
rr.wroteHeader = true rr.wroteHeader = true
// decide whether we should buffer the response
if rr.shouldBuffer == nil {
rr.stream = true
} else {
rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header())
}
} }
// if informational or not buffered, immediately write header // if informational or not buffered, immediately write header

View file

@ -0,0 +1,84 @@
package reverseproxy
import (
"io"
"testing"
)
type zeroReader struct{}
func (zeroReader) Read(p []byte) (int, error) {
for i := range p {
p[i] = 0
}
return len(p), nil
}
func TestBuffering(t *testing.T) {
var (
h Handler
zr zeroReader
)
type args struct {
body io.ReadCloser
limit int64
}
tests := []struct {
name string
args args
resultCheck func(io.ReadCloser, int64, args) bool
}{
{
name: "0 limit, body is returned as is",
args: args{
body: io.NopCloser(&zr),
limit: 0,
},
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
return res == args.body && read == args.limit && read == 0
},
},
{
name: "negative limit, body is read completely",
args: args{
body: io.NopCloser(io.LimitReader(&zr, 100)),
limit: -1,
},
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
brc, ok := res.(bodyReadCloser)
return ok && brc.body == nil && brc.buf.Len() == 100 && read == 100
},
},
{
name: "positive limit, body is read partially",
args: args{
body: io.NopCloser(io.LimitReader(&zr, 100)),
limit: 50,
},
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
brc, ok := res.(bodyReadCloser)
return ok && brc.body != nil && brc.buf.Len() == 50 && read == 50
},
},
{
name: "positive limit, body is read completely",
args: args{
body: io.NopCloser(io.LimitReader(&zr, 100)),
limit: 101,
},
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
brc, ok := res.(bodyReadCloser)
return ok && brc.body == nil && brc.buf.Len() == 100 && read == 100
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, read := h.bufferedBody(tt.args.body, tt.args.limit)
if !tt.resultCheck(res, read, tt.args) {
t.Error("Handler.bufferedBody() test failed")
return
}
})
}
}

View file

@ -18,6 +18,7 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
@ -130,15 +131,18 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// is equivalent to a route consisting of: // is equivalent to a route consisting of:
// //
// # Add trailing slash for directory requests // # Add trailing slash for directory requests
// # This redirection is automatically disabled if "{http.request.uri.path}/index.php"
// # doesn't appear in the try_files list
// @canonicalPath { // @canonicalPath {
// file {path}/index.php // file {path}/index.php
// not path */ // not path */
// } // }
// redir @canonicalPath {path}/ 308 // redir @canonicalPath {path}/ 308
// //
// # If the requested file does not exist, try index files // # If the requested file does not exist, try index files and assume index.php always exists
// @indexFiles file { // @indexFiles file {
// try_files {path} {path}/index.php index.php // try_files {path} {path}/index.php index.php
// try_policy first_exist_fallback
// split_path .php // split_path .php
// } // }
// rewrite @indexFiles {http.matchers.file.relative} // rewrite @indexFiles {http.matchers.file.relative}
@ -179,7 +183,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
indexFile := "index.php" indexFile := "index.php"
// set up for explicitly overriding try_files // set up for explicitly overriding try_files
tryFiles := []string{} var tryFiles []string
// if the user specified a matcher token, use that // if the user specified a matcher token, use that
// matcher in a route that wraps both of our routes; // matcher in a route that wraps both of our routes;
@ -310,37 +314,60 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
// if the index is turned off, we skip the redirect and try_files // if the index is turned off, we skip the redirect and try_files
if indexFile != "off" { if indexFile != "off" {
// route to redirect to canonical path if index PHP file dirRedir := false
redirMatcherSet := caddy.ModuleMap{ dirIndex := "{http.request.uri.path}/" + indexFile
"file": h.JSON(fileserver.MatchFile{ tryPolicy := "first_exist_fallback"
TryFiles: []string{"{http.request.uri.path}/" + indexFile},
}),
"not": h.JSON(caddyhttp.MatchNot{
MatcherSetsRaw: []caddy.ModuleMap{
{
"path": h.JSON(caddyhttp.MatchPath{"*/"}),
},
},
}),
}
redirHandler := caddyhttp.StaticResponse{
StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
Headers: http.Header{"Location": []string{"{http.request.orig_uri.path}/"}},
}
redirRoute := caddyhttp.Route{
MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)},
}
// if tryFiles wasn't overridden, use a reasonable default // if tryFiles wasn't overridden, use a reasonable default
if len(tryFiles) == 0 { if len(tryFiles) == 0 {
tryFiles = []string{"{http.request.uri.path}", "{http.request.uri.path}/" + indexFile, indexFile} tryFiles = []string{"{http.request.uri.path}", dirIndex, indexFile}
dirRedir = true
} else {
if !strings.HasSuffix(tryFiles[len(tryFiles)-1], ".php") {
// use first_exist strategy if the last file is not a PHP file
tryPolicy = ""
}
for _, tf := range tryFiles {
if tf == dirIndex {
dirRedir = true
break
}
}
}
if dirRedir {
// route to redirect to canonical path if index PHP file
redirMatcherSet := caddy.ModuleMap{
"file": h.JSON(fileserver.MatchFile{
TryFiles: []string{dirIndex},
}),
"not": h.JSON(caddyhttp.MatchNot{
MatcherSetsRaw: []caddy.ModuleMap{
{
"path": h.JSON(caddyhttp.MatchPath{"*/"}),
},
},
}),
}
redirHandler := caddyhttp.StaticResponse{
StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
Headers: http.Header{"Location": []string{"{http.request.orig_uri.path}/{http.request.orig_uri.prefixed_query}"}},
}
redirRoute := caddyhttp.Route{
MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)},
}
routes = append(routes, redirRoute)
} }
// route to rewrite to PHP index file // route to rewrite to PHP index file
rewriteMatcherSet := caddy.ModuleMap{ rewriteMatcherSet := caddy.ModuleMap{
"file": h.JSON(fileserver.MatchFile{ "file": h.JSON(fileserver.MatchFile{
TryFiles: tryFiles, TryFiles: tryFiles,
TryPolicy: tryPolicy,
SplitPath: extensions, SplitPath: extensions,
}), }),
} }
@ -352,7 +379,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)}, HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)},
} }
routes = append(routes, redirRoute, rewriteRoute) routes = append(routes, rewriteRoute)
} }
// route to actually reverse proxy requests to PHP files; // route to actually reverse proxy requests to PHP files;

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