Compare commits

..

1331 Commits

Author SHA1 Message Date
Matthew Holt 7ce5c0aaac caddyhttp: Reject conflicting values in query strings 2022-10-24 11:07:29 -06:00
BakaFT a999b70727 cmd: Add missing \n to HelpTemplate (#5151) 2022-10-17 11:51:41 +03:00
Francis Lavoie 1cd594963e docs: Fix templates documentation, stray newline breaks godoc (#5149) 2022-10-16 12:25:44 -04:00
Matt Holt 6bad878a22 httpcaddyfile: Improve detection of indistinguishable TLS automation policies (#5120)
* httpcaddyfile: Skip some logic if auto_https off

* Try removing this check altogether...

* Refine test timeouts slightly, sigh

* caddyhttp: Assume udp for unrecognized network type

Seems like the reasonable thing to do if a plugin registers its own
network type.

* Add comment to document my lack of knowledge

* Clean up and prepare to merge

Add comments to try to explain what happened
2022-10-13 11:30:57 -06:00
Matt Holt 3e1fd2a8d4 httpcaddyfile: Wrap site block in subroute if host matcher used (#5130)
* httpcaddyfile: Wrap site block in subroute if host matcher used (fix #5124)

* Correct boolean logic (oops)
2022-10-12 09:27:08 -06:00
Abdussamet Koçak 33f60da9f2 fileserver: stop listing dir when request context is cancelled (#5131)
Prevents caddy from performing disk IO needlessly when the request is cancelled before the listing is finished.

Closes #5129
2022-10-08 12:56:35 -06:00
Kévin Dunglas b4e28af953 replacer: working directory global placeholder (#5127) 2022-10-07 05:54:41 -04:00
Francis Lavoie d46ba2e27f httpcaddyfile: Fix metrics global option parsing (#5126) 2022-10-06 19:40:08 -06:00
Cory Cooper 498f32bab9 caddyconfig: Implement retries into HTTPLoader (#5077)
* httploader: Add max_retries

* caddyconfig: dependency-free http config loading retries

* caddyconfig: support `retry_delay` in http loader

* httploader: Implement retries

* Apply suggestions from code review

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-10-05 22:34:49 -06:00
Ioannis Cherouvim ed118f2b09 Fix typo in comment (#5121) 2022-10-05 12:36:06 -06:00
Francis Lavoie 99ffe93388 logging: Fix skip_hosts with wildcards (#5102)
Fix #4859
2022-10-05 12:14:13 -06:00
Matthew Holt e07a267276 caddytest: Revise sleep durations
Attempt to reduce flakiness a bit more

Test suite needs to be rewritten.
2022-10-05 11:40:41 -06:00
Adam Weinberger e4fac1294f core: Set version manually via CustomVersion (#5072)
* Allow version to be set manually

When Caddy is built from a release tarball (as downloaded from GitHub),
`caddy version` returns an empty string. This causes confusion for
downstream packagers.

With this commit, VersionString can be set with eg.
  go build (...) -ldflags '-X (...).VersionString=v1.2.3'
Then the short form version will be "v1.2.3", and the full version
string will begin with "v1.2.3 ".

* Prefer embedded version, then CustomVersion

* Prefer "unknown" for full version over empty

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-10-05 10:59:57 -06:00
Matt Holt 2153a81ec8 forwardauth: Canonicalize header fields (fix #5038) (#5097) 2022-10-05 01:37:01 -04:00
Francis Lavoie ea58d51907 logging: Perform filtering on arrays of strings (where possible) (#5101)
* logging: Perform filtering on arrays of strings (where possible)

* Add test for ip_mask filter

* Oops, need to continue when it's not an IP

* Test for invalid IPs
2022-10-04 23:21:23 -06:00
Francis Lavoie 9e1d964bd6 logging: Add time_local option to use local time instead of UTC (#5108) 2022-10-05 00:23:14 -04:00
xufanglu 2be56c526c fileserver: Treat invalid file path as NotFound (#5099)
treat invalid file path as notFound so that PassThru can work
2022-10-04 21:32:40 -06:00
Francis Lavoie 01e192edc9 logging: Better console encoder defaults (#5109)
This is something that has bothered me for a while, so I figured I'd do something about it now since I'm playing in the logging code lately.

The `console` encoder doesn't actually match the defaults that zap's default logger uses. This makes it match better with the rest of the logs when using the `console` encoder alongside somekind of filter, which requires you to configure an encoder to wrap.
2022-10-04 21:18:48 -06:00
Francis Lavoie 2808de1e30 httpcaddyfile: Skip automate when auto_https off is specified (#5110) 2022-10-04 20:58:19 -06:00
Tobias Gruetzmacher 253d97c93d core: Chdir to executable location on Windows (#5115)
Since all Windows services are run from the Windows system directory,
make it easier for users by switching to our program directory right
after the start.
2022-10-04 11:04:02 -06:00
Mohammed Al Sahaf c28cd29fe7 ci: enhance the CI/CD flow (#5118) 2022-10-04 17:03:10 +03:00
Tobias Gruetzmacher da24f57dac Fix inverted logic in Windows service detection (#5106) 2022-10-02 16:56:54 -04:00
iliana etaoin b1d04f5b39 fileserver: better dark mode visited link contrast (#5105)
PR #4066 added a dark color scheme to the file_server browse template.
PR #4356 later set the links for the `:visited` pseudo-class, but did
not set anything for the dark mode, resulting in poor contrast. I
selected some new colors by feel.

This commit also adds an `a:visited:hover` for both, to go along with
the normal blue hover colors.
2022-10-01 18:14:27 -06:00
Matthew Holt fe91de67b6 go.mod: Upgrade select dependencies 2022-09-30 13:39:37 -06:00
Matthew Holt 9873ff9918 caddyhttp: Remote IP prefix placeholders
See https://github.com/mholt/caddy-ratelimit/issues/12
2022-09-30 13:29:33 -06:00
Matt Holt 5e52bbb136 map: Remove infinite recursion check (#5094)
It was not accurate. Placeholders could be used in outputs that are
defined in the same mapping as long as that placeholder does not do the
same.

A more general solution would be to detect it at run-time in the
replacer directly, but that's a bit tedious
and will require allocations I think.

A better implementation of this check could still be done, but I don't
know if it would always be accurate. Could be a "best-effort" thing?
But I've also never heard of an actual case where someone configured
infinite recursion...
2022-09-29 12:46:38 -06:00
Matthew Holt fcdbc69fab Fix comment
I apparently read the diff backwards in
2a8c458ffe
2022-09-29 12:38:36 -06:00
Matthew Holt 2a8c458ffe reverseproxy: Parse humanized byte size (fix #5095) 2022-09-29 12:37:06 -06:00
Cory Cooper 037dc23cad admin: Use replacer on listen addresses (#5071)
* admin: use replacer on listen address

* admin: consolidate replacer logic
2022-09-29 11:24:52 -06:00
Matthew Holt ab720fb768 core: Fix ListenQUIC listener key conflict
Reported on commit e3e8aabbcf

Abused this change in some bash for loops to rapidly reload config
while making requests and didn't observe any memory or resource leaks.
2022-09-29 10:32:02 -06:00
Matt Holt e2991eb019 reverseproxy: On 103 don't delete own headers (#5091)
See #5074
2022-09-29 08:19:56 -06:00
Matt Holt 897a38958c Merge pull request #5076 from caddyserver/fastcgi-redir
fastcgi: Redirect using original URI path (fix #5073) and rewrite: Only trim prefix if matched
2022-09-28 15:22:45 -06:00
Will Norris 61822f129b caddyhttp: replace placeholders in map defaults (#5081)
This updates the map directive to replace placeholders in default values
in the same way as matched values.
2022-09-28 13:38:20 -06:00
Matt Holt e3e8aabbcf core: Refactor and improve listener logic (#5089)
* core: Refactor, improve listener logic

Deprecate:
- caddy.Listen
- caddy.ListenTimeout
- caddy.ListenPacket

Prefer caddy.NetworkAddress.Listen() instead.

Change:
- caddy.ListenQUIC (hopefully to remove later)
- caddy.ListenerFunc signature (add context and ListenConfig)

- Don't emit Alt-Svc header advertising h3 over HTTP/3

- Use quic.ListenEarly instead of quic.ListenEarlyAddr; this gives us
more flexibility (e.g. possibility of HTTP/3 over UDS) but also
introduces a new issue:
https://github.com/lucas-clemente/quic-go/issues/3560#issuecomment-1258959608

- Unlink unix socket before and after use

* Appease the linter

* Keep ListenAll
2022-09-28 13:35:51 -06:00
Matthew Holt 013b510352 rewrite: Only trim prefix if matched
See #5073
2022-09-28 00:13:12 -06:00
lemmi d0556929a4 reverseproxy: fix upstream scheme handling in command (#5088)
e338648fed introduced multiple upstream
addresses. A comment notes that mixing schemes isn't supported and
therefore the first valid scheme is supposed to be used.

Fixes setting the first scheme.

fixes #5087
2022-09-27 13:03:30 -06:00
Mohammed Al Sahaf b5727b9c44 ci: fix integration tests (#5079) 2022-09-24 19:00:55 +00:00
Matthew Holt 7041970059 headers: Support repeated WriteHeader if 1xx (fix #5074) 2022-09-23 17:11:53 -06:00
Matthew Holt e747a9bb12 Fix tests 2022-09-23 16:47:59 -06:00
Matthew Holt f7c1a51efb fastcgi: Redirect using original URI path (fix #5073) 2022-09-23 14:36:38 -06:00
Mohammed Al Sahaf eead00f54a ci: extend goreleaser timeout to 1-hour (#5067) 2022-09-22 15:09:18 +00:00
Matthew Holt 9206e8a738 Tweak some comments 2022-09-21 12:59:44 -06:00
Matt Holt 1426c97da5 core: Reuse unix sockets (UDS) and don't try to serve HTTP/3 over UDS (#5063)
* core: Reuse unix sockets

* Don't serve HTTP/3 over unix sockets

This requires upstream support, if even useful

* Don't use unix build tag... yet

* Fix build tag

* Allow ErrNotExist when unlinking socket
2022-09-21 12:55:23 -06:00
WeidiDeng 44ad0cedaf encode: don't WriteHeader unless called (#5060) 2022-09-21 08:30:42 -06:00
Matthew Holt beb7dcbf2a fileserver: Reinstate --debug flag
I think it got lost during a rebase or something
2022-09-20 16:56:02 -06:00
Francis Lavoie 821a08a6e3 httpcaddyfile: Fix protocols global option parsing (#5054)
* httpcaddyfile: Fix `protocols` global option parsing

When checking for a block, the current nesting must be used, otherwise it returns the wrong thing.

* Adjust adapt test to cover the broken behaviour that is now fixed

* Fix some admin tests which suddenly run even with -short
2022-09-20 08:09:04 -06:00
Francis Lavoie e3d04ff86b caddyhttp: Skip inserting HTTP->HTTPS redir if catch-all for both exist (#5051) 2022-09-19 22:11:19 -06:00
Matt Holt da8b7fe58f caddyhttp: Honor grace period in background (#5043)
* caddyhttp: Honor grace period in background

This avoids blocking during config reloads.

* Don't quit process until servers shut down

* Make tests more likely to pass on fast CI (#5045)

* caddyhttp: Even faster shutdowns

Simultaneously shut down all HTTP servers, rather than one at a time.

In practice there usually won't be more than 1 that lingers. But this
code ensures that they all Shutdown() in their own goroutine
and then we wait for them at the end (if exiting).

We also wait for them to start up so we can be fairly confident the
shutdowns have begun; i.e. old servers no longer
accepting new connections.

* Fix comment typo

* Pull functions out of loop, for readability
2022-09-19 21:54:47 -06:00
Matthew Holt 0950ba4f0b events: Make event data exported
This could lead to bugs if handlers are not careful, but it is surely
useful. We'll see how it goes, what the feedback is like, etc.
2022-09-19 16:20:58 -06:00
WeidiDeng c7a6bc5934 caddyhttp: responseRecorder save status in all cases (#5049) 2022-09-17 18:47:53 -06:00
Matthew Holt 00beec2e34 caddyhttp: Fix write header on responseRecorder 2022-09-17 11:28:13 -06:00
Mohammed Al Sahaf b4643994d5 ci: fix the name template of singing certificate and sboms (#5046) 2022-09-17 08:54:50 -06:00
Matthew Holt e43b6d8178 core: Variadic Context.Logger(); soft deprecation
Ideally I'd just remove the parameter to caddy.Context.Logger(), but
this would break most Caddy plugins.

Instead, I'm making it variadic and marking it as partially deprecated.
In the future, I might completely remove the parameter once most
plugins have updated.
2022-09-16 16:55:36 -06:00
WeidiDeng bffc258732 caddyhttp: Support configuring Server from handler provisioning (#4933)
* configuring http.Server from handlers.

* Minor tweaks

* Run gofmt

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-09-16 14:48:55 -06:00
David Manouchehri 616418281b caddyhttp: Support TLS key logging for debugging (#4808)
* Add SSL key logging.

* Resolve merge conflict with master

* Add Caddyfile support; various fixes

* Also commit go.mod and go.sum, oops

* Appease linter

* Minor tweaks

* Add doc comment

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-09-16 14:05:37 -06:00
Matt Holt 74547f5bed caddyhttp: Make metrics opt-in (#5042)
* caddyhttp: Make metrics opt-in

Related to #4644

* Make configurable in Caddyfile
2022-09-16 13:32:49 -06:00
Matthew Holt 258071d857 caddytls: Debug log on implicit tailscale error (#5041) 2022-09-16 09:42:05 -06:00
Matthew Holt b6cec37893 caddyhttp: Add --debug flag to commands
file-server and reverse-proxy

This might be useful!
2022-09-15 23:10:16 -06:00
WeidiDeng 48d723c07c encode: Fix Accept-Ranges header; HEAD requests (#5039)
* fix encode handler header manipulation
also avoid implementing ReadFrom because it breaks when io.Copied to directly

* strconv.Itoa should be tried as a last resort
WriteHeader during Close
2022-09-15 16:05:08 -06:00
Matthew Holt f1f7a22674 Reject absurdly long duration strings (fix #4175) 2022-09-15 14:25:29 -06:00
Matthew Holt 49b7a25264 Fix #4169 (correct e6c58fd) 2022-09-15 14:13:58 -06:00
Matthew Holt e6c58fdc08 caddyfile: Prevent infinite nesting on fmt (fix #4175) 2022-09-15 14:12:53 -06:00
Matthew Holt 2dc747cf2d Limit unclosed placeholder tolerance (fix #4170) 2022-09-15 13:36:08 -06:00
Isaac Parker e338648fed reverseproxy: Support repeated --to flags in command (#4693)
* feat: Multiple 'to' upstreams in reverse-proxy cmd

* Repeat --to for multiple upstreams, rather than comma-separating in a single flag

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-09-15 12:35:38 -06:00
Francis Lavoie 9ad0ebc956 caddyhttp: Add 'skip_log' var to omit request from logs (#4691)
* caddyhttp: Implement `skip_log` handler

* Refactor to use vars middleware

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-09-15 10:05:36 -06:00
Michael Stapelberg a1ad20e472 httpcaddyfile: Fix bind when IPv6 is specified with network (#4950)
* fix listening on IPv6 addresses: use net.JoinHostPort

Commit 1e18afb5c8 broke my caddy setup.
This commit fixes it.

* Refactor solution; simplify, add descriptive comment

* Move network to host, not copy

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-09-15 08:03:24 -06:00
Matthew Holt 62b0685375 cmd: Improve error message if config missing 2022-09-14 23:24:16 -06:00
Matthew Holt 0b3161aeea cmd: Customizable user agent (close #2795) 2022-09-13 17:21:04 -06:00
Matthew Holt 754fe4f7b4 httpcaddyfile: Fix sorting of repeated directives
Fixes #5037
2022-09-13 13:43:21 -06:00
Matthew Holt 20d487be57 caddyhttp: Very minor optimization to path matcher
If * is in the matcher it will always match so we can just put it first.
2022-09-13 11:26:10 -06:00
Francis Lavoie 61c75f74de caddyhttp: Explicitly disallow multiple regexp matchers (#5030)
* caddyhttp: Explicitly disallow multiple regexp matchers

Fix #5028

Since the matchers would overwrite eachother, we should error out to tell the user their config doesn't make sense.

* Update modules/caddyhttp/matchers.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-09-13 11:18:37 -06:00
Matthew Holt d35f618b10 caddytls: Error if placeholder is empty in 'ask'
Fixes #5036
2022-09-13 08:59:03 -06:00
Mohammed Al Sahaf 9fe4f93bc7 supplychain: publish signing cert, sbom, and signatures of sbom (#5027) 2022-09-12 22:59:53 +00:00
Matthew Holt c5df7bb6bd go.mod: Update truststore 2022-09-10 21:44:35 -06:00
Matthew Holt 076a8b8095 Very minor tweaks 2022-09-08 13:10:40 -06:00
Matthew Holt 50748e19c3 core: Check error on ListenQUIC 2022-09-08 12:36:31 -06:00
Matthew Holt c19f207237 fileserver: Ignore EOF when browsing empty dir
Thanks to @WeidiDeng for reporting this
2022-09-07 21:14:11 -06:00
fleandro dd9813c65b caddyhttp: ensure ResponseWriterWrapper and ResponseRecorder use ReadFrom if the underlying response writer implements it. (#5022)
Doing so allows for splice/sendfile optimizations when available.
Fixes #4731

Co-authored-by: flga <flga@users.noreply.github.com>
Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-09-07 21:13:35 +01:00
Matthew Holt 1c9c8f6a13 cmd: Enhance some help text 2022-09-06 14:19:58 -06:00
Francis Lavoie 8cc8f9fddd httpcaddyfile: Add a couple more placeholder shortcuts (#5015)
This adds:
- `{file.*}` -> `{http.request.uri.path.file.*}`
- `{file_match.*}` -> `{http.matchers.file.*}`

This is a follow-up to #4993 which introduces the new URI file placeholders, and a shortcut for using `file` matcher output.

For example, where the `try_files` directive is a shortcut for this:

```
@try_files file <files...>
rewrite @try_files {http.matchers.file.relative}
```

It could instead be:
```
@try_files file <files...>
rewrite @try_files {file_match.relative}
```
2022-09-05 21:41:48 -06:00
Dave Henderson 8f6a88e2b0 Merge pull request #5018 from hairyhenderson/allow-fs.FS-for-virtual-filesystems
Drop requirement for filesystems to implement fs.StatFS
2022-09-05 20:10:48 -04:00
Dave Henderson fded2644f8 Drop requirement for filesystems to implement fs.StatFS
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2022-09-05 19:25:34 -04:00
Mohammed Al Sahaf 487217519c ci: grant the release workflow the write permission to contents (#5017) 2022-09-05 21:35:47 +00:00
Mohammed Al Sahaf 0499d9c1c4 ci: add id-token permission and update the signing command (#5016) 2022-09-05 20:57:27 +00:00
Matthew Holt 5dfa08174a go.mod: Upgrade CertMagic (v0.17.1) 2022-09-05 13:55:48 -06:00
Matt Holt d5ea43fb4b fileserver: Support glob expansion in file matcher (#4993)
* fileserver: Support glob expansion in file matcher

* Fix tests

* Fix bugs and tests

* Attempt Windows fix, sigh

* debug Windows, WIP

* Continue debugging Windows

* Another attempt at Windows

* Plz Windows

* Cmon...

* Clean up, hope I didn't break anything
2022-09-05 13:53:41 -06:00
Matt Holt ca4fae64d9 caddyhttp: Support respond with HTTP 103 Early Hints (#5006)
* caddyhttp: Support sending HTTP 103 Early Hints

This adds support for early hints in the static_response handler.

* caddyhttp: Don't record 1xx responses
2022-09-05 13:50:44 -06:00
Matthew Holt ad69503aef Remove unnecessary error check 2022-09-05 13:42:59 -06:00
Francis Lavoie 6e3063b15a caddyauth: Speed up basicauth provision, deprecate scrypt (#4720)
* caddyauth: Speed up basicauth provisioning, precalculate fake password

* Deprecate scrypt, allow using decoded bcrypt hashes

* Add TODO note

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-09-05 13:32:58 -06:00
Mohammed Al Sahaf d6b3c7d262 ci: generate SBOM and sign artifacts using cosign (#4910)
* ci: sign artifacts using cosign

* include SBOM
2022-09-03 03:37:10 +03:00
Matt Holt 66476d8c8f reverseproxy: Close hijacked conns on reload/quit (#4895)
* reverseproxy: Close hijacked conns on reload/quit

We also send a Close control message to both ends of
WebSocket connections. I have tested this many times in
my dev environment with consistent success, although
the variety of scenarios was limited.

* Oops... actually call Close() this time

* CloseMessage --> closeMessage

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Use httpguts, duh

* Use map instead of sync.Map

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2022-09-02 17:01:55 -06:00
Matt Holt d3c3fa10bd core: Refactor listeners; use SO_REUSEPORT on Unix (#4705)
* core: Refactor listeners; use SO_REUSEPORT on Unix

Just an experiment for now

* Fix lint by logging error

* TCP Keepalive configuration (#4865)

* initial attempt at TCP Keepalive configuration

* core: implement tcp-keepalive for linux

* move canSetKeepAlive interface

* Godoc for keepalive server parameter

* handle return values

* log keepalive errors

* Clean up after bad merge

* Merge in pluggable network types

From 1edc1a45e3

* Slight refactor, fix from recent merge conflict

Co-authored-by: Karmanyaah Malhotra <karmanyaah.gh@malhotra.cc>
2022-09-02 16:59:11 -06:00
WeidiDeng 83b26975bd fastcgi: Optimize FastCGI transport (#4978)
* break up code and use lazy reading and pool bufio.Writer

* close underlying connection when operation failed

* allocate bufWriter and streamWriter only once

* refactor record writing

* rebase from master

* handle err

* Fix type assertion

Also reduce some duplication

* Refactor client and clientCloser for logging

Should reduce allocations

* Minor cosmetic adjustments; apply Apache license

* Appease the linter

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-09-02 16:57:55 -06:00
Matthew Holt 005c5a6382 Minor style adjustments for HTTP redir logging 2022-09-02 13:04:31 -06:00
Matthew Holt 6c0d0511ba Update readme 2022-09-02 10:26:31 -06:00
Matthew Holt 5c7ae5e505 Minor fix of error log 2022-09-02 10:19:51 -06:00
Matthew Holt 59286d2c7e notify: Don't send ready after error (fix #5003)
Also simplify the notify package quite a bit.
Also move stop notification into better place.
Add ability to send status or error.
2022-09-02 09:24:05 -06:00
Avdhut 66959d9f18 templates: Document httpError function (#4972)
* added the httpError function into the document

* Update templates.go

* Update templates.go

* Fix gofmt

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-09-01 22:07:52 -06:00
fleandro f2a7e7c966 fastcgi: allow users to log stderr output (#4967) (#5004)
Co-authored-by: flga <flga@users.noreply.github.com>
2022-09-02 00:02:48 -04:00
Matthew Holt ec2a5762b0 cmd: Don't print long help text on error 2022-09-01 21:43:23 -06:00
Matthew Holt e77992dd99 Fix failing test 2022-09-01 21:43:23 -06:00
Mohammed Al Sahaf aefd821ae0 dist: deb package manpages and bash completion scripts (#5007) 2022-09-01 23:39:18 -04:00
Francis Lavoie d062fb4020 caddyhttp: Copy logger config to HTTP server during AutoHTTPS (#4990) 2022-09-01 23:31:54 -04:00
Matthew Holt 73d4a8ba02 map: Coerce val to string, fix #4987
Also prevent infinite recursion, and enforce placeholder syntax.
2022-09-01 21:15:44 -06:00
Francis Lavoie 7d5108d132 httpcaddyfile: Add shortcut for expression matchers (#4976) 2022-09-01 23:12:37 -04:00
Matthew Holt 7c35bfa57c caddyhttp: Accept placeholders in vars matcher key
Until now, the vars matcher has unintentionally lacked parity with the
map directive: the destination placeholders of the map directive would
be expressed as placeholders, i.e. {foo}. But the vars matcher would
not use { }: vars foo value

This looked weird, and was confusing, since it implied that the key
could be dynamic, which doesn't seem helpful here.

I think this is a proper bug fix, since we're not used to accessing
placeholders literally without { } in the Caddyfile.
2022-09-01 16:49:18 -06:00
Matt Holt 1edc1a45e3 core: Plugins can register listener networks (#5002)
* core: Plugins can register listener networks

This can be useful for custom listeners.

This feature/API is experimental and may change!

* caddyhttp: Expose server listeners
2022-09-01 16:30:03 -06:00
Matthew Holt cb849bd664 caddyhttp: Disable draft versions of QUIC
See comment in #4996
2022-08-31 18:49:34 -06:00
Matthew Holt 3cd7437b3d events: Tune logging and context cancellation 2022-08-31 18:48:46 -06:00
Francis Lavoie d4d8bbcfc6 events: Implement event system (#4912)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-08-31 15:01:30 -06:00
Francis Lavoie 68d8ac9802 httpcaddyfile: Add {cookie.*} placeholder shortcut (#5001) 2022-08-31 10:18:29 -06:00
Matt Holt 2d5a30b908 caddyhttp: Set Content-Type for static response (#4999) 2022-08-31 09:43:46 -06:00
Matthew Holt 687a4b9e81 cmd: Enhance CLI docs 2022-08-30 19:15:52 -06:00
Mohammed Al Sahaf d605ebe75a cmd: add completion command (#4994)
* cmd: add completion command

* error check
2022-08-30 23:24:05 +00:00
Mohammed Al Sahaf 258bc82b69 cmd: Migrate to spf13/cobra, remove single-dash arg support (#4565)
* cmd: migrate to spf13/cobra

* add `manpage` command

* limit Caddy tagline to root `help` only

* hard-code the manpage section to 8
2022-08-30 22:38:38 +00:00
Matthew Holt 8cb3cf540c Minor cleanup, resolve a couple lint warnings 2022-08-29 12:31:56 -06:00
Abirdcfly e1801fdb19 Remove duplicate words in comments (#4986) 2022-08-27 14:39:26 -06:00
Dávid Szabó 0c57facc67 reverseproxy: Add upstreams healthy metrics (#4935) 2022-08-27 12:30:23 -06:00
WeidiDeng 4c282e86da admin: Don't stop old server if new one fails (#4964)
Fixes #4954

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-08-25 22:17:52 -06:00
Matthew Holt 5fb5b81439 reverseproxy: Multiple dynamic upstreams
This allows users to, for example, get upstreams from multiple SRV
endpoints in order (such as primary and secondary clusters).

Also, gofmt went to town on the comments, sigh
2022-08-25 21:42:48 -06:00
Matthew Holt 2cc5d38229 Fix comment indentation 2022-08-25 13:28:58 -06:00
Simon Legner 66596f2d74 zstd: fix typo in comment (#4985) 2022-08-25 12:00:05 +03:00
Ben Burkert b540f195b1 httpcaddyfile: Add ocsp_interval global option (#4980) 2022-08-24 11:22:56 -06:00
Matthew Holt 3aabbc49a2 caddytls: Log error if ask request fails
Errors returned from the DecisionFunc (whether to get a cert on-demand)
are used as a signal whether to allow a cert or not; *any* error
will forbid cert issuance.

We bubble up the error all the way to the caller, but that caller is the
Go standard library which might gobble it up.
Now we explicitly log connection errors so sysadmins can
ensure their ask endpoints are working.

Thanks to our sponsor AppCove for reporting this!
2022-08-23 22:28:15 -06:00
Matt Holt bbc923d66b ci: Increase linter timeout (#4981) 2022-08-23 14:26:19 -06:00
jedy e289ba6187 templates: cap of slice should not be smaller than length (#4975) 2022-08-23 08:26:02 -06:00
Francis Lavoie a22c08a638 caddyhttp: Fix for nil handlerErr.Err (#4977) 2022-08-23 08:17:46 -06:00
Francis Lavoie 72541f1cb8 caddyhttp: Set http.error.message to the HandlerError message (#4971) 2022-08-22 23:31:07 -06:00
Matthew Holt fe5f5dfd6a go.mod: Upgrade CertMagic to v0.16.3 2022-08-18 10:56:27 -06:00
WilczyńskiT c7772588bd core: Change net.IP to netip.Addr; use netip.Prefix (#4966)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-08-17 16:10:57 -06:00
Matthew Holt a944de4ab7 Clean up metrics test code
No need to use != for booleans
2022-08-16 10:03:19 -06:00
Matt Holt a479943acd caddyhttp: Smarter path matching and rewriting (#4948)
Co-authored-by: RussellLuo <luopeng.he@gmail.com>
2022-08-16 08:48:57 -06:00
Abdussamet Koçak dc62d468e9 fileserver: reset buffer before using it (#4962) (#4963) 2022-08-15 22:31:45 -06:00
Matt Holt c79c08627d caddyhttp: Enable HTTP/3 by default (#4707) 2022-08-15 12:01:58 -06:00
Francis Lavoie e2a5e2293a reverseproxy: Add unix+h2c Caddyfile network shortcut (#4953) 2022-08-12 17:09:18 -04:00
Matt Holt f5dce84a70 reverseproxy: Ignore context cancel in stream mode (#4952) 2022-08-12 13:15:41 -06:00
Francis Lavoie 922d9f5c25 reverseproxy: Fix H2C dialer using new stdlib DialTLSContext (#4951) 2022-08-12 13:11:13 -06:00
Matthew Holt 91ab0e6066 httpcaddyfile: redir with "html" emits 200, no Location (fix #4940)
The intent of "html" is to redirect browser clients only, or those which can evaluate JS and/or meta tags. So return HTTP 200 and no Location header. See #4940.
2022-08-09 11:12:09 -06:00
Kévin Dunglas 085df25c7e reverseproxy: Support 1xx status codes (HTTP early hints) (#4882) 2022-08-09 10:53:24 -06:00
Francis Lavoie fe61209df2 logging: Fix cookie filter (#4943) 2022-08-08 19:11:02 -06:00
lewandowski-stripe 7f6a328b47 go.mod: Upgrade OpenTelemetry dependencies (#4937) 2022-08-08 15:04:18 -06:00
Matthew Holt 7ab61f46f0 fileserver: Better fix for Etag of compressed files 2022-08-08 13:09:57 -06:00
Matthew Holt 8c72f34357 fileserver: Generate Etag from sidecar file
Don't use the primary/uncompressed file for Etag when serving sidecars.

This was just overlooked initially.
2022-08-08 12:50:06 -06:00
Matthew Holt b9618b8b98 Improve docs for ZeroSSL issuer 2022-08-08 12:50:06 -06:00
Chirag Maheshwari d26559316f Replace strings.Index with strings.Cut (#4932) 2022-08-06 22:03:37 -06:00
WilczyńskiT 2642bd72b7 Replace strings.Index usages with strings.Cut (#4930) 2022-08-04 11:17:35 -06:00
Matt Holt 17ae5acaba cmd: Use newly-available version information (#4931) 2022-08-04 11:16:59 -06:00
Matt Holt 1960a0dc11 httpserver: Configurable shutdown delay (#4906) 2022-08-03 11:04:51 -06:00
Matthew Holt 63c7720e84 go.mod: Upgrade CertMagic and acmez 2022-08-02 15:35:19 -06:00
Francis Lavoie 141872ed80 chore: Bump up to Go 1.19, minimum 1.18 (#4925) 2022-08-02 16:39:09 -04:00
Matthew Holt db1aa5b5bc Oops (sigh)
Forgot to remove this redundant line
2022-08-01 13:40:09 -06:00
Matt Holt f783290f40 caddyhttp: Implement caddy respond command (#4870) 2022-08-01 13:36:22 -06:00
Matthew Holt ebd6abcbd5 fileserver: Support virtual file system in Caddyfile 2022-07-31 21:41:26 -06:00
Matt Holt 6668271661 fileserver: Support virtual file systems (#4909)
* fileserver: Support virtual file systems (close #3720)

This change replaces the hard-coded use of os.Open() and os.Stat() with
the use of the new (Go 1.16) io/fs APIs, enabling virtual file systems.
It introduces a new module namespace, caddy.fs, for such file systems.

Also improve documentation for the file server. I realized it was one of
the first modules written for Caddy 2, and the docs hadn't really been
updated since!

* Virtualize FS for file matcher; minor tweaks

* Fix tests and rename dirFS -> osFS

(Since we do not use a root directory, it is dynamic.)
2022-07-30 13:07:44 -06:00
Matthew Holt 07ed3e7c30 Minor docs clarification
Related to #4565
2022-07-29 16:56:02 -06:00
WingLim 1e0cdc54f8 core: Windows service integration (#4790)
Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-07-29 14:06:54 -06:00
Francis Lavoie 2f43aa0629 chore: Add .gitattributes to force *.go to LF (#4919)
* chore: Add .gitattributes to force *.go to LF

* What if I remove this flag
2022-07-29 08:46:45 -04:00
Matthew Holt 56c139f003 Fix compilation on Windows 2022-07-28 15:44:36 -06:00
Matthew Holt 35a81d7c5b Ignore linter warnings
Use of non-cryptographic random numbers in the load balancing
is intentional.
2022-07-28 15:40:23 -06:00
Matthew Holt 2e70d1d3bf Fix deprecation notice by using UTF16PtrFromString 2022-07-28 15:24:08 -06:00
Francis Lavoie ff2ba6de8a caddyhttp: Clear out matcher error immediately after grabbing it (#4916)
Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-07-28 15:19:48 -06:00
Matthew Holt 4fced0b6e1 Finish fixing lint errors from ea8df6ff
Follows up #4915
2022-07-28 15:16:36 -06:00
Matthew Holt 1bdd451913 caddytls: Remove PreferServerCipherSuites
It has been deprecated by Go
2022-07-28 14:50:51 -06:00
Matthew Holt ea8df6ff11 caddyhttp: Use new CEL APIs (fix #4915)
Hahaha this is the ultimate "I have no idea what I'm doing" commit but it
compiles and the tests pass and I declare victory!

... probably broke something, should be tested more.

It is nice that the protobuf dependency becomes indirect now.
2022-07-28 14:50:28 -06:00
Y.Horie c833e3b249 ci: Run golangci-lint on multiple os(#4875) (#4913) 2022-07-27 09:27:18 -04:00
Matthew Holt 7991cd1250 go.mod: Upgrade dependencies 2022-07-26 11:07:20 -06:00
Matthew Holt 1e18afb5c8 httpcaddyfile: Detect ambiguous site definitions (fix #4635)
Previously, our "duplicate key in server block" logic was flawed because
it did not account for the site's bind address. We defer this check to
when the listener addresses have been assigned, but before we commit
a server block to its listener.

Also refined how network address parsing and joining works, which was
necessary for a less convoluted fix.
2022-07-25 17:28:20 -06:00
Matthew Holt 0bebea0d4c caddyhttp: Log shutdown errors, don't return (fix #4908) 2022-07-25 10:39:59 -06:00
Matt Holt a379fa4c6c reverseproxy: Implement read & write timeouts for HTTP transport (#4905) 2022-07-23 22:38:41 -06:00
Francis Lavoie abad9bc256 cmd: Fix reload with stdin (#4900) 2022-07-20 18:14:33 -06:00
Matthew Holt 8bdee04651 caddyhttp: Enhance comment 2022-07-16 23:33:49 -06:00
Francis Lavoie 7d1f7771c9 reverseproxy: Implement retry count, alternative to try_duration (#4756)
* reverseproxy: Implement retry count, alternative to try_duration

* Add Caddyfile support for `retry_match`

* Refactor to deduplicate matcher parsing logic

* Fix lint
2022-07-13 14:15:00 -06:00
Matthew Holt 04a14ee37a caddyhttp: Make query matcher more efficient
Only parse query string once
2022-07-13 12:20:00 -06:00
Matthew Holt c2bbe42fc3 reverseproxy: Export SetScheme() again
Turns out the NTLM transport uses it. Oops.
2022-07-13 08:52:30 -06:00
jhwz ad3a83fb91 admin: expect quoted ETags (#4879)
* expect quoted etags

* admin: Minor refactor of etag facilities

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-07-12 12:23:55 -06:00
Francis Lavoie 53c4d788d4 headers: Only replace known placeholders (#4880) 2022-07-12 12:16:03 -06:00
Matthew Holt d6bc9e0b5c reverseproxy: Err 503 if all upstreams unavailable 2022-07-08 13:01:32 -06:00
Francis Lavoie 54d1923ccb reverseproxy: Adjust new TLS Caddyfile directive names (#4872) 2022-07-08 13:04:22 -04:00
Matthew Holt c0f76e9ed4 fileserver: Use safe redirects in file browser 2022-07-07 14:10:19 -06:00
jhwz f259ed52bb admin: support ETag on config endpoints (#4579)
* admin: support ETags

* support etags

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-07-06 13:50:07 -06:00
Francis Lavoie 8bac134f26 go.mod: Bump up quic-go to v0.28.0, fixes for BC breaks (#4867) 2022-07-06 12:14:32 -06:00
Matt Holt 412dcc07d3 caddytls: Reuse issuer between PreCheck and Issue (#4866)
This enables EAB reuse for ZeroSSLIssuer (which is now supported by ZeroSSL).
2022-07-05 18:12:25 -06:00
Matt Holt 660c59b6f3 admin: Implement /adapt endpoint (close #4465) (#4846) 2022-06-29 00:43:57 -04:00
Francis Lavoie 58e05cab15 forwardauth: Fix case when copy_headers is omitted (#4856)
See https://caddy.community/t/using-forward-auth-and-writing-my-own-authenticator-in-php/16410, apparently it didn't work when `copy_headers` wasn't used. This is because we were skipping adding a handler to the routes in the "good response handler", but this causes the logic in `reverseproxy.go` to ignore the response handler since it's empty. Instead, we can just always put in the `header` handler, even with an empty `Set` operation, it's just a no-op, but it fixes that condition in the proxy code.
2022-06-28 19:23:30 -06:00
Tristan Swadell 10f85558ea Expose several Caddy HTTP Matchers to the CEL Matcher (#4715)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2022-06-22 18:53:46 -04:00
Francis Lavoie 98468af8b6 reverseproxy: Fix double headers in response handlers (#4847) 2022-06-22 15:10:14 -04:00
Francis Lavoie 25f10511e7 reverseproxy: Fix panic when TLS is not configured (#4848)
* reverseproxy: Fix panic when TLS is not configured

* Refactor and simplify setScheme

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-06-22 15:01:57 -04:00
Kiss Károly Pál b6e96fa3c5 reverseproxy: Skip TLS for certain configured ports (#4843)
* Make reverse proxy TLS server name replaceable for SNI upstreams.

* Reverted previous TLS server name replacement, and implemented thread safe version.

* Move TLS servername replacement into it's own function

* Moved SNI servername replacement into httptransport.

* Solve issue when dynamic upstreams use wrong protocol upstream.

* Revert previous commit.

Old commit was: Solve issue when dynamic upstreams use wrong protocol upstream.
Id: 3c9806ccb6

* Added SkipTLSPorts option to http transport.

* Fix typo in test config file.

* Rename config option as suggested by Matt

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

* Update code to match renamed config option.

* Fix typo in config option name.

* Fix another typo that I missed.

* Tests not completing because of apparent wrong ordering of options.

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-06-20 11:51:42 -06:00
Matthew Holt 56013934a4 go.mod: Update some dependencies 2022-06-20 10:50:50 -06:00
Francis Lavoie 0b6f764356 forwardauth: Support renaming copied headers, block support (#4783) 2022-06-16 14:28:11 -06:00
Matthew Holt 050d6e0aeb Add comment about xcaddy to main 2022-06-15 15:20:59 -06:00
Matt Holt 0bcd02d5f6 headers: Support wildcards for delete ops (close #4830) (#4831) 2022-06-15 09:57:43 -06:00
Kiss Károly Pál c82fe91104 reverseproxy: Dynamic ServerName for TLS upstreams (#4836)
* Make reverse proxy TLS server name replaceable for SNI upstreams.

* Reverted previous TLS server name replacement, and implemented thread safe version.

* Move TLS servername replacement into it's own function

* Moved SNI servername replacement into httptransport.

* Solve issue when dynamic upstreams use wrong protocol upstream.

* Revert previous commit.

Old commit was: Solve issue when dynamic upstreams use wrong protocol upstream.
Id: 3c9806ccb6

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-06-14 21:53:05 -06:00
Matthew Holt f9b42c3772 reverseproxy: Make TLS renegotiation optional 2022-06-14 09:05:25 -06:00
Yaacov Akiba Slama aaf6794b31 reverseproxy: Add renegotiation param in TLS client (#4784)
* Add renegotiation option in reverseproxy tls client

* Update modules/caddyhttp/reverseproxy/httptransport.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-06-10 09:33:35 -06:00
Matthew Holt 1498132ea3 caddyhttp: Log error from CEL evaluation (fix #4832) 2022-06-08 16:42:24 -06:00
Francis Lavoie 7f9b1f43c9 reverseproxy: Correct the tls_server_name docs (#4827)
* reverseproxy: Correct the `tls_server_name` docs

* Update modules/caddyhttp/reverseproxy/httptransport.go

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-06-06 12:37:09 -06:00
Matt Holt 5e729c1e85 reverseproxy: HTTP 504 for upstream timeouts (#4824)
Closes #4823
2022-06-03 14:13:47 -06:00
Gr33nbl00d 0a14f97e49 caddytls: Make peer certificate verification pluggable (#4389)
* caddytls: Adding ClientCertValidator for custom client cert validations

* caddytls: Cleanups for ClientCertValidator changes

caddytls: Cleanups for ClientCertValidator changes

* Update modules/caddytls/connpolicy.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Update modules/caddytls/connpolicy.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Update modules/caddytls/connpolicy.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Update modules/caddytls/connpolicy.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Update modules/caddytls/connpolicy.go

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

* Update modules/caddytls/connpolicy.go

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

* Unexported field Validators, corrected renaming of LeafVerificationValidator to LeafCertClientAuth

* admin: Write proper status on invalid requests (#4569) (fix #4561)

* Apply suggestions from code review

* Register module; fix compilation

* Add log for deprecation notice

Co-authored-by: Roettges Florian <roettges.florian@scheidt-bachmann.de>
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Co-authored-by: Alok Naushad <alokme123@gmail.com>
2022-06-02 14:25:07 -06:00
Matthew Holt 9864b138fb reverseproxy: api: Remove misleading 'healthy' value
In v2.5.0, upstream health was fixed such that whether an upstream is
considered healthy or not is mostly up to each individual handler's
config. Since "healthy" is an opinion, it is not a global value.

I unintentionally left in the "healthy" field in the API endpoint for
checking upstreams, and it is now misleading (see #4792).

However, num_requests and fails remains, so health can be determined by
the API client, rather than having it be opaquely (and unhelpfully)
determined for the client.

If we do restore this value later on, it'd need to be replicated once
per reverse_proxy handler according to their individual configs.
2022-06-02 12:32:23 -06:00
Matthew Holt 3d18bc56b9 go.mod: Update go-yaml to v3 2022-06-01 15:15:20 -06:00
Matthew Holt 886ba84baa Fix #4822 and fix #4779
The fix for 4822 is the change at the top of the file, and
4779's fix is toward the bottom of the file.
2022-06-01 15:12:57 -06:00
Alexander M a9267791c4 reverseproxy: Add --internal-certs CLI flag #3589 (#4817)
added flag --internal-certs
when set, for non-local domains the internal CA will be used for cert generation
2022-05-29 14:33:01 -06:00
Francis Lavoie ef0aaca0d6 ci: Fix build caching on Windows (#4811)
* ci: Fix build caching on Windows

I was getting tired of Windows being slow as molasses in our CI jobs, so I went to look at our trusty source of github actions + golang information, and found a somewhat recent commit that actually fixed it. See https://github.com/mvdan/github-actions-golang/commit/4b754729baa709da219a5889c459010d4eda1888

I'll do a 2nd empty commit to re-trigger CI shortly to confirm that it actually fixes it.

* Retrigger CI
2022-05-25 11:56:39 -06:00
Aleks 6891f7f421 templates: Add humanize function (#4767)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2022-05-24 19:47:08 -04:00
Kévin Dunglas 499ad6d182 core: Micro-optim in run() (#4810) 2022-05-24 13:52:50 -06:00
Matthew Holt 8e6bc36084 go.mod: Upgrade some dependencies 2022-05-24 12:44:16 -06:00
Francis Lavoie 58970cae92 httpcaddyfile: Add {err.*} placeholder shortcut (#4798) 2022-05-24 10:06:46 -06:00
David Larlet 9e760e2e0c templates: Documentation consistency (#4796) 2022-05-17 18:56:40 -04:00
世界 4b4e99bdb2 chore: Bump quic-go to v0.27.0 (#4782) 2022-05-12 01:25:17 -04:00
Matt Holt 57d27c1b58 reverseproxy: Support http1.1>h2c (close #4777) (#4778) 2022-05-10 17:25:58 -04:00
Matthew Holt 693e9b5283 rewrite: Handle fragment before query (fix #4775) 2022-05-09 11:09:42 -06:00
Francis Lavoie b687d7b967 httpcaddyfile: Support multiple values for default_bind (#4774)
* httpcaddyfile: Support multiple values for `default_bind`

* Fix ordering of server blocks
2022-05-08 21:32:10 -04:00
Francis Lavoie f7be0ee101 map: Prevent output destinations overlap with Caddyfile shorthands (#4657) 2022-05-06 10:25:31 -06:00
Francis Lavoie f6900fcf53 reverseproxy: Support performing pre-check requests (#4739) 2022-05-06 10:50:26 -04:00
Francis Lavoie ec86a2f7a3 caddyfile: Shortcut for remote_ip for private IP ranges (#4753) 2022-05-04 12:42:37 -06:00
Francis Lavoie e7fbee8c82 reverseproxy: Permit resolver addresses to not specify a port (#4760)
Context: https://caddy.community/t/caddy-2-5-dynamic-upstreams-and-consul-srv-dns/15839

I realized it probably makes sense to allow `:53` to be omitted, since it's the default port for DNS.
2022-05-04 12:40:39 -06:00
Tyler Kropp e84e19a04e templates: Add custom template function registration (#4757)
* Add custom template function registration

* Rename TemplateFunctions to CustomFunctions

* Add documentation

* Document CustomFunctions interface

* Preallocate custom functions map list

* Fix interface name in error message
2022-05-02 14:55:34 -06:00
Francis Lavoie 4a223f5203 reverseproxy: Fix Caddyfile support for replace_status (#4754) 2022-05-02 11:44:28 -06:00
Francis Lavoie af7321511c httpcaddyfile: Fix duplicate access log when debug is on (#4746) 2022-04-28 12:16:25 -04:00
Francis Lavoie 0be3d99543 logging: Implement rename filter, changes field key names (#4745) 2022-04-28 11:38:44 -04:00
Francis Lavoie 3017b245c9 logging: Use RedirectStdLog to capture more stdlib logs (#4732)
* logging: Use `RedirectStdLog`

* .gitignore a file pattern that I'm constantly using for testing
2022-04-28 08:42:30 -06:00
Francis Lavoie 2e4c09155a cmd: Fix unix socket addresses for admin API requests (#4742)
Fixes a regression in c2327161f7
2022-04-28 08:31:59 -06:00
Francis Lavoie dcc98da4d2 caddyhttp: Improve listen addr error message for IPv6 (#4740) 2022-04-28 08:18:45 -06:00
Marco Kaufmann 3ab648382d templates: Add missing backticks in docs (#4737) 2022-04-27 11:41:37 -06:00
Matt Holt 40b193fb79 reverseproxy: Improve hashing LB policies with HRW (#4724)
* reverseproxy: Improve hashing LB policies with HRW

Previously, if a list of upstreams changed, hash-based LB policies
would be greatly affected because the hash relied on the position of
upstreams in the pool. Highest Random Weight or "rendezvous" hashing
is apparently robust to pool changes. It runs in O(n) instead of
O(log n), but n is very small usually.

* Fix bug and update tests
2022-04-27 10:39:22 -06:00
Francis Lavoie d543ad1ffd caddypki: Fix caddy trust command to use the correct API endpoint (#4730) 2022-04-25 22:00:39 -06:00
Francis Lavoie a8bb4a665a httpcaddyfile: Add {vars.*} placeholder shortcut, reverse vars sort order (#4726)
* httpcaddyfile: Add `{vars.*}` placeholder shortcut

I'm yoinking this from my https://github.com/caddyserver/caddy/pull/4657 PR because I think we should get this in ASAP for v2.5.0 along with the new `vars` directive.

* Sort vars by matchers in reverse
2022-04-25 10:47:12 -06:00
Francis Lavoie 3a1e0dbf47 httpcaddyfile: Deprecate paths in site addresses; use zap logs (#4728) 2022-04-25 10:12:10 -06:00
Francis Lavoie 77a77c0219 caddytls: Add propagation_delay, support propagation_timeout -1 (#4723) 2022-04-22 16:09:11 -06:00
Matthew Holt db62942d63 Make file modes consistent
No need to have executable bit on .go or .txt files
2022-04-21 15:06:55 -06:00
Matthew Holt dadd4b59b0 Update smallstep/certificates 2022-04-20 11:32:33 -06:00
Mohammed Al Sahaf d230b33007 ci: use latest Go version on macOS (#4708) 2022-04-15 13:58:48 -04:00
Matthew Holt 0d13173071 ci: Fix typo 2022-04-13 14:11:03 -06:00
Francis Lavoie c3a82f53d5 ci: Ensure we always check for latest version of Go (#4703)
* ci: Ensure we always check for latest version of Go

* Try to force 1.18.1, 1.17.9

* Use includes for the actual go semver

* Use `~` for semver here, apparently

* Try to make tests still run on 1.18.0 for Mac, for now
2022-04-13 14:03:38 -06:00
Matthew Holt 30b6d1f47a cmd: Enhance .env (dotenv) file parsing
Basic support for quoted values, newlines in quoted values, and comments.

Does not support variable or command expansion.
2022-04-13 11:38:20 -06:00
Francis Lavoie bc15b4b0e7 caddypki: Load intermediate for signing on-the-fly (#4669)
* caddypki: Load intermediate for signing on-the-fly

Fixes #4517

Big thanks to @maraino for adding an API in `smallstep/certificates` so that we can fix this

* Debug log

* Trying a hunch, does it need to be a pointer receiver?

* Clarify pointer receiver

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-04-13 10:20:42 -06:00
cui fliter e2535233bb fix typo (#4702)
Signed-off-by: cuishuang <imcusg@gmail.com>
2022-04-13 10:13:28 -06:00
Francis Lavoie 00234c8ac2 templates: Switch to BurntSushi/toml (#4700) 2022-04-12 13:48:42 -06:00
Francis Lavoie 6512832f9f cmd: Add --diff option for caddy fmt (#4695) 2022-04-12 14:49:19 -04:00
Francis Lavoie 3e3bb00265 reverseproxy: Add _ms placeholders for proxy durations (#4666)
* reverseproxy: Add `_ms` placeholders for proxy durations

* Add http.request.duration_ms

Also add comments, and change duration_sec to duration_ms

* Add response.duration_ms for consistency

* Add missing godoc comment

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-04-11 13:04:05 -06:00
Francis Lavoie e4ce40f8ff reverseproxy: Sync up handleUpgradeResponse with stdlib (#4664)
* reverseproxy: Sync up `handleUpgradeResponse` with stdlib

I had left this as a TODO for when we bump to minimum 1.17, but I should've realized it was under `internal` so it couldn't be used directly.

Copied the functions we needed for parity. Hopefully this is ok!

* Add tests and fix godoc comments

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-04-11 12:49:56 -06:00
Y.Horie afca242111 staticfiles: Expand placeholder for index files (#4679) 2022-04-07 15:01:09 -06:00
Francis Lavoie 7d229665ed logging: Caddyfile support for duration_format (#4684)
Somehow, this was missed. Oops!
2022-04-07 11:23:28 -06:00
Francis Lavoie 22d8edb984 cmd: Fix defaulting admin address if empty in config, fixes reload (#4674) 2022-04-03 12:04:33 -04:00
Francis Lavoie 734acc776a chore: Fix for xcaddy builds (#4665)
* chore: Attempt fix for xcaddy builds

* Upgrade smallstep/certificates which avoids the problem
2022-03-28 15:07:43 -06:00
Francis Lavoie b4f1a71397 chore: Bump minimum Go to 1.17 (#4662) 2022-03-25 14:56:29 -04:00
Matthew Holt d06d0e79f8 go.mod: Upgrade CertMagic to v0.16.0
Includes several breaking changes; code base updated accordingly.

- Added lots of context arguments
- Use fs.ErrNotExist
- Rename ACMEManager -> ACMEIssuer; CertificateManager -> Manager
2022-03-25 11:28:54 -06:00
Francis Lavoie a58f240d3e httpcaddyfile: Fix #4640 (auto-HTTPS edgecase) (#4661)
Guh, this is complicated.

Fixes #4640

This also follows up on #4398 (reverting it) which made a change that technically worked, but was incorrect. It changed the condition in `hostsFromKeysNotHTTP` from `&&` to `||`, but then the function no longer did what its name said it would do, and it would return hosts even if they were marked with `http://`, if they used a non-HTTP port. That wasn't the intent of it. The test added in there was kept though, because it is a valid usecase.

The actual fix is to check _earlier_ whether all the addresses explicitly have `http://`, and if so we can short circuit and skip considering the rest.
2022-03-24 22:54:03 -06:00
Francis Lavoie 4b75f3e2f0 chore: Clean up adapt test line endings (#4660)
Lots of the files were using CRLF instead of LF. Mostly my fault cause sometimes I make the files on Windows and VSCode for some reason kept making them with the wrong line endings. Sigh.

Since .txt files typically default to spaces for indentation, I'm also adding an .editorconfig to ensure they use tabs instead
2022-03-24 22:48:45 -06:00
Matthew Holt b8dbecb841 reverseproxy: Include port in A upstreams cache
Should fix #4659
2022-03-24 10:44:36 -06:00
Francis Lavoie 134b805644 caddyfile: Prevent bad block opening tokens (#4655)
* caddyfile: Prevent bad block opening tokens

* Clarifying comments
2022-03-23 12:34:13 -06:00
Artem Mikheev c9b5e7f77b Fix http3 servers dying after reload (#4654) 2022-03-22 19:47:57 -04:00
Matthew Holt 79cbe7bfd0 httpcaddyfile: Add 'vars' directive
See discussion in #4650
2022-03-22 10:47:21 -06:00
Matthew Holt 55b4c12e04 map: Evaluate placeholders in output vals (#4650) 2022-03-21 17:05:38 -06:00
Matthew Holt 2196c92c0e reverseproxy: Don't clear name in SRV upstreams
Fix for dc4d147388
2022-03-21 08:33:24 -06:00
Matthew Holt c2327161f7 cmd: Set Origin header properly on API requests
Ref. https://caddy.community/t/bug-in-enforce-origin/15417
2022-03-19 22:51:32 -06:00
Francis Lavoie c5fffb4ac2 caddyfile: Support for raw token values; improve map, expression (#4643)
* caddyfile: Support for raw token values, improve `map`, `expression`

* Applied code review comments

* Rename RawVal to ValRaw

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-03-18 15:08:23 -06:00
Matthew Holt dc4d147388 reverseproxy: Expand SRV/A addrs for cache key
Hopefully fix #4645
2022-03-18 13:42:29 -06:00
Matthew Holt 93c99f6734 map: Support numeric and bool types with Caddyfile
Based on caddyserver/website#221
2022-03-17 17:53:32 -06:00
Francis Lavoie 4e9fbee1e2 ci: Build on Go 1.18, bump actions versions (#4637)
* ci: Build on Go 1.18, bump actions versions

* Revert linter version bump for now

* Try linter again
2022-03-15 22:09:19 +00:00
Francis Lavoie a9c7e94a38 chore: Comment fixes (#4634) 2022-03-13 01:38:11 -05:00
Matthew Holt 3d616e8c6d requestbody: Return HTTP 413 (fix #4558) 2022-03-11 12:34:55 -07:00
Mohammed Al Sahaf b82e22b459 caddyhttp: retain all values of vars matcher when specified multiple times (#4629) 2022-03-11 10:55:37 -05:00
Matthew Holt bf6a1b7538 go.mod: Upgrade some dependencies
Fixes bug in yuin/goldmark
https://github.com/caddyserver/website/issues/217
2022-03-10 11:40:03 -07:00
Francis Lavoie c7d6c4cbb9 reverseproxy: copy_response and copy_response_headers for handle_response routes (#4391)
* reverseproxy: New `copy_response` handler for `handle_response` routes

Followup to #4298 and #4388.

This adds a new `copy_response` handler which may only be used in `reverse_proxy`'s `handle_response` routes, which can be used to actually copy the proxy response downstream. 

Previously, if `handle_response` was used (with routes, not the status code mode), it was impossible to use the upstream's response body at all, because we would always close the body, expecting the routes to write a new body from scratch.

To implement this, I had to refactor `h.reverseProxy()` to move all the code that came after the `HandleResponse` loop into a new function. This new function `h.finalizeResponse()` takes care of preparing the response by removing extra headers, dealing with trailers, then copying the headers and body downstream.

Since basically what we want `copy_response` to do is invoke `h.finalizeResponse()` at a configurable point in time, we need to pass down the proxy handler, the response, and some other state via a new `req.WithContext(ctx)`. Wrapping a new context is pretty much the only way we have to jump a few layers in the HTTP middleware chain and let a handler pick up this information. Feels a bit dirty, but it works.

Also fixed a bug with the `http.reverse_proxy.upstream.duration` placeholder, it always had the same duration as `http.reverse_proxy.upstream.latency`, but the former was meant to be the time taken for the roundtrip _plus_ copying/writing the response.

* Delete the "Content-Length" header if we aren't copying

Fixes a bug where the Content-Length will mismatch the actual bytes written if we skipped copying the response, so we get a message like this when using curl:

```
curl: (18) transfer closed with 18 bytes remaining to read
```

To replicate:

```
{
	admin off
	debug
}

:8881 {
	reverse_proxy 127.0.0.1:8882 {
		@200 status 200
		handle_response @200 {
			header Foo bar
		}
	}
}

:8882 {
	header Content-Type application/json
	respond `{"hello": "world"}` 200
}
```

* Implement `copy_response_headers`, with include/exclude list support

* Apply suggestions from code review

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-03-09 11:00:51 -07:00
Andrii Kushch d0b608af31 tracing: New OpenTelemetry module (#4361)
* opentelemetry: create a new module

* fix imports

* fix test

* Update modules/caddyhttp/opentelemetry/README.md

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Update modules/caddyhttp/opentelemetry/README.md

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Update modules/caddyhttp/opentelemetry/README.md

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Update modules/caddyhttp/opentelemetry/tracer.go

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* rename error ErrUnsupportedTracesProtocol

* replace spaces with tabs in the test data

* Update modules/caddyhttp/opentelemetry/README.md

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Update modules/caddyhttp/opentelemetry/README.md

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* replace spaces with tabs in the README.md

* use default values for a propagation and exporter protocol

* set http attributes with helper

* simplify code

* Cleanup modules/caddyhttp/opentelemetry/README.md

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Update link in README.md

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Update documentation in README.md

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Update link to naming spec in README.md

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Rename module from opentelemetry to tracing

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Rename span_name to span

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Rename span_name to span

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Simplify otel resource creation

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* handle extra attributes

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* update go.opentelemetry.io/otel/semconv to 1.7.0

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* update go.opentelemetry.io/otel version

* remove environment variable handling

* always use tracecontext,baggage as propagators

* extract tracer name into variable

* rename OpenTelemetry to Tracing

* simplify resource creation

* update go.mod

* rename package from opentelemetry to tracing

* cleanup tests

* update Caddyfile example in README.md

* update README.md

* fix test

* fix module name in README.md

* fix module name in README.md

* change names in README.md and tests

* order imports

* remove redundant tests

* Update documentation README.md

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Fix grammar

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Update comments

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* Update comments

Co-authored-by: Dave Henderson <dhenderson@gmail.com>

* update go.sum

* update go.sum

* Add otelhttp instrumentation, update OpenTelemetry libraries.

* Use otelhttp instrumentation for instrumenting HTTP requests.

This change uses context.WithValue to inject the next handler into the
request context via a "nextCall" carrier struct, and pass it on to a
standard Go HTTP handler returned by otelhttp.NewHandler. The
underlying handler will extract the next handler from the context,
call it and pass the returned error to the carrier struct.

* use zap.Error() for the error log

* remove README.md

* update dependencies

* clean up the code

* change comment

* move serveHTTP method from separate file

* add syntax to the UnmarshalCaddyfile comment

* go import the file

* admin: Write proper status on invalid requests (#4569) (fix #4561)

* update dependencies

Co-authored-by: Dave Henderson <dhenderson@gmail.com>
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: Vibhav Pant <vibhavp@gmail.com>
Co-authored-by: Alok Naushad <alokme123@gmail.com>
Co-authored-by: Cedric Ziel <cedric@cedric-ziel.com>
2022-03-08 12:18:32 -07:00
Ran Chen d9b1d46325 caddytls: dns_challenge_override_domain for challenge delegation (#4596)
* Add a override_domain option to allow DNS chanllenge delegation

CNAME can be used to delegate answering the chanllenge to another DNS
zone. One usage is to reduce the exposure of the DNS credential [1].
Based on the discussion in caddy/certmagic#160, we are adding an option
to allow the user explicitly specify the domain to delegate, instead of
following the CNAME chain.

This needs caddy/certmagic#160.

* rename override_domain to dns_challenge_override_domain

* Update CertMagic; fix spelling

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-03-08 12:03:43 -07:00
Francis Lavoie c8f2834b51 fastcgi: Protect against requests with null bytes in the path (#4614) 2022-03-07 10:06:33 -07:00
Matt Holt ab0455922a reverseproxy: Dynamic upstreams (with SRV and A/AAAA support) (#4470)
* reverseproxy: Begin refactor to enable dynamic upstreams

Streamed here: https://www.youtube.com/watch?v=hj7yzXb11jU

* Implement SRV and A/AAA upstream sources

Also get upstreams at every retry loop iteration instead of just once
before the loop. See #4442.

* Minor tweaks from review

* Limit size of upstreams caches

* Add doc notes deprecating LookupSRV

* Provision dynamic upstreams

Still WIP, preparing to preserve health checker functionality

* Rejigger health checks

Move active health check results into handler-specific Upstreams.

Improve documentation regarding health checks and upstreams.

* Deprecation notice

* Add Caddyfile support, use `caddy.Duration`

* Interface guards

* Implement custom resolvers, add resolvers to http transport Caddyfile

* SRV: fix Caddyfile `name` inline arg, remove proto condition

* Use pointer receiver

* Add debug logs

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2022-03-06 17:43:39 -07:00
Francis Lavoie c50094fc9d reverseproxy: Implement trusted proxies for X-Forwarded-* headers (#4507) 2022-03-06 18:51:55 -05:00
Francis Lavoie d058dee11d reverseproxy: Refactor dial address parsing, augment command parsing (#4616) 2022-03-05 16:34:19 -07:00
Francis Lavoie 09ba9e994e fileserver: Add pass_thru Caddyfile option (#4613) 2022-03-04 20:50:05 -07:00
Matthew Holt be82cc7aca Appease the linter 2022-03-04 20:26:37 -07:00
Matt Holt 2bb8550a4c caddyhttp: Honor wildcard hosts in log SkipHosts (#4606) 2022-03-04 13:44:59 -07:00
Matthew Holt a72acd21b0 core: Retry dynamic config load if config unchanged
(see discussion in #4603)
2022-03-03 21:41:51 -07:00
Matthew Holt a6199cf814 templates: Fix docs for .Args 2022-03-03 11:12:37 -07:00
Matthew Holt ceef70dbc5 core: Retry dynamic config load if error or no-op (#4603)
Also fix ineffectual assignment (unrelated)
2022-03-03 10:58:15 -07:00
Francis Lavoie f5e104944e reverseproxy: Make shallow-ish clone of the request (#4551)
* reverseproxy: Make shallow-ish clone of the request

* Refactor request cloning into separate function

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2022-03-03 09:54:45 -07:00
Matthew Holt 6b385a36f9 caddyhttp: Don't attempt to manage Tailscale certs
If .ts.net domains are explicitly added to config,
don't try to manage a cert for them (it will fail, and our
implicit Tailscale module will
get those certs at run-time).
2022-03-02 13:42:38 -07:00
Matthew Holt 9b7cdfa2f2 caddypki: Try to fix lint warnings 2022-03-02 13:38:05 -07:00
Matthew Holt 78e381b29f caddypki: Refactor /pki/ admin endpoints
Remove /pki/certificates/<ca> endpoint and split into two endpoints:

- GET /pki/ca/<id> to get CA info and certs in JSON format
- GET /pki/ca/<id>/certificates to get cert in PEM chain
2022-03-02 13:00:37 -07:00
ttys3 de490c7cad fastcgi: Set SERVER_PORT to 80 or 443 depending on scheme (#4572) 2022-03-02 11:24:16 -07:00
Francis Lavoie bbad6931e3 pki: Implement API endpoints for certs and caddy trust (#4443)
* admin: Implement /pki/certificates/<id> API

* pki: Lower "skip_install_trust" log level to INFO

See https://github.com/caddyserver/caddy/issues/4058#issuecomment-976132935

It's not necessary to warn about this, because this was an option explicitly configured by the user. Still useful to log, but we don't need to be so loud about it.

* cmd: Export functions needed for PKI app, return API response to caller

* pki: Rewrite `caddy trust` command to use new admin endpoint instead

* pki: Rewrite `caddy untrust` command to support using admin endpoint

* Refactor cmd and pki packages for determining admin API endpoint
2022-03-02 11:08:36 -07:00
Francis Lavoie 5bd96a6ac2 httpcaddyfile: Support explicitly turning off strict_sni_host (#4592) 2022-03-01 20:02:39 -05:00
BitWuehler ac14b64e08 caddyhttp: Support zone identifiers in remote_ip matcher (#4597)
* Update matchers.go

* Update matchers.go

* implementation of zone_id handling

* last changes in zone handling

* give return true values instead of bool

* Apply suggestions from code review

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* changes as suggested

* Apply suggestions from code review

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Update matchers.go

* shortened the Match function

* changed mazcher handling

* Update matchers.go

* delete space

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2022-03-01 15:50:12 -07:00
Francis Lavoie 15c95e9d5b fileserver: Canonical redir when whole path is stripped (#4549) 2022-03-01 15:32:39 -07:00
Matthew Holt bc447e307f core: Config LoadInterval -> LoadDelay for clarity
And improve/clarify docs about this feature

See #4577
2022-03-01 15:05:12 -07:00
Francis Lavoie 87a1f228b4 reverseproxy: Move status replacement intercept to replace_status (#4300) 2022-03-01 14:12:43 -07:00
Matthew Holt acbee94708 core: Revert 7f364c7; simplify dynamic config load
Fixes #4577
2022-03-01 13:00:14 -07:00
Noorain Panjwani 7ea5b2a818 core: Config load interval only reloads if changed (#4603) 2022-03-01 11:32:33 -07:00
Francis Lavoie 186fdba916 caddyhttp: Move HTTP redirect listener to an optional module (#4585) 2022-02-19 15:36:36 -07:00
Mohammed Al Sahaf 7778912d4e ci: update goreleaser (#4582) 2022-02-19 15:16:11 -07:00
Francis Lavoie c921e08296 logging: Add roll_local_time Caddyfile option (#4583) 2022-02-19 15:12:28 -07:00
Francis Lavoie ddbb234d91 caddyhttp: Always log handled errors at debug level (#4584) 2022-02-19 15:10:49 -07:00
Francis Lavoie 0de51593a6 go.mod: Revert version bump of CEL (#4587) 2022-02-19 15:09:09 -07:00
Francis Lavoie 26d633baf8 httpcaddyfile: Disabling OCSP stapling for both managed and unmanaged (#4589) 2022-02-19 14:20:38 -07:00
Matthew Holt ff137d17d0 caddyconfig: Support placeholders in HTTP loader 2022-02-17 22:58:25 -07:00
Matt Holt 57a708d189 caddytls: Support external certificate Managers (like Tailscale) (#4541)
Huge thank-you to Tailscale (https://tailscale.com) for making this change possible!
This is a great feature for Caddy and Tailscale is a great fit for a standard implementation.

* caddytls: GetCertificate modules; Tailscale

* Caddyfile support for get_certificate

Also fix AP provisioning in case of empty subject list (persist loaded
module on struct, much like Issuers, to surive reprovisioning).

And implement start of HTTP cert getter, still WIP.

* Update modules/caddytls/automation.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Use tsclient package, check status for name

* Implement HTTP cert getter

And use reuse CertMagic's PEM functions for private keys.

* Remove cache option from Tailscale getter

Tailscale does its own caching and we don't need the added complexity...
for now, at least.

* Several updates

- Option to disable cert automation in auto HTTPS
- Support multiple cert managers
- Remove cache feature from cert manager modules
- Minor improvements to auto HTTPS logging

* Run go mod tidy

* Try to get certificates from Tailscale implicitly

Only for domains ending in .ts.net.

I think this is really cool!

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2022-02-17 15:40:34 -07:00
Alok Naushad 32aad90938 admin: Write proper status on invalid requests (#4569) (fix #4561) 2022-02-15 12:13:33 -07:00
Matthew Holt 40b54434f3 admin: Enforce and refactor origin checking
Using URLs seems a little cleaner and more correct

cf: https://caddy.community/t/protect-admin-endpoint/15114

(This used to work. Something must have changed recently.)
2022-02-15 12:08:12 -07:00
Francis Lavoie 1d0425b26f templates: Elaborate on what's supported by the markdown function (#4564) 2022-02-06 22:14:41 -07:00
Francis Lavoie 7557d1d922 reverseproxy: Avoid returning a nil error during GetClientCertificate (#4550) 2022-02-01 23:33:36 -07:00
Matthew Holt ff74a0aa09 go.mod: Upgrade dependencies
Including crucial CertMagic upgrade
2022-02-01 21:00:23 -07:00
Matthew Holt 599c81d753 Interrim upgrade CertMagic
For auto-replace certificate on revocation for on-demand mode,
until a proper release is made.
2022-01-30 22:46:25 -07:00
Dave Henderson 741b0502ee Merge pull request #4545 from hairyhenderson/metrics-restrict-http-methods
metrics: Enforce smaller set of method labels
2022-01-25 15:34:35 -05:00
Dave Henderson 7ca5921a87 move common metrics-related funcs to internal package
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2022-01-25 15:07:17 -05:00
Francis Lavoie da4a759bad Update modules/caddyhttp/metrics_test.go 2022-01-25 15:07:17 -05:00
Dave Henderson 042abeb431 other is not uppercase
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2022-01-25 15:07:17 -05:00
Dave Henderson eb891d4683 metrics: Enforce smaller set of method labels
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2022-01-25 15:07:17 -05:00
Kevin Daudt 44e5e9e43f caddyhttp: Fix test when /tmp/etc already exists (#4544)
The TestFileListing test in tplcontext_test has one test that verifies
if directory traversal is not happening. The context root is set to
'/tmp' and then it tries to open '../../../../../etc', which gets
normalized to '/tmp/etc'.

The test then expects an error to be returned, assuming that '/tmp/etc'
does not exist on the system. When it does exist, it results in a test
failure:

```
--- FAIL: TestFileListing (0.00s)
    tplcontext_test.go:422: Test 4: Expected error but had none
    FAIL
    FAIL
    github.com/caddyserver/caddy/v2/modules/caddyhttp/templates	0.042s
```

Instead of using '/tmp' as root, use a dedicated directory created with
`os.MkdirTemp()` instead. That way, we know that the directory is empty.
2022-01-24 14:41:08 -07:00
Matt Holt bf380d00ab caddyhttp: Reject absurd methods (#4538)
* caddyhttp: Reject absurdly long methods

* Limit method to 32 chars and truncate

* Just reject the request and debug-log it

* Log remote address
2022-01-19 13:44:09 -07:00
Vojtech Vitek 94035c1797 Improve the reverse-proxy CLI --to flag help message (#4535) 2022-01-19 14:51:46 -05:00
Forest Johnson b3f7ce34b4 More explanatory error message from Listen (#4534)
* explain cryptic unix socket listener error related to process kill

https://github.com/caddyserver/caddy/pull/4533

* less ambiguous wording: clean up -> delete

* shorten error message explanation

* link back to pull request in comment for later archeaology
2022-01-19 12:26:44 -07:00
Francis Lavoie a79b4055e5 caddytls: Add internal Caddyfile lifetime, sign_with_root opts (#4513) 2022-01-18 12:19:50 -07:00
Francis Lavoie 5a07156894 httpcaddyfile: Add pki app root and intermediate cert/key config (#4514) 2022-01-18 12:18:31 -07:00
Francis Lavoie bcb7a19cd3 rewrite: Add method Caddyfile directive (#4528) 2022-01-18 12:17:35 -07:00
Francis Lavoie 6e6ce2be6b caddyhttp: Fix HTTP->HTTPS redir not preferring HTTPS port if ambiguous (#4530) 2022-01-18 11:56:00 -07:00
Francis Lavoie 1b7ff5d76c httpcaddyfile: Add default_bind global option (#4531) 2022-01-18 11:29:07 -07:00
Francis Lavoie 93a7a45e7e httpcaddyfile: Fix incorrect handling of IPv6 bind addresses (#4532)
The `net.JoinHostPort()` function has some naiive logic for handling IPv6, it just checks if the host part has a `:` and if so it wraps the host part with `[ ]` but this causes our network type prefix to get wrapped as well, which is invalid for `caddy.NetworkAddress`. Instead, we can just concatenate the host and port manually here to avoid this side-effect.
2022-01-18 11:27:43 -07:00
Matthew Holt 1a7a78a1f2 cmd: Print error if fmt overwrite fails (fix #4524) 2022-01-16 17:30:14 -07:00
Francis Lavoie 1feb65952a rewrite: Fix a double-encode issue when using the {uri} placeholder (#4516) 2022-01-13 12:17:15 -05:00
GallopingKylin 66de438a98 caddytls: Fix MatchRemoteIP provisoning with multiple CIDR ranges (#4522) 2022-01-13 11:56:18 -05:00
rayjlinden 850e1605df caddyhttp: Return HTTP 421 for mismatched Host header (#4023)
Potential fix for #4017 although the consensus is unclear.

Made change to return status code 421 instead of 403 when StrictSNIHost matching is on.
2022-01-12 14:24:22 -07:00
Matthew Holt af1ac9cd2e Fix lint warnings 2022-01-10 23:27:39 -07:00
Matthew Holt 64a3218f5c core: Simplify shared listeners, fix deadline bug
When this listener code was first written, UsagePool didn't exist. We
can simplify much of the wrapped listener logic by utilizing UsagePool.

This also fixes a bug where new servers were able to clear deadlines
set by old servers, even if the old server didn't get booted out of its
Accept() call yet. And with the deadline cleared, they never would.
(Sometimes. Based on reports and difficulty of reproducing the bug,
this behavior was extremely rare.) I don't know why that happened
exactly, maybe some polling mechanism in the kernel and if the timings
worked out just wrong it would expose the bug.

Anyway, now we ensure that only the closer that set the deadline is the
same one that clears it, ensuring that old servers always return out of
Accept(), because the deadline doesn't get cleared until they do.

Of course, all this hinges on the hope that my suspicions in the middle
of the night are correct and that kernels work the way I think they do
in my head.

Also minor enhancement to UsagePool where if a value errors upon
construction (a very real possibility with listeners), it is removed from
the pool. Not 100% sure the sync logic is correct there, or maybe we
don't have to even put it in the pool until after construction, but it's
subtle either way and I think this is safe... right?
2022-01-10 23:24:58 -07:00
Matthew Holt c634bbe9cc caddypki: Return error if no PEM data found
Best guess for https://caddy.community/t/on-fly-certificate-generation-based-on-sni/14639/4
2022-01-07 10:55:11 -07:00
Francis Lavoie 4b9849c792 httpcaddyfile: Support configuring pki app names via global options (#4450) 2022-01-05 22:45:41 -05:00
Francis Lavoie 80d7a356b3 caddyhttp: Redirect HTTP requests on the HTTPS port to https:// (#4313)
* caddyhttp: Redirect HTTP requests on the HTTPS port to https://

* Apply suggestions from code review

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2022-01-05 18:01:15 -07:00
Matthew Holt b4bfa29be2 admin: Require identity for remote (fix #4478) 2022-01-05 17:55:09 -07:00
Matthew Holt 6cadb60fa2 templates: Document .OriginalReq
Close caddyserver/website#91
2022-01-05 13:59:59 -07:00
Денис Телюх 2e46c2ac1d admin, reverseproxy: Stop timers if canceled to avoid goroutine leak (#4482) 2022-01-04 12:14:18 -07:00
Francis Lavoie 249adc1c87 logging: Support turning off roll compression via Caddyfile (#4505) 2022-01-04 12:11:27 -07:00
Francis Lavoie e9dde23024 headers: Fix + in Caddyfile to properly append rather than set (#4506) 2022-01-04 10:10:11 -07:00
Francis Lavoie 3fe2c73dd0 caddyhttp: Fix MatchPath sanitizing (#4499)
This is a followup to #4407, in response to a report on the forums: https://caddy.community/t/php-fastcgi-phishing-redirection/14542

Turns out that doing `TrimRight` to remove trailing dots, _before_ cleaning the path, will cause double-dots at the end of the path to not be cleaned away as they should. We should instead remove the dots _after_ cleaning.
2021-12-30 04:15:48 -05:00
Francis Lavoie 5333c3528b reverseproxy: Fix incorrect health_headers Caddyfile parsing (#4485)
Fixes #4481
2021-12-17 08:53:11 -07:00
Rainer Borene 180ae0cc48 caddyhttp: Implement http.request.uuid placeholder (#4285) 2021-12-15 00:17:53 -07:00
Matthew Holt a1c41210d3 caddypki: Minor tweak, don't use context pointer 2021-12-13 16:13:38 -07:00
Matt Holt ecac03cdcb caddyhttp: Enhance vars matcher (#4433)
* caddyhttp: Enhance vars matcher

Enable "or" logic for multiple values.
Fall back to checking placeholders if not a var name.

* Fix tests (thanks @mohammed90 !)
2021-12-13 13:59:58 -07:00
Francis Lavoie c04d24cafa pki: Avoid provisioning the local CA when not necessary (#4463)
* pki: Avoid provisioning the `local` CA when not necessary

* pki: Refactor CA loading to keep the logic in the PKI app
2021-12-13 12:25:35 -07:00
Francis Lavoie 81ee34e962 httpcaddyfile: Fix sorting edgecase for nested handle_path (#4477) 2021-12-13 13:42:08 -05:00
Mohammed Al Sahaf 78b5356f2b fileserver: do not double-escape paths (#4447) 2021-12-11 09:26:21 -05:00
Francis Lavoie 6f9b6ad78e go.mod: Update smallstep/certificates, no longer need replace (#4475) 2021-12-10 14:58:53 -05:00
Francis Lavoie 4906b9357a go.mod: Update smallstep/truststore, fix build on FreeBSD (#4473) 2021-12-09 15:57:26 -05:00
Runzhi He e90d751732 caddyfile: impove fmt warning message (#4444)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2021-12-07 10:03:58 -07:00
Adam Burgess dce81e85d5 docs: use backticks to not italicise glob path (#4460) 2021-12-05 23:48:40 -07:00
Kévin Dunglas a1b417c832 logging: add support for hashing data (#4434)
* logging: add support for hashing data

* Update modules/logging/filters.go

Co-authored-by: wiese <wiese@users.noreply.github.com>

* Update modules/logging/filters.go

Co-authored-by: wiese <wiese@users.noreply.github.com>

Co-authored-by: wiese <wiese@users.noreply.github.com>
2021-12-02 13:51:37 -07:00
Francis Lavoie 5bf0adad87 caddyhttp: Make logging of credential headers opt-in (#4438) 2021-12-02 13:26:24 -07:00
Francis Lavoie 8e5aafa5cd fastcgi: Fix a TODO, prevent zap using reflection for logging env (#4437)
* fastcgi: Fix a TODO, prevent zap using reflection for logging env

* Update modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2021-12-02 13:23:19 -07:00
Francis Lavoie c133153447 go.mod: Update to latest smallstep/truststore, support FreeBSD (#4453) 2021-11-29 17:15:41 -07:00
Tim Culverhouse ec14ccdd40 templates: fix inconsistent nested includes (#4452) 2021-11-29 12:29:40 -05:00
Francis Lavoie f55b123d63 caddyhttp: Split up logged remote address into IP and port (#4403) 2021-11-29 01:18:35 -05:00
Matt Holt 0eb0b60f47 logging: Remove common_log field and single_field encoder (#4149) (#4282) 2021-11-29 01:08:52 -05:00
Rainer Borene 5e5af50e64 caddyfile: make renew_interval option configurable (#4451) 2021-11-28 17:22:26 -05:00
Francis Lavoie 9ee68c1bd5 reverseproxy: Adjust defaults, document defaults (#4436)
* reverseproxy: Adjust defaults, document defaults

Related to some of the issues in https://github.com/caddyserver/caddy/issues/4245, a complaint about the proxy transport defaults not being properly documented in https://caddy.community/t/default-values-for-directives/14254/6.

- Dug into the stdlib to find the actual defaults for some of the timeouts and buffer limits, documenting them in godoc so the JSON docs get them next release.

- Moved the keep-alive and dial-timeout defaults from `reverseproxy.go` to `httptransport.go`. It doesn't make sense to set defaults in the proxy, because then any time the transport is configured with non-defaults, the keep-alive and dial-timeout defaults are lost!

- Sped up the dial timeout from 10s to 3s, in practice it rarely makes sense to wait a whole 10s for dialing. A shorter timeout helps a lot with the load balancer retries, so using something lower helps with user experience.

* reverseproxy: Make keepalive interval configurable via Caddyfile

* fastcgi: DialTimeout default for fastcgi transport too
2021-11-24 01:32:25 -05:00
Kévin Dunglas 789efa5dee logging: add a regexp filter (#4426) 2021-11-23 10:00:20 -07:00
Kévin Dunglas 8887adb027 logging: add a filter for cookies (#4425)
* feat(logging): add a filter for cookies

* Improve godoc and add validation
2021-11-23 09:40:20 -07:00
Kévin Dunglas bcac2beee7 logging: add a filter for query parameters (#4424)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2021-11-23 04:01:43 -05:00
Mohammed Al Sahaf 1e10f6f725 fileserver: browse: do not encode the paths in breadcrumbs and page title (#4410) 2021-11-23 03:13:09 -05:00
Jeremy Lin c8b5a81607 fileserver: Fix handling of symlink sizes in directory listings (#4415) 2021-11-22 14:59:09 -07:00
Francis Lavoie eead337324 caddyhttp: Log non-500 handler errors at debug level (#4429)
Fixes #4428

It's best to still log handler errors at debug level so that they're hidden by default, but still accessible if additional details are necessary.
2021-11-22 11:58:25 -07:00
Matthew Holt 7d5047c1f1 caddyhttp: Log empty value for typical password headers
Work around for common misconfiguration
2021-11-22 11:31:50 -07:00
Matthew Holt 7f364c777a core: Load config at interval instead of just once 2021-11-16 13:08:22 -07:00
Matthew Holt b47af6ef04 caddyfile: Copy input before parsing (fix #4422) 2021-11-15 14:41:19 -07:00
Jeremy Lin e81369e220 fileserver: Move default browse template into a separate file (#4417)
This makes it easier for users to find the default browse template if they
want to create a custom template based on that. It also makes it easier to
view the template with proper syntax highlighting.
2021-11-15 11:53:54 -07:00
Francis Lavoie e7457b43e4 caddyhttp: Sanitize the path before evaluating path matchers (#4407) 2021-11-08 13:45:03 -07:00
Matthew Holt f376a38b25 go.mod: Update ACMEz and CertMagic 2021-11-08 13:08:50 -07:00
Francis Lavoie 749e55c738 caddycmd: Add --keep-backup to upgrade commands (#4387)
* caddycmd: Add `--skip-cleanup` to upgrade commands

This is a partial fix for https://github.com/caddyserver/caddy/issues/4057, making it possible to retain the old build of Caddy, in case something went wrong.

* caddycmd: Fix duplicate error message

The error message "download succeeded, but unable to execute" was repeated, because it was both in the `listModules`/`showVersion` functions and in the calling `upgradeBuild` function. Oversight when this was refactored.

* caddycmd: Implement fix for performing cleanup on Windows

Without this, the cleanup operation would fail with an error message like this:

upgrade: download succeeded, but unable to clean up backup binary: remove C:\caddy\caddy.exe.tmp: Access is denied.

* caddycmd: Rename to `--keep-backup`, simplify build constraints
2021-11-08 11:35:46 -07:00
Matt Holt 24fda7514d caddytls: Mark storage clean timestamp at end of routine (#4401)
See discussion on 42b7134ffa
2021-11-02 08:27:25 -06:00
Matthew Holt 3385856966 Fix lint message in metrics tests 2021-10-27 13:44:46 -06:00
Francis Lavoie f73f55dba7 reverseproxy: Sanitize scheme and host on incoming requests (#4237)
* caddyhttp: Sanitize scheme and host on incoming requests

* reverseproxy: Sanitize the URL scheme and host before proxying

* Apply suggestions from code review

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2021-10-26 14:41:28 -06:00
Marc Easen 012d235314 httpcaddyfile: Empty tls policy for internal http localhost (#4398)
* test: replicated empty tls automation policy issue

* fix: empty tls policy for an http:// endpoint running on a non-standard http port
2021-10-26 13:54:19 -06:00
Matthew Holt 997e41deae go.mod: Replace promptui with Apache-compatible fork (fix #4394)
Ideally this needs to be fixed upstream in github.com/manifoldco/promptui, but it appears unmaintained. Our dependency is extremely indirect:

    $ go mod why github.com/juju/ansiterm
    # github.com/juju/ansiterm
    github.com/caddyserver/caddy/v2/modules/caddypki
    github.com/smallstep/certificates/authority
    go.step.sm/cli-utils/ui
    github.com/manifoldco/promptui
    github.com/juju/ansiterm

And it appears that all dependencies in this chain are in conflict with the LGPL license.

Ref:
- https://github.com/manifoldco/promptui/issues/173
- https://github.com/manifoldco/promptui/pull/181

/cc @maraino
2021-10-21 13:44:16 -06:00
Matthew Holt 0ffb2229b0 httpcaddyfile: Preserve IPv6 addresses through normalization (fix #4381)
Remove unnecessary Key() method and improve related tests
2021-10-20 10:27:59 -06:00
Klaus Helenius a21d5a001f fileserver: Prevent focusing filter from scrolling on page load (#4393) 2021-10-20 12:15:58 -04:00
Matthew Holt a2119c09e9 map: Fix 95c03506 (avoid repeated expansions) 2021-10-19 12:25:36 -06:00
Francis Lavoie 062657d0d8 caddycmd: Add --skip-standard to list-modules command, quieter output (#4386)
* caddycmd: Add --skip-standard to list-modules command, quieter output

* caddycmd: Also quiet `caddy upgrade` output, redundant information
2021-10-18 12:19:04 -06:00
Francis Lavoie b092061591 reverseproxy: Prevent copying the response if a response handler ran (#4388) 2021-10-18 14:00:43 -04:00
Y.Horie 64f8b557b1 fileserver: Fix compression breaks using httpInclude (#4352) (#4358) 2021-10-16 11:09:16 -04:00
Matthew Holt 95c035060f map: Fix regex mappings
It didn't really make sense how we were doing them before. See https://caddy.community/t/map-directive-and-regular-expressions/13866/6?u=matt
2021-10-13 17:58:20 -06:00
Matthew Holt c4790d7f9d go.mod: Carefully upgrade some dependencies (fix #4251)
The upgrade of smallstep/certificates fixes #4251. The upgrade of CertMagic fixes an issue reported in the forum that a longer timeout was confirmed to resolve (without any particular explanation, but oh well). Other upgrades have minor improvements and seem safe.
2021-10-12 01:08:28 -06:00
Simão Gomes Viana 837cdc566d caddyhttp: reverseproxy: clarify warning for -insecure (#4379)
The question would only receive bad answers so it's better
to just say what the option actually does.
2021-10-11 16:15:00 -06:00
M. Ángel Jimeno be5f77e84d caddycmd: fix caddy validate/fmt help message (#4377)
* caddycmd: fix caddy validate help message

Fixes #4376

* caddycmd: fix caddy fmt help message
2021-10-11 11:56:03 -04:00
Oleg cbb045a121 caddyhttp: Placeholder for client cert in DER + base64 format (#4241)
* client.certificate_pem_encoded in base64 format

* base64-encoding without pem encoding;naming change

* fix cert.Raw instead of block.bytes
2021-10-01 16:27:29 -06:00
KallyDev c48fadc4a7 Move from deprecated ioutil to os and io packages (#4364) 2021-09-29 11:17:48 -06:00
Matthew Holt 059fc32f00 Revert 3336faf2 (close #4360)
Debug log is correct level for this
2021-09-27 12:06:06 -06:00
Matthew Holt e2d964ea30 Add explanation for project name to readme 2021-09-27 10:33:32 -06:00
Matthew Holt 501da21f20 General minor improvements to docs 2021-09-24 18:31:01 -06:00
Matthew Holt 3336faf254 reverseproxy: Log error at error level (fix #4360) 2021-09-24 18:29:23 -06:00
Tim Culverhouse 16f752125f templates: Add tests for funcInclude and funcImport (#4357)
* Update tplcontext.go

Add {{ render "/path/to/file.ext" $data }} via funcRender

* Update tplcontext.go

* Refactor funcInclude, add funcImport to enable {{block}} and {{template}}

* Fix funcImport return of nil showing up in html

* Update godocs for  and

* Add tests for funcInclude

* Add tests for funcImport

* os.RemoveAll -> os.Remove for TestFuncInclude and TestFuncImport
2021-09-20 12:29:37 -06:00
Slavik 0a5f7a677f fileserver: Make file listing links purple once visited (#4356) 2021-09-19 22:01:11 -06:00
HayatoShiba d3a0259944 fileserver: Fix displayed file size if it is symlink (#4354)
* Fix file size if it is symlink

* change the variable name for readability
2021-09-18 05:51:59 -06:00
Tim Culverhouse 5fda9610f9 templates: Add 'import' action (#4321)
Related to (closed) Issue #2094 on template inheritance. This PR adds a new function called "import" which works like "include", except it only takes one argument and passes it to the referenced file to be used as "." in that file.

* Update tplcontext.go

Add {{ render "/path/to/file.ext" $data }} via funcRender

* Update tplcontext.go

* Refactor funcInclude, add funcImport to enable {{block}} and {{template}}

* Fix funcImport return of nil showing up in html

* Update godocs for  and
2021-09-17 13:00:36 -06:00
Francis Lavoie 3f2c3ecf85 fastcgi: Implement try_files override in Caddyfile directive (#4347) 2021-09-17 08:23:06 -06:00
Francis Lavoie 907e2d8d3a caddyhttp: Add support for triggering errors from try_files (#4346)
* caddyhttp: Add support for triggering errors from `try_files`

* caddyhttp: Use vars instead of placeholders/replacer for matcher errors

* caddyhttp: Add comment for matcher error var key
2021-09-17 00:52:32 -06:00
Mohammed Al Sahaf 33c70f418f fileserver: properly handle escaped/non-ascii paths (#4332)
* fileserver: properly handle escaped/non-ascii paths

* fileserver: tests: accommodate Windows hate of colons in files names
2021-09-16 20:40:31 +00:00
Matthew Holt 2ebfda1ae9 Make copyright notice more consistent
Some files had the old copyright or were missing the license comment entirely.

Also change Light Code Labs to Dyanim in security contact and releases.
2021-09-16 12:50:32 -06:00
Matthew Holt 2392478bd3 templates: Propagate httpError to HTTP response
Now possible with Go 1.17.
See https://github.com/golang/go/issues/34201.
2021-09-15 09:55:57 -06:00
Matthew Holt a437206643 headers: Canonicalize case in replace (fix #4330) 2021-09-13 10:13:32 -06:00
Francis Lavoie a779e1b383 fastcgi: Fix Caddyfile parsing when handle_response is used (#4342) 2021-09-11 14:12:21 -06:00
Matthew Holt 46ab93be51 go.mod: Update CertMagic
Adds one more debug log
2021-09-03 11:42:13 -06:00
Mohammed Al Sahaf e0fc46a911 ci: revert workaround implemented in #4306 (#4328) 2021-09-03 10:05:04 -04:00
peymaneh 9f6393c64c cmd: export CaddyVersion(), Commands() (#4316)
* cmd: Export CaddyVersion()

* cmd: Add getter Commands()
2021-09-01 18:08:02 -06:00
Francis Lavoie 105dac8c2a ci: Only test cross-build on latest Go version (#4319)
This generated way too many test jobs, which weren't really that useful. Cross-build is just to keep us posted on which architectures are building okay, so it's not necessary to do it twice. Only plan9 is not working at this point (see https://github.com/caddyserver/caddy/issues/3615)
2021-08-31 13:44:07 -06:00
Steffen Brüheim 4ebf100f09 encode: ignore flushing until after first write (#4318)
* encode: ignore flushing until after first write (fix #4314)

The first write will determine if encoding has to be done and will add an Content-Encoding. Until then Flushing has to be delayed so the Content-Encoding header can be added before headers and status code is written. (A passthrough flush would write header and status code)

* Update modules/caddyhttp/encode/encode.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2021-08-31 13:36:36 -06:00
Matthew Holt f43fd6f388 go.mod: Upgrade CertMagic to v0.14.4
Adds more debug logging
2021-08-30 13:14:42 -06:00
Matthew Holt 84b906a248 go.mod: Upgrade some dependencies 2021-08-26 15:00:25 -06:00
Francis Lavoie 403732c433 httpcaddyfile: Reorder some directives (#4311)
We realized we made some mistakes with the directive ordering, so we're making some minor adjustments.

`abort` and `error` don't really make sense to be after other handler directives, because you would expect to be able to "fail-fast" and throw an error before falling through to some `file_server` or `respond` typically. So we're moving them up to just before `respond`, i.e. before the common handler directives. 

This is also more consistent with our existing examples in the docs, which actually didn't work due to the directive ordering. See https://caddyserver.com/docs/caddyfile/directives/error#examples

Also, `push` doesn't quite make sense to be after `handle`/`route`, since its job is to read from response headers to push additional resources if necessary, and `handle`/`route` may be terminal so push would not be reached if it was declared outside those. And also, it would make sense to be _before_ `templates` because a template _could_ add a `Link` header to the response dynamically.
2021-08-26 14:31:55 -06:00
Francis Lavoie f6d5ec2fd6 chore: Upgrade smallstep libs (#4307)
See https://github.com/smallstep/nosql/issues/12 for context.
2021-08-25 12:16:55 -06:00
Mohammed Al Sahaf 19a55d6aeb chore: promote creating 'caddy-build' to the release action (#4306)
The commit goreleaser/goreleaser@013bd69126 of GoReleaser is now checking the `go version` prior to executing any of the pre-hooks, which involves setting the current dir of the command to the `build.dir` of the build config. At the time of version check, the buil dir does not exist. It's created in the pre-hook. As a workaround, the build-dir is now created in the Github Action prior to executing goreleaser action.
2021-08-25 17:30:24 +00:00
Matthew Holt bfbc459c0a httpcaddyfile: Improve unrecognized directive errors 2021-08-25 10:30:39 -06:00
Francis Lavoie f70a7578fa reverseproxy: Remove redundant flushing (#4299)
From reading through the code, I think this code path is now obsoleted by the changes made in https://github.com/caddyserver/caddy/pull/4266.

Basically, `h.flushInterval()` will set the flush interval to `-1` if we're in a bi-directional stream, and the recent PR ensured that `h.copyResponse()` properly flushes headers immediately when the flush interval is non-zero. So now there should be no need to call Flush before calling `h.copyResponse()`.
2021-08-23 11:54:28 -06:00
Francis Lavoie 51f125bd44 caddyfile: Better error message for missing site block braces (#4301)
Some new users mistakenly try to define two sites without braces around each. Doing this can yield a confusing error message saying that their site address is an "unknown directive".

We can do better by keeping track of whether the current site block was parsed with or without a brace, then changing the error message later based on that.

For example, now this invalid config:

```
foo.example.com
respond "foo"

bar.example.com
respond "bar"
```

Will yield this error message:

```
$ caddy adapt
2021/08/22 19:21:31.028 INFO    using adjacent Caddyfile
adapt: Caddyfile:4: unrecognized directive: bar.example.com
Did you mean to define a second site? If so, you must use curly braces around each site to separate their configurations.
```
2021-08-23 11:53:27 -06:00
Francis Lavoie d74913f871 caddyfile: Error on invalid site addresses containing comma (#4302)
Some users forget to use a comma between their site addresses. This is invalid (commas aren't a valid character in domains) and later parts of the code like certificate automation will try to use this otherwise, which doesn't make sense. Best to error as early as possible.

Example thread on the forums where this happened: https://caddy.community/t/simplify-caddyfile/13281/9
2021-08-23 11:26:07 -06:00
Pascal Zarrad ce5a45db45 cmd: Fix paths when using an env file (#4296)
* core: Fix paths when using an env file

* refactor: move path logic to loadFromEnv
2021-08-20 15:51:31 -06:00
Adam Weinberger e0a6a1efff chore: Update quic-go for go 1.17 support (#4297)
* Update quic-go for go 1.17 support

* Complete quic-go update (go mod tidy)
2021-08-20 10:19:16 -06:00
Scott Mebberson c1cd192ee7 caddyhttp: Updated the documentation for MatchQuery (#4295) 2021-08-19 22:44:28 -06:00
Francis Lavoie a056fcd7ba chore: Upgrade smallstep libs (#4291)
See https://github.com/smallstep/nosql/issues/12 for context.
2021-08-19 16:08:19 -06:00
M. Ángel Jimeno 9e333c39da cmd: use net.ErrClosed for matching returned error (#4289)
Implements #3805
2021-08-18 12:58:19 -06:00
Matthew Holt 8a974a4f8f logging: Warn for deprecated single_field encoder 2021-08-17 10:51:26 -06:00
Francis Lavoie 6bc87ea2ff ci: Start testing on Go 1.17, drop 1.15 (#4283) 2021-08-16 21:56:20 -06:00
Rainer Borene 1b1e625c20 core: Unix ns and Unix ms time placeholders (#4280) 2021-08-16 15:06:44 -06:00
Steven Angles a10910f398 admin: Sync server variables (fix #4260) (#4274)
* Synchronize server assignment/references to avoid data race

* only hold lock during var reassignment
2021-08-16 15:04:47 -06:00
Francis Lavoie ab32440b21 httpcaddyfile: Add shortcut for proxy hostport placeholder (#4263)
* httpcaddyfile: Add shortcut for proxy hostport placeholder

I've noticed that it's a pretty common pattern to write a proxy like this, when needing to proxy over HTTPS:

```
reverse_proxy https://example.com {
	header_up Host {http.reverse_proxy.upstream.hostport}
}
```

I find it pretty hard to remember the exact placeholder to use for this, and I continually need to refer to the docs when I need it. I think a simple fix for this is to add another Caddyfile placeholder for this one to shorten it:

```
reverse_proxy https://example.com {
	header_up Host {proxy_hostport}
}
```

* Switch the shortcut name
2021-08-12 12:08:37 -06:00
Francis Lavoie e6c29ce081 reverseproxy: Incorporate latest proxy changes from stdlib (#4266)
I went through the commits that touched stdlib's `reverseproxy.go` file, and copied over all the changes that are to code that was copied into Caddy.

The commits I pulled changes from:

- https://github.com/golang/go/commit/2cc347382f4df3fb40d8d81ec9331f0748b1c394
- https://github.com/golang/go/commit/a5cea062b305c8502bdc959c0eec279dbcd4391f
- https://github.com/golang/go/commit/ecdbffd4ec68b509998792f120868fec319de59b
- https://github.com/golang/go/commit/21898524f66c075d7cfb64a38f17684140e57675
-https://github.com/golang/go/commit/ca3c0df1f8e07337ba4048b191bf905118ebe251
- https://github.com/golang/go/commit/9c017ff30dd21bbdcdb11f39458d3944db530d7e

This may also fix https://github.com/caddyserver/caddy/issues/4247 because of the change to `copyResponse` to set `mlw.flushPending = true` right away.
2021-08-12 10:48:24 -06:00
Oleg 68c5c71659 cmd: New add-package and remove-package commands (#4226)
* adding package command

* add-package command name

* refactoring duplicate code

* fixed by review

* fixed by review

* remove-package command

* commands in different files, common utils

* fix add, remove, upgrade packages in 1 file

* copyright and downloadPath moved

* refactor

* downloadPath do no export

* adding/removing multiple packages

* addPackages/removePackages, comments, command-desc

* add-package, process case len(args) == 0

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2021-08-11 17:31:41 -06:00
Frederik Ring 569ecdbd02 httpcaddyfile: Ensure hosts to skip for logs can always be collected (#4258)
* httpcaddyfile: ensure hosts to skip can always be collected

Previously, some hosts that should be skipped in logging would
be missed as the current logic would only collect them after
encountering the first server that would log. This change makes sure
the ServerLogConfig is initialized before iterating over the server
blocks.

* httpcaddyfile: add test case for skip hosts behavior
2021-08-02 14:15:27 -06:00
王清雨 c131339c5c admin: Implement load_interval to pull config on a timer (#4246)
* feat: implement a simple timer to pull config

mostly referenced to the issue

re #4106

* Update admin.go

use `caddy.Duration`

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

* Update caddy.go

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

* Update admin.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* fix: sync load config when no pull interval provided

try not to make break change

* fix: change PullInterval to LoadInterval

* fix: change pull_interval to load_interval

* Update caddy.go

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2021-07-28 15:39:08 -06:00
Ggicci b6f51254ea caddyfile: keep error chain info in Dispenser.Errf (#4233)
* caddyfile: Errf enable error chain unwrapping

* refactor: remove parseError
2021-07-19 08:35:14 -06:00
Francis Lavoie 124ba1ba71 logging: Prep for common_log removal (#4149)
See https://github.com/caddyserver/caddy/issues/4148#issuecomment-833207811
2021-07-14 11:07:38 -06:00
Francis Lavoie 1c6c7714a3 caddyhttp: Fix edgecase with auto HTTP->HTTPS logic (#4243) 2021-07-14 10:49:34 -06:00
Leo Di Donato 46d99aba85 logging: Add missing interface guards for replace filter (#4244)
Signed-off-by: Leonardo Di Donato <leodidonato@gmail.com>
2021-07-12 11:13:01 -04:00
diamondburned 9e16e80f3c fileserver: Fix browse name_dir_first sorting (#4218)
This commit fixes the `sortByNameDirFirst` variable inside fileserver to
match what browse's default template has.

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2021-07-07 02:54:54 -04:00
elnoro d882211080 reverseproxy: Keep path to unix socket as dial address (#4232) 2021-07-06 23:43:45 -04:00
hmol233 42e140b1b2 caddyhttp: Fix incorrect determination of gRPC protocol (#4236) 2021-07-06 12:09:44 -04:00
mritd 4245ceb67d fileserver: Add disable_canonical_uris Caddyfile subdirective (#4222)
* feat(fileserver): add 'canonical_uris' parameter to caddyfile

add 'canonical_uris' parameter to caddyfile

reference #2741

Signed-off-by: mritd <mritd@linux.com>

* feat(file_server): rename subdirective canonical_uris to disable_canonical_uris

rename subdirective canonical_uris to disable_canonical_uris

Signed-off-by: mritd <mritd@linux.com>

* test(caddyfile_adapt): add disable_canonical_uris subdirective test file

add disable_canonical_uris subdirective test file

Signed-off-by: mritd <mritd@linux.com>
2021-07-01 17:22:16 -06:00
Matthew Holt 0bdb8aa82d acmeserver: Don't set host for directory links by default
This makes the server more easily proxied.
2021-07-01 17:20:51 -06:00
Matthew Holt 191dc86f9e fileserver: Clarify docs about canonicalization
Related to https://github.com/caddyserver/caddy/issues/4205.
2021-06-25 11:33:18 -06:00
Matthew Holt 81e5318021 caddytls: Remove "IssuerRaw" field
Has been deprecated and printing warnings for about 8 months now.
Replaced by "IssuersRaw" field in v2.3.0.
2021-06-25 11:29:56 -06:00
Matthew Holt b3d35a4995 httpcaddyfile: Don't put localhost in public APs (fix #4220)
If an email is specified in global options, a site called 'localhost' shouldn't be bunched together with public DNS names in the automation policies, which get the default, public-CA issuers. Fix old test that did this.

I also noticed that these two:

    localhost {
    }
    example.com {
    }

and

    localhost, example.com {
    }

produce slightly different TLS automation policies. The former is what the new test case covers, and we have logic that removes the empty automation policy for localhost so that auto-HTTPS can implicitly create one. (We prefer that whenever possible.) But the latter case produces two automation policies, with the second one being for localhost, with an explicit internal issuer. It's not wrong, just more explicit than it needs to be.

I'd really like to completely rewrite the code from scratch that generates automation policies, hopefully there is a simpler, more correct algorithm.
2021-06-25 11:28:32 -06:00
Matthew Holt 2de7e14e1c acmeserver: Trim slashes from path prefix
See https://caddy.community/t/mtls-tls-internal-error/12807
2021-06-21 11:56:41 -06:00
Matthew Holt 885a9aaf48 go.mod: Update dependencies (close #4216) 2021-06-18 12:02:47 -06:00
Klaus Post 69c914483d encode: Tweak compression settings (#4215)
* Tweak compression settings

zstd: Limit window sizes to 128K to keep memory in control both server and client size.
zstd: Write 0 length frames. This may be needed for compatibility.
zstd: Create fewer encoders. Small memory improvement.
gzip: Allow -2 (Huffman only) and -3 (stateless) compression modes.

* Update modules/caddyhttp/encode/zstd/zstd.go

Update docs.

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2021-06-18 11:49:49 -06:00
Matt Holt 9d4ed3a323 caddyhttp: Refactor and export SanitizedPathJoin for use in fastcgi (#4207) 2021-06-17 09:59:08 -06:00
Matthew Holt fbd6560976 fileserver: Only redirect if filename not rewritten (fix #4205)
This is the more correct implementation of  23dadc0d86 (#4179)... I think. This commit effectively undoes the revert in 8848df9c5d, but with corrections to the logic.

We *do* need to use the original request path (the path the browser knows) for redirects, since they are external, and rewrites are only internal.

However, if the path was rewritten to a non-canonical path, we should not redirect to canonicalize that, since rewrites are intentional by the site owner. Canonicalizing the path involves modifying only the suffix (base element, or filename) of the path. Thus, if a rewrite involves only the prefix (like how handle_path strips a path prefix), then we can (hopefully!) safely redirect using the original URI since the filename was not rewritten.

So basically, if rewrites modify the filename, we should not canonicalize those requests. If rewrites only modify another part of the path (commonly a prefix), we should be OK to redirect.
2021-06-17 09:55:49 -06:00
Matthew Holt 238914d70b Some misc. cleanup
The fastcgi changes came from v1 which don't make sense in v2.

Fix comment about default value in reverse proxy keep alive.
2021-06-16 14:29:42 -06:00
Matthew Holt e8ae80adca fileserver: Don't persist parsed template (fix #4202)
Templates are parsed at request-time (like they are in the templates middleware) to allow live changes to the template while the server is running. Fixes race condition.

Also refactored use of a buffer so a buffer put back in the pool will not continue to be used (written to client) in the meantime.

A couple of benchmarks removed due to refactor, which is fine, since we know pooling helps here.
2021-06-16 14:28:34 -06:00
Matthew Holt 32c284b54a reverseproxy: Adjust test related to #4201
Commit 7c68809f4e
2021-06-15 15:02:22 -06:00
Matthew Holt 7c68809f4e reverseproxy: Fix overwriting of max_idle_conns_per_host (closes #4201)
Also split the Caddyfile subdirective keepalive_idle_conns into two properties so the conns and conns_per_host can be set separately.

This is technically a breaking change, but probably anyone who this breaks already had a broken config anyway, and silently fixing it won't help them fix their configs.
2021-06-15 14:54:48 -06:00
Matthew Holt 6d25261c22 Expand and clarify security policy
While the Caddy project has had very few valid security bug reports over the years, we have a low signal-to-noise ratio with them (lots of invalid reports). Most are out of scope, and it can take too much valuable time for us to determine that. We would prefer researchers do this first. Hopefully these paragraphs spell out much more clearly what we do and don't accept.
2021-06-14 14:00:43 -06:00
Matthew Holt 8848df9c5d Revert "fileserver: Redirect within the original URL (#4179)"
This reverts commit f9b54454a1.
/cc @diamondburned (see #4205)
2021-06-14 09:04:30 -06:00
Matt Holt 89aa3a5ef3 go.mod: Use CertMagic v0.14.0 (fix #4191)
* Force auto-renew for OCSP revoked status (maybe) (fix #4191)

* Use latest commit

* go.mod: Use CertMagic v0.14.0 (fix #4191)

Correctly replaces revoked certificates
2021-06-12 14:44:32 -06:00
Matthew Holt 05656a60b3 httpcaddyfile: Don't add HTTP hosts to TLS APs (fix #4176 and fix #4198)
In the Caddyfile, hosts specified for HTTP sockets (either scheme is "http" or it is on the HTTP port) should not be used as subjects in TLS automation policies (APs).
2021-06-09 14:35:09 -06:00
Klooven 1e92258dd6 httpcaddyfile: Add preferred_chains global option and issuer subdirective (#4192)
* Added preferred_chains option to Caddyfile

* Caddyfile adapt tests for preferred_chains
2021-06-08 14:10:37 -06:00
diamondburned 76913b19ff fileserver: Fix browse not redirecting query parameters (#4196)
This commit is a follow up to PR #4179 that introduced a bug where
browse redirections to the right URL would not preserve query
parameters.
2021-06-07 17:33:54 -06:00
Peter Magnusson 4c2da18841 caddytls: Add Caddyfile support for propagation_timeout (#4178)
* add propagation_timeout to UnmarshalCaddyfile

- Closes #4177

* added caddyfile_adapt test
2021-06-07 12:25:12 -06:00
diamondburned f9b54454a1 fileserver: Redirect within the original URL (#4179)
This commit changes the file_server directive to redirect using the
original request's URL instead of the possibly trimmed URL. This should
make file_server work with handle_path.

This fix is taken from mholt's comment in
https://caddy.community/t/file-servers-on-different-paths-not-working/11698/11.
2021-06-07 12:20:08 -06:00
Francis Lavoie 658772ff24 httpcaddyfile: Add skip_install_trust global option (#4153)
Fixes https://github.com/caddyserver/caddy/issues/4002
2021-06-07 12:18:49 -06:00
Matthew Holt 323ffd2076 admin: Replace admin cert cache when reloading (fix #4184) 2021-06-05 11:47:44 -06:00
Matthew Holt 2a8109468c reverseproxy: Always remove hop-by-hop headers
See golang/go#46313

Based on https://github.com/golang/go/commit/950fa11c4cb01a145bb07eeb167d90a1846061b3
2021-06-04 15:21:16 -06:00
Francis Lavoie 94b712009a logging: Actually use level_key (#4189) 2021-06-04 14:15:43 -06:00
Dave Henderson 7b500e74b4 metrics: use buildinfo collector from new collectors pkg (#4187)
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2021-06-04 00:19:16 -04:00
Matthew Holt ecd5eeab38 go.mod: Update direct dependencies 2021-06-03 12:18:25 -06:00
Matt Holt b4cef492cc Update .goreleaser.yml
Ubuntu's package updater doesn't show the name of the package, so just adding "Caddy" to the description helps a lot
2021-05-24 16:21:53 -06:00
Matt Holt e3c369d452 logging: Implement dial timeout for net writer (fix #4083) (#4172)
* logging: Implement dial timeout for net writer (fix #4083)

* Limit how often redials are attempted

This should cause dial blocking to occur only once every 10 seconds at most, but it also means the logger connection might be down for up to 10 seconds after it comes back online; oh well. We shouldn't block for DialTimeout at every single log emission.

* Clarify offline behavior
2021-05-19 15:14:03 -06:00
Matthew Holt c052162203 Merge branch '2.4' 2021-05-19 10:47:06 -06:00
Matthew Holt 7f26a6b3e5 admin: Reinstate internal redirect for /id/ requests
Fix regression from ab80ff4fd2 (probably a mistake when rebasing)

See https://caddy.community/t/id-selector-is-not-working-after-upgrade-to-2-4-0/12513?u=matt
2021-05-19 10:27:25 -06:00
Francis Lavoie b82db994f3 caddyfile: Add parse error on site address with trailing { (#4163)
* caddyfile: Add parse error on site address in `{`

This is an incredibly common mistake made by users, so we should catch it earlier in the parser and give a more friendly message. Often it ends up adapting but with mistakes, or erroring out later due to other site addresses being read as directives.

There's not really ever a situation where a lone '{' is valid at the end of a site address (but I suppose there are edgecases where the user wants to use a path matcher where it ends specifically in `{`, but... why?), so this should be fine.

* Update caddyconfig/caddyfile/parse.go
2021-05-12 16:18:44 -06:00
Francis Lavoie aef8d4decc reverseproxy: Set the headers in the replacer before handle_response (#4165)
Turns out this was an oversight, we assumed we could use `{http.response.header.*}` but that doesn't work because those are grabbed from the response writer, and we haven't copied any headers into the response writer yet.

So the fix is to set all the response headers into the replacer at a new namespace before running the handlers.

This adds the `{http.reverse_proxy.header.*}` replacer.

See https://caddy.community/t/empty-http-response-header-x-accel-redirect/12447
2021-05-12 14:19:08 -06:00
Francis Lavoie 37718560c1 ci: Run CI on PRs targeting minor version branches (#4164)
We decided that we'll use branches like `2.4` as the target for any changes that we might want to release in a `2.4.x` version like `2.4.1`, so that we can continue to merge changes targeting the next minor release (e.g. `2.5.0`) on master.

Our CI config wasn't set up for this to work properly though, since it was only running checks on PRs targeting master. This should fix it.

I couldn't find a way to do a pattern to only match digits for the branch names from Github's docs, it just looks like a pretty generic glob syntax. But this should do until we get to 3.0
2021-05-12 00:26:16 -04:00
Mohammed Al Sahaf 2aefe15686 cmd: upgrade: inherit the permissions of the original executable (#4160) 2021-05-11 16:11:27 -06:00
Matthew Holt dbe164d98a httpcaddyfile: Fix automation policy consolidation again (fix #4161)
Also fix a previous test that asserted incorrect behavior.
2021-05-11 15:26:07 -06:00
Matthew Penner bc22102478 caddyfile: Fix caddy fmt nesting not decrementing (#4157)
* caddyfile(formatter): fix nesting not decrementing

This is an extremely weird edge-case where if you had a environment variable {}
on one line, a comment on the next line, and the closing of the block on the
following line; the rest of the Caddyfile would be indented further than it
should've been.

ref; https://github.com/matthewpi/vscode-caddyfile-support/issues/13

* run gofmt

* fmt: better way of handling edge case
2021-05-10 12:01:27 -06:00
Francis Lavoie f5db41ce1d encode: Drop prefer from Caddyfile (#4156)
Followup to #4150, #4151 /cc @ueffel @polarathene

After a bit of discussion with @mholt, we decided to remove `prefer` as a subdirective and just go with using the order implicitly always. Simpler config, simpler docs, etc.

Effectively changes 7776471 and reverts a small part of f35a7fa.
2021-05-10 11:12:59 -06:00
Francis Lavoie 77764714ad encode: Default to order the formats are enabled for prefer in Caddyfile (#4151) 2021-05-10 10:06:38 -06:00
Francis Lavoie 61642b766b caddytls: Run replacer on ask URL, for env vars (#4154)
Fixes #3922
2021-05-08 22:37:27 -06:00
Francis Lavoie 3cf443f0fe httpcaddyfile: Add grace_period global option (#4152)
See https://caddyserver.com/docs/json/apps/http/#grace_period
2021-05-07 16:18:17 -06:00
Francis Lavoie d4b2f1bcee caddyhttp: Fix fallback for the error handler chain (#4131)
* caddyhttp: Fix fallback for the error handler chain

The fix I went with in the end (after realizing some mistaken assumptions in #4131) is to just make the routes fall back to errorEmptyHandler instead of the non-error empty handler, if Terminal is true, making the routes error-aware. Ultimately this was probably just an oversight when errors was implemented at some point in the early betas of v2.

See https://caddy.community/t/problem-with-basicauth-handle-errors/12243/9 for context.

* Revert "caddyhttp: Fix fallback for the error handler chain"

This reverts commit 95b6ac44a6.

* caddyhttp: Fix via `routes.go`
2021-05-05 15:55:40 -06:00
Matthew Holt a17c3b568d reverseproxy: Minor logging improvements 2021-05-05 14:52:24 -06:00
Francis Lavoie 74f5d66c48 fileserver: Fix file matcher with empty try_files (#4147)
* fileserver: Fix `file` matcher with empty `try_files`

Fixes https://github.com/caddyserver/caddy/issues/4146

If `TryFiles` is empty, we fill it with `r.URL.Path`. In this case, this is `/`. Then later, in `prepareFilePath()`, we run the replacer (which turns `{path}` into `/` at that point) but `file` remains the original value (and the placeholder is still the placeholder there).

So then `strings.HasSuffix(file, "/")` will be `false` for the placeholder, but `true` for the empty `TryFiles` codepath, because `file` was `/` due to being set to the actual request value beforehand.

This means that `suffix` becomes `//` in that case, so after `sanitizedPathJoin`, it becomes `./`, so `strictFileExists`'s `strings.HasSuffix(file, separator)` codepath will return true.

I think we should change the `m.TryFiles == nil` codepath to `m.TryFiles = []string{"{http.request.uri.path}"}` for consistency. (And maybe consider hoisting this to `Provision` cause there's no point doing this on every request). I don't think this "optimization" of directly using `r.URL.Path` is so valuable, cause it causes this edgecase with directories.

* Update modules/caddyhttp/fileserver/matcher.go

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2021-05-04 09:49:13 -06:00
Matthew Holt efe84497d7 go.mod: CertMagic v0.13.1 2021-05-03 11:10:18 -06:00
Francis Lavoie e4a22de9d1 reverseproxy: Add handle_response blocks to reverse_proxy (#3710) (#4021)
* reverseproxy: Add `handle_response` blocks to `reverse_proxy` (#3710)

* reverseproxy: complete handle_response test

* reverseproxy: Change handle_response matchers to use named matchers

reverseproxy: Add support for changing status code

* fastcgi: Remove obsolete TODO

We already have d.Err("transport already specified") in the reverse_proxy parsing code which covers this case

* reverseproxy: Fix support for "4xx" type status codes

* Apply suggestions from code review

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

* caddyhttp: Reorganize response matchers

* reverseproxy: Reintroduce caddyfile.Unmarshaler

* reverseproxy: Add comment mentioning Finalize should be called

Co-authored-by: Maxime Soulé <btik-git@scoubidou.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2021-05-02 12:39:06 -06:00
Jesse e6f6d3a476 cmd: Add --envfile flag to start command (#4141)
* add envfile in start cmd

* fix commandfuncs

* fix commandfuncs

* fix cmdStart envfile from bool to string

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2021-05-02 12:38:16 -06:00
Francis Lavoie ef7f15f3a4 httpcaddyfile: Add auto_https ignore_loaded_certs (#4077) 2021-05-02 12:11:27 -06:00
Francis Lavoie 6e0e3e1537 httpcaddyfile: Add global option for storage_clean_interval (#4134)
Followup to https://github.com/caddyserver/caddy/commit/42b7134ffa3bf3e9e86514c82407979c2627a5ab
2021-05-02 11:57:28 -06:00
Calvin Xiao 53ececda21 caddyhttp: performance improvement in HeaderRE Matcher (#4143)
Below is the report using `benchstat` and cmd:

`go test -run=BenchmarkHeaderREMatcher -bench=BenchmarkHeaderREMatcher -benchmem -count=10`

```
name                old time/op    new time/op    delta
HeaderREMatcher-16     869ns ± 1%     658ns ± 0%  -24.29%  (p=0.000 n=10+10)

name                old alloc/op   new alloc/op   delta
HeaderREMatcher-16      144B ± 0%      112B ± 0%  -22.22%  (p=0.000 n=10+10)

name                old allocs/op  new allocs/op  delta
HeaderREMatcher-16      7.00 ± 0%      5.00 ± 0%  -28.57%  (p=0.000 n=10+10)
```
2021-05-02 10:35:28 -06:00
Jason Du 637fd8f67b fileserver: Share template logic for both templates and file_server browse (#4093)
Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2021-04-30 22:17:23 -04:00
Matt Holt 956f01163d caddytls: Implement remote IP connection matcher (#4123)
* caddytls: Implement remote IP connection matcher

* Implement IP range negation

If both Ranges and NotRanges are specified, both must match.
2021-04-30 10:14:52 -06:00
Alban Lecocq ff6ca577ec httpcaddyfile: Fix unexpectedly removed policy (#4128)
* httpcaddyfile: Fix unexpectedly removed policy

When user set on_demand tls option in a catch-all (:443) policy,
we expect other policies to not have the on_demand enabled
See ex in tls_automation_policies_5.txt

Btw, we can remove policies if they are **all** empty.

* Update caddyconfig/httpcaddyfile/tlsapp.go

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2021-04-29 10:56:01 -06:00
Simão Gomes Viana 9017557169 reverseproxy: fix hash selection policy (#4137)
* caddyhttp: reverseproxy: fix hash selection policy

Fixes: #4135
Test: go test './...' -count=1

* caddyhttp: reverseproxy: add test to catch #4135

If you revert the last commit, the test will fail.
2021-04-29 10:52:22 -06:00
Francis Lavoie 3a1e81dbf6 fileserver: Better handling of HTTP status override (#4132) 2021-04-29 02:01:48 -04:00
Francis Lavoie a8d45277ca caddyfile: Fix import replacing unrelated placeholders (#4129)
* caddyfile: Fix `import` replacing unrelated placeholders

See https://caddy.community/t/snippet-issue-works-outside-snippet/12231

So it turns out that `NewReplacer()` gives a replacer with some global defaults (like `{env.*}` and some system and time placeholders), which is not ideal when running `import` because we just want to replace `{args.*}` only, and nothing else.

* caddyfile: Add test
2021-04-22 18:29:04 -06:00
Francis Lavoie 1e218e1d2e caddytls: Add load_storage module (#4055)
An idea that came up in https://caddy.community/t/save-internally-issued-wildcard-certificate-in-consul/11740, this a simple module that might be useful for anyone who uses storage modules that aren't filesystem, to let them load certs/keys externally issued for use by Caddy.

Bit goofy, since we need to fetch the certmagic.Storage during provisioning, it needs a wrapping struct instead of just being an array like `load_files`.

Future work might involve adding Caddyfile support via a subdirective of the `tls` directive maybe?
2021-04-21 17:05:55 -06:00
Francis Lavoie 4d0474e3b8 reverseproxy: Admin endpoint for reporting upstream statuses (#4125) 2021-04-21 13:43:34 -06:00
Francis Lavoie d789596bc0 caddyhttp: Implement better logic for inserting the HTTP->HTTPS redirs (#4033)
* caddyhttp: Implement better logic for inserting the HTTP->HTTPS redirs

* caddyhttp: Add integration test
2021-04-19 19:54:12 -06:00
Matthew Holt 96bb365929 httpcaddyfile: Take into account host scheme/port (fix #4113) 2021-04-16 11:17:22 -06:00
Mohammed Al Sahaf 00e12aa918 fuzz: fix the FuzzFormat comparison (#4117) 2021-04-15 23:03:54 +00:00
Matthew Holt 2250920e1d caddytls: Disable OCSP stapling for manual certs (#4064) 2021-04-12 16:09:02 -06:00
Matthew Holt 42b7134ffa caddytls: Configurable storage clean interval
Can drastically reduce costs on storage backends where scans are expensive.

Also reduced default interval to 24h.

See https://github.com/silinternational/certmagic-storage-dynamodb/issues/18
2021-04-12 15:41:22 -06:00
Mohammed Al Sahaf 3903642aa7 caddyfile: reject cyclic imports (#4022)
* caddyfile: reject recursive self-imports

* caddyfile: detect and reject cyclic imports of snippets and files

* caddyfile: do not be stickler about connected nodes not being connected already

* caddyfile: include missing test artifacts of cyclic imports

* address review comments
2021-04-09 12:06:25 -06:00
Mohammed Al Sahaf 03b5debd95 ci: fuzz: add 4 more fuzzing targets (#4105) 2021-04-08 11:45:19 -06:00
Francis Lavoie 3f6283b385 fileserver: Add status code override (#4076)
After reading a question about the `handle_response` feature of `reverse_proxy`, I realized that we didn't have a way of serving an arbitrary file with a status code other than 200. This is an issue in situations where you want to serve a custom error page in routes that are not errors, like the aforementioned `handle_response`, where you may want to retain the status code returned by the proxy but write a response with content from a file.

This feature is super simple, basically if a status code is configured (can be a status code number, or a placeholder string) then that status will be written out before serving the file - if we write the status code first, then the stdlib won't write its own (only the first HTTP status header wins).
2021-04-08 11:09:12 -06:00
Carl George 45fb7202ac notify: Send all sd_notify signals from main caddy process (#4060)
Initial sd_notify support was added in #3963, but that sent signals from
both cmdRun and cmdReload.  This approach has two drawbacks:

- Reloads initiated via the API do not send signals.
- The signals are sent from different processes, which requires the
  `NotifyAccess=exec` directive in the unit file.

This change moves the NotifyReloading and NotifyReadiness invocations to
Load, which address both of those drawbacks.  It also adds a
complimentary NotifyStopping method which is invoked from handleStop.
All the notify methods are defined in a notify package to avoid an
import loop.
2021-04-05 14:01:20 -06:00
Marten Seemann 66783eb4d9 go.mod: Update quic-go to v0.20.1 (#4075) 2021-04-05 13:09:30 -06:00
Francis Lavoie 1455d6bb69 httpcaddyfile: Fix panic in automation policy consolidation (#4104)
* httpcaddyfile: Add reproduce test

* httpcaddyfile: Don't allow `i` to go below zero
2021-04-02 16:47:04 -06:00
Francis Lavoie 3401f91dbe caddyfile: Normalize line endings before comparing fmt result (#4103) 2021-04-02 11:55:34 -06:00
Mohammed Al Sahaf eb3955a960 ci: accommodate go1.16 changes to go mod (#4102)
As of go1.16, the `go` commands will no longer make automatic changes to go.{mod,sum} files (see: https://blog.golang.org/go116-module-changes). This broke the release script which relied on `go mod download` and/or `go build` to automatically generate the go.sum file. This commit explicitly invokes `go mod tidy` to have the go.sum file generated.
2021-04-01 16:07:32 -06:00
Matthew Holt d21e88ae3a Minor tweaks 2021-04-01 12:49:51 -06:00
Matthew Holt a0a7c60cb9 go.mod: Use latest CertMagic 2021-04-01 12:48:59 -06:00
Matthew Holt 7da9241fd7 Use 600 instead of 644 for UUID file
Are you happy, linter?
2021-04-01 12:12:28 -06:00
Matthew Holt e68dbe9cf8 Change os to ioutil for now
My editor automatically changed ioutil.ReadFile() to os.ReadFile() in accordance
with Go 1.16 changes. I didn't notice this until pushing.

But we still have to support Go 1.15 for a little while.
2021-03-30 15:42:04 -06:00
Dimitri Masson bd357bf005 reverseproxy: Set cookie path to / when using cookie lb_policy (#4096) 2021-03-30 15:29:00 -06:00
Matthew Holt aac1ccf12d caddy: Add InstanceID() method
Caddy can now generate and persist its own instance ID, a UUID that is stored in
the data directory.

This makes it possible to differentiate it from other instances in a cluster.
2021-03-30 14:15:20 -06:00
Steffen Brüheim f35a7fa466 encode,staticfiles: Content negotiation, precompressed files (#4045)
* encode: implement prefer setting

* encode: minimum_length configurable via caddyfile

* encode: configurable content-types which to encode

* file_server: support precompressed files

* encode: use ReponseMatcher for conditional encoding of content

* linting error & documentation of encode.PrecompressedOrder

* encode: allow just one response matcher

also change the namespace of the encoders back, I accidently changed to precompressed >.>
default matchers include a *  to match to any charset, that may be appended

* rounding of the PR

* added integration tests for new caddyfile directives
* improved various doc strings (punctuation and typos)
* added json tag for file_server precompress order and encode matcher

* file_server: add vary header, remove accept-ranges when serving precompressed files

* encode: move Suffix implementation to precompressed modules
2021-03-29 18:47:19 -06:00
Francis Lavoie 75f797debd reverseproxy: Implement health_uri, deprecate health_path, supports query (#4050)
* reverseproxy: Implement health_uri, replaces health_path, supports query

Also fixes a bug with `health_status` Caddyfile parsing , it would always only take the first character of the status code even if it didn't end with "xx".

* reverseproxy: Rename to URI, named logger, warn in Provision (for JSON)
2021-03-29 18:36:40 -06:00
Simão Gomes Viana 1c8ea00828 go.mod: Migrate to golang.org/x/term (#4073)
golang.org/x/crypto/ssh/terminal is deprecated in favor of golang.org/x/term

See https://github.com/caddyserver/caddy/pull/4073/checks?check_run_id=2152150495
Error: SA1019: package golang.org/x/crypto/ssh/terminal is deprecated: this package moved to golang.org/x/term.  (staticcheck)

See https://github.com/caddyserver/caddy/pull/4073/checks?check_run_id=2152228516
Error: SA1019: package golang.org/x/crypto/ssh/terminal is deprecated: this package moved to golang.org/x/term.  (staticcheck)

Test: go test -count=1 './...'
2021-03-29 12:39:08 -06:00
Simão Gomes Viana d63d5ae1ce caddyhttp: improve grammar of comment for AllowH2C (#4072) 2021-03-29 12:04:25 -06:00
Simão Gomes Viana a6bc58153b sigtrap_posix: add missing comma to SIGTERM info (#4078)
Was missing a comma, so added it
2021-03-29 11:04:22 -06:00
Francis Lavoie 911c8a371a cmd: Use formatted logger for config adapter warnings (#4080) 2021-03-29 11:03:50 -06:00
Simão Gomes Viana 87fbc0783a cmd: main: fix minor doc typos (#4082)
Fixed typos in the documentation
2021-03-29 11:02:21 -06:00
Francis Lavoie f1c36680fc headers: Fix Caddyfile parsing for request_header with matchers (#4085) 2021-03-29 10:55:29 -06:00
Simão Gomes Viana a87f757fcc .gitignore: add IDE files (#4087)
These tend to clutter up repositories.
2021-03-29 10:54:38 -06:00
Francis Lavoie 0018b9be0d fileserver: Add a few more debug lines (#4063) 2021-03-19 11:42:26 -06:00
rai a48c6205b7 fileserver: Browse listing supports dark mode (#4066)
* Add dark color scheme media query

* Theme search box, make everything less contrasting

* Further contrast tweaks
2021-03-19 11:41:02 -06:00
Simão Gomes Viana 28a4159933 CONTRIBUTING: fix spelling (#4070)
Minor spelling fixes to make this document even better
2021-03-19 07:37:43 -04:00
Francis Lavoie 0d7fe36007 httpcaddyfile: Add error directive for the existing handler (#4034)
* httpcaddyfile: Add `error` directive for the existing handler

* httpcaddyfile: Move `error` to the end of the order
2021-03-12 13:25:49 -07:00
Aaron Taylor f137b82227 logging: add replace filter for static value replacement (#4029)
This filter is intended to be useful in scenarios where you may want to
redact a value with a static string, giving you information that the
field did previously exist and was present, but not revealing the value
itself in the logs.

This was inspired by work on adding more complete support for removing
sensitive values from logs [1]. An example use case would be the
Authorization header in request log output, for which the value should
usually not be logged, but it may be quite useful for debugging to
confirm that the header was present in the request.

[1] https://github.com/caddyserver/caddy/issues/3958
2021-03-12 13:01:34 -07:00
Aaron Taylor 2a127ac3d1 caddyconfig: add global option for configuring loggers (#4028)
This change is aimed at enhancing the logging module within the
Caddyfile directive to allow users to configure logs other than the HTTP
access log stream, which is the current capability of the Caddyfile [1].
The intent here is to leverage the same syntax as the server log
directive at a global level, so that similar customizations can be added
without needing to resort to a JSON-based configuration.

Discussion for this approach happened in the referenced issue.

Closes https://github.com/caddyserver/caddy/issues/3958

[1] https://caddyserver.com/docs/caddyfile/directives/log
2021-03-12 13:00:02 -07:00
Rajat Jain 802f80c382 map: Accept regex substitution in outputs (#3991)
* Replace placeholders with regex groups

* using Matcher methods

* test added

* linting fix

* Revert "linting fix"

This reverts commit cafd7296f4.

* Revert "test added"

This reverts commit 3a76cc7b0b.

* Revert "using Matcher methods"

This reverts commit cc34337b8e.

* tests added
2021-03-10 14:22:33 -07:00
Francis Lavoie 51f35ba03f reverseproxy: Fix upstreams with placeholders with no port (#4046) 2021-03-03 10:12:31 -07:00
Matthew Holt ad8d01cb66 rewrite: Implement regex path replacements
https://caddy.community/t/collapsing-multiple-forward-slashes-in-path-only/11626
2021-03-01 18:27:59 -07:00
Matthew Holt 5bf0a55df4 fileserver: Don't replace in request paths (fix #4027) 2021-03-01 13:49:13 -07:00
Matthew Holt ec309c6d52 caddypki: Add SignWithRoot option for ACME server
See https://caddy.community/t/setting-up-a-caddy-pki-based-on-a-windows-
root-ca-was-getting-pki-config/11616/7

Also improved a godoc comment in the caddytls package.
2021-02-26 19:27:58 -07:00
Matthew Holt ce5a0934a8 reverseproxy: Fix round robin data race (#4038) 2021-02-25 09:41:52 -07:00
Matthew Holt b54fa41239 Update docs; commit setcap.sh 2021-02-24 11:55:56 -07:00
Matthew Holt 427bbe99d0 go.mod: Latest CertMagic (updated libdns conventions) 2021-02-24 11:50:23 -07:00
Matthew Holt a8fdc0a998 core: Initialize logging before admin
We'll see how this goes.

https://caddy.community/t/calling-the-admin-api-from-within-a-route-route-causes-a-deadlock/11319
2021-02-23 12:57:10 -07:00
Matthew Holt f6bb02b303 caddytls: Remove old asset migration code (close #3894) 2021-02-22 15:19:35 -07:00
Matt Holt 6722ae3a83 reverseproxy: Add duration/latency placeholders (close #4012) (#4013)
* reverseproxy: Add duration/latency placeholders (close #4012) (and #2268)

Adds 4 placeholders, one is actually outside reverse proxy though:

{http.request.duration} is how long since the server decoded the HTTP request (headers).
{http.reverse_proxy.upstream.latency} is how long it took a proxy upstream to write the response header.
{http.reverse_proxy.upstream.duration} is total time proxying to the upstream, including writing response body to client.
{http.reverse_proxy.duration} is total time spent proxying, including selecting an upstream and retries.

Obviously, most of these are only useful at the end of a request, like when writing response headers or logs.

See also: https://caddy.community/t/any-equivalent-of-request-time-and-upstream-header-time-from-nginx/11418

* Add new placeholders to documentation
2021-02-22 11:57:21 -07:00
Matthew Holt edb362aa96 httpcaddyfile: Fix catch-all site block sorting
A site block that has a catch-all and the shortest address is now sorted better.

https://caddy.community/t/caddy-suddenly-directs-my-site-to-the-wrong-directive/11597/2
2021-02-22 11:15:11 -07:00
Francis Lavoie 5376e5113e ci: Build and test on Go 1.16, bump minimum to 1.15 (#4024)
* ci: Build and test on Go 1.16

* ci: Drop Go 1.14 support
2021-02-18 07:09:49 -05:00
Matthew Holt ec3ac840cf caddy: Support SetReadBuffer and SyscallConn for QUIC (fix #3998)
Supersedes #3999
2021-02-16 14:05:31 -07:00
Matthew Holt fbd00e4b53 Improve security warnings 2021-02-16 14:05:31 -07:00
Francis Lavoie bafb562991 httpcaddyfile: Configure other apps from global options (#3990) 2021-02-15 20:10:27 -07:00
Matthew Holt ed678235a4 cmd: Clean up build-info and upgrade output 2021-02-15 12:07:55 -07:00
Matthew Holt cc63c5805e caddyhttp: Support placeholders in header matcher values (close #3916) 2021-02-11 16:27:09 -07:00
Matthew Holt 51e3fdba77 caddytls: Save email with account if not already specified
I'm pretty sure this fixes a bug when the default email is used...
2021-02-10 19:49:23 -07:00
Matthew Holt 5ef76ff3e6 reverseproxy: Response buffering & configurable buffer size
Proxy response bodies can now be buffered, and the size of the request body and
response body buffer can be limited. Any remaining content that doesn't fit in the
buffer will remain on the wire until it can be read; i.e. bodies are not truncated,
even if the buffer is not big enough.

This fulfills a customer requirement. This was made possible by their sponsorship!
2021-02-09 14:15:04 -07:00
Matthew Holt 653a0d3f6b httpcaddyfile: Fix automation policies
Fixes a bug introduced in #3862
2021-02-08 11:06:19 -07:00
Mohammed Al Sahaf 0aefa7b047 ci: deflake integration tests (#3966)
* ci: deflake integration tests by pulling Caddy for the running config until new config is loaded
2021-02-05 15:36:52 +00:00
Matthew Holt 8c291298c9 httpcaddyfile: Add resolvers subdir of tls (close #4008)
Allows conveniently setting the resolvers for the DNS challenge using a TLS subdirective, which applies to default issuers, rather than having to explicitly define the issuers and overwrite the defaults.
2021-02-02 23:07:50 -07:00
Matthew Holt bf50d7010a acmeserver: Support custom CAs from Caddyfile
The HTTP Caddyfile adapter can now configure the PKI app, and the acme_server directive can now be used to specify a custom CA used for issuing certificates. More customization options can follow later as needed.
2021-02-02 17:23:52 -07:00
Matthew Holt 8ec90f1c40 caddyhttp: Check for invalid subdirectives of static_response
Ref: https://caddy.community/t/acme-server-implementation/11256/
2021-02-02 16:19:58 -07:00
Matthew Holt 90284e8017 httpcaddyfile: Fix default issuers when email provided
If `tls <email>` is used, we should apply that to all applicable default issuers, not drop them. This refactoring applies implicit ACME issuer settings from the tls directive to all default ACME issuers, like ZeroSSL.

We also consolidate some annoying logic and improve config validity checks.

Ref: https://caddy.community/t/error-obtaining-certificate-after-caddy-restart/11335/8
2021-02-02 16:17:26 -07:00
Matthew Holt 2772ede43c cmd: Add --force flag to reload command (close #4005)
Can be useful if user wants to reload manual certificates, for example.
2021-02-01 18:14:03 -07:00
Matthew Holt c986110678 httpcaddyfile: Warn if site address uses unspecified IP (close #4004) 2021-02-01 17:02:01 -07:00
Matthew Holt 55e49ff5c8 httpcaddyfile: Sort catch-all site blocks properly (fix #4003) 2021-02-01 11:45:28 -07:00
Mohammed Al Sahaf e2940c8c03 ci: update the command to run tests on the s390x machine (#3995) 2021-01-28 22:40:36 +00:00
Tyler Kropp bef80cd806 caddyhttp: Fix redir html status code, improve flow (#3987)
* Fix html redir code, improve flow

* Fix integer check error and add tests
2021-01-28 12:59:50 -07:00
Matt Holt e2c5c28597 caddyhttp: Implement handler abort; new 'abort' directive (close #3871) (#3983)
* caddyhttp: Implement handler abort; new 'abort' directive (close #3871)

* Move abort directive ordering; clean up redirects

Seems logical for the end-all of handlers to go at the... end.

The Connection header no longer needs to be set there, since Close is
true, and the static_response handler now does that.
2021-01-28 12:54:55 -07:00
Matt Holt ab80ff4fd2 admin: Identity management, remote admin, config loaders (#3994)
This commits dds 3 separate, but very related features:

1. Automated server identity management

How do you know you're connecting to the server you think you are? How do you know the server connecting to you is the server instance you think it is? Mutually-authenticated TLS (mTLS) answers both of these questions. Using TLS to authenticate requires a public/private key pair (and the peer must trust the certificate you present to it).

Fortunately, Caddy is really good at managing certificates by now. We tap into that power to make it possible for Caddy to obtain and renew its own identity credentials, or in other words, a certificate that can be used for both server verification when clients connect to it, and client verification when it connects to other servers. Its associated private key is essentially its identity, and TLS takes care of possession proofs.

This configuration is simply a list of identifiers and an optional list of custom certificate issuers. Identifiers are things like IP addresses or DNS names that can be used to access the Caddy instance. The default issuers are ZeroSSL and Let's Encrypt, but these are public CAs, so they won't issue certs for private identifiers. Caddy will simply manage credentials for these, which other parts of Caddy can use, for example: remote administration or dynamic config loading (described below).

2. Remote administration over secure connection

This feature adds generic remote admin functionality that is safe to expose on a public interface.

- The "remote" (or "secure") endpoint is optional. It does not affect the standard/local/plaintext endpoint.
- It's the same as the [API endpoint on localhost:2019](https://caddyserver.com/docs/api), but over TLS.
- TLS cannot be disabled on this endpoint.
- TLS mutual auth is required, and cannot be disabled.
- The server's certificate _must_ be obtained and renewed via automated means, such as ACME. It cannot be manually loaded.
- The TLS server takes care of verifying the client.
- The admin handler takes care of application-layer permissions (methods and paths that each client is allowed to use).\
- Sensible defaults are still WIP.
- Config fields subject to change/renaming.

3. Dyanmic config loading at startup

Since this feature was planned in tandem with remote admin, and depends on its changes, I am combining them into one PR.

Dynamic config loading is where you tell Caddy how to load its config, and then it loads and runs that. First, it will load the config you give it (and persist that so it can be optionally resumed later). Then, it will try pulling its _actual_ config using the module you've specified (dynamically loaded configs are _not_ persisted to storage, since resuming them doesn't make sense).

This PR comes with a standard config loader module called `caddy.config_loaders.http`.

Caddyfile config for all of this can probably be added later.

COMMITS:

* admin: Secure socket for remote management

Functional, but still WIP.

Optional secure socket for the admin endpoint is designed
for remote management, i.e. to be exposed on a public
port. It enforces TLS mutual authentication which cannot
be disabled. The default port for this is :2021. The server
certificate cannot be specified manually, it MUST be
obtained from a certificate issuer (i.e. ACME).

More polish and sensible defaults are still in development.

Also cleaned up and consolidated the code related to
quitting the process.

* Happy lint

* Implement dynamic config loading; HTTP config loader module

This allows Caddy to load a dynamic config when it starts.

Dynamically-loaded configs are intentionally not persisted to storage.

Includes an implementation of the standard config loader, HTTPLoader.
Can be used to download configs over HTTP(S).

* Refactor and cleanup; prevent recursive config pulls

Identity management is now separated from remote administration.

There is no need to enable remote administration if all you want is identity
management, but you will need to configure identity management
if you want remote administration.

* Fix lint warnings

* Rename identities->identifiers for consistency
2021-01-27 16:16:04 -07:00
Matt Holt 3366384d93 caddycmd: Add upgrade command (#3972)
Replaces the current Caddy executable with a new one from the build server. Honors custom builds, as long as plugins are registered on the Caddy website. Requires permissions to replace current executable, of course.

This is an experimental command that may get changed or removed later.
2021-01-19 18:45:49 -07:00
Matthew Holt 1ac6351705 Revert "requestbody: Allow overwriting remote address"
This reverts commit 0bf2046da7.

No actual use case.
2021-01-19 18:43:01 -07:00
Matthew Holt 160d199999 caddytest: Update Caddyfile tests for formatting, HTTP-only blocks
Previous commit improved the Caddyfile adapter so it doesn't unnecessarily add names to "skip" in "auto_https" when the server is already HTTP-only.

This commit updates the tests to reflect that change, while also fixing the Caddyfile formatting in many of the tests.

We also print the line number of the divergence between input and formatted version in Caddyfile adapt warnings - very useful for finding initial formatting problems.
2021-01-19 14:21:11 -07:00
Matthew Holt d68cff8eb6 httpcaddyfile: Skip TLS APs for HTTP-only hosts (fix #3977)
This is probably an invasive change, but existing tests continue to pass.
It seems to make sense this way. There is likely an edge case I haven't
considered.
2021-01-19 14:16:06 -07:00
Matthew Holt 8f6f9865d4 cmd: Print more detailed version with --environ 2021-01-16 12:52:33 -07:00
Matthew Holt 58e83a811b map: Add missing json struct tag 2021-01-16 09:56:06 -07:00
Sven Dowideit f0c0f38ba5 tests: use actual admin port value in error message (#3973)
Signed-off-by: Sven Dowideit <sven.dowideit@csiro.au>
2021-01-13 18:30:49 -07:00
Stefan Tatschner 59071ea15d cmd: Implement sd_notify() to notify systemd about readiness (#3963)
Issue: #3786
Based on Gaurav Dhameeja's work in #3908.
2021-01-12 14:38:53 -07:00
Matthew Holt 14f50d9dfb templates: Add fileExists and httpError template actions
The httpError function isn't particularly useful until https://github.com/golang/go/issues/34201 is fixed in the Go standard lib.
2021-01-11 13:49:20 -07:00
Matthew Holt 0bf2046da7 requestbody: Allow overwriting remote address
An experimental feature, let's see if it's useful.
2021-01-11 13:35:12 -07:00
go-d 88a38bd00d rewrite: Use RawPath instead of Path (fix #3596) (#3918)
Prevent information loss, i.e. the encoded form that was sent by the
client, when using URL strip/replace.
2021-01-11 09:18:53 -07:00
Matthew Holt 4f64105fbb Update docs 2021-01-08 16:00:36 -07:00
Matthew Holt 09432ba64d caddytls: Configurable OCSP stapling; global option (closes #3714)
Allows user to disable OCSP stapling (including support in the Caddyfile via the ocsp_stapling global option) or overriding responder URLs. Useful in environments where responders are not reachable due to firewalls.
2021-01-07 15:52:58 -07:00
Matthew Holt ef54483249 logging: Remove logfmt encoder (close #3575)
Has been deprecated for about 6 months now because it is broken.
2021-01-07 14:29:19 -07:00
Matthew Holt c2b91dbd65 httpcaddyfile: Support repeated use of cert_issuer global option
This changes the signature of UnmarshalGlobalFunc but this is probably OK since it's only used by this repo as far as we know.

We need this change in order to "remember" the previous value in case a global option appears more than once, which is now a possibility with the cert_issuer option since Caddy now supports multiple issuers in the order defined by the user.

Bonus: the issuer subdirective of tls now supports one-liner for "acme" when all you need to set is the directory:

issuer acme <dir>
2021-01-07 11:02:06 -07:00
Jordi Masip 8b6fdc04da caddytls: add 'key_type' subdirective (#3956)
* caddytls: add 'key_type' subdirective

* Suggested change

* *string -> string

* test
2021-01-06 12:02:58 -07:00
Matthew Holt f0216967dc caddyfile: Refactor unmarshaling of module tokens
Eliminates a fair amount of repeated code
2021-01-05 14:39:30 -07:00
Matthew Holt b1bec8c899 go.mod: Update CertMagic and acmez (improved IDN support) 2021-01-04 15:59:48 -07:00
yaxin 3c9256a1be reverseproxy: Caddyfile health check headers, host header support (#3948)
* reverse_proxy: 1.health check headers can be set through Caddyfile using health_headers directive; 2.health check header host can be set properly

* reverse_proxy:
replace example with syntax definition
inline health_headers directive parse function

* bugfix: change caddyfile_adapt testcase file from space to tab

* reverseproxy: modify health_header value document as optional and add more test cases
2021-01-04 11:26:18 -07:00
Matthew Holt 7846bc1e06 httpcaddyfile: Adjust iterator when removing AP (fix #3953) 2021-01-04 11:25:41 -07:00
Matt Holt 144b65cf99 cmd: Organize list-modules output; --packages flag (#3925) 2021-01-04 11:11:56 -07:00
Matt Holt c8557dc00b caddyfile: Introduce basic linting and fmt check (#3923)
* caddyfile: Introduce basic linting and fmt check

This will help encourage people to keep their Caddyfiles tidy.

* Remove unrelated tests

I am not sure that testing the output of warnings here is quite the
right idea; these tests are just for syntax and parsing success.
2021-01-04 11:11:36 -07:00
Mohammed Al Sahaf 1b453dd4fb ci: force fetch the upstream tags (#3947) 2020-12-30 21:02:54 +00:00
Dave Henderson ebc278ec98 metrics: allow disabling OpenMetrics negotiation (#3944)
* metrics: allow disabling OpenMetrics negotiation

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* fixup! metrics: allow disabling OpenMetrics negotiation
2020-12-30 11:44:02 -07:00
Francis Lavoie 79f3af9927 ci: Add pushing to cloudsmith (#3941)
* ci: Add pushing to cloudsmith

* ci: Update comments, remove env TODO

* ci: Fix Cloudsmith installation by setting PATH

* docs: Add Cloudsmith attribution to README

* ci: Switch to keeping armv7 as the armhf .deb
2020-12-30 10:54:58 -07:00
Matthew Holt d8bcf5be4e fileserver: Fix "go up" links in browse listings (closes #3942)
At some point we changed how paths are represented down the function calls of browse listings and forgot to update the canGoUp logic. I think this is right? It's simpler now.
2020-12-30 08:05:01 -07:00
Francis Lavoie 38a83ca6f8 ci: Update goreleaser to fix deprecation notices (#3945)
See https://goreleaser.com/deprecations#nfpmsfiles and https://goreleaser.com/deprecations#nfpmsconfig_files
2020-12-30 09:28:20 -05:00
Mohammed Al Sahaf 2b90cdba52 ci: reject tags if not signed by Matthew Holt's key (#3932)
* ci: reject tags if not signed by Matthew Holt's key

* ci: don't reject tags if an intermediate commits are not signed
2020-12-29 12:52:13 -07:00
Matthew Holt 635f075f18 caddyfile: Fix minor bug in formatter 2020-12-16 15:22:16 -07:00
Matthew Holt e384f07a3c caddytls: Improve alt chain preference settings
This allows for finer-grained control when choosing alternate chains than
simply the previous/Certbot-esque behavior of "choose first chain that
contains an issuer's common name." This update allows you to sort by
length (if optimizing for efficiency on the wire) and also to select the
chain with a specific root CommonName.
2020-12-15 12:16:04 -07:00
Matthew Holt 132525de3b reverseproxy: Minor lint fixes 2020-12-14 15:30:55 -07:00
Matthew Holt deedf8abb0 caddyhttp: Optionally use forwarded IP for remote_ip matcher
The remote_ip matcher was reading the X-Forwarded-For header by default, but this behavior was not documented in anything that was released. This is also a less secure default, as it is trivially easy to spoof request headers. Reading IPs from that header should be optional, and it should not be the default.

This is technically a breaking change, but anyone relying on the undocumented behavior was just doing so by coincidence/luck up to this point since it was never in any released documentation. We'll still add a mention in the release notes about this.
2020-12-10 16:09:30 -07:00
Matthew Holt 63bda6a0dc caddyhttp: Clean up internal auto-HTTPS redirect code
Refactor redirect route creation into own function.

Improve condition for appending port.
Fixes a bug manifested through new test case:
TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses
2020-12-10 14:36:46 -07:00
Matthew Holt b8a799df9f caddyhttp: Document that remote_ip reads X-Forwarded-For header
https://caddy.community/t/remote-ip-behaviour/10762?u=matt
2020-12-09 13:07:11 -07:00
Matthew Holt a748151666 go.mod: Update CertMagic (fix #3911) 2020-12-09 13:07:11 -07:00
Jack Baron c898a37f40 httpcaddyfile: support matching headers that do not exist (#3909)
* add integration test for null header matcher

* implement null header matcher syntax

* avoid repeating magic !

* check for field following ! character
2020-12-09 11:28:14 -07:00
Matthew Holt 31fbcd7401 go.mod: Upgrade some dependencies 2020-12-08 14:06:52 -07:00
Matthew Holt 7e719157d9 httpcaddyfile: Decrement counter when removing conn policy (fix #3906) 2020-12-07 14:22:47 -07:00
Francis Lavoie 6e9ac248dd fastcgi: Set PATH_INFO to file matcher remainder as fallback (#3739)
* fastcgi: Set PATH_INFO to file matcher remainder as fallback

* fastcgi: Avoid changing scriptName when not necessary

* Stylistic tweaks

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-12-04 17:12:13 -07:00
Marten Seemann 5643dc3fb9 go.mod: update quic-go to v0.19.3 (#3901) 2020-12-04 08:49:26 -07:00
Matthew Holt 3d0e046238 caddyauth: Use structured log 2020-12-03 11:33:55 -07:00
Matthew Holt bac82073d0 Merge remote-tracking branch 'origin/master' 2020-12-03 11:33:18 -07:00
Jordi Masip e7a5a3850f cmd: add ability to read config from stdin (#3898) 2020-12-03 10:02:18 -07:00
Matthew Holt aca7ef0d4c Add setcap script to gitignore 2020-12-02 13:48:13 -07:00
Matthew Holt 792fca40f1 Minor comments 2020-12-02 13:27:08 -07:00
Matthew Holt 9157051f45 caddyhttp: Optimize large host matchers 2020-12-02 13:26:28 -07:00
Cuong Manh Le 4cff36d731 caddyauth: Use buffered channel passed to signal.Notify (#3895)
The docs at os/signal.Notify warn about this signal delivery loss bug at
https://golang.org/pkg/os/signal/#Notify, which says:

    Package signal will not block sending to c: the caller must ensure
    that c has sufficient buffer space to keep up with the expected signal
    rate. For a channel used for notification of just one signal value,
    a buffer of size 1 is sufficient.

Caught by a static analysis tool from Orijtech, Inc. called "sigchanyzer"
2020-12-01 08:27:46 -07:00
Francis Lavoie a26f70a12b headers: Fix Caddyfile parsing with request matcher (#3892) 2020-11-30 10:20:30 -07:00
Francis Lavoie 4afcdc49d1 docs: Mention {http.auth.user.id} placeholder in basicauth JSON docs (#3886) 2020-11-26 22:31:25 -05:00
Matthew Holt 7d7434c9ce fileserver: Add debug logging 2020-11-26 09:37:42 -07:00
Daniel Santos 53aa60afff reverseproxy: Handle "operation was canceled" errors (#3816)
* fix(caddy): Avoid "operation was canceled" errors

- Also add error handling for StatusGatewayTimeout

* revert(caddy): Revert 504 handling

- This will potentially break load balancing and health checks

* Handle client cancellation as different error

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-11-25 10:54:23 -07:00
Matt Holt b0f8fc7aae caddytls: Configure trusted CAs from PEM files (#3882)
Closes #3563
2020-11-25 10:53:00 -07:00
Matthew Holt 03d853e2ec httpcaddyfile: Fix test on Windows 2020-11-24 18:04:37 -07:00
Matthew Holt 63afffc2e3 httpcaddyfile: Proper log config with catch-all blocks (fix #3878) 2020-11-24 16:36:58 -07:00
Matthew Holt 2d5498ee6f Update readme 2020-11-24 12:57:12 -07:00
Matthew Holt 0a7721dcfe fileserver: Preserve transformed root (fix #3838) 2020-11-24 12:24:44 -07:00
Ian c5197f5999 acme_server: fix reload of acme database (#3874)
* acme_server: Refactor database creation apart from authority creation

This is a WIP commit that doesn't really offer anything other than
setting us up for using a UsagePool to gracefully reload acme_server
configs.

* Implement UsagePool

* Remove unused context

* Fix initializing non-ACME CA

This will handle cases where a DB is not provided

* Sanitize acme db path and clean debug logs

* Move regex to package level to prevent recompiling
2020-11-23 13:58:26 -07:00
Ian 06ba006f9b acme_server: switch to bbolt storage (#3868)
* acme_server: switch to bbolt storage

There have been some issues with the badger storage engine
being used by the embedded acme_server. This will replace
the storage engine with bbolt

* Switch database path back to acme_server/db and remove if directory
2020-11-23 13:03:58 -07:00
Francis Lavoie c6dec30535 caddyfile: Add support for env var defaults; add tests (#3682)
* caddyfile: Add support for env var defaults, tests

* caddyfile: Use ?? instead, fix redundant cast, remove env chaining

* caddyfile: Use : instead
2020-11-23 12:51:35 -07:00
Francis Lavoie 3cfefeb0f7 httpcaddyfile: Configure servers via global options (#3836)
* httpcaddyfile: First pass at implementing server options

* httpcaddyfile: Add listener wrapper support

* httpcaddyfile: Sort sbaddrs to make adapt output more deterministic

* httpcaddyfile: Add server options adapt tests

* httpcaddyfile: Windows line endings lol

* caddytest: More windows line endings lol (sorry Matt)

* Update caddyconfig/httpcaddyfile/serveroptions.go

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

* httpcaddyfile: Reword listener address "matcher"

* Apply suggestions from code review

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

* httpcaddyfile: Deprecate experimental_http3 option (moved to servers)

* httpcaddyfile: Remove validation step, no longer needed

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-11-23 12:46:50 -07:00
Francis Lavoie 4a641f6c6f reverseproxy: Add Caddyfile scheme shorthand for h2c (#3629)
* reverseproxy: Add Caddyfile scheme shorthand for h2c

* reverseproxy: Use parentheses for condition

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-11-23 12:18:26 -07:00
Dave Henderson bd17eb205d ci: Use golangci's github action for linting (#3794)
* ci: Use golangci's github action for linting

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix most of the staticcheck lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the prealloc lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the misspell lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the varcheck lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the errcheck lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the bodyclose lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the deadcode lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the unused lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the gosec lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the gosimple lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the ineffassign lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Fix the staticcheck lint errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Revert the misspell change, use a neutral English

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Remove broken golangci-lint CI job

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Re-add errantly-removed weakrand initialization

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* don't break the loop and return

* Removing extra handling for null rootKey

* unignore RegisterModule/RegisterAdapter

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* single-line log message

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

* Fix lint after a1808b0dbf209c615e438a496d257ce5e3acdce2 was merged

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Revert ticker change, ignore it instead

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Ignore some of the write errors

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Remove blank line

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Use lifetime

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* close immediately

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

* Preallocate configVals

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Update modules/caddytls/distributedstek/distributedstek.go

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-11-22 14:50:29 -07:00
Marten Seemann 1e480b818b go.mod: update quic-go to v0.19.2 (#3880) 2020-11-21 14:54:11 -07:00
Francis Lavoie 96058538f0 reverseproxy: Logging for streaming and upgrades (#3689)
* reverseproxy: Enable error logging for connection upgrades

* reverseproxy: Change some of the error levels, unsugar

* Use unsugared log in one spot

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-11-20 14:24:58 -07:00
Dimitri Masson 6e0849d4c2 reverseproxy: Implement cookie hash selection policy (#3809)
* add CookieHashSelection for session affinity

* add CookieHashSelection for session affinity

* register module

* reverse_proxy: Add and fix cookie lb_policy

* reverse_proxy: Manage hmac.write error on cookie hash selection

* reverse_proxy: fix some comments

* reverse_proxy: variable `cookieValue` is inside the else block

* reverse_proxy: Abstract duplicate nuanced logic of reservoir sampling into a function

* reverse_proxy: Set a default secret is indeed useless

* reverse_proxy: add configuration syntax for cookie lb_policy

* reverse_proxy: doc typo and improvement

Co-authored-by: utick <123liuqingdong@163.com>
2020-11-20 12:39:26 -07:00
Gilbert Gilb's b0d5c2c8ae headers: Support default header values in Caddyfile with '?' (#3807)
* implement default values for header directive

closes #3804

* remove `set_default` header op and rely on "require" handler instead

This has the following advantages over the previous attempt:

- It does not introduce a new operation for headers, but rather nicely
  extends over an existing feature in the header handler.
- It removes the need to specify the header as "deferred" because it is
  already implicitely deferred by the use of the require handler. This
  should be less confusing to the user.

* add integration test for header directive in caddyfile

* bubble up errors when parsing caddyfile header directive

* don't export unnecessarily and don't canonicalize headers unnecessarily

* fix response headers not passed in blocks

* caddyfile: fix clash when using default header in block

Each header is now set in a separate handler so that it doesn't clash
with other headers set/added/deleted in the same block.

* caddyhttp: New idle_timeout default of 5m

* reverseproxy: fix random hangs on http/2 requests with server push (#3875)

see https://github.com/golang/go/issues/42534

* Refactor and cleanup with improvements

* More specific link

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
Co-authored-by: Денис Телюх <telyukh.denis@gmail.com>
2020-11-20 12:38:16 -07:00
Matthew Holt 12cc69ab7a Merge branch 'master' of https://github.com/caddyserver/caddy 2020-11-18 16:14:55 -07:00
Matthew Holt 349457cc1b caddyhttp: Return error if error handling error
Before, if there was an error in the error handler, we would not write a
status code, which resulted in Go writing a 200 for us by default, which
does not make sense when there's an error. Now we write the second
error's status if available, otherwise 500.
2020-11-18 16:14:50 -07:00
Денис Телюх 6ea6f3ebe0 reverseproxy: fix random hangs on http/2 requests with server push (#3875)
see https://github.com/golang/go/issues/42534
2020-11-18 11:53:43 -07:00
Matthew Holt 1438e4dbc8 caddyhttp: New idle_timeout default of 5m 2020-11-18 10:57:54 -07:00
Matthew Holt 4fc570711e caddyhttp: Fix header matcher when using nil
Uncovered in #3807
2020-11-17 11:29:43 -07:00
Dimitri Masson 99b8f44486 reverse_proxy: Fix random_choose selection policy (#3811) 2020-11-16 12:47:15 -07:00
Nicola Piccinini 670b723e38 requestbody: Add Caddyfile support (#3859)
* Add Caddyfile support for request_body:

```
  request_body {
    max_size 10000000
  }
```

* Improve Caddyfile parser for request_body module

* Remove unnecessary `continue`

* Add sample for caddyfile_adapt_test
2020-11-16 11:43:39 -07:00
Matt Holt 13781e67ab caddytls: Support multiple issuers (#3862)
* caddytls: Support multiple issuers

Defaults are Let's Encrypt and ZeroSSL.

There are probably bugs.

* Commit updated integration tests, d'oh

* Update go.mod
2020-11-16 11:05:55 -07:00
Aurelia 7a3d9d81fe basicauth: Minor internal improvements (#3861)
* nitpicks and small improvements in basicauth module

1:
roll two if statements into one, since err will be nil in the second case anyhow

2:
unlock cache mutex after reading the key, as this happens by-value and reduces code complexity

3:
switch cache sync.Mutex to sync.RWMutex for better concurrency on cache fast track

* allocate the right kind of mutex
2020-11-13 15:28:21 -07:00
Matthew Holt 95af4262a8 caddytls: Support ACME alt cert chain preferences 2020-11-12 15:03:07 -07:00
Matthew Holt 3db60e6cba Update contact info 2020-11-12 15:03:07 -07:00
Gaurav Dhameeja 7c28ecb5f4 httpcaddyfile: Add certificate_pem placeholder short, add to godoc (#3846)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2020-11-04 13:37:41 -05:00
Mohammed Al Sahaf 9e28f60aab ci: remove the continuous fuzzing job (#3845)
Between Github Actions deprecting a command we use[0] and Fuzzit planning to deprecate their standalone service after being acquired by Gitlab[1][2], there are no reasons to keep this job.

[0] https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/

[1] https://about.gitlab.com/press/releases/2020-06-11-gitlab-acquires-peach-tech-and-fuzzit-to-expand-devsecops-offering.html

[2] https://fuzzit.dev/2020/06/11/news-fuzzit-is-acquired-by-gitlab/
2020-11-04 16:32:07 +00:00
Francis Lavoie b4f49e2962 caddyhttp: Merge query matchers in Caddyfile (#3839)
Also, turns out that `Add` on headers will work even if there's nothing there yet, so we can remove the condition I introduced in #3832
2020-11-02 16:05:01 -07:00
Christoph Kluge dd26875ffc logging: Fix for IP filtering 2020-11-02 16:01:58 -07:00
Francis Lavoie eda9a1b377 fastcgi: Add timeouts support to Caddyfile adapter (#3842)
* fastcgi: Add timeouts support to Caddyfile adapter

* fastcgi: Use tabs instead of spaces
2020-11-02 15:11:17 -07:00
Francis Lavoie 860cc6adfe reverseproxy: Wire up some http transport options in Caddyfile (#3843) 2020-11-02 14:59:02 -07:00
Matt Holt 8d038ca515 fileserver: Improve and clarify file hiding logic (#3844)
* fileserver: Improve and clarify file hiding logic

* Oops, forgot to run integration tests

* Make this one integration test OS-agnostic

* See if this appeases the Windows gods

* D'oh
2020-11-02 14:20:12 -07:00
Matthew Holt 937ec34201 caddyauth: Prevent user enumeration by timing
Always follow the code path of hashing and comparing a plaintext
password even if the account is not found by the given username; this
ensures that similar CPU cycles are spent for both valid and invalid
usernames.

Thanks to @tylerlm for helping and looking into this!
2020-10-31 10:51:05 -06:00
Francis Lavoie 966d5e6b42 caddyhttp: Merge header matchers in Caddyfile (#3832) 2020-10-31 10:27:01 -06:00
Francis Lavoie b66099379d reverseproxy: Add max_idle_conns_per_host; fix godocs (#3829) 2020-10-30 12:05:21 -06:00
Jason McCallister c9fdff9976 reverseproxy: caddyfile: Don't add port if upstream has placeholder (#3819)
* check if the host is a placeholder

* Update modules/caddyhttp/reverseproxy/caddyfile.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-10-29 13:51:42 -06:00
Matt Holt db4f1c0277 httpcaddyfile: Revise automation policy generation (#3824)
* httpcaddyfile: Revise automation policy generation

This should fix a frustrating edge case where wildcard subjects are
used, which potentially get shadowed by more specific versions of
themselves; see the new tests for an example. This change is motivated
by an actual customer requirement.

Although all the tests pass, this logic is incredibly complex and
nuanced, and I'm worried it is not correct. But it took me about 4 days
to get this far on a solution. I did my best.

* Fix typo
2020-10-28 20:36:00 -06:00
Matthew Holt b6e96d6f4a go.mod: Update CertMagic 2020-10-22 12:42:06 -06:00
Matthew Holt b6686a54d8 httpcaddyfile: Improve AP logic with OnDemand
We have users that have site blocks like *.*.tld with on-demand TLS
enabled. While *.*.tld does not qualify for a publicly-trusted cert due
to its wildcards, On-Demand TLS does not actually obtain a cert with
those wildcards, since it uses the actual hostname on the handshake.

This improves on that logic, but I am still not 100% satisfied with the
result since I think we need to also check if another site block is more
specific, like foo.example.tld, which might not have on-demand TLS
enabled, and make sure an automation policy gets created before the
more general policy with on-demand...
2020-10-22 12:40:23 -06:00
Matthew Holt 97caf368ee readme: Add zerossl 2020-10-19 10:44:46 -06:00
Matt Holt 385adf5d87 caddyhttp: Restore original request params before error handlers (#3781)
* caddyhttp: Restore original request params before error handlers

Fixes #3717

* Add comment
2020-10-13 10:52:39 -06:00
Matt Holt c7efb0307d reverseproxy: Fix dial placeholders, SRV, active health checks (#3780)
* reverseproxy: Fix dial placeholders, SRV, active health checks

Supercedes #3776
Partially reverts or updates #3756, #3693, and #3695

* reverseproxy: add integration tests

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2020-10-13 10:35:20 -06:00
AJ ONeal e34d9f1244 readme: Add link to website for download instructions (#3785)
* add Webi as install method

* link to install page
2020-10-09 11:32:09 -06:00
Matthew Holt ef8a372a1c map: Bug fixes; null literal with hyphen in Caddyfile 2020-10-02 16:08:28 -06:00
Matthew Holt 0fc47e8357 map: Apply default if mapped output is nil 2020-10-02 15:23:52 -06:00
Matthew Holt 25d2b4bf29 map: Reimplement; multiple outputs; optimize 2020-10-02 14:23:56 -06:00
Matt Holt 023d702f30 Update SECURITY.md 2020-10-01 17:11:10 -06:00
Mohammed Al Sahaf 6722426f1a reverseproxy: allow no port for SRV; fix regression in d55d50b (#3756)
* reverseproxy: fix breakage in handling SRV lookup introduced by 3695

* reverseproxy: validate against incompatible config options with lookup_srv

* reverseproxy: add integration test cases for validations involving lookup_srv

* reverseproxy: clarify the reason for skipping an iteration

* grammar.. Oxford comma

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

Fixes #3753
2020-10-01 14:05:39 -06:00
Aleksei 3b9eae70c9 reverseproxy: Change 500 error to 502 for lookup_srv config (#3771)
Fixes #3763
2020-10-01 14:02:31 -06:00
Mohammed Al Sahaf aa9c3eb732 reverseproxy: default to port 80 for upstreams in Caddyfile (#3772)
* reverseproxy: default to port 80 for port-less upstream dial addresses

* reverseproxy: replace integration test with an adapter test

Fixes #3761
2020-10-01 13:53:19 -06:00
Christian Flach fdfdc03339 reverseproxy: Ignore RFC 1521 params in Content-Type header (#3758)
Without this change, a Content-Type header like "text/event-stream;charset=utf-8"
would not trigger the immediate flushing.

Fixes #3765
2020-10-01 12:15:45 -06:00
Dave Henderson dadfe1933b metrics: fix handler to not run the next route (#3769)
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2020-10-01 10:57:14 -06:00
Dave Henderson 85152679ce admin: lower log level to Debug for /metrics requests (#3749)
* admin: lower log level to Debug for /metrics requests

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Apply suggestions from code review

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-09-25 18:00:08 -06:00
Mohammed Al Sahaf a33e4b5426 caddyfile: Add support for vars and vars_regexp matchers (#3730)
* caddyfile: support vars and vars_regexp matchers in the caddyfile

* caddyfile: matchers: Brian Kernighan said printf is good debugging tool but didn't say keep them around
2020-09-25 17:50:26 -06:00
Dave Henderson f197cec7f3 metrics: Always track method label in uppercase (#3742)
* metrics: Always track method label in uppercase

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Just use strings.ToUpper for clarity

Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2020-09-22 20:10:34 -06:00
Francis Lavoie be6daa5fd4 httpcaddyfile: Fix panic when parsing route with matchers (#3746)
Fixes #3745
2020-09-22 17:37:15 -06:00
Francis Lavoie fe27f9cf0c httpcaddyfile: Disallow args on route/handle directive family (#3740) 2020-09-21 13:44:41 -06:00
Dave Henderson b1d456d8ab metrics: Fix panic when headers aren't written (#3737)
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2020-09-21 13:42:47 -06:00
Dave Henderson d16ede358a metrics: Fix hidden panic while observing with bad exemplars (#3733)
* metrics: Fixing panic while observing with bad exemplars

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

* Minor cleanup

The server is already added to the context. So, we can simply use that
to get the server name, which is a field on the server.

* Add integration test for auto HTTP->HTTPS redirects

A test like this would have caught the problem in the first place

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-09-17 21:46:24 -06:00
Matthew Holt c82c231ba7 caddyhttp: Remove server name from metrics
For some reason this breaks automatic HTTP->HTTPS redirects. I am not
sure why yet, but as a hotfix remove this until we understand it better.
2020-09-17 17:23:58 -06:00
Matthew Holt 3ee663dee1 go.mod: Upgrade dependencies 2020-09-17 12:35:25 -06:00
Dave Henderson 8ec51bbede metrics: Initial integration of Prometheus metrics (#3709)
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2020-09-17 12:01:20 -06:00
Mohammed Al Sahaf bc453fa6ae reverseproxy: Correct alternate port for active health checks (#3693)
* reverseproxy: construct active health-check transport from scratch (Fixes #3691)

* reverseproxy: do upstream health-check on the correct alternative port

* reverseproxy: add integration test for health-check on alternative port

* reverseproxy: put back the custom transport for health-check http client

* reverseproxy: cleanup health-check integration test

* reverseproxy: fix health-check of unix socket upstreams

* reverseproxy: skip unix socket tests on Windows

* tabs > spaces

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* make the linter (and @francislavoie) happy

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* One more lint fix

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2020-09-17 10:25:34 -06:00
Francis Lavoie e3324aa6de httpcaddyfile: Ensure handle_path is sorted equally to handle (#3676)
* httpcaddyfile: Ensure handle_path is sorted as equal to handle

* httpcaddyfile: Make mutual exclusivity grouping deterministic (I hope)

* httpcaddyfile: Add comment linking to the issue being fixed

* httpcaddyfile: Typo fix, comment clarity

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

* Update caddyconfig/httpcaddyfile/httptype.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-09-16 20:01:22 -06:00
Mohammed Al Sahaf d55d50b3b3 reverseproxy: Enforce port range size of 1 at provision (#3695)
* reverse_proxy: ensure upstream address has port range of only 1

* reverse_proxy: don't log the error if upstream range size is more than 1
2020-09-16 19:48:37 -06:00
Francis Lavoie b95b87381a fileserver: Fix try_files for directories; windows fix (#3684)
* fileserver: Fix try_files for directories, windows fix

* fileserver: Add new file type placeholder, refactoring, tests

* fileserver: Review cleanup

* fileserver: Flip the return args order
2020-09-16 18:09:28 -06:00
Gaurav Dhameeja b01bb275b3 caddyhttp: New placeholder for PEM of client certificate (#3662)
* Fix-3585: added placeholder for a PEM encoded value of the certificate

* Update modules/caddyhttp/replacer.go

Change type of block and empty headers removed

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

* fixed tests

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-09-16 15:06:51 -06:00
Francis Lavoie 309c1fec62 logging: Implement Caddyfile support for filter encoder (#3578)
* logging: Implement Caddyfile support for filter encoder

* logging: Add support for parsing IP masks from strings


wip

* logging: Implement Caddyfile support for ip_mask

* logging: Get rid of unnecessary logic to allow strings, not that useful

* logging: Add adapt test
2020-09-15 12:37:41 -06:00
Matthew Penner b88e2b6a49 cmd: Allow caddy fmt to read from stdin (#3680)
* Allow 'caddy fmt' to read from stdin

* fmt: use '-' as the file name for reading from stdin

* Minor adjustments

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-09-14 12:30:12 -06:00
Matthew Holt 4217217bad httpcaddyfile: Properly record whether we added catch-all conn policy
We recently introduced `if !cp.SettingsEmpty()` which conditionally
adds the connection policy to the list. If the condition evaluates to
false, the policy wouldn't actually be added, even if
hasCatchAllTLSConnPolicy was set to true on the previous line.

Now we set that variable in accordance with whether we actually add
the policy.

While debugging this I noticed that catch-all policies added early in
that loop (i.e. not at the end if we later determine we need one) are
not always at the end of the list. They should be, though, since they
are selected by which one matches first, and having a catch-all first
would nullify any more specific ones later in the list. So I added a
sort in consolidateConnPolicies to take care of that.

Should fix #3670 and
https://caddy.community/t/combining-on-demand-tls-with-custom-ssl-certs-doesnt-seem-to-work-in-2-1-1/9719
but I won't know for sure until somebody verifies it, since at least in
the GitHub issue there is not yet enough information (the configs are
redacted).
2020-09-11 13:45:21 -06:00
Matt Holt 1c5969b576 fileserver: Fix new file hide tests on Windows (#3719) 2020-09-11 13:09:16 -06:00
Matthew Holt 0ee4378227 fileserver: Improve file hiding logic for directories and prefixes
Now, a filename to hide that is specified without a path separator will
count as hidden if it appears in any component of the file path (not
only the last component); semantically, this means hiding a file by only
its name (without any part of a path) will hide both files and folders,
e.g. hiding ".git" will hide "/.git" and also "/.git/foo".

We also do prefix matching so that hiding "/.git" will hide "/.git"
and "/.git/foo" but not "/.gitignore".

The remaining logic is a globular match like before.
2020-09-11 12:20:39 -06:00
Matthew Holt 9859ab8148 caddytls: Fix resolvers option of acme issuer (Caddyfile)
Reported in:
https://caddy.community/t/dns-challenge-with-namecheap-and-split-horizon-dns/9611/17?u=matt
2020-09-09 10:21:59 -06:00
Francis Lavoie 00e6b77fe4 caddytls: Add dns config to acmeissuer (#3701) 2020-09-08 11:36:46 -06:00
Mohammed Al Sahaf d4f249741e browse: align template to struct field renames from 4940325 (#3706) 2020-09-08 10:45:48 -06:00
Francis Lavoie 04f50a9759 caddyhttp: Wrap http.Server logging with zap (#3668) 2020-09-08 10:44:58 -06:00
Francis Lavoie 4cd7ae35b3 reverseproxy: Add buffer_requests option to reverse_proxy directive (#3710) 2020-09-08 10:37:46 -06:00
Matthew Holt 24f34780b6 caddytls: Customize DNS resolvers for DNS challenge with Caddyfile 2020-08-31 13:23:26 -06:00
Matthew Holt 724b74d981 reverseproxy: Abort active health checks on context cancellation 2020-08-31 13:22:34 -06:00
Matthew Holt 4940325844 fileserver: Fix inconsistencies in browse JSON 2020-08-31 12:33:43 -06:00
Matthew Holt 744d04c258 caddytls: Configure custom DNS resolvers for DNS challenge (close #2476)
And #3391

Maybe also related: #3664
2020-08-21 20:30:14 -06:00
Francis Lavoie ecbc1f85c5 ci: Tweaks for multi go version tests (#3673) 2020-08-20 22:40:26 -04:00
Matthew Holt 997ef522bc go.mod: Use v0.15(.1) of smallstep libs
Update internal issuer for compatibility -- yay simpler code!

The .1 version also fixes non-critical SAN extensions that caused trust
issues on several clients.
2020-08-20 19:28:25 -06:00
Francis Lavoie 0279a57ac4 ci: Upgrade to Go 1.15 (#3642)
* ci: Try Go 1.15 RC1 out of curiosity

* Go 1.15 was released; let's try it

* Update to latest quic-go

* Attempt at fixing broken test

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-08-20 14:04:10 -06:00
Matthew Holt c94f5bb7dd reverseproxy: Make default buffer size const 2020-08-17 16:17:16 -06:00
Francis Lavoie 0afbab8667 httpcaddyfile: Improve directive sorting logic (#3658)
* httpcaddyfile: Flip `root` directive sort order

* httpcaddyfile: Sort directives with any matcher before those with none

* httpcaddyfile: Generalize reverse sort directives, improve logic

* httpcaddyfile: Fix "spelling" issue

* httpcaddyfile: Turns out the second change precludes the first


httpcaddyfile: Delete test that no longer makes sense

* httpcaddyfile: Shorten logic

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-08-17 16:15:51 -06:00
Francis Lavoie fc65320e9c reverseproxy: Support header selection policy on Host field (#3653) 2020-08-17 15:14:46 -06:00
Matthew Holt e385be9225 Update comment and Caddy 1 EOL 2020-08-11 11:26:19 -06:00
Matt Holt 66863aad3b caddytls: Add support for ZeroSSL; add Caddyfile support for issuers (#3633)
* caddytls: Add support for ZeroSSL; add Caddyfile support for issuers

Configuring issuers explicitly in a Caddyfile is not easily compatible
with existing ACME-specific parameters such as email or acme_ca which
infer the kind of issuer it creates (this is complicated now because
the ZeroSSL issuer wraps the ACME issuer)... oh well, we can revisit
that later if we need to.

New Caddyfile global option:

    {
        cert_issuer <name> ...
    }

Or, alternatively, as a tls subdirective:

    tls {
        issuer <name> ...
    }

For example, to use ZeroSSL with an API key:

    {
        cert_issuser zerossl API_KEY
    }

For now, that still uses ZeroSSL's ACME endpoint; it fetches EAB
credentials for you. You can also provide the EAB credentials directly
just like any other ACME endpoint:

    {
        cert_issuer acme {
            eab KEY_ID MAC_KEY
        }
    }

All these examples use the new global option (or tls subdirective). You
can still use traditional/existing options with ZeroSSL, since it's
just another ACME endpoint:

    {
        acme_ca  https://acme.zerossl.com/v2/DV90
        acme_eab KEY_ID MAC_KEY
    }

That's all there is to it. You just can't mix-and-match acme_* options
with cert_issuer, because it becomes confusing/ambiguous/complicated to
merge the settings.

* Fix broken test

This test was asserting buggy behavior, oops - glad this branch both
discovers and fixes the bug at the same time!

* Fix broken test (post-merge)

* Update modules/caddytls/acmeissuer.go

Fix godoc comment

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* Add support for ZeroSSL's EAB-by-email endpoint

Also transform the ACMEIssuer into ZeroSSLIssuer implicitly if set to
the ZeroSSL endpoint without EAB (the ZeroSSLIssuer is needed to
generate EAB if not already provided); this is now possible with either
an API key or an email address.

* go.mod: Use latest certmagic, acmez, and x/net

* Wrap underlying logic rather than repeating it

Oops, duh

* Form-encode email info into request body for EAB endpoint

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2020-08-11 08:58:06 -06:00
Matthew Holt c42bfaf31e go.mod: Bump CertMagic 2020-08-08 08:42:01 -06:00
Matthew Holt e2f913bb7f reverseproxy: Minor fixes and cleanup
Now use context cancellation to stop active health checker, which is
simpler than and just as effective as using a separate stop channel.
2020-08-07 18:02:24 -06:00
Matt Holt 65a09524c3 caddyhttp: Add TLS client cert info to logs (#3640) 2020-08-07 12:12:29 -06:00
Matthew Holt c6d6a775a1 go.mod: Update some dependencies
We can't update smallstep/nosql and klauspost/cpuid yet because of
upstream breakage.
2020-08-06 14:36:21 -06:00
Matt Holt 4accf737a6 ci: Ignore s390x failures (#3644)
As of early August 2020 the VM has been down for several days due to
lack of power due related to bad weather at the data center... sigh.
2020-08-06 14:17:40 -06:00
Matthew Holt ff19bddac5 httpcaddyfile: Avoid repeated subjects in APs (fix #3618)
When consolidating automation policies, ensure same subject names do not
get appended to list.
2020-08-06 13:56:23 -06:00
Francis Lavoie 584eba94a4 httpcaddyfile: Allow named matchers in route blocks (#3632) 2020-08-05 13:42:29 -06:00
Kevin Lin 904f149e5b reverse_proxy: fix bidirectional streams with encodings (fix #3606) (#3620)
* reverse_proxy: fix bi-h2stream breaking gzip encode handle(#3606).

* reverse_proxy: check http version of both sides to avoid affecting non-h2 upstream.

* Minor cleanup; apply review suggestions

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-08-03 20:50:38 -06:00
Ye Zhihao 8b80a3201f httpcaddyfile: Bring enforce_origin and origins to admin config (#3595)
* Bring `ensure_origin` and `origins` to caddyfile admin config

* Add unit test for caddyfile admin config update

* Add caddyfile adapt test for typical admin setup

* httpcaddyfile: Replace admin config error message when there's more arguments than needed

Replace d.Err() to d.ArgErr() since the latter provides similarly informative error message

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-08-03 13:44:38 -06:00
Matthew Holt 68529e2f9e cmd: Print caddy version with environ or --environ (#3627) 2020-08-03 10:42:42 -06:00
Mohammed Al Sahaf 399eff415c ci: Include tracking of GOOS for which Caddy fails to build (#3617)
* ci: include tracking of GOOS for which Caddy fails to build

* ci: split cross-build check into separate workflow

* ci: cross-build check: make it clear the cross-build check is not a blocker

* ci: cross-build check: set annotation instead of failing the build

* ci: cross-build check: explicitly set continue-on-error to force success marker

* ci: cross-build check: send stderr to /dev/null

* ci: Simplify workflow names

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-08-01 20:23:22 +00:00
Matt Holt c054a818a1 fileserver: Fix newly-introduced failing test on Linux (#3625)
* fileserver: First attempt to fix failing test on Linux

I think I updated the wrong test case before

* Make new test function

I guess what we really are trying to test is the case insensitivity of
firstSplit. So a new test function is better for that.
2020-08-01 12:43:30 -06:00
Bart af5c148ed1 admin,templates,core: Minor enhancements and error handling (#3607)
* fix 2 possible bugs

* handle unhandled errors
2020-07-31 16:54:18 -06:00
v-rosa 514eef33fe caddyhttp: Add support to resolve DN in CEL expression (#3608) 2020-07-31 15:06:30 -06:00
Matthew Holt 3860b235d0 fileserver: Don't assume len(str) == len(ToLower(str)) (fix #3623)
We can't use a positional index on an original string that we got from
its lower-cased equivalent. Implement our own IndexFold() function b/c
the std lib does not have one.
2020-07-31 13:55:01 -06:00
Ye Zhihao 6f73a358f4 httpcaddyfile: Add compression to http transport config (#3624)
* httpcaddyfile: Add `compression` to http transport config

* Add caddyfile adapt test for typical h2c setup
2020-07-31 11:30:20 -06:00
Matt Holt 6a14e2c2a8 caddytls: Replace lego with acmez (#3621)
* Replace lego with acmez; upgrade CertMagic

* Update integration test
2020-07-30 15:18:14 -06:00
Patrick Hein 2bc30bb780 templates: Implement placeholders function (#3324)
* caddyhttp, httpcaddyfile: Implement placeholders in template

* caddyhttp, httpcaddyfile: Remove support for placeholder shorthands in templates

* Update modules/caddyhttp/templates/templates.go

updates JSON doc

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

* Update modules/caddyhttp/templates/tplcontext.go

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-07-20 17:17:38 -06:00
Matthew Holt 28d870c193 go.mod: Update quic-go, truststore, and goldmark 2020-07-20 14:57:40 -06:00
Francis Lavoie fb9d874fa9 caddyfile: Export Tokenize function for lexing (#3549) 2020-07-20 13:55:51 -06:00
Matt Holt 6cea1f239d push: Implement HTTP/2 server push (#3573)
* push: Implement HTTP/2 server push (close #3551)

* push: Abstract header ops by embedding into new struct type

This will allow us to add more fields to customize headers in
push-specific ways in the future.

* push: Ensure Link resources are pushed before response is written

* Change header name from X-Caddy-Push to Caddy-Push
2020-07-20 12:28:40 -06:00
Manuel Dalla Lana 2ae8c11927 fastcgi: Add resolve_root_symlink (#3587) 2020-07-20 12:16:13 -06:00
Kevin Lin e9b1d7dcb4 reverse_proxy: flush HTTP/2 response when ContentLength is unknown (#3561)
* reverse proxy: Support more h2 stream scenarios (#3556)

* reverse proxy: add integration test for better h2 stream (#3556)

* reverse proxy: adjust comments as francislavoie suggests

* link to issue #3556 in the comments
2020-07-20 12:14:46 -06:00
Mohammed Al Sahaf bd9d796e6e reverseproxy: add support for custom DNS resolver (#3479)
* reverse proxy: add support for custom resolver

* reverse proxy: don't pollute the global resolver with bootstrap resolver setup

* Improve documentation of reverseproxy.UpstreamResolver fields

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

* reverse proxy: clarify the name resolution conventions of upstream resolvers and bootstrap resolver

* remove support for bootstraper of resolver

* godoc and code-style changes

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-07-18 15:00:00 -06:00
Matthew Holt 246a31aacd reverseproxy: Restore request's original host and header (fix #3509)
We already restore them within the retry loop, but after successful
proxy we didn't reset them, so as handlers bubble back up, they would
see the values used for proxying.

Thanks to @ziddey for identifying the cause.
2020-07-17 17:54:58 -06:00
Francis Lavoie 0665a86eb7 fastcgi: Ensure leading slash, omit SERVER_PORT if empty for compliance (#3570)
See https://tools.ietf.org/html/rfc3875#section-4.1.13 for SCRIPT_NAME requiring leading slash
See https://tools.ietf.org/html/rfc3875#section-4.1.15 for SERVER_PORT requiring omission if empty
2020-07-17 14:48:50 -06:00
Francis Lavoie 3fdaf50785 fastcgi: Fill REMOTE_USER with http.auth.user.id placeholder (#3577)
Completing a TODO!
2020-07-17 13:33:40 -06:00
Francis Lavoie 19cc2bd3c3 reverseproxy: Fix Caddyfile parsing for empty non-http transports (#3576)
* reverseproxy: Fix Caddyfile parsing for empty non-http transports

* Update modules/caddyhttp/reverseproxy/caddyfile.go

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

* Rename empty transport test

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-07-17 13:18:32 -06:00
Matthew Holt 705de11bef readme: Minor tweaks 2020-07-17 12:53:48 -06:00
Matthew Holt 8a0fff58aa caddyauth: hash-password: Set bcrypt cost to 14 (#3580) 2020-07-17 12:20:53 -06:00
Matthew Holt 6f0f159ba5 caddyhttp: Add {http.request.body} placeholder 2020-07-16 19:25:37 -06:00
Matthew Holt 6eafd4e82f readme: Update badges 2020-07-16 15:29:06 -06:00
Matthew Holt eda54c22a6 logging: ⚠️ Deprecate logfmt encoder
It is essentially broken because it occludes many log fields.

See: https://github.com/caddyserver/caddy/issues/3575
2020-07-13 16:18:34 -06:00
Matthew Holt 2c71fb116b chore: Rename file to be consistent 2020-07-11 17:53:33 -06:00
Kévin Dunglas 724613a1be docs: Remove extra word in README.md (#3564) 2020-07-10 10:00:24 -04:00
snu-ceyda 735c86658d fileserver: Enable browse pagination with offset parameter (#3542)
* Update browse.go

* Update browselisting.go

* Update browsetpl.go

* fix linter err

* Update modules/caddyhttp/fileserver/browse.go

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

* Update modules/caddyhttp/fileserver/browselisting.go

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

* Update browsetpl.go

change from -> offset

* Update browse.go

* Update browselisting.go

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-07-08 23:56:15 -06:00
Matthew Holt a2dae1d43f templates: Fix front matter closing fence search
This makes it choose first matching closing fence instead of last one,
which could appear in document body.
2020-07-08 16:46:56 -06:00
Matthew Holt efc0cc5e85 caddytls: Move initial storage clean op into goroutine
Sometimes this operation can take a while (we observed 7 minutes
recently, with a large, globally-distributed storage backend).
2020-07-08 10:59:49 -06:00
Matthew Holt 0bf2565c37 caddyhttp: Reorder some access log fields; add host matcher test case
This field order reads a little more naturally.
2020-07-07 08:11:35 -06:00
Matthew Holt 7bfe5b6c95 httpcaddyfile: Reorder automation policy logic (close #3550) 2020-07-07 08:10:37 -06:00
Matthew Holt 2a5599e2ad go.mod: Upgrade and downgrade smallstep, quic-go, and cpuid
Closes #3537 and fixes #3535
2020-07-06 12:10:35 -06:00
Greg Anders c35820012b templates: Disable hard wraps in Markdown rendering (#3553) 2020-07-06 11:53:40 -06:00
Francis Lavoie 2d0f8831f8 ci: Fix another oops with publish workflow (#3536) 2020-06-30 15:36:17 -04:00
Mohammed Al Sahaf d7dbf85525 cel: fix validation of expression result type (#3526)
* cel: fix validation of expression result type

The earlier code used the proto.Equals from github.com/gogo/protobuf, which failed to compare two messages of the same type for some reason. Switching to proto.Equal from the canonical github.com/golang/protobuf fixes the issue.

* deps: remove deprecated github.com/golang/protobuf in favor of google.golang.org/protobuf

* downgrade github.com/smallstep/nosql to resolve warning pb.proto warning
2020-06-30 11:53:29 -06:00
Matthew Holt 77f233a484 caddyhttp: Corrected host label index check (fix #3502) 2020-06-30 11:43:01 -06:00
James Birtles ddd690de4c caddyhttp: Support placeholders in query matcher (#3521) 2020-06-26 15:14:47 -06:00
Mark Sargent 6004d3f779 caddyhttp: Add 'map' handler (#3199)
* inital map implementation

* resolve the value during middleware execution

* use regex instead

* pr feedback

* renamed mmap to maphandler

* refactored GetString implementation

* fixed mispelling

* additional feedback
2020-06-26 15:12:37 -06:00
Francis Lavoie caca55e582 ci: Fix release publish trigger (#3524)
Looks like event payloads need to be prefixed with `github.event` to get the actual payload contents. Didn't dig deep enough.

https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#github-context
2020-06-26 16:00:54 -04:00
Matthew Holt c9049bdc24 go.mod: Minor dependency updates 2020-06-26 12:19:51 -06:00
Matt Holt 21c00a3cd2 caddyhttp: Better host matching for logger names (fix #3488) (#3522)
First try an exact lookup like before, but if it fails, strip the port
and try again. example.com:1234 should still use a logger keyed for
example.com if there is no key example.com:1234.
2020-06-26 12:01:50 -06:00
Francis Lavoie 61b7002d26 ci: Apparently only single-quote strings are supported (#3523)
https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#literals

https://github.com/caddyserver/caddy/actions/runs/147953515
2020-06-25 21:58:44 +00:00
Francis Lavoie b1480eb52f fastcgi: Fix php_fastcgi matcher regression (#3512) 2020-06-22 11:45:18 -06:00
Xiuming Chen 5bc4777be9 chore: Fix typo in reverse-proxy subcommand help message (#3513) 2020-06-22 00:40:54 -04:00
Matthew Holt 3af15c0725 caddyhttp: Empty, not nil, query matcher matches empty query string 2020-06-16 12:02:23 -06:00
Matthew Holt 6db3615547 caddyhttp: Enable matching empty query string
Caddyfile syntax: query ""

Or a nil matcher in the JSON should also match an empty query string.

See https://caddy.community/t/v2-match-empty-query/8708?u=matt
2020-06-16 10:41:37 -06:00
Matthew Holt 32cafbb630 httpcaddyfile: Fix ordering of catch-all site blocks
Catch-alls should always go last. Normally this is the case, but we have
a special case for comparing one wildcard-host site block to another
non-wildcard host site block; and a catch-all site block is also a
non-wildcard host site block, so now we have to special-case the
catch-all site block. Sigh.

This could be reproduced with a Caddyfile that has two site blocks:
":80" and "*.example.com", in that order.
2020-06-16 10:02:06 -06:00
Francis Lavoie 003403ecbc templates: Add support for dots to close yaml frontmatter (#3498)
* templates: Add support for dots to close yaml frontmatter

* templates: Fix regression in body output
2020-06-15 12:38:51 -06:00
Mohammed Al Sahaf 5b48f784ae ci: don't run s390x tests on PRs of forks (#3494)
* ci: don't run s390x tests on PRs of forks

* ci: check if fork by matchinging name from event against name of repo
2020-06-12 19:51:04 +00:00
Chris Ortman d84a5d8427 httpcaddyfile: New acme_eab option (#3492)
* Adds global options for external account bindings

* Maybe other people use ctags too?

* Use nested block to configure external account

* go format files

* Restore acme_ca directive in test file

* Change Caddyfile config syntax for acme_eab

* Update test

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-06-12 13:37:56 -06:00
Mohammed Al Sahaf 7da32f493a ci: skip s390x tests on forks (#3493) 2020-06-12 18:03:29 +00:00
Matthew Holt cb0d9838cb go.mod: Update quic-go to 0.17.1 (draft 29) and certmagic 0.11.2 (eab) 2020-06-12 11:52:12 -06:00
Matthew Holt d81a69ef16 Merge branch 'eab-fix' 2020-06-12 11:49:45 -06:00
Mohammed Al Sahaf 99dcc10f31 ci: add CI on s390x (#3463)
* ci: lay out foundation for s390x tests

* ci: uncomment the s390x test script & replace placeholders with real values

* ci: amend the s390x test job name to be more consistent with others
2020-06-12 17:11:46 +00:00
Wynn Wolf Arbor fa4cdde7d8 fastcgi: Make sure splitPos handles empty SplitPath correctly (#3491)
In commit f2ce81c, support for multiple path splitters was added. The
type of SplitPath changed from string to []string, and splitPos was
changed to loop through all values in SplitPath.

Before that commit, if SplitPath was empty, strings.Index returned 0 and
PATH_INFO was set correctly in buildEnv.

Currently, however, splitPos returns -1 for empty values of SplitPath,
behaving as if a split position could not be found at all. PATH_INFO is
then never set in buildEnv and remains empty.

Restore the old behaviour by explicitly checking whether SplitPath is
empty and returning 0 in splitPos.

Closes #3490
2020-06-12 10:07:59 -06:00
Matthew Holt d55c3b31eb caddyhttp: Add client cert SAN placeholders 2020-06-11 16:19:07 -06:00
Matthew Holt 6d03fb48f9 caddytls: Don't decode HMAC
https://caddy.community/t/trouble-with-external-account-hmac/8600?u=matt
2020-06-11 15:33:27 -06:00
Matthew Holt b3bff13f7d reverseproxy: Close websocket conn if req context cancels
This is a recent patch in the Go standard library
2020-06-11 15:25:26 -06:00
Francis Lavoie 7211101c52 ci: Fix gemfury upload condition, move triggers to publish event (#3483) 2020-06-08 12:21:20 -06:00
Mohammed Al Sahaf 90dba172cb ci: fix an oopsie in the release script (#3482) 2020-06-08 11:10:28 -06:00
Matthew Holt 4b10ae5ce6 reverseproxy: Add Caddyfile support for ClientCertificateAutomate 2020-06-08 10:30:26 -06:00
NWHirschfeld 1dfb11486e httpcaddyfile: Add client_auth options to tls directive (#3335)
* reading client certificate config from Caddyfile

Signed-off-by: NWHirschfeld <Niclas@NWHirschfeld.de>

* Update caddyconfig/httpcaddyfile/builtins.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* added adapt test for parsing client certificate configuration from Caddyfile

Signed-off-by: NWHirschfeld <Niclas@NWHirschfeld.de>

* read client ca and leaf certificates from file https://github.com/caddyserver/caddy/pull/3335#discussion_r421633844

Signed-off-by: NWHirschfeld <Niclas@NWHirschfeld.de>

* Update modules/caddytls/connpolicy.go

* Make review adjustments

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-06-05 12:19:36 -06:00
Matthew Holt 11a132d48b caddytls: Configurable cache size limit 2020-06-05 11:14:39 -06:00
Matthew Holt 9dafa63933 go.mod: Update dependencies 2020-06-05 11:14:09 -06:00
Francis Lavoie 21c1da101c ci: Disable publishing .deb on beta tags (#3473) 2020-06-05 10:23:15 -06:00
Matthew Holt 7a99835dab reverseproxy: Enable changing only the status code (close #2920) 2020-06-04 12:06:38 -06:00
Matthew Holt 7b0962ba4d caddyhttp: Default to error status if found in context
This is just a convenience if using a static_response handler in an
error route, by setting the default status code to the same one as
the error status.
2020-06-04 10:32:01 -06:00
Matthew Holt 2d1f7b9da8 caddyhttp: Auto-redirects from all bind addresses (fix #3443) 2020-06-03 10:56:26 -06:00
Matthew Holt a285fe4129 caddypki: Add 'acme_server' Caddyfile directive 2020-06-03 09:59:36 -06:00
Matthew Holt 97e61c16a3 httpcaddyfile: Sort site blocks with wildcards last (fix #3410) 2020-06-03 09:35:13 -06:00
Matthew Holt 83551edf3e cmd: Only stop admin server on signal if it exists (fix #3470) 2020-06-03 07:31:31 -06:00
Matthew Holt e18c373064 caddytls: Actually use configured test CA 2020-06-02 11:13:44 -06:00
Matt Holt 9a7756c6e4 caddyauth: Cache basicauth results (fixes #3462) (#3465)
Cache capacity is currently hard-coded at 1000 with random eviction.
It is enabled by default from Caddyfile configurations because I assume
this is the most common preference.
2020-06-01 23:56:47 -06:00
Francis Lavoie fdf2a77feb caddyfile: Add args on imports (#3423)
* caddyfile: Add support for args on imports

* caddyfile: Add more import args tests
2020-06-01 10:43:06 -06:00
Georges Haidar a496308f6e httpcaddyfile: Let modules add listener wrappers (#3397)
* httpcaddyfile: allow modules to customize listener wrappers

* Update caddyconfig/httpcaddyfile/httptype.go

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

* Update caddyconfig/httpcaddyfile/httptype.go

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

* Update caddyconfig/httpcaddyfile/httptype.go

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

* Update caddyconfig/httpcaddyfile/httptype.go

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-06-01 09:50:00 -06:00
Matthew Holt d5d7fb5954 go.mod: Update dependencies 2020-06-01 09:31:08 -06:00
Matt Holt 996af0915d cmd: Support admin endpoint on unix socket (#3320) 2020-05-29 14:21:55 -06:00
Matthew Holt 6c051cd27d caddyconfig: Minor internal and godoc tweaks 2020-05-29 11:49:25 -06:00
Matt Holt 9415feca7c logging: Net writer redials if write fails (#3453)
* logging: Net writer redials if write fails

https://caddy.community/t/v2-log-output-net-does-not-reconnect-after-lost-connection/8386?u=matt

* Only replace connection if redial succeeds

* Fix error handling
2020-05-28 10:40:14 -06:00
Matthew Holt 881b826fb5 reverseproxy: Pool copy buffers (minor optimization) 2020-05-27 11:42:19 -06:00
Matthew Holt 538ddb8587 reverseproxy: Enable response interception (#1447, #2920)
It's a raw, low-level implementation for now, but it's very flexible.
More sugar-coating can be added after error handling is more developed.
2020-05-27 10:17:45 -06:00
Francis Lavoie 69b5643130 chore: Fix typo in dispenser.go (#3456) 2020-05-27 08:13:57 -06:00
Matthew Holt e5bbed1046 caddyhttp: Refactor header matching
This allows response matchers to benefit from the same matching logic
as the request header matchers (mainly prefix/suffix wildcards).
2020-05-26 17:35:27 -06:00
Matthew Holt 294910c68c caddyhttp: Add client.public_key(_sha256) placeholders 2020-05-26 15:52:53 -06:00
Francis Lavoie 8c5d00b2bc httpcaddyfile: New handle_path directive (#3281)
* caddyconfig: WIP implementation of handle_path

* caddyconfig: Complete the implementation - h.NewRoute was key

* caddyconfig: Add handle_path integration test

* caddyhttp: Use the path matcher as-is, strip the trailing *, update test
2020-05-26 15:27:51 -06:00
Rui Lopes aa20878887 cmd: file-server: add --access-log flag (#3454) 2020-05-26 15:04:04 -06:00
Francis Lavoie c1e5c09294 reverseproxy: Improve error message when using scheme+placeholder (#3393)
* reverseproxy: Improve error message when using scheme+placeholder

* reverseproxy: Simplify error message

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-05-26 14:13:15 -06:00
Francis Lavoie ffc125d6f5 caddyfile: Move NewTestDispenser into non-test file (#3439) 2020-05-26 13:45:22 -06:00
AndyBan 22055c5e0f reverseproxy: Fix https active health checks #3450 (#3451) 2020-05-26 12:40:57 -06:00
Mohammed Al Sahaf dfe802aed3 chore: forego the use of deprecated cel func NewIdent in favor of NewVar (#3444) 2020-05-25 03:59:38 +00:00
Mohammed Al Sahaf 7a365af5df chore: simplify goreleaser flow, add bash completions to .deb (#3436) 2020-05-22 15:13:31 -04:00
Matthew Holt 0cbf467b3f caddyhttp: Add time.now placeholder and update cel-go (closes #2594) 2020-05-21 18:19:01 -06:00
Francis Lavoie bb67e19d7b cmd: hash-password: Fix broken terminal state on SIGINT (#3416)
* caddyauth: Fix hash-password broken terminal state on SIGINT

* caddycmd: Move TrapSignals calls to only subcommands that run long
2020-05-21 13:09:49 -06:00
Matthew Holt 1dc4ec2d77 admin: Disallow websockets
No currently-known exploit here, just being conservative
2020-05-21 12:29:19 -06:00
Matt Holt 452d4726f7 Update SECURITY.md 2020-05-20 14:24:47 -06:00
Matthew Holt 2a8a198568 reverseproxy: Don't overwrite existing X-Forwarded-Proto header
Correct behavior is not well defined because this is a non-standard
header field. This could be a "hop-by-hop" field much like
X-Forwarded-For is, but even our X-Forwarded-For implementation
preserves prior entries. Or, it could be best to preserve the original
value from the first hop, representing the protocol as facing the
client.

Let's try it the other way for a bit and see how it goes.

See https://caddy.community/t/caddy2-w-wordpress-behind-nginx-reverse-proxy/8174/3?u=matt
2020-05-20 11:33:17 -06:00
Francis Lavoie cc8fb488d3 httpcaddyfile: Improve error on matcher declared outside site block (#3431) 2020-05-20 10:37:48 -06:00
Francis Lavoie fae064262d httpcaddyfile: Add auto_https global option (#3284) 2020-05-19 16:59:51 -06:00
Matthew Holt 9ee01dceac reverseproxy: Make debug log safe if error occurs 2020-05-18 14:08:11 -06:00
Matthew Holt 812278acd8 reverseproxy: Emit debug log before checking error (#3425)
This way the upstream request will always be available even if it failed
2020-05-18 13:50:46 -06:00
Matthew Holt c47ddbeffb pki: Add docs to some struct fields 2020-05-18 13:50:46 -06:00
Thorkild Gregersen 483e31b978 templates: trim windows whitespace in SplitFrontMatter; fix #3386 (#3387)
* add test case for SplitFrontMatter showing issue with windows newline

* fix issue with windows newline when using SplitFrontMatter

* Update modules/caddyhttp/templates/frontmatter.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

* make it mere explicit what is trimmed from firstLine

* Update modules/caddyhttp/templates/frontmatter.go

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-05-18 13:01:04 -06:00
Francis Lavoie 41a682ddde caddyauth: Add realm to basicauth Caddyfile directive (#3315) 2020-05-18 12:19:28 -06:00
Francis Lavoie 7243454a96 fastcgi: php_fastcgi subdirectives to override shortcut behaviour (#3255)
* fastcgi: Add new php_fastcgi subdirectives to override the shortcut

* fastcgi: Support "index off" to disable redir and try_files

* fastcgi: Remove whitespace to satisfy linter

* fastcgi: Run gofmt

* fastcgi: Make a new dispenser instead of using rewind

* fastcgi: Some fmt

* fastcgi: Add a couple adapt tests

* fastcgi: Clean up for loops

* fastcgi: Move adapt tests to separate files
2020-05-18 12:15:38 -06:00
Matthew Holt 3fb2c394d1 go.mod: Update dependencies
Notably, this adds Caddyfile syntax highlighting in markdown rendering
2020-05-17 17:12:34 -06:00
Francis Lavoie 21de227fe9 httpcaddyfile: Be stricter about log syntax (#3419) 2020-05-15 15:57:16 -06:00
elcore 62c9f2cf3e cmd: Add --envfile flag to run command (#3278)
* run: Add the possibility to load an env file

* run: change envfile flag var

* run: do not ignore err values

* Apply suggestions from code review

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-05-15 15:49:51 -06:00
Francis Lavoie bde3823b76 caddytest: Refactor Caddyfile adapt tests to separate files (#3398) 2020-05-14 17:53:28 -04:00
Matthew Holt 4df56c77e3 cmd: Add pidfile support (closes #3235) 2020-05-13 11:28:15 -06:00
Mohammed Al Sahaf cee5589b98 docs: link to CEL standard definitions (#3407)
* docs: link to CEL standard definitions

* Rephrase the anchor to CEL standard definitions

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

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-05-13 17:11:31 +00:00
Matt Holt 90c7b4b0a1 reverseproxy: Apply response header ops before copying it (fix #3382) (#3401) 2020-05-13 09:52:20 -06:00
Matthew Holt aef560c7fc all: Recover from panics in goroutines 2020-05-12 11:36:20 -06:00
linquize 44536a7594 cmd: reverse-proxy: add --insecure flag (with warning) (#3389) 2020-05-12 10:43:18 -06:00
Francis Lavoie ea7e4b4024 httpcaddyfile: Shorthands for parameterized placeholders (#3305)
* httpcaddyfile: Add shorthands for parameterized placeholders


httpcaddyfile: Now with regexp instead


httpcaddyfile: Allow dashes, gofmt


httpcaddyfile: Compile regexp only once


httpcaddyfile: Cleanup struct


httpcaddyfile: Optimize the replacers, pull out of the loop


httpcaddyfile: Add `{port}` shorthand

* httpcaddyfile: Switch `r.` to `re.`
2020-05-11 16:50:49 -06:00
Francis Lavoie ef6e53bb5f core: Add support for d duration unit (#3323)
* caddy: Add support for `d` duration unit

* Improvements to ParseDuration; add unit tests

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-05-11 16:41:11 -06:00
Francis Lavoie 35e1d92d58 ci: Delete .travis.yml (#3396)
Too flaky. We'll explore different avenues to testing s390x and ppc64le.

See discussion here: https://github.com/caddyserver/caddy/pull/3355

/cc @grooverdan, @Mohammed90 said he'll reach out to Elizabeth as you suggested.
2020-05-11 15:07:02 -06:00
Francis Lavoie dc9f4f13fc httpcaddyfile: Make global options pluggable (#3265)
* httpcaddyfile: Make global options pluggable

* httpcaddyfile: Add a global options adapt test

* httpcaddyfile: Wrap err

Co-Authored-By: Dave Henderson <dhenderson@gmail.com>

* httpcaddyfile: Revert wrap err

Co-authored-by: Dave Henderson <dhenderson@gmail.com>
2020-05-11 15:00:35 -06:00
Francis Lavoie 4c55d26f11 caddyhttp: Fix merging of Caddyfile matchers in not blocks (#3379) 2020-05-11 14:38:33 -06:00
Gregory Dosh d534162556 caddyhttp: Match hostnames with wildcards to loggers (#3378)
* adding wildcard matching of logger names

* reordering precedence for more specific loggers to match first

* removing dependence on certmagic and extra loop

Co-authored-by: GregoryDosh <GregoryDosh@users.noreply.github.com>
2020-05-11 14:17:59 -06:00
Andrew Zhou 5bde8d705b cmd: hash-password: Support reading from stdin (#3373)
Closes #3365 

* http: Add support in hash-password for reading from terminals/stdin

* FIXUP: Run gofmt -s

* FIXUP

* FIXUP: Apply suggestions from code review

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

* FIXUP

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-05-11 14:10:47 -06:00
Matthew Holt 7960b4259d caddyhttp: Minor refactoring for preparing requests
While building a layer4 app for Caddy, I discovered that we need the
ability to fill a request's context just like the HTTP server does,
hence this exported function PrepareRequest().
2020-05-11 12:14:47 -06:00
Mark Sargent 2c91688f39 fix testharness, dumps the current config, only if the config was successfully loaded (#3385) 2020-05-10 08:11:35 +12:00
Chandler Swift 513e0240fd docs: Fix TOC/section header mismatch (#3380) 2020-05-08 19:46:40 -04:00
Jeremy Lin bf8c3c25c1 log: improve rounding logic for log rolling directives (#3367)
* For `roll_size` and `roll_keep_for` directives, round up instead of down.
  For example, if a user wants to be able to look back on 36 hours of logs,
  but you must round to a 24-hour multiple, then it's better to round up to
  48 hours (which includes the desired 36 hours) instead of down to 24 hours.

* `roll_size` had an off-by-one error that caused the size to be as much as
  1 MB larger than requested. For example, requests of `1MB` and `1.1MB`
  both became 2 MB. Now `1MB` means 1 MB, and `1.1MB` is rounded up to 2 MB.
2020-05-07 13:06:00 -06:00
Matthew Holt c8da8ca673 Update readme 2020-05-07 13:01:33 -06:00
Jose Donizetti 43fba378d6 docs: Fix command.Func documentation (#3371) 2020-05-07 09:31:58 -06:00
Matthew Holt cd9317e5df httpcaddyfile: Fix route ordering bug
https://caddy.community/t/cant-get-simple-alias-to-work/7911/8?u=matt

This removes an optimization where we amortized path matcher decoding.
The decoded matchers were index by... position... which obviously
changes during sorting. Duh.

Anyway, sorting is sliiightly slower now but the Caddyfile is not
really CPU-sensitive, so this is fine.
2020-05-06 19:41:37 -06:00
Matthew Holt 8dbc5f70a5 Update dependencies and get rid of placeholder hacks in CA code
With the latest commit on smallstep/certificates, placeholders in config
are no longer needed.
2020-05-06 16:02:21 -06:00
Francis Lavoie 07c6076ea0 ci: Add release tagged event triggers to sister repos (#3321) 2020-05-06 16:42:55 -04:00
Matthew Holt 28ab0bfb13 core: Support loading modules from [][]json.RawMessage fields 2020-05-06 13:18:56 -06:00
Matthew Holt 1c17e6c6bb reverseproxy: Allow using TLS for port 80 upstreams (see #3361)
An upstream like https://localhost:80 is still forbidden, but an addr of
localhost:80 can be used while explicitly enabling TLS as an override;
we just don't allow the implicit behavior to be ambiguous.
2020-05-06 12:37:44 -06:00
Karol Będkowski b814c0af9c tls/client auth: verify first certificates in client request (#3344)
When client certificate is enabled Caddy check only last certificate from
request. When this cert is not in list of trusted leaf certificates,
connection is rejected. According to RFC TLS1.x the sender's certificate
must come first in the list.  Each following certificate must directly
certify the one preceding it.

This patch fix this problem - first certificate is checked instead of last.
2020-05-06 10:07:13 -06:00
Dave Henderson 9e5d9e2530 ci: Add linux-armv5 builds (#3356)
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2020-05-05 19:13:56 -06:00
Dave Henderson 9408dacc27 Fixing goreleaser syntax error (#3355)
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2020-05-06 00:50:30 +00:00
Daniel Black 12cfc19487 ci: add s390x and ppc64le builds for linux (#3325) 2020-05-05 12:41:08 -06:00
Francis Lavoie afecd90a6c reverseproxy: Add tls_server_name option to Caddyfile (#3322) 2020-05-05 12:39:39 -06:00
Matt Holt 2f59467ac3 httpcaddyfile: Only append TLS conn policy if it's non-empty (#3319)
This can lead to nicer, smaller JSON output for Caddyfiles like this:

	a {
		tls internal
	}
	b {
		tls foo@bar.com
	}

i.e. where the tls directive only configures automation policies, and
is merely meant to enable TLS on a server block (if it wasn't implied).
This helps keeps implicit config implicit.

Needs a little more testing to ensure it doesn't break anything
important.
2020-05-05 12:37:52 -06:00
Matt Holt 184e8e9f71 pki: Embedded ACME server (#3198)
* pki: Initial commit of embedded ACME server (#3021)

* reverseproxy: Support auto-managed TLS client certificates (#3021)

* A little cleanup after today's review session
2020-05-05 12:35:32 -06:00
Matt Holt 1e8c9764df file_server: Accept files args in one-liner of Caddyfile matcher (#3298)
Previously, matching by trying files other than the actual path of the
URI was:

    file {
        try_files <files...>
    }

Now, the same can be done in one line:

    file <files...>

As before, an empty file matcher:

    file

still matches if the request URI exists as a file in the site root.
2020-05-05 12:34:58 -06:00
Matt Holt 41c7bd27b4 httpserver: Add experimental H2C support (#3289)
* reverse_proxy: Initial attempt at H2C transport/client support (#3218)

I have not tested this yet

* Experimentally enabling H2C server support (closes #3227)

See also #3218

I have not tested this

* reverseproxy: Clean up H2C transport a bit

* caddyhttp: Update godoc for h2c server; clarify experimental status

* caddyhttp: Fix trailers when recording responses (fixes #3236)

* caddyhttp: Tweak h2c config settings and docs
2020-05-05 12:33:21 -06:00
Francis Lavoie 96d6d277a4 caddyconfig: Don't start comments in middle of tokens (#3267)
* caddyconfig: Only parse # as start of comment if preceded by space

* caddyconfig: Simplify # logic using len(val), add a test
2020-05-05 12:32:12 -06:00
Francis Lavoie 26e559662d httpcaddyfile: Support single-line matchers (#3263)
* httpcaddyfile: Support single-line matchers

* httpcaddyfile: Add single-line matcher test

* httpcaddyfile: Add a matcher syntax adapt test
2020-05-05 12:29:21 -06:00
Matt Holt 52305618df caddyfile: Support backticks as quotes (closes #2591) (#3242) 2020-05-05 12:27:49 -06:00
Mohammed Al Sahaf e051e119d1 ci: add tests on s390x and ppc64le (#3328)
* ci: add tests on s390x and ppc64le

* ci: use Travis as CI for ppc64le and s390x

* ci: cache Go builds on Travis

* ci: avoid Travis duplicate builds
2020-05-02 17:24:54 -06:00
Matthew Holt 8e42661060 caddytls: Finish upgrading to libdns DNS providers for ACME challenges
Until we finish the migration to the new acme library, we have to bring
the solver type in-house. It's small and temporary.
2020-05-02 17:23:36 -06:00
Matthew Holt 86a4f2c9f4 caddytls: Fix namespace tls.dns -> dns.providers
Coulda sworn I did this already but I think I messed up my git commands
2020-05-02 16:28:10 -06:00
Matthew Holt a507a5bbc7 reverseproxy: Remove circuitbreaker module (see #3331)
Moving to https://github.com/caddyserver/circuitbreaker

Nobody was using it anyway -- it works well, but something got fumbled
in a refactoring *months* ago. Turns out that we forgot the interface
guards AND botched a method name (my bad) - Ok() should have been OK().
So it would always have thrown a runtime panic if it tried to be loaded.
The module itself works well, but obviously nobody used it because
nobody reported the error. Fixing this while we move it to the new repo.

Removing this removes the last Bazaar/Launchpad dependency (I think).
2020-05-01 19:47:46 -06:00
Mark Sargent d0770dbbb3 expose caddytest timeouts (#3329) 2020-05-02 10:24:35 +12:00
Matthew Holt a77bd1d887 httpcaddyfile: Update tls parsing for DNS providers 2020-05-01 10:41:08 -06:00
Matthew Holt bca610fbde httpcaddyfile: Minor fixes to parsing storage options 2020-05-01 09:34:32 -06:00
Matthew Holt 1fa8c185a8 go.mod: Remove DNSProviderMaker interface; update to lego 3.6 2020-04-30 18:17:39 -06:00
Matthew Holt a1796c2f14 caddytls: Adjust DNS challenge structure; clarify some docs 2020-04-30 16:15:20 -06:00
Matthew Holt f931c26f68 caddyhttp: Better duration logging
Also un-nest all the error handling, that was unnecessary indentation
2020-04-28 15:38:45 -06:00
Matt Holt 10db57027d caddyhttp: General improvements to access logging (#3301)
* httpcaddyfile: Exclude access logs written to files from default log

Even though any logs can just be ignored, most users don't seem to like
configuring an access log to go to a file only to have it doubly appear
in the default log.

Related to:
- #3294
- https://caddy.community/t/v2-logging-format/7642/4?u=matt
- https://caddy.community/t/caddyfile-questions/7651/3?u=matt

* caddyhttp: General improvements to access log controls (fixes #3310)

* caddyhttp: Move log config nil check higher

* Rename LoggerName -> DefaultLoggerName
2020-04-28 08:32:04 -06:00
Matthew Holt c11d0e47a3 cmd: Clean up, simplify reverse proxy command; fix some edge cases
Now we take advantage of the address parsing capabilities of the HTTP
caddyfile.
2020-04-27 15:53:38 -06:00
Matthew Holt 9770ce7c9f Minor comment fix 2020-04-27 14:49:27 -06:00
Francis Lavoie 5ae1a5617c caddyhttp: Add split_path to file matcher (used by php_fastcgi) (#3302)
* matcher: Add `split_path` option to file matcher; used in php_fastcgi

* matcher: Skip try_files split if not the final part of the filename

* matcher: Add MatchFile tests

* matcher: Clarify SplitPath godoc
2020-04-27 14:46:46 -06:00
Matthew Holt 83c85c53f5 caddyhttp: Fix listener overlap detection on Linux
Sigh, apparently Linux is incapable of distinguishing host interfaces
in socket addresses, even though it works fine on Mac. I suppose we just
have to assume that any listeners with the same port are the same
address, completely ignoring the host interface on Linux... oh well.
2020-04-26 22:28:49 -06:00
Francis Lavoie 768383a610 ci: Enable GoReleaser .deb support (#3309)
* ci: Enable GoReleaser .deb support

* ci: Test .deb build

* ci: Fix typo

* ci: Turn off snapshot (breaks due to go mod edit)

* ci: Force the tag to rc3 for now

* ci: Let's try to publish the .debs

* ci: Attempt to enable build cache, rebuild after fixed line endings

* ci: Fix yml dupe ID issue, add caddy-api.service

* ci: Split cache keys between files so they're separate

* ci: Fix bindir

* ci: Update the script files

* ci: Retrigger

* ci: Push to gemfury

* ci: Use loop, fix bad env var

* ci: Retrigger

* ci: Try to force blank password?

* ci: Check if the token is actually present

* ci: Cleanup, remove debugging stuff

* ci: Remove useless comment
2020-04-26 20:20:14 -06:00
Mark Sargent 570d84f7d3 refactored caddytest helpers (#3285)
* refactored caddytest helpers
* added cookie jar support. Added support for more http verbs
2020-04-27 13:23:46 +12:00
Christoffer Andersson a6761153cb Fix misspelling in onDemandAskRequest error (#3308) 2020-04-25 10:34:56 -06:00
Matthew Holt 02845bc9fd docs: Improve template documentation slightly; use const, not literal 2020-04-24 21:05:09 -06:00
Matthew Holt 97ed9e111d httpcaddyfile: Add nil check to prevent panic, fix validation logic
Panic would happen if an automation policy was specified in a singular
server block that had no hostnames in its address. Definitely an edge
case.

Fixed a bug related to checking for server blocks with a host-less key
that tried to make an automation policy. Previously if you had only two
server blocks like ":443" and another one at ":80", the one at ":443"
could not create a TLS automation policy because it thought it would
interfere with TLS automation for the block at ":80", but obviously that
key doesn't enable TLS because it is on the HTTP port. So now we are a
little smarter and count only non-HTTP-empty-hostname keys.

Also fixed a bug so that a key like "https://:1234" is sure to have TLS
enabled by giving it a TLS connection policy. (Relaxed conditions
slightly; the previous conditions were too strict, requiring there to be
a TLS conn policy already or a default SNI to be non-empty.)

Also clarified a comment thanks to feedback from @Mohammed90
2020-04-24 20:57:51 -06:00
Matthew Holt 100d19e3af dangit, of course I would bork my git commit 2020-04-24 17:48:33 -06:00
Matthew Holt ebf07f853b caddyhttp: Fix auto redirects for catch-all HTTPS sites
Prior logic was not setting up redirects for the case when domain names
are not known, but the server still clearly has TLS enabled.
2020-04-24 17:36:52 -06:00
Matthew Holt 1b061815b2 reverseproxy: Don't forget to provision embedded headers handler
https://caddy.community/t/set-cookie-manipulation-in-reverse-proxy/7666?u=matt
2020-04-22 19:57:06 -06:00
Matthew Holt 026937fab5 caddyhttp: Fix trailers when recording responses (fixes #3236) 2020-04-22 11:10:13 -06:00
Matthew Holt 295604d6df httpcaddyfile: Why was this code repeated?? 2020-04-22 09:20:39 -06:00
Francis Lavoie bacf50a59e caddyhttp: Fix common_log format's user ID placeholder (#3300) 2020-04-22 09:05:26 -06:00
westwin da8686c4b9 reverseproxy: always set req.URL.Host with upstream (#3297) 2020-04-21 20:34:00 -06:00
Matthew Holt e3a8f72f1c docs: Minor improvements 2020-04-21 19:30:04 -06:00
Mohammed Al Sahaf bae4f15fad ci: fuzz: remove the fuzzer of the Caddyfile parser (#3288) 2020-04-20 15:21:19 -06:00
Francis Lavoie 0798459e44 readme: Fix broken links (#3283)
Credit to @kanagawa41 for spotting these!

Fixes #3282
2020-04-19 17:37:03 -06:00
Matthew Holt f980170909 doc: Improve comment 2020-04-17 12:03:57 -06:00
Francis Lavoie 6963a72a63 ci: Cache the GOCACHE directory to speed up builds and tests (#3273)
* ci: Let's see if caching GOCACHE helps...

* ci: Use GOCACHE env instead (fixes windows), remove build -a

* ci: Hack to pull the GOCACHE env up to CI vars

* ci: Change cache key (mainly to wipe cache now)
2020-04-17 11:54:35 -06:00
Matt Holt 76bbb473a5 reverseproxy: Set X-Forwarded-Proto (closes #3275) (#3276) 2020-04-17 09:53:06 -06:00
Francis Lavoie 3c70950fa1 docs: Pull contributing document from v1 branch (#3270)
* docs: Pull contributing document from v1 branch

* Update .github/CONTRIBUTING.md

Co-Authored-By: Matt Holt <mholt@users.noreply.github.com>

* docs: [Responsible -> Coordinated] Disclosure

* docs: Link to the new security policy page

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-04-16 18:32:42 -06:00
Matthew Holt 7c171542ed Add security policy 2020-04-16 17:20:03 -06:00
Matthew Holt 9a572635f5 admin: Close admin endpoint when shutting down (fixes #3269) 2020-04-16 12:34:28 -06:00
Matthew Holt f5ccb904a3 admin: Disable host checking if wildcard interface is specified
To clarify, listening on wildcard interfaces is NOT the default and
should only be done under certain circumstances and when you know
what you're doing. Emits a warning in the log.

Fixes https://github.com/caddyserver/caddy-docker/issues/71
2020-04-16 11:41:32 -06:00
Matthew Holt 829e36d535 httpcaddyfile: Don't lowercase placeholder contents (fixes #3264) 2020-04-14 16:11:46 -06:00
Matthew Holt 2609a72893 go.mod: Update dependencies including CertMagic (fixes #3202) 2020-04-14 11:28:41 -06:00
Matthew Holt ec456811bb core: Don't return error on RegisterModule() and RegisterAdapter()
These functions are called at init-time, and their inputs are hard-coded
so there are no environmental or user factors that could make it fail
or succeed; the error return values are often ignored, and when they're
not, they are usually a fatal error anyway. To ensure that a programmer
mistake is not missed, we now panic instead.

Last breaking change 🤞
2020-04-13 09:48:54 -06:00
Matthew Holt 68cebb28d0 Fix some godocs 2020-04-11 09:01:40 -06:00
Matthew Holt a3bdc22234 admin: Always enforce Host header checks
With a simple heuristic for loopback addresses, we can enable this by
default without adding unnecessary inconvenience.
2020-04-10 17:31:38 -06:00
Matthew Holt d3383ced2a Update link in readme 2020-04-10 09:19:03 -06:00
Matthew Holt c024ae096d tests: Clean up redundant type declarations 2020-04-10 08:48:21 -06:00
Matthew Holt 3bee569a8a httpcaddyfile: Don't remove empty TLS conn policies (fix #3249)
Not sure why I thought that would be a good idea
2020-04-10 08:24:12 -06:00
Matthew Holt 999ab22b8c caddyhttp: Add nil check (fixes #3248 and fixes #3250) 2020-04-10 08:12:42 -06:00
Matthew Holt 9991fdc495 Update readme 2020-04-10 08:10:35 -06:00
Matthew Holt f29023bf8f reverseproxy: Minor tweaks
We'll need that context in v2.1 when the transport can manage its own
client certificates; see #3198
2020-04-09 13:22:05 -06:00
Matthew Holt 85f5f47f31 caddytls: Don't initialize default internal issuer unless necessary
Otherwise, a password prompt can occur unnecessarily.
2020-04-09 13:09:48 -06:00
Matthew Holt 6e4132eb89 logging: Colorize output in all cases of stdout/stderr 2020-04-09 13:06:06 -06:00
Matt Holt d89ad2fd5b caddytls: Fix for TLS conn policy being applied to HTTP-only servers (#3243)
* httpcaddyfile: Don't add TLS policy to HTTP-only server (#3193, #3223)

* Account for HTTP port

* Add integration test written by @sarge
2020-04-09 12:39:05 -06:00
Matthew Holt d33926b63f go.mod: Update certmagic 2020-04-09 12:32:57 -06:00
Matthew Holt c5f9227a48 go.mod: Try smallstep again
See if the broken dependency cycle has been... well, broken
2020-04-09 12:10:52 -06:00
Matthew Holt 88d391c1f5 go.mod: Update smallstep/cli 2020-04-09 11:16:47 -06:00
Matthew Holt b4a7d6267f go.mod: Update dependencies
Should fix the builds with GOPROXY=direct!
2020-04-09 10:57:23 -06:00
Matthew Holt e5dc76b054 caddyhttp: CEL matcher checks return type; slight refactor
As per https://github.com/caddyserver/caddy/issues/3051#issuecomment-611200414
2020-04-08 15:39:30 -06:00
Mohammed Al Sahaf 7dfd69cdc5 chore: make the linter happier (#3245)
* chore: make the linter happier

* chore: remove reference to maligned linter in .golangci.yml
2020-04-08 15:31:51 -06:00
Matthew Holt 28fdf64dc5 httpcaddyfile, caddytls: Multiple edge case fixes; add tests
- Create two default automation policies; if the TLS app is used in
  isolation with the 'automate' certificate loader, it will now use
  an internal issuer for internal-only names, and an ACME issuer for
  all other names by default.
- If the HTTP Caddyfile adds an 'automate' loader, it now also adds an
  automation policy for any names in that loader that do not qualify
  for public certificates so that they will be issued internally. (It
  might be nice if this wasn't necessary, but the alternative is to
  either make auto-HTTPS logic way more complex by scanning the names in
  the 'automate' loader, or to have an automation policy without an
  issuer switch between default issuer based on the name being issued
  a certificate - I think I like the latter option better, right now we
  do something kind of like that but at a level above each individual
  automation policies, we do that switch only when no automation
  policies match, rather than when a policy without an issuer does
  match.)
- Set the default LoggerName rather than a LoggerNames with an empty
  host value, which is now taken literally rather than as a catch-all.
- hostsFromKeys, the function that gets a list of hosts from server
  block keys, no longer returns an empty string in its resulting slice,
  ever.
2020-04-08 14:46:44 -06:00
Matthew Holt 0fe98038b6 caddyhttp: Fix logging name associations by adding a default 2020-04-08 14:39:20 -06:00
Matthew Holt 6e4c688ea7 logging: Only colorize console output 2020-04-08 14:37:37 -06:00
Francis Lavoie 5110643201 httpcaddyfile: Add key_type global option (#3231) 2020-04-08 11:09:38 -06:00
Matthew Holt 4d9b63d909 cel: Leverage DefaultAdapter to extend CEL's type system
Thanks to @TristonianJones for the tip!
https://github.com/caddyserver/caddy/commit/105acfa08664c97460a6fe3fb49635618be5bcb2#r38358983
2020-04-08 10:44:40 -06:00
Matthew Holt e30deedcc1 caddyhttp: Return port placeholders as ints 2020-04-08 10:44:40 -06:00
Matt Holt fbd9515d35 basicauth: Re-prompt after invalid credentials (fix #3239) (#3240) 2020-04-07 20:39:13 -06:00
Matthew Holt 95f6bd7e5c templates: Update docs 2020-04-07 12:29:09 -06:00
Matthew Holt b1ce9d4db7 templates: Add env function (closes #3237) 2020-04-07 12:26:08 -06:00
Matthew Holt 61679b74f5 Merge branch 'remove-ntlm' 2020-04-07 11:41:49 -06:00
Matthew Holt 2c1b663156 reverseproxy: Remove NTLM transport; refactor and improve docs 2020-04-07 11:39:14 -06:00
Matthew Holt 8b2dbc52ec core: Rename ParsedAddress -> NetworkAddress 2020-04-07 08:33:45 -06:00
Matthew Holt 657f0cab17 docs: Clarify "not" matcher structure (see #3233) 2020-04-06 18:44:12 -06:00
Francis Lavoie 7be747fbe9 caddyhttp: Add missing LB policy Caddyfile unmarshalers (#3230) 2020-04-06 13:08:42 -06:00
Francis Lavoie 5b355cbed0 caddyhttp: Strictly forbid unnecessary blocks on matchers (#3229) 2020-04-06 13:07:07 -06:00
Francis Lavoie a3cfe437b1 caddyhttp: Support single-line not matcher (#3228)
* caddyhttp: Support single-line not matcher shortcut

* caddyhttp: Some tests, I guess
2020-04-06 13:05:49 -06:00
Matthew Holt 437d5095a6 templates: Use text/template; add experimental notice to docs
Using html/template.HTML like we were doing before caused nested include
to be HTML-escaped, which breaks sites. Now we do not escape any of the
output; template input is usually trusted, and if it's not, users should
employ escaping actions within their templates to keep it safe. The docs
already said this.
2020-04-06 12:51:53 -06:00
Matthew Holt 145aebbba5 httpcaddyfile: Carry bind setting through to ACME issuer (fixes #3232) 2020-04-06 12:24:35 -06:00
Matthew Holt 6a32daa225 caddytls: Support custom bind host for challenges (#3232) 2020-04-06 11:22:06 -06:00
Matthew Holt 81cdebf648 tests: Remove noisy logs 2020-04-06 10:41:42 -06:00
Matthew Holt 84c729e96a ci: Tweak commit prefixes to ignore 2020-04-04 13:29:48 -06:00
Matthew Holt 346c33b4d5 cmd: Log warning if --resume and --config used together
There's nothing actually risky/dangerous in this situation, it's mostly
an attempt to get the user's attention
2020-04-04 13:29:48 -06:00
Mark Sargent 78717ce5b0 chore: add adapt tests. fix load failure not failing tests (#3222)
* add adaption tests. fix load failure not failing tests

* removed unnecessary assignment
2020-04-03 21:02:46 -06:00
Matthew Holt 3d6fc1e1b7 httpcaddyfile: Yield cleaner JSON when conn policy or log name is empty 2020-04-03 20:19:46 -06:00
Matthew Holt c7ac7de38a go.mod: Update CertMagic (again) v0.10.10 2020-04-03 17:46:43 -06:00
Matthew Holt 05164c895a go.mod: Use latest Certmagic (v0.10.9) 2020-04-03 16:16:22 -06:00
Matthew Holt 1e8af27329 fastcgi: Account for lack of split path configuration (fix #3221) 2020-04-03 10:25:25 -06:00
Matthew Holt b6482e53c1 go.mod: Update CertMagic to v0.10.8
Fixes occasional panic due to closing closed channel
2020-04-03 09:33:04 -06:00
Matt Holt 20f6795413 Create FUNDING.yml
I guess this got left in the v1 branch when we switched, oops
2020-04-03 09:07:14 -06:00
Matt Holt 84f16852ab ci: goreleaser: Drop some platforms and replacements (#3217)
Based on download stats, demand for 32-bit binaries these days is
extremely low. Also unify some of the filename conventions; just a
few bikeshedding changes :)
2020-04-02 18:07:57 -06:00
Matthew Holt 1456f15f9a readme: So much more ... what? Fixed cliffhanger 2020-04-02 16:46:52 -06:00
Mohammed Al Sahaf fdfe2ae53b chore: ci: fix release action script (#3216)
* chore: ci: fixing the step name that captures the pushed tag

* chrore: ci: exclude commits prefixed with `ci:` from changelog
2020-04-02 16:44:44 -06:00
Matthew Holt 1c190b001b httpcaddyfile: Refactor site key parsing; detect conflicting schemes
We now store the parsed site/server block keys with the server block,
rather than parsing the addresses every time we read them.

Also detect conflicting schemes, i.e. TLS and non-TLS cannot be served
from the same server (natively -- modules could be built for it).

Also do not add site subroutes (subroutes generated specifically from
site blocks in the Caddyfile) that are empty.
2020-04-02 14:24:53 -06:00
Mohammed Al Sahaf 3634c4593f ci: fuzz: skip fuzz data that contains import (#3214)
Thus far the fuzzers have found a few crashers in the Caddyfile parser. However, the fuzzer have been stuck at import glob expansion after import glob expansion, which aren't reproducible.
2020-04-02 10:40:21 -06:00
Matthew Holt 7ca15861dd caddytls: Encode big.Int as string with JSON 2020-04-02 09:43:33 -06:00
Matthew Holt 8ff330c555 Update readme 2020-04-02 09:43:08 -06:00
Matthew Holt 626f19a264 Fix for last commit 2020-04-01 21:07:38 -06:00
Matthew Holt 6ca5828221 caddytls: Refactor certificate selection policies (close #1575)
Certificate selection used to be a module, but this seems unnecessary,
especially since the built-in CustomSelectionPolicy allows quite complex
selection logic on a number of fields in certs. If we need to extend
that logic, we can, but I don't think there are SO many possibilities
that we need modules.

This update also allows certificate selection to choose between multiple
matching certs based on client compatibility and makes a number of other
improvements in the default cert selection logic, both here and in the
latest CertMagic.

The hardest part of this was the conn policy consolidation logic
(Caddyfile only, of course). We have to merge connection policies that
we can easily combine, because if two certs are manually loaded in a
Caddyfile site block, that produces two connection policies, and each
cert is tagged with a different tag, meaning only the first would ever
be selected. So given the same matchers, we can merge the two, but this
required improving the Tag selection logic to support multiple tags to
choose from, hence "tags" changed to "any_tag" or "all_tags" (but we
use any_tag in our Caddyfile logic).

Combining conn policies with conflicting settings is impossible, so
that should return an error if two policies with the exact same matchers
have non-empty settings that are not the same (the one exception being
any_tag which we can merge because the logic for them is to OR them).

It was a bit complicated. It seems to work in numerous tests I've
conducted, but we'll see how it pans out in the release candidates.
2020-04-01 20:49:35 -06:00
Matthew Holt 6fe04a30b1 caddyfile: Export NewTestDispenser() (close #2930)
This allows modules to test their UnmarshalCaddyfile methods.
2020-04-01 16:34:54 -06:00
Matthew Holt 19b45546a7 go.mod: Update smallstep/truststore
So that installation continues if Firefox is not installed

See https://github.com/smallstep/truststore/issues/3
2020-04-01 15:28:09 -06:00
Matthew Holt d322de6b42 gzip: Use klauspost/gzip, an optimized gzip implementation 2020-04-01 14:09:57 -06:00
Matthew Holt ce3ca541d8 caddytls: Update cipher suite names and curve names
Now using IANA-compliant names and Go 1.14's CipherSuites() function so
we don't have to maintain our own mapping of currently-secure cipher
suites.
2020-04-01 14:09:29 -06:00
Matthew Holt 581f1defcb caddyhttp: Print actual listener address in log message (closes #2992)
Needed if port is 0, thus chosen by OS
2020-04-01 12:23:07 -06:00
Matthew Holt 0d2a3511dc caddyhttp: Update host matcher docs about wildcards 2020-04-01 11:41:04 -06:00
Matt Holt 73643ea736 caddyhttp: 'not' matcher now accepts multiple matcher sets and OR's them (#3208)
See https://caddy.community/t/v2-matcher-or-in-not/7355/
2020-04-01 10:58:29 -06:00
Matthew Holt 809e72792c rewrite: Fix for rewrites with URI placeholders (#3209)
If a placeholder in the path component injects a query string such as
the {http.request.uri} placeholder is wont to do, we need to separate it
out from the path.
2020-04-01 00:43:40 -06:00
Matthew Holt 9fb0b1e838 caddytls: Add support for externalAccountBinding ACME extension 2020-03-31 21:08:02 -06:00
Matthew Holt 244b839f98 pki: Add trust subcommand to install root cert (closes #3204) 2020-03-31 17:56:36 -06:00
Matthew Holt 904d9cab39 httpcaddyfile: Include non-standard ports when mapping logger names
If a site block has a key like "http://localhost:2016", then the log for
that site must be mapped to "localhost:2016" and not just "localhost"
because "localhost:2016" will be the value of the Host header of requests.
But a key like "localhost:80" does not include the port since the Host
header will not include ":80" because it is a standard port.

Fixes https://caddy.community/t/v2-common-log-format-not-working/7352?u=matt
2020-03-30 18:39:21 -06:00
Matthew Holt ac65f690ae caddyhttp: Rename MatchNegate type to MatchNot type
This is more congruent with its module name. A change that affects only
code, not configurations.
2020-03-30 11:53:19 -06:00
Matthew Holt 37aa516a6e headers: Trim any trailing colon from field names as a courtesy 2020-03-30 11:52:11 -06:00
Matthew Holt 105acfa086 Keep type information with placeholders until replacements happen 2020-03-30 11:49:53 -06:00
Matthew Holt deba26d225 caddyfile: Minor fixes to the formatter 2020-03-29 13:53:00 -06:00
Matthew Holt 178ba024fe httpcaddyfile: Put root directive first, before redir and rewrite
See https://caddy.community/t/v2-match-any-path-but-files/7326/8?u=matt

If rewrites (or redirects, for that matter) match on file existence,
the file matcher would need to know the root of the site.

Making this change implies that root directives that depend on rewritten
URIs will not work as expected. However, I think this is very uncommon,
and am not sure I have ever seen that. Usually, dynamic roots are based
on host, not paths or query strings.

I suspect that rewrites based on file existence will be more common than
roots based on rewritten URIs, so I am moving root to be the first in
the list.

Users can always override this ordering with the 'order' global option.
2020-03-28 19:07:51 -06:00
Matthew Holt e207240f9a reverse_proxy: Upstream.String() method returns either LookupSRV or Dial
Either Dial or LookupSRV will be set, but if we rely on Dial always
being set, we could run into bugs.

Note: Health checks don't support SRV upstreams.
2020-03-27 14:29:01 -06:00
Robin Lambertz 397e04ebd9 caddyauth: Add Metadata field to caddyauth.User (#3174)
* caddyauth: Add Metadata field to caddyauth.User

* Apply gofmt

* Tidy it up a bit

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-03-27 11:10:51 -06:00
Mohammed Al Sahaf d2c15bea1b ci: fuzz: remove fuzzing trigger on PR (#3195) 2020-03-26 18:34:12 -06:00
Mohammed Al Sahaf 8da9eaee34 ci: fuzz: switch engine from libfuzzer to native go-fuzz (#3194) 2020-03-26 18:20:34 -06:00
Matthew Holt ea3688e1c0 caddytls: Remove ManageSync
This seems unnecessary for now and we can always add it in later if
people have a good reason to need it.
2020-03-26 14:02:29 -06:00
Matthew Holt c87f82f0ce caddytls: Match automation policies by wildcard subjects too
https://caddy.community/t/wildcard-snis-not-being-matched/7271/24?u=matt

Also use new CertMagic function for matching wildcard names
2020-03-26 14:01:38 -06:00
Pascal 5c55e5d53f caddytls: Support placeholders in key_type (#3176)
* tls: Support placeholders in key_type

* caddytls: Simplify placeholder support for ap.KeyType

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-03-25 23:16:12 -06:00
Matthew Holt 7ee3ab7baa caddyfile: Formatter enhancements 2020-03-25 18:45:54 -06:00
Mark Sargent ba08833b2a ci: exclude integration tests for now (#3188)
A workaround for inconsistent results on Windows
2020-03-25 08:55:14 -06:00
Matthew Holt 9eecd698da Merge branch 'v2' of https://github.com/caddyserver/caddy 2020-03-24 23:14:27 -06:00
Mohammed Al Sahaf 0fa1a3b630 ci: preliminary CD with goreleaser (#3173)
* chore: ci: preliminary CD support

* chore: ci: split release process into its own workflow

* chore: ci: cleanup the ci.yml and .goreleaser.yml

* chore: ci: unshallowify the clone before searching for the closes tag

* chore: tidy up goreleaser config & the release githubaction

* chore: add --no-tty to gpg args

* chore: more gpg args

* chore: try with default gpg args by goreleaser

* chore: gpg...

* chore: set GPG_TTY

* chore: preset gpg conf

* Apply suggestions from code review

chore: tidy up the .goreleaser.yml

Co-Authored-By: Dave Henderson <dhenderson@gmail.com>

* chore: gpg debugging

* chore: set and export the tty for gpg

* chore: gpg..

* chore: use the exact same line from goreleaser-action README for singing

* chore: remove signing stanzas from ymls

* chore: clean up the release action for final submission

* quote the arguments of echo

Co-Authored-By: Francis Lavoie <lavofr@gmail.com>

Co-authored-by: Dave Henderson <dhenderson@gmail.com>
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
2020-03-24 23:13:36 -06:00
Matthew Holt 673d3d00f2 file_server: Fix dumb error check I must have written at 1am 2020-03-24 16:48:04 -06:00
Matthew Holt 2acb208e32 caddyhttp: Specify default access log for a server (fix #3185) 2020-03-24 13:21:18 -06:00
Matt Holt e02117cb8a reverse_proxy: Add support for SRV backends (#3180)
* reverse_proxy: Begin SRV lookup support (WIP)

* reverse_proxy: Finish adding support for SRV-based backends (#3179)
2020-03-24 10:53:53 -06:00
Matthew Holt 95b2863df2 admin: Fix regex for removing @id fields (closes #3187) 2020-03-24 10:52:05 -06:00
Matthew Holt 341d4fb805 Remove some non-essential plugins from this repo (#2780)
Brotli encoder, jsonc and json5 config adapters, and the unfinished
HTTP cache handler are removed.

They will be available in separate repos.
2020-03-24 10:37:47 -06:00
Matthew Holt 745cb0e9e6 fastcgi: Add debug log (#3178) 2020-03-24 08:34:15 -06:00
Matthew Holt 9af05719bc logging: Fix off-by-one for roll size MB from Caddyfile
"10mb" now results in 10, rather than 9.
2020-03-24 08:20:49 -06:00
Mark Sargent d08cbefff8 report error on failed location response (#3184)
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-03-23 21:18:53 -06:00
Matt Holt 2eede58b3a fastcgi: Ensure root is always absolute (issue #3178) (#3182) 2020-03-23 21:12:54 -06:00
Matthew Holt 235357abc8 fastcgi: Fix PATH_INFO (issue #3178) 2020-03-23 18:29:16 -06:00
Matthew Holt 4b4e16edaf cmd: Ensure certmagic defaults are set for any and all subcommands
This is really crucial and I'm surprised no one reported a problem yet
2020-03-23 14:43:42 -06:00
Matthew Holt ee64719d93 Update readme 2020-03-23 14:30:00 -06:00
Francis Lavoie 2491336c11 ci: Update branches to master (#3177)
* Update ci.yml

* Update fuzzing.yml
2020-03-23 14:26:53 -06:00
Matthew Holt 1698838685 tls: Few minor improvements/simplifications 2020-03-23 13:32:17 -06:00
Matthew Holt 4c43bf8cc8 caddyhttp: Always provision ACME issuers (fix terms agree error) 2020-03-23 12:21:39 -06:00
Matthew Holt 348cb798e2 httpcaddyfile: Allow php_fastcgi to be used in route directive
Fixes
https://caddy.community/t/v2-help-to-set-up-a-yourls-instance/7260/22
2020-03-23 09:28:29 -06:00
Matthew Holt e211491407 httpcaddyfile: Fix little typo (Next -> NextArg) 2020-03-22 23:13:08 -06:00
Matthew Holt 6e2fabb2a4 cmd: Add --watch flag to start & run commands (closes #1806)
Because, just for fun.
2020-03-22 22:58:33 -06:00
Mark Sargent 8cc60e6896 ci: test local CA and update SNI tests (#3145)
* run caddy tests in process

* call main with run args

* exclude tests - windows

* include json example

* disable caddyfile tests, include json test with non trusted local ca

* converted SNI tests to json syntax
2020-03-22 18:08:02 -06:00
Matthew Holt bea8dedfb2 httpcaddyfile: Move header before redir (fixes #3148) 2020-03-22 09:04:40 -06:00
Matthew Holt f2ce81cc8b fastcgi: Support multiple path splitters (close #1564) 2020-03-22 07:48:34 -06:00
Francis Lavoie 2cab475ba5 ci: Improve build artifact file names (#3168) 2020-03-21 17:44:51 -06:00
Francis Lavoie c32f383a01 ci: Use matrix to set per-os variables (#3166)
Simplify cross-platform
2020-03-21 16:53:42 -06:00
Mohammed Al Sahaf 37093befd5 caddyconfig: register adapters as Caddy modules (#3132)
* admin: Refactor /load endpoint out of caddy package

This eliminates the caddy package's dependency on the caddyconfig
package, which helps prevent import cycles.

* v2: adapter: register config adapters as Caddy modules

* v2: adapter: simplify adapter registration as adapters and modules

* v2: adapter: let RegisterAdapter be in charge of registering adapters as modules

* v2: adapter: remove underscrores placeholders

* v2: adapter: explicitly ignore the error of writing response of writing warnings back to client

* Implicitly wrap config adapters as modules

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-03-21 16:49:10 -06:00
Matthew Holt d692d503a3 tls/http: Fix auto-HTTPS logic w/rt default issuers (fixes #3164)
The comments in the code should explain the new logic thoroughly.
The basic problem for the issue was that we were overriding a catch-all
automation policy's explicitly-configured issuer with our own, for names
that we thought looked like public names. In other words, one could
configure an internal issuer for all names, but then our auto HTTPS
would create a new policy for public-looking names that uses the
default ACME issuer, because we assume public<==>ACME and
nonpublic<==>Internal, but that is not always the case. The new logic
still assumes nonpublic<==>Internal (on catch-all policies only), but
no longer assumes that public-looking names always use an ACME issuer.

Also fix a bug where HTTPPort and HTTPSPort from the HTTP app weren't
being carried through to ACME issuers properly. It required a bit of
refactoring.
2020-03-20 20:25:46 -06:00
Matthew Holt 3c1def2430 caddytls: Support wildcard matching in ServerName conn policy matcher 2020-03-20 15:51:37 -06:00
Matthew Holt b583007c49 httpcaddyfile: Simplify 'root' directive parsing
I must have written that one before the helper function
`RegisterHandlerDirective`.
2020-03-20 12:50:36 -06:00
Matthew Holt 6b60a301c0 httpcaddyfile: Append access logger name to log's includes (fix #3110) 2020-03-20 12:02:46 -06:00
Mohammed Al Sahaf d6632e2145 v2: update CI badge on README (#3162) 2020-03-20 08:54:53 -06:00
Matthew Holt 903776238e go.mod: Update some deps; add new Strings lib to CEL matcher 2020-03-20 08:53:40 -06:00
Matthew Holt f741ab3463 go.mod: Update CertMagic
Might fix mysterious hangs after certificate validation
2020-03-20 08:40:38 -06:00
Francis Lavoie 76ac28a624 ci: Switch to Github Actions (#3152)
* WIP: Trying to make a new branch

* Create fuzzing.yml

* Update ci.yml

* Try using reviewdog for golangci-lint

* Only run lint on ubuntu

* Whoops, wrong matrix variable

* Let's try just ubuntu for the moment

* Remove integration tests

* Let's see what the tree looks like (where's the binary)

* Let's plant a tree

* Let's look at another tree

* Burn the tree

* Let's build in the right dir

* Turn on publishing artifacts

* Add gobin to path

* Try running golangci-lint earlier

* Try running golangci-lint on its own, with checkout@v1

* Try moving golangci-lint back into ci.yml as a separate job

* Turn off azure-pipelines

* Remove the redundant name, see how it looks

* Trim down the naming some more

* Turn on windows and mac

* Try to fix windows build, cleanup

* Try to fix strange failure on windows

* Print our the coerce reason

* Apparently $? is 'True' on Windows, not 1 or 0

* Try setting CGO_ENABLED as an env in yml

* Try enabling/fixing the fuzzer

* Print out github event to check, fix step name

* Fuzzer needs the code

* Add GOBIN to PATH for fuzzer

* Comment out fork condition, left in-case we want it again

* Remove obsolete comment

* Comment out the coverage/test conversions for now

* Set continue-on-error: true for fuzzer, it runs out of mem

* Add some clarification to the retained commented sections
2020-03-20 08:38:44 -06:00
Mohammed Al Sahaf 61b427fa47 v2: fuzz: update function signature of caddyfile.Parse (#3160) 2020-03-20 06:56:57 -06:00
Paolo Barbolini 42a6628935 reverseproxy: Add Alt-Svc to Hop-by-hop headers list (#3159)
Adds `Alt-Svc` to the list of headers that get removed when proxying
to a backend.

This fixes the issue of having the contents of the Alt-Svc header
duplicated when proxying to another Caddy server.
2020-03-20 06:54:28 -06:00
Matt Holt 6a4d638c1e caddyhttp: Implement CEL matcher (see #3051) (#3155)
* caddyhttp: Implement CEL matcher (see #3051)

CEL (Common Expression Language) is a very fast, flexible way to express
complex logic, useful for matching requests when the conditions are not
easy to express with JSON.

This matcher may be considered experimental even after the 2.0 release.

* Improve CEL module docs
2020-03-19 15:46:22 -06:00
Matt Holt aa6c5fde07 httpcaddyfile: Unify strip_prefix, strip_suffix, uri_replace directives (#3157)
* rewrite: strip_prefix, strip_suffix, uri_replace -> uri (closes #3140)

* Add period, to satisfy @whitestrake :) and my own OCD

* Restore implied / prefix
2020-03-19 11:51:28 -06:00
Matthew Holt 31c6ac097e httpcaddyfile: 'bind' properly parses unix sockets (fixes #2999) 2020-03-19 09:43:17 -06:00
Matthew Holt 406df22a16 templates: Enable Goldmark's footnote extension (closes #3136)
Also remove Table extension, since GFM (already enabled) apparently
enables strikethrough, table, linkify, and tasklist extensions.
https://github.com/yuin/goldmark#built-in-extensions
2020-03-18 23:38:37 -06:00
Matthew Holt afb2ca27c1 caddyhttp: Minor improved Caddyfile support for some matchers
Simply allows the matcher to be specified multiple times in a set
which may be more convenient than one long line.
2020-03-18 23:36:25 -06:00
Matthew Holt ce45353e61 Little tweaky tweaks 2020-03-18 15:51:31 -06:00
Matthew Holt 89124aa570 httpcaddyfile: Prevent rewrite routes from consolidating (fix #3108)
It's hard to say whether this was actually a bug, but the linked issue
shows why the old behavior was confusing. Basically, we infer that a
rewrite handler is supposed to act as an internal redirect, which likely
means it will no longer match the matcher(s) it did before the rewrite.

So if the rewrite directive shares a matcher with any adjacent route or
directive, it can be confusing/misleading if we consolidate the rewrite
into the same route as the next handler, which shouldn't (probably) match
after the rewrite is complete.

This is kiiiind of a hacky workaround to a quirky problem.

For edge cases like these, it is probably "cleaner" to just use handle
blocks instead, to group handlers under the same matcher, nginx-style.
2020-03-18 12:18:10 -06:00
Matthew Holt ab2fc9d066 Update dependencies and readme 2020-03-17 21:03:17 -06:00
Matthew Holt fc7340e11a httpcaddyfile: Many tls-related improvements including on-demand support
Holy heck this was complicated
2020-03-17 21:00:45 -06:00
Mark Sargent 3f48a2eb45 caddyhttp: Add default SNI tests (#3146)
* added sni tests

* set the default sni when there is no host to match

* removed invalid sni test. Disabled tests that rely on host headers.

* readded SNI tests. Added logging of config load times
2020-03-17 12:39:01 -06:00
Vaibhav f192ae5ea5 cmd: fmt: Fix brace opening block indentation (#3153)
This fixes indentation for blocks starting with
a brace as:
```Caddyfile
{
    ...
}
```

Fixes #3144

Signed-off-by: Vaibhav <vrongmeal@gmail.com>
2020-03-17 09:55:36 -06:00
Matthew Holt b62f8e0582 caddyhttp: Support path matcher of "*" without panic 2020-03-16 16:08:33 -06:00
Matthew Holt ae86f6dd91 Use JSON format for logs if not interactive terminal 2020-03-16 14:22:40 -06:00
Matthew Holt b550ea433b Simplify build instructions in readme 2020-03-15 21:29:00 -06:00
Matthew Holt e42514ad4a caddyhttp: Clean up; move some code around 2020-03-15 21:28:42 -06:00
Matthew Holt f596fd77bb caddyhttp: Add support for listener wrapper modules
Wrapping listeners is useful for composing custom behavior related
to accepting, closing, reading/writing connections (etc) below the
application layer; for example, the PROXY protocol.
2020-03-15 21:26:17 -06:00
Matthew Holt 0433f9d075 caddytls: Clean up some code related to automation 2020-03-15 21:22:26 -06:00
Matthew Holt c67c8e60cc cmd: fmt: --write -> --overwrite to make it clear it's destructive 2020-03-15 21:18:31 -06:00
Matthew Holt 8f8ecd2e2a Add missing license texts 2020-03-15 21:18:00 -06:00
Matthew Holt 115b877e1a caddytls: Set Issuer properly on automation policies (fix #3150)
When using the default automation policy specifically, ap.Issuer would
be nil, so we'd end up overwriting the ap.magic.Issuer's default value
(after New()) with nil; this instead sets Issuer on the template before
New() is called, and no overwriting is done.
2020-03-15 09:24:24 -06:00
Matthew Holt 2ce3deb540 fileserver: Add --templates flag to file-server command 2020-03-14 23:31:52 -06:00
Matthew Holt acf4dde1dd pki: Don't treat cert installation failure as error
See https://caddy.community/t/fail-to-start-caddy2-not-nss-security-databases-found/7223?u=matt
2020-03-14 15:20:04 -06:00
Matthew Holt 7a4548c582 Some hotfixes for beta 16 2020-03-13 19:14:49 -06:00
Matthew Holt 6cbd93736f Minor tweaks 2020-03-13 13:04:10 -06:00
Mark Sargent c447236357 caddyhttp: Fix default SNI for default conn policy (#3141)
* add integration tests

* removed SNI test

* remove integration test condition

* minor edit

* fix sni when using static certificates

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-03-13 11:32:53 -06:00
Matt Holt 5a19db5dc2 v2: Implement 'pki' app powered by Smallstep for localhost certificates (#3125)
* pki: Initial commit of PKI app (WIP) (see #2502 and #3021)

* pki: Ability to use root/intermediates, and sign with root

* pki: Fix benign misnamings left over from copy+paste

* pki: Only install root if not already trusted

* Make HTTPS port the default; all names use auto-HTTPS; bug fixes

* Fix build - what happened to our CI tests??

* Fix go.mod
2020-03-13 11:06:08 -06:00
Bill Glover cfe85a9fe6 Fix #3130: Crash at fuzzing target replacer (#3133)
* Fix #3130: Crash at fuzzing target replacer

* Add additional test case based on fuzzer feedback
2020-03-11 16:12:00 -06:00
Francis Lavoie 90f1f7bce7 httpcaddyfile: error for wrong arg count of admin opt (#3126) (#3131) 2020-03-10 08:25:26 -06:00
Matt Holt 2762f8f058 caddyhttp: New algorithm for auto HTTP->HTTPS redirects (fix #3127) (#3128)
It's still not perfect but I think it should be more correct for
slightly more complex configs. Might still fall apart for complex
configs that use on-demand TLS or at a large scale (workarounds are
to just implement your own redirects, very easy to do anyway).
2020-03-09 15:18:19 -06:00
Matthew Holt 99d34f1c1d cmd: Use loadConfig() for validate as run, start, and reload do 2020-03-09 00:09:15 -06:00
Bill Glover 36a6c7daf0 Rework Replacer loop to handle escaped braces (#3121)
Fixes #3116

* Rework Replacer loop to ignore escaped braces

* Add benchmark tests for replacer

* Optimise handling of escaped braces

* Handle escaped closing braces

* Remove additional check for closing brace

This commit removes the additional check for input in which the closing
brace appears before the opening brace. This check has been removed for
performance reasons as it is deemed an unlikely edge case.

* Check for escaped closing braces in placeholder name
2020-03-08 15:36:59 -06:00
evtr ca6e54bbb8 caddytls: customizable client auth modes (#2913)
* ability to specify that client cert must be present in SSL

* changed the clientauthtype to string and make room for the values supported by go as in caddy1

* renamed the config parameter according to review comments and added documentation on allowed values

* missed a reference

* Minor cleanup; docs enhancements

Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>
2020-03-08 09:48:25 -06:00
Mohammed Al Sahaf fb5168d3b4 http_ntlm: fix panic due to unintialized embedded field (#3120) 2020-03-07 17:58:44 -07:00
Matthew Holt 217419f6d9 tls: Couple of quick fixes for 4d18587192 2020-03-07 11:47:55 -07:00
Matthew Holt 4d18587192 tls: Auto-migrate cert assets to new path (details in #3124) 2020-03-07 10:42:50 -07:00
Matthew Holt b216d285df Merge branch 'certmagic-refactor' into v2 2020-03-06 23:26:13 -07:00
Matthew Holt b8cba62643 Refactor for CertMagic v0.10; prepare for PKI app
This is a breaking change primarily in two areas:
 - Storage paths for certificates have changed
 - Slight changes to JSON config parameters

Huge improvements in this commit, to be detailed more in
the release notes.

The upcoming PKI app will be powered by Smallstep libraries.
2020-03-06 23:15:25 -07:00
Matt Holt 3f5d27cd5d ci: Optimize published artifacts (#3118)
Build the published executables with CGO disabled, stripped, and with `-trimpath` for more reproducible build
2020-03-04 13:19:25 -07:00
Mark Sargent 26fb8b3efd httpcaddyfile: remove certificate tags from global state (#3111)
* remove the certificate tag tracking from global state

* refactored helper state, added log counter

* moved state initialisation close to where it is used.

* added helper state comment
2020-03-04 09:58:49 -07:00
Marten Seemann e6c6210772 update quic-go to v0.15.1 (#3109) 2020-03-02 07:13:49 -07:00
Marten Seemann 1324da2241 go.mod: update quic-go to v0.15.0 (supporting QUIC draft-27) (#3107) 2020-03-01 12:34:57 -07:00
Vaibhav 71e81d262b fmt: Add support for block nesting. (#3105)
Previously the formatter did not include support for
blocks inside other blocks. Hence the formatter could
not indent some files properly. This fixes it.

Fixes #3104

Signed-off-by: Vaibhav <vrongmeal@gmail.com>
2020-02-29 13:23:08 -07:00
Vaibhav 5fe69ac4ab cmd: Add caddy fmt command. (#3090)
This takes the config file as input and formats it.
Prints the result to stdout. Can write changes to
file if `--write` flag is passed.

Fixes #3020

Signed-off-by: Vaibhav <vrongmeal@gmail.com>
2020-02-29 10:12:16 -07:00
Mohammed Al Sahaf e717028f83 ci: publish build artifacts (#3103)
* ci: publish build artifacts (per-commit Caddy binaries)

* ci: include OS name in artifact name of *nix binaries so they don't overwrite each other
2020-02-29 20:09:50 +03:00
Matthew Holt a60da8e7ab Simplify the logic in the previous commit 2020-02-28 13:49:51 -07:00
Matthew Holt 00e99df209 httpcaddyfile: Treat no matchers as 0-len path matchers (fix #3100)
+ a couple other minor changes from linter
2020-02-28 13:38:12 -07:00
Matthew Holt c83d40ccd4 reverse_proxy, php_fastcgi: Fix upstream parsing regression (fix #3101) 2020-02-28 08:57:59 -07:00
Matthew Holt e4ec08e977 Couple of minor docs tweaks 2020-02-27 21:08:21 -07:00
Matthew Holt 03ab55b51a httpcaddyfile: Allow "admin off" option 2020-02-27 21:04:28 -07:00
Matthew Holt cef6e098bb Refactor ExtractMatcherSet() 2020-02-27 21:04:28 -07:00
Matthew Holt 260982b2df reverse_proxy: Allow use of URL to specify scheme
This makes it more convenient to configure quick proxies that use HTTPS
but also introduces a lot of logical complexity. We have to do a lot of
verification for consistency and errors.

Path and query string is not supported (i.e. no rewriting).

Scheme and port can be inferred from each other if HTTP(S)/80/443.
If omitted, defaults to HTTP.

Any explicit transport config must be consistent with the upstream
schemes, and the upstream schemes must all match too.

But, this change allows a config that used to require this:

    reverse_proxy example.com:443 {
        transport http {
            tls
        }
    }

to be reduced to this:

    reverse_proxy https://example.com

which is really nice syntactic sugar (and is reminiscent of Caddy 1).
2020-02-27 21:04:28 -07:00
Matthew Holt 0130b699df cmd/reverse_proxy: Add --change-host-header flag
"Transparent mode" is the default, just like the actual handler.
2020-02-27 21:04:28 -07:00
Success Go ca5c679880 Fix typos (#3087)
* Fix typo

* Fix typo, thanks for Spell Checker under VS Code
2020-02-27 19:30:48 -07:00
Matthew Holt e2d41ee761 Revert "reverse_proxy: Add 'transparent' Caddyfile subdirective (closes #2873)"
This reverts commit 86b785e51c.
2020-02-27 11:08:56 -07:00
Matthew Holt 86b785e51c reverse_proxy: Add 'transparent' Caddyfile subdirective (closes #2873) 2020-02-27 10:20:13 -07:00
Success Go f6ae092507 It might be HTTP->HTTPS in the comment (#3086) 2020-02-27 00:50:36 -05:00
Success Go a2a41a5bdf Fix spelling error (#3085) 2020-02-27 00:22:40 -05:00
Mohammed Al Sahaf 6fb98ba188 ci: improve CI flow (#3083)
* ci: update golangci-lint
* ci: build Caddy to catch build error
* ci: remove GO111MODULE env var
* ci: update MacOS image
2020-02-27 03:51:54 +03:00
Zaq? Wiedmann 063ed1e7f9 caddyfile: expand environment variables within caddy files (#3082)
Small expansion to the work done in https://github.com/caddyserver/caddy/pull/2963 which simply calls `os.ExpandEnv` so env vars like `{$URL}` where `$URL=$SCHEME://$HOST:$PORT` (contrived) get the expanded $SCHEME, $HOST, and $PORT variables included
2020-02-26 16:06:34 -07:00
Mark Sargent 2de0acc11f Initial implementation of global default SNI option (#3047)
* add global default sni

* fixed grammar

* httpcaddyfile: Reduce some duplicated code

* Um, re-commit already-committed commit, I guess? (sigh)

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-02-26 16:01:47 -07:00
Matt Holt 5d97522d18 v2: 'log' directive for Caddyfile, and debug mode (#3052)
* httpcaddyfile: Begin implementing log directive, and debug mode

For now, debug mode just sets the log level for all logs to DEBUG
(unless a level is specified explicitly).

* httpcaddyfile: Finish 'log' directive

Also rename StringEncoder -> SingleFieldEncoder

* Fix minor bug in replacer (when vals are empty)
2020-02-25 22:00:33 -07:00
Matthew Holt f6b9cb7122 httpcaddyfile: Matchers can now be embedded into a nested scope
This is useful in 'handle' and 'route' directives, for instance, if you
want to keep your matcher definitions by the directives that use them.
2020-02-25 21:56:43 -07:00
Matthew Holt 78760c0ddc go.mod: Bump to Go 1.14 2020-02-25 19:24:13 -07:00
Cameron Moore b0a491aec8 Expose TLS placeholders (#2982)
* caddytls: Add CipherSuiteName and ProtocolName functions

The cipher_suites.go file is derived from a commit to the Go master
branch that's slated for Go 1.14.  Once Go 1.14 is released, this file
can be removed.

* caddyhttp: Use commonLogEmptyValue in common_log replacer

* caddyhttp: Add TLS placeholders

* caddytls: update unsupportedProtocols

Don't export unsupportedProtocols and update its godoc to mention that
it's used for logging only.

* caddyhttp: simplify getRegTLSReplacement signature

getRegTLSReplacement should receive a string instead of a pointer.

* caddyhttp: Remove http.request.tls.client.cert replacer

The previous behavior of printing the raw certificate bytes was ported
from Caddy 1, but the usefulness of that approach is suspect.  Remove
the client cert replacer from v2 until a use case is presented.

* caddyhttp: Use tls.CipherSuiteName from Go 1.14

Remove ported version of CipherSuiteName in the process.
2020-02-25 19:22:50 -07:00
Success Go 45b171ff3a Make comment more readable about caddy ModuleID's Name() method. (#3080) 2020-02-25 09:11:29 -07:00
Success Go 623a1c588e Fix typo in cmdStart comment 2020-02-25 02:33:33 -05:00
Matthew Holt 7cca291d62 reverse_proxy: Health checks: Don't cross the streams
Fixes https://caddy.community/t/v2-health-checks-are-going-to-the-wrong-upstream/7084?u=matt

... I think
2020-02-23 14:31:05 -07:00
Robin Lambertz e3591009dc caddyhttp: Add handler for unhandled errors in errorChain (#3063)
* Add handler for unhandled errors in errorChain

Currently, when an error chain is defined, the default error handler is
bypassed entirely - even if the error chain doesn't handle every error.
This results in pages returning a blank 200 OK page.

For instance, it's possible for an error chain to match on the error
status code and only handle a certain subtype of errors (like 403s). In
this case, we'd want any other errors to still go through the default
handler and return an empty page with the status code.

This PR changes the "suffix handler" passed to errorChain.Compile to
set the status code of the response to the error status code.

Fixes #3053

* Move the errorHandlerChain middleware to variable

* Style fix
2020-02-20 15:00:30 -07:00
Gilbert Gilb's 30c14084ab caddyhttp: Fixes for header and header_regexp directives (#3061)
* Fix crash when specifying "*" to header directive.

Fixes #3060

* Look Host header in header and header_regexp.

Also, if more than one header is provided, header_regexp now looks for
extra headers values to reflect the behavior from header.

Fixes #3059

* Fix parsing of named header_regexp in Caddyfile.

See #3059
2020-02-20 10:55:47 -07:00
Matthew Holt 99f91c4c6f httpcaddyfile: tls: Load repeated cert files only once, with one tag
See end of issue #3004. Loading the same certificate file multiple times
with different tags will result in it being de-duplicated in the in-
memory cache, because of course they all have the same bytes. This
meant that any certs of the same filename loaded with different tags
would be overwritten by the next certificate of the same filename, and
any conn policies looking for the tags of the previous ones would never
find them, causing connections to fail.

So, now we remember cert filenames and their tags, instead of loading
them multiple times and overwriting previous ones.

A user crafting their own JSON might make this error too... maybe we
won't see it happen. But if it does, one possibility is, when loading
a duplicate cert, instead of discarding it completely, merge the tag
list into the one that's already stored in the cache, then discard.
2020-02-20 10:18:29 -07:00
Matthew Holt 0005e3acdc httpcaddyfile: Combine repeated cert loaders (fix #3004)
Also only append 1 catch-all TLS connection policy to a server, even if
multiple site blocks contribute to that server.
2020-02-20 00:15:11 -07:00
Matthew Holt 0b09b070e5 httpcaddyfile: Properly add all cert loaders across sites (fixes #3056) 2020-02-18 11:13:51 -07:00
Matthew Holt 7f9cfcc0f2 http: Close HTTP/3 servers and listeners; upstream bug irreproducible
See https://github.com/lucas-clemente/quic-go/issues/2103
and https://github.com/caddyserver/caddy/pull/2727
2020-02-18 10:39:34 -07:00
Matthew Holt 87a742c1e5 tls: Fix panic loading automation management modules (fix #3004)
When AutomationPolicy was turned into a pointer, we continued passing
a double pointer to LoadModule, oops.
2020-02-18 09:54:14 -07:00
Robin Lambertz 57c6f22684 basicauth: default hash to bcrypt (#3050)
The documentation specifies that the hash algorithm defaults to bcrypt.
However, the implementation returns an error in provision if no hash is
provided.

Fix this inconsistency by *actually* defaulting to bcrypt.
2020-02-17 12:19:59 -07:00
Marten Seemann dd103a6787 go.mod: update quic-go to v0.14.4 (#3048) 2020-02-17 08:54:03 -07:00
Matthew Holt 23cc26d585 httpcaddyfile: 'handle_errors' directive
Not sure I love the name of the directive; might change it later.
2020-02-16 22:24:20 -07:00
Matthew Holt bc2e406572 httpcaddyfile: Refactor global options parsing; prevent duplicate keys 2020-02-16 15:28:27 -07:00
Matthew Holt bf776e7de7 http: Remove redundant test file
Forgot to delete this when I moved its test into a different file
2020-02-16 15:27:53 -07:00
Matthew Holt f42b138fb1 tls: Avoid duplication AutomationPolicies for large quantities of names
This should greatly reduce memory usage at scale. Part of an overall
effort between Caddy 2 and CertMagic to optimize for large numbers of
names.
2020-02-14 11:14:52 -07:00
Matthew Holt 2cc5d2227d Minor tweaks to docs/comments 2020-02-14 11:01:09 -07:00
Matthew Holt 15bf9c196c caddyfile: Refactor; NewFromNextSegment(); fix repeated matchers
Now multiple instances of the same matcher can be used within a named
matcher without overwriting previous ones.
2020-02-14 11:01:09 -07:00
Mark Sargent eb80165583 tls: Add acme_ca_root and tls/ca_root to caddyfile (#3040) 2020-02-12 13:07:25 -07:00
Matthew Holt 17d938fc54 httpcaddyfile: Add support for DNS challenge solvers
Configuration via the Caddyfile requires use of env variables, but
an upstream issue is currently blocking that:
https://github.com/go-acme/lego/issues/1054

Providers will need to be retrofitted upstream in order to support env
var configuration.
2020-02-08 18:43:35 -07:00
Jeremy Lin 98bbc54fdc browse: allow filter init via filter query param (#3027)
This allows creating links that display only a subset of files in a directory.
2020-02-08 12:36:37 -07:00
Mohammed Al Sahaf 9bdd6caa0b v2: Implement RegExp Vars Matcher (#2997)
* implement regexp var matcher

* use subtests pattern for tests

* be more consistent with naming: MatchVarRE -> MatchVarsRE, var_regexp -> vars_regexp
2020-02-08 12:26:31 -07:00
Matthew Holt f7f6e371ef tls: Slight adjustment to how DNS provider modules are loaded
We don't load the provider directly, because the lego provider types
aren't designed for JSON configuration and they are not implemented
as Caddy modules (there are some setup steps which a Provision call
would need to do, but they do not have Provision methods, they have
their own constructor functions that we have to wrap).

Instead of loading the challenge providers directly, the modules are
simple wrappers over the challenge providers, to facilitate the JSON
config structure and to provide a consistent experience. This also lets
us swap out the underlying challenge providers transparently if needed;
it acts as a layer of abstraction.
2020-02-07 21:59:25 -07:00
Matthew Holt b8cf4d5897 Fix typo in readme 2020-02-07 11:26:48 -07:00
Matthew Holt 04ec3c5f05 Update readme
The list of improvements and FAQ were moved to the wiki for now. They
still need to be updated.
2020-02-07 10:59:09 -07:00
Matthew Holt 8b28c36d48 Remove Starlark, for now
This is temporary as we prepare for a stable v2 release. We don't want
to make promises we don't know we can keep, and the Starlark integration
deserves much more focused attention which resources and funding do not
currently permit. When the project is financially stable, I will be able
to revisit this properly and add flexible, robust Starlark scripting
support to Caddy 2.
2020-02-06 18:46:52 -07:00
Matthew Holt 4a07a5d41e caddyfile: tls: Ensure there is always a catch-all conn policy (#3005)
If user provides their own certs or makes any hostname-specific TLS
connection policy, it means that no TLS connection would be served for
any other hostnames, even though you'd expect that TLS is enabled for
them, too. So now we append a catch-all conn policy if none exist, which
allows all ClientHellos to be matched and served.

We also fix the consolidation of automation policies, which previously
gobbled up automation policies without hosts in favor of automation
policies with hosts. Instead of a host-specific policy eating up an
identical catch-all policy, the catch-all policy eats up the identical
host-specific policy, ensuring that the policy is applied to all hosts
which need it.

See also:
https://caddy.community/t/v2-automatic-https-certificate-errors/6847/9?u=matt
2020-02-06 13:00:41 -07:00
Matthew Holt b81ae38686 caddyfile: tls: Tag manual certificates (#2588)
This ensure that if there are multiple certs that match a particular
ServerName or other parameter, then specifically the one the user
provided in the Caddyfile will be used.
2020-02-06 12:55:26 -07:00
Matthew Holt 5c7ca7d96e http: Split 2-phase auto-HTTPS into 3 phases
This is necessary to avoid a race for sockets. Both the HTTP servers and
CertMagic solvers will try to bind the HTTP/HTTPS ports, but we need to
make sure that our HTTP servers bind first. This is kind of a new thing
now that management is async in Caddy 2.

Also update to CertMagic 0.9.2, which fixes some async use cases at
scale.
2020-02-05 17:34:28 -07:00
Francis Lavoie ec56c25708 caddyhttp: Fix orig_uri placeholder docs (#3002)
Fixes #3001
2020-02-04 15:49:38 -07:00
Matthew Holt c0f827e0bd httpcaddyfile: Add {remote} shorthand placeholders
Also sort the list
2020-02-04 13:31:22 -07:00
Matthew Holt 490cd02f82 httpcaddyfile: Make root directive mutually exclusive
See https://caddy.community/t/caddyfile-and-v2/6766/22?u=matt
2020-02-04 13:04:34 -07:00
Matthew Holt 9639fe7d28 header: caddyfile: Defer header operations for deletions or manually
See https://caddy.community/t/caddy-server-that-returns-only-ip-address-as-text/6928/6?u=matt

In most cases, we will want to apply header operations immediately,
rather than waiting until the response is written. The exceptions are
generally going to be if we are deleting a header field or if a field is
to be overwritten. We now automatically defer header ops if deleting a
header field, and allow the user to manually enable deferred mode with
the defer subdirective.
2020-02-04 11:05:32 -07:00
Matthew Holt 3592e59399 cmd: adapt: Make --config flag optional when Caddyfile exists 2020-02-04 10:48:02 -07:00
Mohammed Al Sahaf f74fed3f54 v2: only compare TLS protocol versions if both are set (#3005) 2020-02-03 09:25:32 -07:00
Matthew Holt 8b2ad61220 httpcaddyfile: Skip hosts from auto-https when http:// scheme (fix #2998) 2020-01-23 13:17:16 -07:00
Matthew Holt 6614d1c495 cmd: Emit error if reload cannot find a config to load 2020-01-22 10:04:58 -07:00
Matthew Holt c6bddbfbe2 http: Fix vars matcher 2020-01-22 09:43:42 -07:00
Matthew Holt 0742530d3d rewrite: Prepend "/" if missing from strip path prefix
Paths always begin with a slash, and omitting the leading slash could be
convenient to avoid confusion with a path matcher in the Caddyfile. I do
not think there would be any harm to implicitly add the leading slash.
2020-01-22 09:36:05 -07:00
Matthew Holt 6b6cd934d0 reverseproxy: Fix casing of RootCAPEMFiles 2020-01-22 09:35:03 -07:00
Matthew Holt 5b878d5bd3 reverseproxy: Accept integer values for flush_interval (fix #2996) 2020-01-22 09:34:16 -07:00
Matthew Holt 2105d59936 httpcaddyfile: Rename 'headers' directive to 'header' 2020-01-22 09:33:53 -07:00
Matthew Holt 9a1370c2c8 cmd: Make --config flag optional for reload command
In case it is using the default Caddyfile
2020-01-22 09:33:22 -07:00
Matthew Holt d810637a9f httpcaddyfile: Update directive docs; put root after rewrite 2020-01-22 09:32:38 -07:00
Matthew Holt 5d3ccf1eb7 httpcaddyfile: Get rid of 'tls off' parameter; probably not useful 2020-01-22 09:29:50 -07:00
Matthew Holt aad9f90cad httpcaddyfile: Fix address parsing; don't infer port at parse-time
Before, listener ports could be wrong because ParseAddress doesn't know
about the user-configured HTTP/HTTPS ports, instead hard-coding port 80
or 443, which could be wrong if the user changed them to something else.
Now we defer port and scheme validation/inference to a later part of
building the output JSON.
2020-01-19 11:51:17 -07:00
Zaq? Wiedmann 07ef4b0c7d Merge pull request #2980 from moorereason/bugfix-ciphersuite-logging
v2: http: Fix ciphersuite logging
2020-01-18 19:37:50 -08:00
Mohammed Al Sahaf 2bfaf8e896 reverse_proxy: CB docs; rename type -> factor (#2986)
* v2: add documentation for circuit breaker config and "random selection" load balancing policy

* v2: rename circuit breaker config inline key from `type` to `breaker` to avoid json key clash between the `circuit_breaker` type and the `type` field of the generic circuit breaker Config struct used by circuit breaking implementations

* v2: restore the circuit breaker inline key to `type` and rename the name circuit breaker config field from `Type` to `Factor`
2020-01-18 18:42:56 -07:00
Matthew Holt 372540f0ee httpcaddyfile: Move redir before rewrite
Using rewrite is like saying, "I accept this request, but I just need
to act on it as if it came in differently."

Whereas redir implies more of, "I reject this request, send it to me
differently, then I will process it."

Makes sense for it to come before rewrites. This can always be changed
using the 'order' global option if needed.
2020-01-17 11:38:49 -07:00
Matthew Holt 793a405810 caddyhttp: Improve docs, and Caddyfile for respond directive 2020-01-17 10:57:57 -07:00
Matthew Holt 85ff0e3604 cmd: version: Add module replace to output 2020-01-17 09:50:23 -07:00
Matthew Holt e51e56a494 httpcaddyfile: Fix nested blocks; add handle directive; refactor
The fix that was initially put forth in #2971 was good, but only for
up to one layer of nesting. The real problem was that we forgot to
increment nesting when already inside a block if we saw another open
curly brace that opens another block (dispenser.go L157-158).

The new 'handle' directive allows HTTP Caddyfiles to be designed more
like nginx location blocks if the user prefers. Inside a handle block,
directives are still ordered just like they are outside of them, but
handler blocks at a given level of nesting are mutually exclusive.

This work benefitted from some refactoring and cleanup.
2020-01-16 17:08:52 -07:00
Cameron Moore 35174a8ba8 http: Fix ciphersuite logging 2020-01-16 15:44:49 -06:00
Matthew Holt 21643a007a httpcaddyfile: Replace 'handler_order' option with 'order'
This allows individual directives to be ordered relative to others,
where order matters (for example HTTP handlers). Will primarily be
useful when developing new directives, so you don't have to modify the
Caddy source code. Can also be useful if you prefer that redir comes
before rewrite, for example. Note that these are global options. The
route directive can be used to give a specific order to a specific group
of HTTP handler directives.
2020-01-16 12:09:54 -07:00
Matthew Holt 2466ed1484 httpcaddyfile: Group try_files routes together (#2891)
This ensures that only the first matching route is used.
2020-01-16 11:29:20 -07:00
Matthew Holt a66f461201 caddyfile: Sort site subroutes by key specificity, and make exclusive
In the v1 Caddyfile, only the first matching site definition would be
used, so setting these `Terminal: true` ensures that only the first
matching one is used in v2, too.

We also have to sort by key specificity... Caddy 1 had a special data
structure for selecting the most specific site definition, but we don't
have that structure in v2, so we need to sort by length (of host and
path, separately). For blocks where more than one key is present, we
choose the longest host and path (independently, need not be from same
key) by which to sort.
2020-01-15 13:51:12 -07:00
Matthew Holt 07ad4655db rewrite: Make URI modifications more transactional (#2891)
Before, modifying the path might have affected how a new query string
was built if the query string relied on the path. Now, we build each
component in isolation and only change the URI on the request later.

Also, prevent trailing & in query string.
2020-01-15 11:44:21 -07:00
Matthew Holt 271b5af148 http: Refactor automatic HTTPS (fixes #2972)
This splits automatic HTTPS into two phases. The first provisions the
route matchers and uses them to build the domain set and configure
auto HTTP->HTTPS redirects. This happens before the rest of the
provisioning does.

The second phase takes place at the beginning of the app start. It
attaches pointers to the tls app to each server, and begins certificate
management for the domains that were found in the first phase.
2020-01-13 16:16:20 -07:00
Matthew Holt 99e2b56519 cmd: adapt: Set config filename so it can be hidden (fixes #2974) 2020-01-12 18:20:19 -07:00
Matthew Holt 64f0173948 http: Fix subroutes, ensure that next handlers can still be called 2020-01-12 13:39:32 -07:00
Matthew Holt fe5a531c58 http: Fix empty responses
Sigh... this is what I get for writing code when I'm tired and sick.

See https://github.com/caddyserver/caddy/commit/8be1f0ea668492000cdefbd937e0359bdc24bfc1#r36764627
2020-01-12 13:34:55 -07:00
Matthew Holt 8c0c1a7b88 cmd: Assume Caddyfile if name starts with Caddyfile
And doesn't have .json extension -- in case someone names their
JSON config something like Caddyfile.json, which is unconventional.
2020-01-11 13:48:29 -07:00
Matthew Holt 25dea2903e http: A little more polish on rewrite handler and try_files directive 2020-01-11 13:47:42 -07:00
Matthew Holt d876de61e5 rewrite: Fix query string logic 2020-01-11 11:40:03 -07:00
Matthew Holt 8be1f0ea66 http: Ensure primary routes always get compiled (fix #2972)
Including servers for HTTP->HTTPS redirects which do not get provisioned
like the rest.
2020-01-11 00:33:47 -07:00
Matthew Holt 2eda21ec6d http: Remove {...query_string} placeholder, in favor of {...query}
I am not sure if the query_string one is necessary or useful yet. We
can always add it later if needed.
2020-01-10 17:02:11 -07:00
Matthew Holt d418e319ab rewrite: Rename parameters; implement custom query string parser
Our new parser also preserves original parameter order, rather than
re-encoding using the std lib (which sorts).

The renamed parameters are a breaking change but they're new enough
that I don't think anyone is using them.
2020-01-10 17:00:57 -07:00
Matthew Holt ba514f9660 cmd: Add build-info command; update CertMagic 2020-01-10 11:53:07 -07:00
Zaq? Wiedmann 3dcc34d341 caddyfile: advance cursor for claimed token in NewFromNextTokens() (#2971)
When we append a token to the new dispenser, we need to consume it in the parent, too; otherwise it gets scanned twice, which in this case messed up the nesting count which got decremented once too many times.
2020-01-09 20:48:15 -07:00
Mark Sargent 871abf1053 caddyfile: fix replacing variables on imported files (#2970)
* fix replacing variables on imported files

* refactored replaceEnvVars to ensure it is always called

* Use byte slices for easier use

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-01-09 19:34:22 -07:00
Matthew Holt 29315847a8 caddyfile: Use of vars no longer requires nesting in subroutes
This is because of our sequential handling logic which was recently
merged; if vars is the first handler in the chain, it will be run before
the next route's matchers are executed, so there's no need to nest the
handlers anymore.
2020-01-09 16:56:20 -07:00
Matthew Holt 994b9033e9 http: Don't use a Host matcher for HTTP->HTTPS redirects
In case on-demand TLS is enabled, in that case we don't know the only
names that have automatic HTTPS.

See https://caddy.community/t/v2-http-to-https-redirects-fail-for-on-demand-ssl-certs/6742?u=matt
2020-01-09 14:39:49 -07:00
Matthew Holt 590480513a Update docs for couple of Caddyfile directives 2020-01-09 14:38:59 -07:00
Matt Holt 7527c01705 v2: Implement Caddyfile enhancements (breaking changes) (#2960)
* http: path matcher: exact match by default; substring matches (#2959)

This is a breaking change.

* caddyfile: Change "matcher" directive to "@matcher" syntax (#2959)

* cmd: Assume caddyfile adapter for config files named Caddyfile

* Sub-sort handlers by path matcher length (#2959)

Caddyfile-generated subroutes have handlers, which are sorted first by
directive order (this is unchanged), but within directives we now sort
by specificity of path matcher in descending order (longest path first,
assuming that longest path is most specific).

This only applies if there is only one matcher set, and the path
matcher in that set has only one path in it. Path matchers with two or
more paths are not sorted like this; and routes with more than one
matcher set are not sorted like this either, since specificity is
difficult or impossible to infer correctly.

This is a special case, but definitely a very common one, as a lot of
routing decisions are based on paths.

* caddyfile: New 'route' directive for appearance-order handling (#2959)

* caddyfile: Make rewrite directives mutually exclusive (#2959)

This applies only to rewrites in the top-level subroute created by the
HTTP caddyfile.
2020-01-09 14:00:32 -07:00
Matthew Holt 8aef859a55 caddyfile: Less strict URL parsing; allows placeholders
See https://caddy.community/t/caddy-v2-reusable-snippets/6744/11?u=matt
2020-01-09 12:35:53 -07:00
Matt Holt a5ebec0041 http: Change routes to sequential matcher evaluation (#2967)
Previously, all matchers in a route would be evaluated before any
handlers were executed, and a composite route of the matching routes
would be created. This made rewrites especially tricky, since the only
way to defer later matchers' evaluation was to wrap them in a subroute,
or to invoke a "rehandle" which often caused bugs.

Instead, this new sequential design evaluates each route's matchers then
its handlers in lock-step; matcher-handlers-matcher-handlers...

If the first matching route consists of a rewrite, then the second route
will be evaluated against the rewritten request, rather than the original
one, and so on.

This should do away with any need for rehandling.

I've also taken this opportunity to avoid adding new values to the
request context in the handler chain, as this creates a copy of the
Request struct, which may possibly lead to bugs like it has in the past
(see PR #1542, PR #1481, and maybe issue #2463). We now add all the
expected context values in the top-level handler at the server, then
any new values can be added to the variable table via the VarsCtxKey
context key, or just the GetVar/SetVar functions. In particular, we are
using this facility to convey dial information in the reverse proxy.

Had to be careful in one place as the middleware compilation logic has
changed, and moved a bit. We no longer compile a middleware chain per-
request; instead, we can compile it at provision-time, and defer only the
evaluation of matchers to request-time, which should slightly improve
performance. Doing this, however, we take advantage of multiple function
closures, and we also changed the use of HandlerFunc (function pointer)
to Handler (interface)... this led to a situation where, if we aren't
careful, allows one request routed a certain way to permanently change
the "next" handler for all/most other requests! We avoid this by making
a copy of the interface value (which is a lightweight pointer copy) and
using exclusively that within our wrapped handlers. This way, the
original stack frame is preserved in a "read-only" fashion. The comments
in the code describe this phenomenon.

This may very well be a breaking change for some configurations, however
I do not expect it to impact many people. I will make it clear in the
release notes that this change has occurred.
2020-01-09 10:00:13 -07:00
Mark Sargent 7c419d5349 caddyfile: Preprocess env vars in {$THIS} format (#2963)
* transform a caddyfile with environment variables

* support adapt time and runtime variables in the caddyfile

* caddyfile: Pre-process environment variables before parsing

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2020-01-09 09:40:16 -07:00
Matthew Holt 3828a3aaac go.mod: Update lego, tidy up 2020-01-08 18:40:17 -07:00
Matthew Holt 8bae8f5f5a http: Always set status code via response recorder
Fixes panic if no upstream handler wrote anything to the response
2020-01-08 18:37:41 -07:00
Zaq? Wiedmann 21f1f95e7b reverse_proxy: Add tls_trusted_ca_certs to Caddyfile (#2936)
Allows specifying ca certs with by filename in
`reverse_proxy.transport`.

Example
```
reverse_proxy /api api:443 {
    transport http {
        tls
        tls_trusted_ca_certs certs/rootCA.pem
    }
}
```
2020-01-07 12:07:42 -07:00
Matthew Holt 78e98c40d3 basicauth: Accept placeholders; move base64 decoding to provision
See https://caddy.community/t/v2-basicauth-bug/6738?u=matt
2020-01-07 08:50:18 -07:00
Matthew Holt 5c99267dd8 A few miscellaneous, minor fixes 2020-01-06 08:10:20 -07:00
Matthew Holt a6df4cdbbc logging: Add doc about which fields can't be filtered 2020-01-03 15:28:05 -07:00
Mohammed Al Sahaf dff78d82ce v2: housekeeping: address minor lint complaints (#2957)
* v2: housekeeping: update tools

* v2: housekeeping: adhere to US locale in spelling

* v2: housekeeping: simplify code
2020-01-03 11:33:22 -07:00
Matthew Holt 8c7c2e4af2 logging: Little fix for filtering object fields 2020-01-01 10:26:37 -07:00
Matthew Holt 3d9f8eac08 Couple of minor fixes, update readme 2019-12-31 22:51:55 -07:00
Matthew Holt 06ea0a5295 Tune AppConfigDir and docs for Storage module 2019-12-31 18:31:43 -07:00
Matthew Holt 788462bd4c file-server command: Use safer defaults; http: improve host matcher docs 2019-12-31 16:57:54 -07:00
Matthew Holt 5a0603ed72 Config auto-save; run --resume flag; update environ output (close #2903)
Config auto-saving is on by default and can be disabled. The --environ
flag (or environ subcommand) now print more useful information from
Caddy and the runtime, including some nifty paths.
2019-12-31 16:56:19 -07:00
Matthew Holt 984d384d14 Change storage paths to follow OS conventions; migrate folder (#2955) 2019-12-31 16:47:35 -07:00
Matthew Holt fdabac51a8 Improve docs, especially w.r.t. placeholders and template actions 2019-12-29 13:16:34 -07:00
Matthew Holt 95d944613b Export Replacer and use concrete type instead of interface
The interface was only making things difficult; a concrete pointer is
probably best.
2019-12-29 13:12:52 -07:00
Matthew Holt 2b33d9a5e5 http: Enable TLS for servers listening only on HTTPS port
It seems silly to have to add a single, empty TLS connection policy to
a server to enable TLS when it's only listening on the HTTPS port. We
now do this for the user as part of automatic HTTPS (thus, it can be
disabled / overridden).

See https://caddy.community/t/v2-catch-all-server-with-automatic-tls/6692/2?u=matt
2019-12-28 23:56:08 -07:00
Matthew Holt 5c8b502964 fastcgi: Set SERVER_SOFTWARE, _NAME, and _PORT properly (fixes #2952) 2019-12-28 16:35:29 -07:00
Matthew Holt 82bebfab8a templates: Change functions, add front matter support, better markdown 2019-12-23 12:56:41 -07:00
Matthew Holt be3849c267 Remove markdown module 2019-12-23 12:55:52 -07:00
Matthew Holt 16ee985c22 admin: Only write most CORS headers in OPTIONS requests 2019-12-23 12:46:01 -07:00
Matthew Holt 95ed603de7 Improve godocs all around
These will be used in the new automated documentation system
2019-12-23 12:45:35 -07:00
Matthew Holt cbb405f6aa cmd: Eliminate unintended use of cgo
This means the stop command can only use the API to stop the instance;
no more signaling, unless we find a cgo-free way of doing it.
2019-12-23 12:41:05 -07:00
Matthew Holt 724c728678 rewrite: Attempt query string fix (#2891) 2019-12-17 16:30:26 -07:00
Matthew Holt 21408212da http: query and query_string placeholders should use RawQuery, probably 2019-12-17 16:29:37 -07:00
Matthew Holt fe516575db core: Add ReplaceFunc method to Replacer to allow dynamic replacements 2019-12-17 16:29:09 -07:00
Matthew Holt 080a62d5c5 Update go.mod; use CertMagic v0.9.0 2019-12-17 10:59:35 -07:00
Matthew Holt dae4913fe3 http: Patch path matcher to ignore dots and spaces (#2917)
(Try saying "patch path match" ten times fast)
2019-12-17 10:14:04 -07:00
Matthew Holt 6455efa5d3 admin: POST /... expands and appends all array elements
Makes it easy to append many items to an array in one command
2019-12-17 10:11:45 -07:00
Matthew Holt 5ab17a3a37 admin: /stop endpoint gracefully shuts down; fixes caddy stop command 2019-12-16 13:46:39 -07:00
Abdelmalek Ihdene c3bcd967bd logging: Implement net writer (#2884)
* Implement UDP writer

* Implement Net Writer

* Utilize Caddy's address parsing functions

* A couple little fixes (see #2884)
2019-12-15 12:58:01 -07:00
Matthew Holt 6ea121ddf8 tls: Ensure conn policy is created when providing certs in Caddyfile
Fixes #2929
2019-12-13 16:32:27 -07:00
Matthew Holt 8005b7ab73 Couple of quick fixes 2019-12-13 15:36:00 -07:00
Matthew Holt b1a456cfe3 rewrite: strip_prefix, strip_suffix, and uri_replace dirs (closes #2906) 2019-12-12 15:46:13 -07:00
Matthew Holt 5e9d81b507 try_files, rewrite: allow query string in try_files (fix #2891)
Also some minor cleanup/improvements discovered along the way
2019-12-12 15:27:09 -07:00
Matthew Holt 09a8517065 rewrite: query string enh.; substring replace; add tests (see #2891) 2019-12-12 14:32:35 -07:00
Matthew Holt 87b6cf470b Minor improvements; comments and shorter placeholders & module IDs 2019-12-12 14:31:20 -07:00
Matthew Holt f935458e3e cmd: Fix validate command when JSON contains "@id" fields
Also, don't run admin server when validating...
2019-12-12 14:30:22 -07:00
Matt Holt 2e0615270d fuzz: Remove Caddyfile adapter from fuzz corpus (#2925)
The Caddyfile adapter does not need to be fuzzed, as all it really does
is invoke the Caddyfile parser, which is already fuzzed
2019-12-10 15:00:31 -07:00
Matthew Holt fab5e4372a core: Add godoc examples for LoadModule 2019-12-10 14:06:35 -07:00
Matt Holt 3c90e370a4 v2: Module documentation; refactor LoadModule(); new caddy struct tags (#2924)
This commit goes a long way toward making automated documentation of
Caddy config and Caddy modules possible. It's a broad, sweeping change,
but mostly internal. It allows us to automatically generate docs for all
Caddy modules (including future third-party ones) and make them viewable
on a web page; it also doubles as godoc comments.

As such, this commit makes significant progress in migrating the docs
from our temporary wiki page toward our new website which is still under
construction.

With this change, all host modules will use ctx.LoadModule() and pass in
both the struct pointer and the field name as a string. This allows the
reflect package to read the struct tag from that field so that it can
get the necessary information like the module namespace and the inline
key.

This has the nice side-effect of unifying the code and documentation. It
also simplifies module loading, and handles several variations on field
types for raw module fields (i.e. variations on json.RawMessage, such as
arrays and maps).

I also renamed ModuleInfo.Name -> ModuleInfo.ID, to make it clear that
the ID is the "full name" which includes both the module namespace and
the name. This clarity is helpful when describing module hierarchy.

As of this change, Caddy modules are no longer an experimental design.
I think the architecture is good enough to go forward.
2019-12-10 13:36:46 -07:00
Marten Seemann a8533e5630 update quic-go to v0.14.1 (#2918) 2019-12-07 10:29:03 -07:00
Matthew Holt b07f6958ac Use "IsUnixNetwork" function instead of repeating the logic 2019-12-06 12:00:04 -07:00
Matthew Holt 33a318d173 Don't append port to unix sockets
See https://caddy.community/t/caddy-v2-php-fpm-502-error/6571?u=matt
2019-12-06 11:45:50 -07:00
lu4p 68adfdc559 Fix misspellings (#2908) 2019-12-04 16:28:13 -07:00
Marten Seemann a841688cc0 update quic-go to v0.14.0 (#2916) 2019-12-03 20:49:01 -07:00
Matthew Holt 52ae5f70d2 Merge branch 'v2' of ssh://github.com/caddyserver/caddy into v2 2019-11-30 17:53:38 -07:00
Matthew Holt 44f23a67bb http: Don't listen 1 port beyond port range 2019-11-30 17:53:25 -07:00
Mark Sargent 8b7d6a9ee8 v2: fixes query matcher parsing (#2901)
* fixes query matcher parsing

* return correct argument error when parsing query matcher
2019-11-29 13:05:22 -07:00
Matthew Holt 7c7ef8d40e http: Shorten regexp matcher placeholders; allow "=/" for simple matcher 2019-11-29 11:23:49 -07:00
Matthew Holt 14d3fd7d03 http: path matcher supports exact matching with = prefix 2019-11-28 21:11:45 -07:00
Matthew Holt 512b004332 http: header matcher supports fast prefix and suffix matching (#2888) 2019-11-27 11:52:31 -07:00
Matthew Holt db4293cb5f reverse_proxy: Add flush_interval to caddyfile syntax (#1460)
Also add godoc for Caddyfile syntax for file_server
2019-11-27 11:51:32 -07:00
Matthew Holt 6e10586303 admin: Preserve "@id" fields through partial changes (fixes #2902) 2019-11-27 11:49:49 -07:00
Matthew Holt 8de1a76227 reverse_proxy: Fix invalid argument to Intn in RandomChoice selection 2019-11-18 14:22:55 -07:00
Matthew Holt 9fe54e1c60 file_server: Use HTTPS port when a qualifying domain is specified
Also little comment cleanups
2019-11-16 10:44:45 -07:00
Matthew Holt b43e986a52 file_server: Optional pass_thru mode
If enabled, will call the next handler in the chain instead of returning
a 404.
2019-11-15 17:32:13 -07:00
Matthew Holt 1228dd7d93 reverse_proxy: Allow buffering of client requests
This is a bad idea, but some backends apparently require it. See
discussion in #176.
2019-11-15 17:15:33 -07:00
Matthew Holt af26a03da1 http: Only enable access logs if configured 2019-11-15 17:01:07 -07:00
Matthew Holt 8025ad9107 cmd: Disable admin endpoint for file-server and reverse-proxy commands
This makes it easier to use multiple instances on the same machine
2019-11-15 15:52:19 -07:00
Matthew Holt 6cdb2392d7 cmd: Improve stop command by trying API before signaling process
This allows graceful shutdown on all platforms
2019-11-15 15:45:18 -07:00
Matthew Holt 0ca109db4a Minor cleanups 2019-11-15 12:47:38 -07:00
Matthew Holt 0fc97211ab http: Make path matcher case-insensitive
Adds tests for both the path matcher and host matcher for case
insensitivity.

If case sensitivity is required for the path, a regexp matcher can
be used instead.

This is the v2 equivalent fix of PR #2882.
2019-11-15 12:47:06 -07:00
Matthew Holt ad90b273db core: Add tests to Replacer; fix panic (fixes #2852) 2019-11-11 19:29:31 -07:00
Mohammed Al Sahaf 93bc1b72e3 core: Use port ranges to avoid OOM with bad inputs (#2859)
* fix OOM issue caught by fuzzing

* use ParsedAddress as the struct name for the result of ParseNetworkAddress

* simplify code using the ParsedAddress type

* minor cleanups
2019-11-11 15:33:38 -07:00
Matthew Holt a19da07b72 http: Add response headers to access logs 2019-11-11 14:02:01 -07:00
Matthew Holt 16782d9988 http: Use permanent redirects for HTTP->HTTPS 2019-11-11 14:01:42 -07:00
Sarat Chandra dfdddcfacb logging: Support placeholders in level and filename (#2872)
* Add support for placeholders in Config

Fixes #2870

* Replace placeholders only in logging config.

Placeholders in log level and filename incase of file output are replaced.

* Add Provision to filewriter module for replacing placeholders
2019-11-11 11:04:41 -07:00
Marten Seemann 7ff02f37b6 go.mod: update quic-go to v0.13.1 (#2871) 2019-11-09 08:10:43 -07:00
Matthew Holt e4a2add73f cmd: Print errors to stderr 2019-11-08 09:59:49 -07:00
Matthew Holt 95615f5377 reverse_proxy: Fix NTLM auth detection
D'oh. Got mixed up in a refactoring.
2019-11-06 00:16:16 -07:00
Matthew Holt 8e515289cb reverse_proxy: Add support for NTLM 2019-11-05 16:29:10 -07:00
Matthew Holt 6e95477224 http: Eliminate allocation in cloneURL; add RemoteAddr to origRequest 2019-11-05 16:28:33 -07:00
Matthew Holt 97d918df3e reverse_proxy: Make HTTP versions configurable, don't set NextProtos 2019-11-05 16:27:51 -07:00
Matthew Holt f5c6a8553c Prepare for beta 9 tag 2019-11-04 13:43:39 -07:00
Matthew Holt 263ffbfaec caddyfile: Fix bug with Delete
It now will delete the current token even if it is the last one
2019-11-04 13:25:37 -07:00
Matthew Holt bf363f061d reverse_proxy: Add UnmarshalCaddyfile for random_choose selection policy
Also allow caddy.Duration to be given integer values which are treated
like regular time.Duration values (nanoseconds).

Fixes #2856
2019-11-04 12:54:46 -07:00
Matthew Holt 7129f6c1c0 admin: Remove /unload endpoint (is same as DELETE /config/) 2019-11-04 12:53:14 -07:00
Matthew Holt cb25dd72ab reverse_proxy: Add port to upstream address if only implied in scheme 2019-11-04 12:18:42 -07:00
Matthew Holt d55fa68902 http: Only log handler errors >= 500
Errors in the 4xx range are client errors, and they don't need to be
entered into the server's error logs. 4xx errors are still recorded in
the access logs at the error level.
2019-11-04 12:18:01 -07:00
Matthew Holt b1f41d0ff1 logging: Default logger should use wall time with milliseconds
This format is easier for humans to read and is still very precise.
2019-11-04 12:14:22 -07:00
Matthew Holt 6011ce120a cmd: Move module imports into standard packages
This makes it easier to make "standard" caddy builds, since you'll only
need to add a single import to get all of Caddy's standard modules.

There is a package for all of Caddy's standard modules (modules/standard)
and a package for the HTTP app's standard modules only
(modules/caddyhttp/standard).

We still need to decide which of these, if not all of them, should be
kept in the standard build. Those which aren't should be moved out of
this repo. See #2780.
2019-11-04 12:13:21 -07:00
Matthew Holt 27e288ab19 core: Synchronize calls to SetDeadline within fakeCloseListener
First evidenced in #2658, listener deadlines would sometimes be set
after clearing them, resulting in endless i/o timeout errors, which
leave all requests hanging. This bug is fixed by synchronizing the
calls to SetDeadline: when Close() is called, the deadline is first
set to a time in the past, and the lock is released only after the
deadline is set, so when the other servers break out of their Accept()
calls, they will clear the deadline *after* it was set. Before, the
clearing could sometimes come before the set, which meant that it was
left in a timeout state indefinitely.

This may not yet be a perfect solution -- ideally, the setting and
clearing of the deadline would happen exactly once per underlying
listener, not once per fakeCloseListener, but in rigorous testing with
these changes (comprising tens of thousands of config reloads), I was
able to verify that no race condition is manifest.
2019-11-04 12:10:03 -07:00
Matthew Holt 35f70c98fa core: Major refactor of admin endpoint and config handling
Fixed several bugs and made other improvements. All config changes are
now mediated by the global config state manager. It used to be that
initial configs given at startup weren't tracked, so you could start
caddy with --config caddy.json and then do a GET /config/ and it would
return null. That is fixed, along with several other general flow/API
enhancements, with more to come.
2019-11-04 12:05:20 -07:00
Matthew Holt fb06c041c4 http: Ensure server loggers are not nil (fixes #2849) 2019-10-31 11:45:18 -06:00
Matthew Holt 8ef0a0b4f8 reverse_proxy: Fix panic for some CLI flag values (closes #2848) 2019-10-31 11:34:54 -06:00
Matthew Holt 8d3c64932e http: Avoid panic if handler errors lack underlying error value
Fixes #2845
2019-10-30 21:41:52 -06:00
Mohammed Al Sahaf 0dd9243478 Re-remove admin fuzz target from azure-pipelines.yml (#2846)
Fixing a git-oopsie on my behalf
2019-10-31 01:49:18 +03:00
Andreas Schneider 432b94239d admin listener as opt-in for initial config (#2834)
* Always cleanup admin endpoint first

* Error out if no config has been set (#2833)

* Ignore explicitly missing admin config (#2833)

* Separate config loading from admin initialization (#2833)

* Add admin option to specify admin listener address (#2833)

* Use zap for reporting admin endpoint status
2019-10-30 15:12:42 -06:00
Mohammed Al Sahaf 4611537f06 Add missing fuzzer (#2844)
* fuzz: add missing fuzzer by fixing .gitignore adding a negation for caddyfile/ directory

* ci: print fuzzing type for debuggability and traceability

* README: update the Fuzzit badge to point to the correct Caddy server Github organization
2019-10-30 23:57:22 +03:00
Matthew Holt 76c22c7b38 auth: Clean up basicauth 2019-10-30 13:56:27 -06:00
Matthew Holt c7da6175bc fuzz: Remove admin fuzzer
Not really necessary; underlying work is done by json.Unmarshal which
is part of the Go standard lib. Also, it called Run, which potentially
tries to get certificates; we should not let that happen.
2019-10-30 12:19:59 -06:00
Matthew Holt 11a2733dc2 ci: Change fuzz type from regression to local-regression
As per recommendation from Fuzzit devs
2019-10-30 11:50:19 -06:00
Matthew Holt 1be121cec7 fuzz: Don't call Load() in HTTP caddyfile adapter fuzz tests
Doing so has a tendency to request certificates...
2019-10-30 11:48:21 -06:00
Matthew Holt dccba71276 reverse_proxy: Structured logs 2019-10-29 16:02:58 -06:00
Mohammed Al Sahaf be36aade9a ci: Update fuzzer target name (#2841)
Update the fuzzer target name for the address parser so it better matches the func name
2019-10-29 13:20:34 -06:00
Matthew Holt ba0000678d Remove unused fields from HandlerError 2019-10-29 11:59:08 -06:00
Matthew Holt c4c45f8e01 logging: Tweak defaults (enable logging by default, color level enc.) 2019-10-29 11:58:29 -06:00
Matthew Holt 54e458b756 proxy: Forgot to commit import 2019-10-29 10:22:49 -06:00
Matthew Holt d803561212 caddyhttp: Fix nil pointer dereference 2019-10-29 00:08:06 -06:00
Matthew Holt 813fff0584 proxy: Enable HTTP/2 on transport to backend 2019-10-29 00:07:45 -06:00
Matthew Holt d2e7baed8d Plug in distributed STEK module 2019-10-29 00:06:04 -06:00
Matthew Holt d6dad04e96 cache: Make peer addresses configurable 2019-10-28 15:09:12 -06:00
Matthew Holt 442fd748f6 caddyhttp: Minor cleanup and fix nil pointer deref in caddyfile adapter 2019-10-28 15:08:45 -06:00
Matt Holt b00dfd3965 v2: Logging! (#2831)
* logging: Initial implementation

* logging: More encoder formats, better defaults

* logging: Fix repetition bug with FilterEncoder; add more presets

* logging: DiscardWriter; delete or no-op logs that discard their output

* logging: Add http.handlers.log module; enhance Replacer methods

The Replacer interface has new methods to customize how to handle empty
or unrecognized placeholders. Closes #2815.

* logging: Overhaul HTTP logging, fix bugs, improve filtering, etc.

* logging: General cleanup, begin transitioning to using new loggers

* Fixes after merge conflict
2019-10-28 14:39:37 -06:00
Mohammed Al Sahaf 6c533558a3 fuzz-ci: fix & enhance fuzzing process (#2835)
* fuzz-ci: fix the authentication call for fuzzit by using the --api-key flag rather than the `auth` command

* Allow fuzzing on schedules as well as non-fork PRs

Closes #2710
2019-10-28 20:45:55 +03:00
Mohammed Al Sahaf 2fbe2ff40b fuzz: introduce continuous fuzzing for Caddy (#2723)
* fuzz: lay down the foundation for continuous fuzzing

* improve the fuzzers and add some

* fuzz: add Fuzzit badge to README & enable fuzzers submission in CI

* v2-fuzz: do away with the submodule approach for fuzzers

* fuzz: enable fuzzit
2019-10-25 18:52:16 -06:00
Matthew Holt faf67b1067 tls: Make the on-demand rate limiter actually work
This required a custom rate limiter implementation in CertMagic
2019-10-21 12:03:51 -06:00
Matthew Holt 208f2ff93c rewrite: Options to strip prefix/suffix and issue redirects
Fixes #2011
2019-10-19 19:22:29 -06:00
Mohammed Al Sahaf 19e834cf36 v2 ci: speed up some of powershell's processes (#2818)
* v2: speed up some of powershell's processes

* v2-ci: downloading latest Go on Windows isn't slow anymore, so update the log message accordingly

* v2: CI: use 7z on Windows instead of Expand-Archive
2019-10-17 14:58:22 -06:00
Matthew Holt bce2edd22d tls: Asynchronous cert management at startup (uses CertMagic v0.8.0) 2019-10-16 15:20:27 -06:00
Matthew Holt a458544d9f Minor enhancements/fixes to rewrite directive and template virt req's 2019-10-16 15:18:02 -06:00
Matt Holt 2f91b44587 v2: Make tests work on Windows (#2782)
* file_server: Make tests work on Windows

* caddyfile: Fix escaping when character is not escapable

We only escape certain characters depending on inside or outside of
quotes (mainly newlines and quotes). We don't want everyone to have to
escape Windows file paths like C:\\Windows\\... but we can't drop the
\ either if it's just C:\Windows\...
2019-10-15 16:05:53 -06:00
Mohammed Al Sahaf e3726588b4 v2: Project-and-CI-wide linter config (#2812)
* v2: split golangci-lint configuration into its own file to allow code editors to take advantage of it

* v2: simplify code

* v2: set the correct lint output formatting

* v2: invert the logic of linter's configuration of output formatting to allow the editor  convenience over CI-specific customization. Customize the output format in CI by passing the flag.

* v2: remove irrelevant golangci-lint config
2019-10-15 15:37:46 -06:00
Matthew Holt abf5ab340e caddyhttp: Improve ResponseRecorder to buffer headers 2019-10-15 14:07:10 -06:00
Matthew Holt acf7dea68f caddyhttp: host labels placeholders endianness from right->left
https://caddy.community/t/labeln-placeholder-endian-issue/5366

(I thought we had this before but it must have gotten lost somewhere)
2019-10-14 12:09:43 -06:00
Pascal bc738991b6 caddyhttp: Support placeholders in MatchHost (#2810)
* Replace global placeholders in host matcher

* caddyhttp: Fix panic on MatchHost tests
2019-10-14 11:29:36 -06:00
yzongyue fcd8869f51 reverse_proxy: optimize MaxIdleConnsPerHost default (#2809) 2019-10-11 23:57:11 -06:00
Matthew Holt 1e31be8de0 reverse_proxy: Allow dynamic backends (closes #990 and #1539)
This PR enables the use of placeholders in an upstream's Dial address.

A Dial address must represent precisely one socket after replacements.

See also #998 and #1639.
2019-10-11 14:25:39 -06:00
Matthew Holt 4aa3af4b78 go.mod: Use latest certmagic which uses lego v3.1.0 2019-10-11 10:48:06 -06:00
Matthew Holt 8715a28320 reverse_proxy: Customize SNI value in upstream request (closes #2483) 2019-10-10 17:17:06 -06:00
Matthew Holt 715e6ddf51 go.mod: Update dependencies 2019-10-10 15:47:26 -06:00
Matthew Holt 9c0bf311f9 Miscellaneous cleanups / comments 2019-10-10 15:38:30 -06:00
Matthew Holt 5300949e0d caddyhttp: Make responseRecorder capable of counting body size 2019-10-10 15:36:28 -06:00
Matthew Holt 411152016e Remove unused/placeholder log handler 2019-10-10 15:35:33 -06:00
Matthew Holt 5c7640a8d9 cmd: Plug in the http.handlers.authentication module 2019-10-10 15:05:33 -06:00
Matthew Holt f8366c2f09 http: authentication module; hash-password cmd; http_basic provider
This implements HTTP basicauth into Caddy 2. The basic auth module will
not work with passwords that are not securely hashed, so a subcommand
hash-password was added to make it convenient to produce those hashes.

Also included is Caddyfile support.

Closes #2747.
2019-10-10 14:37:27 -06:00
Pascal fe36d26b63 caddyhttp: Add RemoteAddr placeholders (#2801)
* Ignore build artifacts

* Add RemoteAddr placeholders
2019-10-10 13:37:08 -06:00
Matt Holt b38365ff3b Merge pull request #2799 from caddyserver/v2-enterprise-merge
v2: Merge enterprise code into open source v2 branch
2019-10-10 11:27:45 -06:00
Matthew Holt 26cc883708 http: Add Starlark handler
This migrates a feature that was previously reserved for enterprise
users, according to #2786.

The Starlark integration needs to be updated since this was made before
some significant changes in the v2 code base. When functional, it makes
it possible to have very dynamic HTTP handlers. This will be a long-term
ongoing project.

Credit to Danny Navarro
2019-10-10 11:02:16 -06:00
Matthew Holt 93943a6ac2 readme: Remove mentions of Caddy Enterprise (as per #2786) 2019-10-09 20:30:21 -06:00
Matthew Holt 85ce15a5ad tls: Add custom certificate selection policy
This migrates a feature that was previously reserved for enterprise
users, according to https://github.com/caddyserver/caddy/issues/2786.

Custom certificate selection policies allow advanced control over which
cert is selected when multiple qualify to satisfy a TLS handshake.
2019-10-09 19:41:45 -06:00
Matthew Holt dedcfd4e3d tls: Add distributed_stek module
This migrates a feature that was previously reserved for enterprise
users, according to https://github.com/caddyserver/caddy/issues/2786.

TLS session ticket keys are sensitive, so they should be rotated on a
regular basis. Only Caddy does this by default. However, a cluster of
servers that rotate keys without synchronization will lose the benefits
of having sessions in the first place if the client is routed to a
different backend. This module coordinates STEK rotation in a fleet so
the same keys are used, and rotated, across the whole cluster. No other
server does this, but Twitter wrote about how they hacked together a
solution a few years ago:
https://blog.twitter.com/engineering/en_us/a/2013/forward-secrecy-at-twitter.html
2019-10-09 19:38:26 -06:00
Matthew Holt 20fe9cf024 tls: Add pem_loader module
This migrates a feature that was previously reserved for enterprise
users, according to https://github.com/caddyserver/caddy/issues/2786.

The PEM loader allows you to embed PEM files (certificates and keys)
directly into your config, rather than requiring them to be stored on
potentially insecure storage, which adds attack vectors. This is useful
in automated settings where sensitive key material is stored only in
memory.

Note that if the config is persisted to disk, that added benefit may go
away, but there will still be the benefit of having lesser dependence on
external files.
2019-10-09 19:34:14 -06:00
Matthew Holt bcbe1c220d reverse_proxy: Add local circuit breaker
This migrates a feature that was previously reserved for enterprise
users, according to https://github.com/caddyserver/caddy/issues/2786.

The local circuit breaker is a simple metrics counter that can cause
the reverse proxy to consider a backend unhealthy before it actually
goes offline, by measuring recent latencies over a sliding window.

Credit to Danny Navarro
2019-10-09 19:28:07 -06:00
Matthew Holt a53b27c62e http: Add work-in-progress cache handler module
This migrates a feature that was previously reserved for enterprise
users, according to https://github.com/caddyserver/caddy/issues/2786.

The cache HTTP handler will be a high-performing, distributed cache
layer for HTTP requests. Right now, the implementation is a very basic
proof-of-concept, and further development is required.
2019-10-09 19:22:46 -06:00
Matthew Holt 03306e646e admin: /config and /id endpoints
This integrates a feature that was previously reserved for enterprise
users, according to https://github.com/caddyserver/caddy/issues/2786.

The /config and /id endpoints make granular config changes possible as
well as the exporting of the current configuration.

The /load endpoint has been modified to wrap the /config handler so that
the currently-running config can always be available for export. The
difference is that /load allows configs of varying formats and converts
them using config adapters. The adapted config is then processed with
/config as JSON. The /config and /id endpoints accept only JSON.
2019-10-09 19:10:00 -06:00
yzongyue 53dd600b4d cmd: Built-in commands all use RegisterCommand (#2794) 2019-10-08 20:12:15 -06:00
Matthew Holt ce1205239a cmd/main: Plug in json5 and jsonc config adapters 2019-10-06 20:48:31 -06:00
Matthew Holt bc3e44c1a6 cmd: adapt: Default --adapter value is "caddyfile" 2019-10-06 20:48:09 -06:00
Matthew Holt 8c55167f71 rewrite: Return parse error if too many Caddyfile args (fixes #2791) 2019-10-06 20:46:10 -06:00
Matthew Holt be7abda7d4 reverse_proxy: Implement retry_match; by default only retry GET requests
See https://caddy.community/t/http-proxy-and-non-get-retries/6304
2019-10-05 16:22:05 -06:00
Matthew Holt 6fd28b81dc caddyhttp: Define MatcherSets and RawMatcherSets types 2019-10-05 16:20:07 -06:00
Matthew Holt 65c060f56e file_server: Set default address to :2015 if --listen not specified 2019-10-04 17:30:51 -06:00
Matthew Holt 44cb804b9e reverse_proxy: Configurable request headers on active health checks
See https://caddy.community/t/health-check-user-agent/6309
2019-10-04 17:21:38 -06:00
Matthew Holt c11e3bffd6 Add file-server and reverse-proxy subcommands 2019-10-03 16:00:41 -06:00
Matthew Holt f29a9eee0d caddytls: nil check on storageClean fields on Stop 2019-10-02 23:39:32 -06:00
Matthew Holt 370b78c5c7 Update CLI docs in README 2019-10-01 20:45:31 -06:00
Mohammed Al Sahaf 1ecb216001 v2: introduce CI (#2768)
* v2: introduce CI for v2 branch

* v2-ci: split test report generation from test pass to preserve exit code

* v2-ci: spilt lint results from unit test results

* v2-ci: fix testRunTitle name

* v2-ci: break up the steps for more accurate status indicators

* v2-ci: break steps into different jobs

* v2-ci: revert back to single-job pattern

* v2-ci: reflect the true result by coercing SucceededWithIssues into Failed in the last step

* v2-ci: don't fail the build on lint errors
2019-10-01 16:47:29 -06:00
Matthew Holt 94f98c0733 go.mod: Use latest certmagic 2019-10-01 11:25:52 -06:00
Matthew Holt 2c3657bb8a cmd: CLI improvements; add --validate to adapt command 2019-10-01 11:02:13 -06:00
Matthew Holt 5b36424cf0 cmd: Add validate subcommand; list-modules --versions; some renaming
Renames --config-adapter flag to --adapter, adapt-config command to
adapt, --print-env flag to --environ, and --input flag to --config.
2019-09-30 23:43:39 -06:00
aca 0006df6026 cmd: Refactor subcommands, add help, make them pluggable
* cli: Change command structure, add help subcommand (#328)

* cli: improve subcommand structure

- make help command as normal subcommand
- add flag usage message for each command

* cmd: Refactor subcommands and command line help; make commands pluggable
2019-09-30 21:23:58 -06:00
Matthew Holt c95db3551d caddytls: Ensure automation field is not nil when appending (fix #2779) 2019-09-30 11:53:21 -06:00
Matthew Holt 8eb2c37251 Clean up provisioned modules on error; refactor Run(); add Validate()
Modules that return an error during provisioning should still be cleaned
up so that they don't leak any resources they may have allocated before
the error occurred. Cleanup should be able to run even if Provision does
not complete fully.
2019-09-30 09:16:01 -06:00
Matthew Holt 1e66226217 httpcaddyfile: Add acme_ca and email global options
Also add ability to access options from individual unmarshalers through
the Helper values
2019-09-30 09:11:30 -06:00
Matthew Holt 7b4aa108c7 caddyhttp: 'not' matcher: Support Caddyfile unmarshaling 2019-09-30 09:09:57 -06:00
Matthew Holt 8b11ed347b Add license header to filestorage.go 2019-09-30 09:08:04 -06:00
Matthew Holt b249b45d10 tls: Change struct fields to pointers, add nil checks; rate.Burst update
Making them pointers makes for cleaner JSON when adapting configs, if
the struct is empty now it will be omitted entirely.

The x/time/rate package was updated to support changing the burst, so
we've incorporated that here and removed a TODO.
2019-09-30 09:07:43 -06:00
Matthew Holt c12bf4054c caddyfile: Fix lexer behavior with regards to escaped newlines
Newlines (\n) can be escaped outside of quoted areas and the newline
will be treated as whitespace but not as an actual line break. Escaping
newlines inside a quoted area is not necessary, and because quotes
trigger literal interpretation of the contents, the escaping backslash
will be parsed as a literal backslash, and the newline will not be
escaped.

Caveat: When a newline is escaped, tokens after it until an unescaped
newline will appear to the parser be on the same line as the initial
token after the last unescaped newline. This may technically lead to
some false line numbers if errors are given, but escaped newlines are
counted so that the next token after an unescaped newline is correct.

See #2766
2019-09-28 21:18:36 -06:00
Matthew Holt 735d6ce405 httpcaddyfile: Fix missing module name of storage adapter 2019-09-26 17:06:15 -07:00
Matthew Holt 7b33c8db31 tls: Make cert and OCSP check intervals configurable
This enables use of ACME CAs that issue shorter-lived certs
2019-09-24 17:04:03 -07:00
Matt Holt 11696793bd tls/acme: Ability to customize trusted roots for ACME servers (#2756)
Closes #2702
2019-09-24 15:46:39 -07:00
Matthew Holt 3e8bff594a go.mod: Update certmagic to v0.7.3 2019-09-20 13:17:17 -06:00
Matthew Holt 2f684e42d5 reverse_proxy/headers: Expose header replacement ability in Caddyfile
Adds header_up and header_down subdirectives to reverse_proxy
2019-09-20 13:13:49 -06:00
Matthew Holt ba29f9d41d httpcaddyfile: Global storage configuration (closes #2758) 2019-09-19 12:42:36 -06:00
Matthew Holt 40e05e5a01 http: Improve auto HTTP->HTTPS redirects, fix edge cases
See https://caddy.community/t/v2-issues-with-multiple-server-blocks-in-caddyfile-style-config/6206/13?u=matt

Also print pid when using `caddy start`
2019-09-18 18:01:32 -06:00
Matthew Holt 39d61cad2d httpcaddyfile: Fix nil pointer dereference 2019-09-18 10:51:49 -06:00
Matthew Holt bc9f944837 host matcher: Strip [ ] from IPv6 addresses 2019-09-18 09:45:21 -06:00
Matthew Holt 4c289fc6ad Allow domain fronting with TLS client auth if explicitly configured 2019-09-17 23:13:21 -06:00
Matthew Holt 19f36667f7 tls: Clean up expired OCSP staples and certificates 2019-09-17 16:00:15 -06:00
Matt Holt 484cee1ac1 fastcgi: Implement / redirect for index.php with php_fastcgi directive (#2754)
* fastcgi: Implement / redirect for index.php with php_fastcgi directive

See #2752 and https://caddy.community/t/v2-redirect-path-to-path-index-php-with-assets/6196?u=matt

* caddyhttp: MatchNegate implements json.Marshaler

* fastcgi: Add /index.php element to try_files matcher

* fastcgi: Make /index.php redirect permanent
2019-09-17 15:16:17 -06:00
Matthew Holt d030bfdae0 httpcaddyfile: static_response -> respond; minor cleanups 2019-09-16 11:04:18 -06:00
Matthew Holt db4c73dd58 reverse_proxy: Close idle connections on module unload 2019-09-14 18:10:29 -06:00
Matthew Holt f15f0d5839 Eliminate some TODOs 2019-09-14 18:05:45 -06:00
Matthew Holt e73b117332 reverse_proxy: Ability to mutate headers; set upstream placeholders 2019-09-14 13:25:26 -06:00
Matthew Holt 2fd22139c6 headers: Ability to mutate request headers including http.Request.Host
Also a few bug fixes
2019-09-14 13:22:48 -06:00
Mohammed Al Sahaf 5c9ebe3af1 Use keybase fork of mitchellh/go-ps for bug fixes (#2750) 2019-09-13 23:40:29 -06:00
Matthew Holt 2ab2d5bf9e Forgot to commit caddyfile.go changes in last commit 2019-09-13 23:38:52 -06:00
Matthew Holt c09e86fddc headers: Add ability to replace substrings in header fields
This will probably be useful so the proxy can rewrite header values.
2019-09-13 16:24:51 -06:00
Matthew Holt 46aaf02371 encode: Fix bug where default status code was being written
for small responses.

See https://caddy.community/t/v2-permanent-redirect-prompt/6190?u=matt
2019-09-13 16:00:03 -06:00
374 changed files with 57733 additions and 7418 deletions
+5
View File
@@ -0,0 +1,5 @@
[*]
end_of_line = lf
[caddytest/integration/caddyfile_adapt/*.txt]
indent_style = tab
+1
View File
@@ -0,0 +1 @@
*.go text eol=lf
+187
View File
@@ -0,0 +1,187 @@
Contributing to Caddy
=====================
Welcome! Thank you for choosing to be a part of our community. Caddy wouldn't be great without your involvement!
For starters, we invite you to join [the Caddy forum](https://caddy.community) where you can hang out with other Caddy users and developers.
## Common Tasks
- [Contributing code](#contributing-code)
- [Writing a Caddy module](#writing-a-caddy-module)
- [Asking or answering questions for help using Caddy](#getting-help-using-caddy)
- [Reporting a bug](#reporting-bugs)
- [Suggesting an enhancement or a new feature](#suggesting-features)
- [Improving documentation](#improving-documentation)
Other menu items:
- [Values](#values)
- [Coordinated Disclosure](#coordinated-disclosure)
- [Thank You](#thank-you)
### Contributing code
You can have a huge impact on the project by helping with its code. To contribute code to Caddy, first submit or comment in an issue to discuss your contribution, then open a [pull request](https://github.com/caddyserver/caddy/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.** You can get familiar with Caddy's code base by using [code search at Sourcegraph](https://sourcegraph.com/github.com/caddyserver/caddy).
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions&mdash;even if it seems small or insignificant. Please don't take it personally. :blue_heart: If your change is on the right track, we can guide you to make it mergable.
Here are some of the expectations we have of contributors:
- **Open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that any changes are in-line with the project's goals and the best interests of its users. We can also discuss the best possible implementation. If there's already an issue about it, comment on the existing issue to claim it. A lot of valuable time can be saved by discussing a proposal first.
- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we want to avoid.](https://twitter.com/iamdevloper/status/397664295875805184)
- **Keep related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other.
- **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or profiling.
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
- **Own your contributions.** Caddy is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
- **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious.
- **Pull requests may still get closed.** The longer a PR stays open and idle, the more likely it is to be closed. If we haven't reviewed it in a while, it probably means the change is not a priority. Please don't take this personally, we're trying to balance a lot of tasks! If nobody else has commented or reacted to the PR, it likely means your change is useful only to you. The reality is this happens quite a bit. We don't tend to accept PRs that aren't generally helpful. For these reasons or others, the PR may get closed even after a review. We are not obligated to accept all proposed changes, even if the best justification we can give is something vague like, "It doesn't sit right." Sometimes PRs are just the wrong thing or the wrong time. Because it is open source, you can always build your own modified version of Caddy with a change you need, even if we reject it in the official repo.
We often grant [collaborator status](#collaborator-instructions) to contributors who author one or more significant, high-quality PRs that are merged into the code base!
#### HOW TO MAKE A PULL REQUEST TO CADDY
Contributing to Go projects on GitHub is fun and easy. After you have proposed your change in an issue, we recommend the following workflow:
1. [Fork this repo](https://github.com/caddyserver/caddy). This makes a copy of the code you can write to.
2. If you don't already have this repo (caddyserver/caddy.git) repo on your computer, clone it down: `git clone https://github.com/caddyserver/caddy.git`
3. Tell git that it can push the caddyserver/caddy.git repo to your fork by adding a remote: `git remote add myfork https://github.com/<your-username>/caddy.git`
4. Make your changes in the caddyserver/caddy.git repo on your computer.
5. Push your changes to your fork: `git push myfork`
6. [Create a pull request](https://github.com/caddyserver/caddy/pull/new/master) to merge your changes into caddyserver/caddy @ master. (Click "compare across forks" and change the head fork.)
This workflow is nice because you don't have to change import paths. You can get fancier by using different branches if you want.
### Writing a Caddy module
Caddy can do more with modules! Anyone can write one. Caddy modules are Go libraries that get compiled into Caddy, extending its feature set. They can add directives to the Caddyfile, add new configuration adapters, and even implement new server types (e.g. HTTP, DNS).
[Learn how to write a module here](https://caddyserver.com/docs/extending-caddy). You should also share and discuss your module idea [on the forums](https://caddy.community) to have people test it out. We don't use the Caddy issue tracker for third-party modules.
### Getting help using Caddy
If you have a question about using Caddy, [ask on our forum](https://caddy.community)! There will be more people there who can help you than just the Caddy developers who follow our issue tracker. Issues are not the place for usage questions.
Many people on the forums could benefit from your experience and expertise, too. Once you've been helped, consider giving back by answering other people's questions and participating in other discussions.
### Reporting bugs
Like every software, Caddy has its flaws. If you find one, [search the issues](https://github.com/caddyserver/caddy/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/caddyserver/caddy/issues/new) and describe the bug, and somebody will look into it! (This repository is only for Caddy and its standard modules.)
**You can help us fix bugs!** Speed up the patch by identifying the bug in the code. This can sometimes be done by adding `fmt.Println()` statements (or similar) in relevant code paths to narrow down where the problem may be. It's a good way to [introduce yourself to the Go language](https://tour.golang.org), too.
We may reply with an issue template. Please follow the template so we have all the needed information. Unredacted&mdash;yes, actual values matter. We need to be able to repeat the bug using your instructions. Please simplify the issue as much as possible. If you don't, we might close your report. The burden is on you to make it easily reproducible and to convince us that it is actually a bug in Caddy. This is easiest to do when you write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we will be able to help you!
We suggest reading [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
Please be kind. :smile: Remember that Caddy comes at no cost to you, and you're getting free support when we fix your issues. If we helped you, please consider helping someone else!
#### Bug reporting expectations
Maintainers---or more generally, developers---need three things to act on bugs:
1. To agree or be convinced that it's a bug (reporter's responsibility).
- A bug is unintentional, undesired, or surprising behavior which violates documentation or relevant spec. It might be either a mistake in the documentation or a bug in the code.
- This project usually does not work around bugs in other software, systems, and dependencies; instead, we recommend that those bugs are fixed at their source. This sometimes means we close issues or reject PRs that attempt to fix, workaround, or hide bugs in other projects.
2. To be able to understand what is happening (mostly reporter's responsibility).
- If the reporter can provide satisfactory instructions such that a developer can reproduce the bug, the developer will likely be able to understand the bug, write a test case, and implement a fix. This is the least amount of work for everyone and path to the fastest resolution.
- Otherwise, the burden is on the reporter to test possible solutions. This is less preferable because it loosens the feedback loop, slows down debugging efforts, obscures the true nature of the problem from the developers, and is unlikely to result in new test cases.
3. A solution, or ideas toward a solution (mostly maintainer's responsibility).
- Sometimes the best solution is a documentation change.
- Usually the developers have the best domain knowledge for inventing a solution, but reporters may have ideas or preferences for how they would like the software to work.
- Security, correctness, and project goals/vision all take priority over a user's preferences.
- It's simply good business to yield a solution that satisfies the users, and it's even better business to leave them impressed.
Thus, at the very least, the reporter is expected to:
1. Convince the reader that it's a bug in Caddy (if it's not obvious).
2. Reduce the problem down to the minimum specific steps required to reproduce it.
The maintainer is usually able to do the rest; but of course the reporter may invest additional effort to speed up the process.
### Suggesting features
First, [search to see if your feature has already been requested](https://github.com/caddyserver/caddy/issues). If it has, you can add a :+1: reaction to vote for it. If your feature idea is new, open an issue to request the feature. Please describe your idea thoroughly so that we know how to implement it! Really vague requests may not be helpful or actionable and, without clarification, will have to be closed.
While we really do value your requests and implement many of them, not all features are a good fit for Caddy. Most of those [make good modules](#writing-a-caddy-module), which can be made by anyone! But if a feature is not in the best interest of the Caddy project or its users in general, we may politely decline to implement it into Caddy core. Additionally, some features are bad ideas altogether (for either obvious or non-obvious reasons) which may be rejected. We'll try to explain why we reject a feature, but sometimes the best we can do is, "It's not a good fit for the project."
### Improving documentation
Caddy's documentation is available at [https://caddyserver.com/docs](https://caddyserver.com/docs) and its source is in the [website repo](https://github.com/caddyserver/website). If you would like to make a fix to the docs, please submit an issue there describing the change to make.
Note that third-party module documentation is not hosted by the Caddy website, other than basic usage examples. They are managed by the individual module authors, and you will have to contact them to change their documentation.
Our documentation is scoped to the Caddy project only: it is not for describing how other software or systems work, even if they relate to Caddy or web servers. That kind of content [can be found in our community wiki](https://caddy.community/c/wiki/13), however.
## Collaborator Instructions
Collaborators have push rights to the repository. We grant this permission after one or more successful, high-quality PRs are merged! We thank them for their help. The expectations we have of collaborators are:
- **Help review pull requests.** Be meticulous, but also kind. We love our contributors, but we critique the contribution to make it better. Multiple, thorough reviews make for the best contributions! Here are some questions to consider:
- Can the change be made more elegant?
- Is this a maintenance burden?
- What assumptions does the code make?
- Is it well-tested?
- Is the change a good fit for the project?
- Does it actually fix the problem or is it creating a special case instead?
- Does the change incur any new dependencies? (Avoid these!)
- **Answer issues.** If every collaborator helped out with issues, we could count the number of open issues on two hands. This means getting involved in the discussion, investigating the code, and yes, debugging it. It's fun. Really! :smile: Please, please help with open issues. Granted, some issues need to be done before others. And of course some are larger than others: you don't have to do it all yourself. Work with other collaborators as a team!
- **Do not merge pull requests until they have been approved by one or two other collaborators.** If a project owner approves the PR, it can be merged (as long as the conversation has finished too).
- **Prefer squashed commits over a messy merge.** If there are many little commits, please [squash the commits](https://stackoverflow.com/a/11732910/1048862) so we don't clutter the commit history.
- **Don't accept new dependencies lightly.** Dependencies can make the world crash and burn, but they are sometimes necessary. Choose carefully. Extremely small dependencies (a few lines of code) can be inlined. The rest may not be needed. For those that are, Caddy uses [go modules](https://github.com/golang/go/wiki/Modules). All external dependencies must be installed as modules, and _Caddy must not export any types defined by those dependencies_. Check this diligently!
- **Be extra careful in some areas of the code.** There are some critical areas in the Caddy code base that we review extra meticulously: the `caddyhttp` and `caddytls` packages especially.
- **Make sure tests test the actual thing.** Double-check that the tests fail without the change, and pass with it. It's important that they assert what they're purported to assert.
- **Recommended reading**
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) for an idea of what we look for in good, clean Go code
- [Linus Torvalds describes a good commit message](https://gist.github.com/matthewhudson/1475276)
- [Best Practices for Maintainers](https://opensource.guide/best-practices/)
- [Shrinking Code Review](https://alexgaynor.net/2015/dec/29/shrinking-code-review/)
## Values (WIP)
- A person is always more important than code. People don't like being handled "efficiently". But we can still process issues and pull requests efficiently while being kind, patient, and considerate.
- The ends justify the means, if the means are good. A good tree won't produce bad fruit. But if we cut corners or are hasty in our process, the end result will not be good.
## Security Policy
If you think you've found a security vulnerability, please refer to our [Security Policy](https://github.com/caddyserver/caddy/security/policy) document.
## Thank you
Thanks for your help! Caddy would not be what it is today without your contributions.
+12
View File
@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: [mholt] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+59
View File
@@ -0,0 +1,59 @@
# Security Policy
The Caddy project would like to make sure that it stays on top of all practically-exploitable vulnerabilities.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2.x | :white_check_mark: |
| 1.x | :x: |
| < 1.x | :x: |
## Acceptable Scope
A security report must demonstrate a security bug in the source code from this repository.
Some security problems are the result of interplay between different components of the Web, rather than a vulnerability in the web server itself. Please only report vulnerabilities in the web server itself, as we cannot coerce the rest of the Web to be fixed (for example, we do not consider IP spoofing, BGP hijacks, or missing/misconfigured HTTP headers a vulnerability in the Caddy web server).
Vulnerabilities caused by misconfigurations are out of scope. Yes, it is entirely possible to craft and use a configuration that is unsafe, just like with every other web server; we recommend against doing that.
We do not accept reports if the steps imply or require a compromised system or third-party software, as we cannot control those. We expect that users secure their own systems and keep all their software patched. For example, if untrusted users are able to upload/write/host arbitrary files in the web root directory, it is NOT a security bug in Caddy if those files get served to clients; however, it _would_ be a valid report if a bug in Caddy's source code unintentionally gave unauthorized users the ability to upload unsafe files or delete files without relying on an unpatched system or piece of software.
Client-side exploits are out of scope. In other words, it is not a bug in Caddy if the web browser does something unsafe, even if the downloaded content was served by Caddy. (Those kinds of exploits can generally be mitigated by proper configuration of HTTP headers.) As a general rule, the content served by Caddy is not considered in scope because content is configurable by the site owner or the associated web application.
Security bugs in code dependencies are out of scope. Instead, if a dependency has patched a relevant security bug, please feel free to open a public issue or pull request to update that dependency in our code.
## Reporting a Vulnerability
We get a lot of difficult reports that turn out to be invalid. Clear, obvious reports tend to be the most credible (but are also rare).
First please ensure your report falls within the accepted scope of security bugs (above).
We'll need enough information to verify the bug and make a patch. To speed things up, please include:
- Most minimal possible config (without redactions!)
- Command(s)
- Precise HTTP requests (`curl -v` and its output please)
- Full log output (please enable debug mode)
- Specific minimal steps to reproduce the issue from scratch
- A working patch
Please DO NOT use containers, VMs, cloud instances or services, or any other complex infrastructure in your steps. Always prefer `curl` instead of web browsers.
We consider publicly-registered domain names to be public information. This necessary in order to maintain the integrity of certificate transparency, public DNS, and other public trust systems. Do not redact domain names from your reports. The actual content of your domain name affects Caddy's behavior, so we need the exact domain name(s) to reproduce with, or your report will be ignored.
It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, depending on available resources. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. (We get a lot of invalid reports.) Thank you for understanding.
When you are ready, please email Matt Holt (the author) directly: matt at dyanim dot com.
Please don't encrypt the email body. It only makes the process more complicated.
Please also understand that due to our nature as an open source project, we do not have a budget to award security bounties. We can only thank you.
If your report is valid and a patch is released, we will not reveal your identity by default. If you wish to be credited, please give us the name to use and/or your GitHub username. If you don't provide this we can't credit you.
Thanks for responsibly helping Caddy&mdash;and thousands of websites&mdash;be more secure!
+183
View File
@@ -0,0 +1,183 @@
# Used as inspiration: https://github.com/mvdan/github-actions-golang
name: Tests
on:
push:
branches:
- master
- 2.*
pull_request:
branches:
- master
- 2.*
jobs:
test:
strategy:
# Default is true, cancels jobs for other platforms in the matrix if one fails
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
go: [ '1.18', '1.19' ]
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.18'
GO_SEMVER: '~1.18.4'
- go: '1.19'
GO_SEMVER: '~1.19.0'
# Set some variables per OS, usable via ${{ matrix.VAR }}
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
# SUCCESS: the typical value for $? per OS (Windows/pwsh returns 'True')
- os: ubuntu-latest
CADDY_BIN_PATH: ./cmd/caddy/caddy
SUCCESS: 0
- os: macos-latest
CADDY_BIN_PATH: ./cmd/caddy/caddy
SUCCESS: 0
- os: windows-latest
CADDY_BIN_PATH: ./cmd/caddy/caddy.exe
SUCCESS: 'True'
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
- name: Checkout code
uses: actions/checkout@v3
# These tools would be useful if we later decide to reinvestigate
# publishing test/coverage reports to some tool for easier consumption
# - name: Install test and coverage analysis tools
# run: |
# go get github.com/axw/gocov/gocov
# go get github.com/AlekSi/gocov-xml
# go get -u github.com/jstemmer/go-junit-report
# echo "::add-path::$(go env GOPATH)/bin"
- name: Print Go version and environment
id: vars
run: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
printf "Git version: $(git version)\n\n"
# Calculate the short SHA1 hash of the git commit
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
- name: Cache the build cache
uses: actions/cache@v2
with:
# In order:
# * Module download cache
# * Build cache (Linux)
# * Build cache (Mac)
# * Build cache (Windows)
path: |
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
~\AppData\Local\go-build
key: ${{ runner.os }}-${{ matrix.go }}-go-ci-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-${{ matrix.go }}-go-ci
- name: Get dependencies
run: |
go get -v -t -d ./...
# mkdir test-results
- name: Build Caddy
working-directory: ./cmd/caddy
env:
CGO_ENABLED: 0
run: |
go build -trimpath -ldflags="-w -s" -v
- name: Publish Build Artifact
uses: actions/upload-artifact@v1
with:
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
path: ${{ matrix.CADDY_BIN_PATH }}
# Commented bits below were useful to allow the job to continue
# even if the tests fail, so we can publish the report separately
# For info about set-output, see https://stackoverflow.com/questions/57850553/github-actions-check-steps-status
- name: Run tests
# id: step_test
# continue-on-error: true
run: |
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
go test -v -coverprofile="cover-profile.out" -short -race ./...
# echo "::set-output name=status::$?"
# Relevant step if we reinvestigate publishing test/coverage reports
# - name: Prepare coverage reports
# run: |
# mkdir coverage
# gocov convert cover-profile.out > coverage/coverage.json
# # Because Windows doesn't work with input redirection like *nix, but output redirection works.
# (cat ./coverage/coverage.json | gocov-xml) > coverage/coverage.xml
# To return the correct result even though we set 'continue-on-error: true'
# - name: Coerce correct build result
# if: matrix.os != 'windows-latest' && steps.step_test.outputs.status != ${{ matrix.SUCCESS }}
# run: |
# echo "step_test ${{ steps.step_test.outputs.status }}\n"
# exit 1
s390x-test:
name: test (s390x on IBM Z)
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == github.repository
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
steps:
- name: Checkout code into the Go module directory
uses: actions/checkout@v3
- name: Run Tests
run: |
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
# short sha is enough?
short_sha=$(git rev-parse --short HEAD)
# The environment is fresh, so there's no point in keeping accepting and adding the key.
rsync -arz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress --delete --exclude '.git' . "$CI_USER"@ci-s390x.caddyserver.com:/var/tmp/"$short_sha"
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t "$CI_USER"@ci-s390x.caddyserver.com "cd /var/tmp/$short_sha; go version; go env; printf "\n\n";CGO_ENABLED=0 go test -v ./..."
test_result=$?
# There's no need leaving the files around
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$CI_USER"@ci-s390x.caddyserver.com "rm -rf /var/tmp/'$short_sha'"
echo "Test exit code: $test_result"
exit $test_result
env:
SSH_KEY: ${{ secrets.S390X_SSH_KEY }}
CI_USER: ${{ secrets.CI_USER }}
goreleaser-check:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
- uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: check
env:
TAG: ${{ steps.vars.outputs.version_tag }}
+74
View File
@@ -0,0 +1,74 @@
name: Cross-Build
on:
push:
branches:
- master
- 2.*
pull_request:
branches:
- master
- 2.*
jobs:
cross-build-test:
strategy:
fail-fast: false
matrix:
goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd']
go: [ '1.19' ]
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.19'
GO_SEMVER: '~1.19.0'
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
- name: Print Go version and environment
id: vars
run: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
- name: Cache the build cache
uses: actions/cache@v2
with:
# In order:
# * Module download cache
# * Build cache (Linux)
path: |
~/go/pkg/mod
~/.cache/go-build
key: cross-build-go${{ matrix.go }}-${{ matrix.goos }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
cross-build-go${{ matrix.go }}-${{ matrix.goos }}
- name: Checkout code into the Go module directory
uses: actions/checkout@v3
- name: Run Build
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
shell: bash
continue-on-error: true
working-directory: ./cmd/caddy
run: |
GOOS=$GOOS go build -trimpath -o caddy-"$GOOS"-amd64 2> /dev/null
if [ $? -ne 0 ]; then
echo "::warning ::$GOOS Build Failed"
exit 0
fi
+35
View File
@@ -0,0 +1,35 @@
name: Lint
on:
push:
branches:
- master
- 2.*
pull_request:
branches:
- master
- 2.*
jobs:
# From https://github.com/golangci/golangci-lint-action
golangci:
name: lint
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '~1.18.4'
check-latest: true
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.47
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
args: --timeout 10m
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
+184
View File
@@ -0,0 +1,184 @@
name: Release
on:
push:
tags:
- 'v*.*.*'
jobs:
release:
name: Release
strategy:
matrix:
os: [ ubuntu-latest ]
go: [ '1.19' ]
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.19'
GO_SEMVER: '~1.19.0'
runs-on: ${{ matrix.os }}
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
# https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings
permissions:
id-token: write
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps#permission-on-contents
# "Releases" is part of `contents`, so it needs the `write`
contents: write
steps:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
# Force fetch upstream tags -- because 65 minutes
# tl;dr: actions/checkout@v3 runs this line:
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
# git fetch --prune --unshallow
# which doesn't overwrite that tag because that would be destructive.
# Credit to @francislavoie for the investigation.
# https://github.com/actions/checkout/issues/290#issuecomment-680260080
- name: Force fetch upstream tags
run: git fetch --tags --force
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
- name: Print Go version and environment
id: vars
run: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\//}"
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
# Add "pip install" CLI tools to PATH
echo ~/.local/bin >> $GITHUB_PATH
# Parse semver
TAG=${GITHUB_REF/refs\/tags\//}
SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)'
TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"`
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
echo "::set-output name=tag_major::${TAG_MAJOR}"
echo "::set-output name=tag_minor::${TAG_MINOR}"
echo "::set-output name=tag_patch::${TAG_PATCH}"
echo "::set-output name=tag_special::${TAG_SPECIAL}"
# Cloudsmith CLI tooling for pushing releases
# See https://help.cloudsmith.io/docs/cli
- name: Install Cloudsmith CLI
run: pip install --upgrade cloudsmith-cli
- name: Validate commits and tag signatures
run: |
# Import Matt Holt's key
curl 'https://github.com/mholt.gpg' | gpg --import
echo "Verifying the tag: ${{ steps.vars.outputs.version_tag }}"
# tags are only accepted if signed by Matt's key
git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1
- name: Cache the build cache
uses: actions/cache@v2
with:
# In order:
# * Module download cache
# * Build cache (Linux)
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go${{ matrix.go }}-release-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go${{ matrix.go }}-release
- name: Install Cosign
uses: sigstore/cosign-installer@main
- name: Cosign version
run: cosign version
- name: Install Syft
uses: anchore/sbom-action/download-syft@main
- name: Syft version
run: syft version
# GoReleaser will take care of publishing those artifacts into the release
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist --timeout 60m
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.vars.outputs.version_tag }}
COSIGN_EXPERIMENTAL: 1
# Only publish on non-special tags (e.g. non-beta)
# We will continue to push to Gemfury for the foreseeable future, although
# Cloudsmith is probably better, to not break things for existing users of Gemfury.
# See https://gemfury.com/caddy/deb:caddy
- name: Publish .deb to Gemfury
if: ${{ steps.vars.outputs.tag_special == '' }}
env:
GEMFURY_PUSH_TOKEN: ${{ secrets.GEMFURY_PUSH_TOKEN }}
run: |
for filename in dist/*.deb; do
# armv6 and armv7 are both "armhf" so we can skip the duplicate
if [[ "$filename" == *"armv6"* ]]; then
echo "Skipping $filename"
continue
fi
curl -F package=@"$filename" https://${GEMFURY_PUSH_TOKEN}:@push.fury.io/caddy/
done
# Publish only special tags (unstable/beta/rc) to the "testing" repo
# See https://cloudsmith.io/~caddy/repos/testing/
- name: Publish .deb to Cloudsmith (special tags)
if: ${{ steps.vars.outputs.tag_special != '' }}
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
run: |
for filename in dist/*.deb; do
# armv6 and armv7 are both "armhf" so we can skip the duplicate
if [[ "$filename" == *"armv6"* ]]; then
echo "Skipping $filename"
continue
fi
echo "Pushing $filename to 'testing'"
cloudsmith push deb caddy/testing/any-distro/any-version $filename
done
# Publish stable tags to Cloudsmith to both repos, "stable" and "testing"
# See https://cloudsmith.io/~caddy/repos/stable/
- name: Publish .deb to Cloudsmith (stable tags)
if: ${{ steps.vars.outputs.tag_special == '' }}
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
run: |
for filename in dist/*.deb; do
# armv6 and armv7 are both "armhf" so we can skip the duplicate
if [[ "$filename" == *"armv6"* ]]; then
echo "Skipping $filename"
continue
fi
echo "Pushing $filename to 'stable'"
cloudsmith push deb caddy/stable/any-distro/any-version $filename
echo "Pushing $filename to 'testing'"
cloudsmith push deb caddy/testing/any-distro/any-version $filename
done
+34
View File
@@ -0,0 +1,34 @@
name: Release Published
# Event payload: https://developer.github.com/webhooks/event-payloads/#release
on:
release:
types: [published]
jobs:
release:
name: Release Published
strategy:
matrix:
os: [ ubuntu-latest ]
runs-on: ${{ matrix.os }}
steps:
# See https://github.com/peter-evans/repository-dispatch
- name: Trigger event on caddyserver/dist
uses: peter-evans/repository-dispatch@v1
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/dist
event-type: release-tagged
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
- name: Trigger event on caddyserver/caddy-docker
uses: peter-evans/repository-dispatch@v1
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/caddy-docker
event-type: release-tagged
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
+15
View File
@@ -1,13 +1,28 @@
_gitignore/
*.log
Caddyfile
Caddyfile.*
!caddyfile/
# artifacts from pprof tooling
*.prof
*.test
# build artifacts and helpers
cmd/caddy/caddy
cmd/caddy/caddy.exe
# mac specific
.DS_Store
# go modules
vendor
# goreleaser artifacts
dist
caddy-build
caddy-dist
# IDE files
.idea/
.vscode/
+98
View File
@@ -0,0 +1,98 @@
linters-settings:
errcheck:
ignore: fmt:.*,go.uber.org/zap/zapcore:^Add.*
ignoretests: true
linters:
disable-all: true
enable:
- bodyclose
- deadcode
- errcheck
- gofmt
- goimports
- gosec
- gosimple
- govet
- ineffassign
- misspell
- prealloc
- staticcheck
- structcheck
- typecheck
- unconvert
- unused
- varcheck
# these are implicitly disabled:
# - asciicheck
# - depguard
# - dogsled
# - dupl
# - exhaustive
# - exportloopref
# - funlen
# - gci
# - gochecknoglobals
# - gochecknoinits
# - gocognit
# - goconst
# - gocritic
# - gocyclo
# - godot
# - godox
# - goerr113
# - gofumpt
# - goheader
# - golint
# - gomnd
# - gomodguard
# - goprintffuncname
# - interfacer
# - lll
# - maligned
# - nakedret
# - nestif
# - nlreturn
# - noctx
# - nolintlint
# - rowserrcheck
# - scopelint
# - sqlclosecheck
# - stylecheck
# - testpackage
# - unparam
# - whitespace
# - wsl
run:
# default concurrency is a available CPU number.
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
deadline: 5m
issues-exit-code: 1
tests: false
# output configuration options
output:
format: 'colored-line-number'
print-issued-lines: true
print-linter-name: true
issues:
exclude-rules:
# we aren't calling unknown URL
- text: "G107" # G107: Url provided to HTTP request as taint input
linters:
- gosec
# as a web server that's expected to handle any template, this is totally in the hands of the user.
- text: "G203" # G203: Use of unescaped data in HTML templates
linters:
- gosec
# we're shelling out to known commands, not relying on user-defined input.
- text: "G204" # G204: Audit use of command execution
linters:
- gosec
# the choice of weakrand is deliberate, hence the named import "weakrand"
- path: modules/caddyhttp/reverseproxy/selectionpolicies.go
text: "G404" # G404: Insecure random number source (rand)
linters:
- gosec
+148
View File
@@ -0,0 +1,148 @@
before:
hooks:
# The build is done in this particular way to build Caddy in a designated directory named in .gitignore.
# This is so we can run goreleaser on tag without Git complaining of being dirty. The main.go in cmd/caddy directory
# cannot be built within that directory due to changes necessary for the build causing Git to be dirty, which
# subsequently causes gorleaser to refuse running.
- rm -rf caddy-build caddy-dist
- mkdir -p caddy-build
- cp cmd/caddy/main.go caddy-build/main.go
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
# 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
- go mod edit -require=github.com/caddyserver/caddy/v2@{{.Env.TAG}} ./caddy-build/go.mod
# as of Go 1.16, `go` commands no longer automatically change go.{mod,sum}. We now have to explicitly
# run `go mod tidy`. The `/bin/sh -c '...'` is because goreleaser can't find cd in PATH without shell invocation.
- /bin/sh -c 'cd ./caddy-build && go mod tidy'
- git clone --depth 1 https://github.com/caddyserver/dist caddy-dist
- mkdir -p caddy-dist/man
- go mod download
- go run cmd/caddy/main.go manpage --directory ./caddy-dist/man
- gzip -r ./caddy-dist/man/
- /bin/sh -c 'go run cmd/caddy/main.go completion bash > ./caddy-dist/scripts/bash-completion'
builds:
- env:
- CGO_ENABLED=0
- GO111MODULE=on
main: main.go
dir: ./caddy-build
binary: caddy
goos:
- darwin
- linux
- windows
- freebsd
goarch:
- amd64
- arm
- arm64
- s390x
- ppc64le
goarm:
- "5"
- "6"
- "7"
ignore:
- goos: darwin
goarch: arm
- goos: darwin
goarch: ppc64le
- goos: darwin
goarch: s390x
- goos: windows
goarch: ppc64le
- goos: windows
goarch: s390x
- goos: freebsd
goarch: ppc64le
- goos: freebsd
goarch: s390x
- goos: freebsd
goarch: arm
goarm: "5"
flags:
- -trimpath
- -mod=readonly
ldflags:
- -s -w
signs:
- cmd: cosign
signature: "${artifact}.sig"
certificate: '{{ trimsuffix (trimsuffix .Env.artifact ".zip") ".tar.gz" }}.pem'
args: ["sign-blob", "--output-signature=${signature}", "--output-certificate", "${certificate}", "${artifact}"]
artifacts: all
sboms:
- artifacts: binary
documents:
- '{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{if .Arm}}v{{ .Arm }}{{end}}.sbom'
cmd: syft
args: ["$artifact", "--file", "${document}", "--output", "cyclonedx-json"]
archives:
- format_overrides:
- goos: windows
format: zip
replacements:
darwin: mac
checksum:
algorithm: sha512
nfpms:
- id: default
package_name: caddy
vendor: Dyanim
homepage: https://caddyserver.com
maintainer: Matthew Holt <mholt@users.noreply.github.com>
description: |
Caddy - Powerful, enterprise-ready, open source web server with automatic HTTPS written in Go
license: Apache 2.0
formats:
- deb
# - rpm
bindir: /usr/bin
contents:
- src: ./caddy-dist/init/caddy.service
dst: /lib/systemd/system/caddy.service
- src: ./caddy-dist/init/caddy-api.service
dst: /lib/systemd/system/caddy-api.service
- src: ./caddy-dist/welcome/index.html
dst: /usr/share/caddy/index.html
- src: ./caddy-dist/scripts/bash-completion
dst: /etc/bash_completion.d/caddy
- src: ./caddy-dist/config/Caddyfile
dst: /etc/caddy/Caddyfile
type: config
- src: ./caddy-dist/man/*
dst: /usr/share/man/man8/
scripts:
postinstall: ./caddy-dist/scripts/postinstall.sh
preremove: ./caddy-dist/scripts/preremove.sh
postremove: ./caddy-dist/scripts/postremove.sh
release:
github:
owner: caddyserver
name: caddy
draft: true
prerelease: auto
changelog:
sort: asc
filters:
exclude:
- '^chore:'
- '^ci:'
- '^docs?:'
- '^readme:'
- '^tests?:'
- '^\w+\s+' # a hack to remove commit messages without colons thus don't correspond to a package
+159 -367
View File
@@ -1,396 +1,188 @@
Caddy 2 Development Branch
===========================
<p align="center">
<a href="https://caddyserver.com"><img src="https://user-images.githubusercontent.com/1128849/36338535-05fb646a-136f-11e8-987b-e6901e717d5a.png" alt="Caddy" width="450"></a>
<br>
<h3 align="center">a <a href="https://zerossl.com"><img src="https://caddyserver.com/resources/images/zerossl-logo.svg" height="28" valign="middle"></a> project</h3>
</p>
<hr>
<h3 align="center">Every site on HTTPS</h3>
<p align="center">Caddy is an extensible server platform that uses TLS by default.</p>
<p align="center">
<a href="https://github.com/caddyserver/caddy/actions?query=workflow%3ACross-Platform"><img src="https://github.com/caddyserver/caddy/workflows/Cross-Platform/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>
<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://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
<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://cloudsmith.io/~caddy/repos/"><img src="https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith" alt="Cloudsmith"></a>
</p>
<p align="center">
<a href="https://github.com/caddyserver/caddy/releases">Releases</a> ·
<a href="https://caddyserver.com/docs/">Documentation</a> ·
<a href="https://caddy.community">Get Help</a>
</p>
This is the development branch for Caddy 2. This code (version 2) is not yet feature-complete or production-ready, but is already being used in production, and we encourage you to deploy it today on sites that are not very visible or important so that it can obtain crucial experience in the field.
Please file issues to propose new features and report bugs, and after the bug or feature has been discussed, submit a pull request! We need your help to build this web server into what you want it to be. (Caddy 2 issues and pull requests will usually receive priority over Caddy 1 issues and pull requests.)
**We want Caddy 2 to be the web server of the Go community!** We are looking for maintainers to represent the community. Please become involved (issues, PRs, [our forum](https://caddy.community) etc.) and express interest if you are committed to being a collaborator on the Caddy project.
### Menu
- [Features](#features)
- [Install](#install)
- [Build from source](#build-from-source)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Full Documentation](#full-documentation)
- [List of Improvements](#list-of-improvements)
- [FAQ](#faq)
- [For development](#for-development)
- [With version information and/or plugins](#with-version-information-andor-plugins)
- [Quick start](#quick-start)
- [Overview](#overview)
- [Full documentation](#full-documentation)
- [Getting help](#getting-help)
- [About](#about)
<p align="center">
<b>Powered by</b>
<br>
<a href="https://github.com/caddyserver/certmagic"><img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250"></a>
</p>
## [Features](https://caddyserver.com/v2)
- **Easy configuration** with the [Caddyfile](https://caddyserver.com/docs/caddyfile)
- **Powerful configuration** with its [native JSON config](https://caddyserver.com/docs/json/)
- **Dynamic configuration** with the [JSON API](https://caddyserver.com/docs/api)
- [**Config adapters**](https://caddyserver.com/docs/config-adapters) if you don't like JSON
- **Automatic HTTPS** by default
- [ZeroSSL](https://zerossl.com) and [Let's Encrypt](https://letsencrypt.org) for public names
- Fully-managed local CA for internal names & IPs
- Can coordinate with other Caddy instances in a cluster
- Multi-issuer fallback
- **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
- **Scales to hundreds of thousands of sites** as proven in production
- **HTTP/1.1, HTTP/2, and HTTP/3** supported all by default
- **Highly extensible** [modular architecture](https://caddyserver.com/docs/architecture) lets Caddy do anything without bloat
- **Runs anywhere** with **no external dependencies** (not even libc)
- Written in Go, a language with higher **memory safety guarantees** than other servers
- Actually **fun to use**
- So much more to [discover](https://caddyserver.com/v2)
## Install
The simplest, cross-platform way to get started is to download Caddy from [GitHub Releases](https://github.com/caddyserver/caddy/releases) and place the executable file in your PATH.
See [our online documentation](https://caddyserver.com/docs/install) for other install instructions.
## Build from source
Requirements:
- [Go 1.13 or newer](https://golang.org/dl/)
- Make sure you do not disable [Go modules](https://github.com/golang/go/wiki/Modules) (`export GO111MODULE=auto`)
- [Go 1.18 or newer](https://golang.org/dl/)
Download the `v2` source code:
```bash
$ git clone -b v2 "https://github.com/caddyserver/caddy.git"
```
Build:
### For development
_**Note:** These steps [will not embed proper version information](https://github.com/golang/go/issues/29228). For that, please follow the instructions in the next section._
```bash
$ git clone "https://github.com/caddyserver/caddy.git"
$ cd caddy/cmd/caddy/
$ go build
```
That will put a `caddy(.exe)` binary into the current directory. You can move it into your PATH or use `go install` to do that automatically (assuming `$GOPATH/bin` is already in your PATH). You can also use `go run main.go` for quick, temporary builds while developing.
When you run Caddy, it may try to bind to low ports unless otherwise specified in your config. If your OS requires elevated privileges for this, you will need to give your new binary permission to do so. On Linux, this can be done easily with: `sudo setcap cap_net_bind_service=+ep ./caddy`
The initial build may be slow as dependencies are downloaded. Subsequent builds should be very fast. If you encounter any Go-module-related errors, try clearing your Go module cache (`$GOPATH/pkg/mod`) and read [the Go wiki page about modules for help](https://github.com/golang/go/wiki/Modules). If you have issues with Go modules, please consult the Go community for help. But if there is an actual error in Caddy, please report it to us.
## Quick Start
(Until the stable 2.0 release, there may be breaking changes in v2, please be aware!)
These instructions assume an executable build of Caddy 2 is named `caddy` in the current folder. If it's in your PATH, you may omit the path to the binary (`./`).
Start Caddy:
If you prefer to use `go run` which only creates temporary binaries, you can still do this with the included `setcap.sh` like so:
```bash
$ ./caddy start
$ go run -exec ./setcap.sh main.go
```
There are no config files with Caddy 2. Instead, you POST configuration to it:
```bash
$ curl -X POST "http://localhost:2019/load" \
-H "Content-Type: application/json" \
-d @- << EOF
{
"apps": {
"http": {
"servers": {
"example": {
"listen": ["127.0.0.1:2080"],
"routes": [
{
"handle": [{
"handler": "file_server",
"browse": {}
}]
}
]
}
}
}
}
}
EOF
```
Now visit http://localhost:2080 in your browser and you will see the contents of the current directory displayed.
To change Caddy's configuration, simply POST a new payload to that endpoint. Config changes are extremely lightweight and efficient, and should be graceful on all platforms -- _even Windows_.
Updating configuration using heredoc can be tedious, so you can still use a config file if you prefer. Put your configuration in any file (`caddy.json` for example) and then POST that instead:
```bash
$ curl -X POST "http://localhost:2019/load" \
-H "Content-Type: application/json" \
-d @caddy.json
```
Or you can tell Caddy to load its configuration from a file in the first place (this simply does the work of the above curl command for you):
```bash
$ ./caddy start --config caddy.json
```
To stop Caddy:
```bash
$ ./caddy stop
```
Note that this will stop any process named the same as `os.Args[0]`.
For other commands, please see [the Caddy 2 documentation](https://github.com/caddyserver/caddy/wiki/v2:-Documentation).
### Caddyfile
Caddy 2 can be configured with a Caddyfile, much like in v1, for example:
```plain
example.com
templates
encode gzip zstd
try_files {path}.html {path}
reverse_proxy /api localhost:9005
file_server
```
Instead of being its primary mode of configuration, an internal _config adapter_ adapts the Caddyfile to Caddy's native JSON structure. You can see it in action with the [`adapt-config` command](https://github.com/caddyserver/caddy/wiki/v2:-Documentation#adapt-config):
```bash
$ ./caddy adapt-config --input path/to/Caddyfile --adapter caddyfile --pretty
```
But if you just want to run Caddy with your Caddyfile directly, the CLI wraps this up for you nicely. Either of the following commands:
```bash
$ ./caddy start
$ ./caddy run
```
will apply your Caddyfile if it is called `Caddyfile` in the current directory.
If your Caddyfile is somewhere else, you can still use it:
```bash
$ ./caddy start|run --config path/to/Caddyfile --config-adapter caddyfile
```
[Learn more about the Caddyfile in v2.](https://github.com/caddyserver/caddy/wiki/v2:-Documentation#caddyfile-adapter)
## Configuration
Caddy 2 exposes an unprecedented level of control compared to any web server in existence. In Caddy 2, you are usually setting the actual values of the initialized types in memory that power everything from your HTTP handlers and TLS handshakes to your storage medium. Caddy 2 is also ridiculously extensible, with a module system that makes vast improvements over Caddy 1's plugin system.
Nearly all of Caddy 2's configuration is contained in a single config document, rather than being spread across CLI flags and env variables and a configuration file as with other web servers (and Caddy 1).
To wield the power of this design, you need to know how the config document is structured. Please see the [the Caddy 2 documentation in our wiki](https://github.com/caddyserver/caddy/wiki/v2:-Documentation) for details about Caddy's config structure.
Configuration is normally given to Caddy through an API endpoint, which is likewise documented in the wiki pages. However, you can also use config files of various formats with [config adapters](https://github.com/caddyserver/caddy/wiki/v2:-Documentation#config-adapters).
## Full Documentation
Caddy 2 is very much in development, so the documentation is an ongoing WIP, but the latest will be in our wiki for now:
**https://github.com/caddyserver/caddy/wiki/v2:-Documentation**
Note that breaking changes are expected until the stable 2.0 release.
## List of Improvements
The following is a non-comprehensive list of significant improvements over Caddy 1. Not everything in this list is finished yet, but they will be finished or at least will be possible with Caddy 2 or Caddy Enterprise:
- Centralized configuration. No more disparate use of environment variables, config files (potentially multiple!), CLI flags, etc.
- REST API. Control Caddy with HTTP requests to an administration endpoint. Changes are applied immediately and efficiently.
- Dynamic configuration. Any and all specific config values can be modified directly through the admin API with a REST endpoint.
- Enterprise: Change only specific configuration settings instead of needing to specify the whole config each time. This makes it safe and easy to change Caddy's config with manually-crafted curl commands, for example.
- No configuration files. Except optionally to bootstrap its configuration at startup. You can still use config files if you wish, and we expect that most people will.
- Enterprise: Export the current Caddy configuration with an API GET request.
- Silky-smooth graceful reloads. Update the configuration up to dozens of times per second with no dropped requests and very little memory cost. Our unique graceful reload technology is lighter and faster **and works on all platforms, including Windows**.
- An embedded scripting language! Caddy2 has native Starlark integration. Do things you never thought possible with higher performance than Lua, JavaScript, and other VMs. Starlark is expressive, familiar (dialect of Python), _almost_ Turing-complete, and highly efficient. (We're still improving performance here.)
- Using [XDG standards](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables) instead of dumping all assets in `$HOME/.caddy`.
- Caddy plugins are now called "Caddy modules" (although the terms "plugin" and "module" may be used interchangably). Caddy modules are a concept unrelated [Go modules](https://github.com/golang/go/wiki/Modules), except that Caddy modules may be implemented by Go modules. Caddy modules are centrally-registered, properly namespaced, and generically loaded & configured, as opposed to how scattered and unorganized Caddy 1-era plugins are.
- Modules are easier to write, since they do not have to both deserialize their own configuration from a configuration DSL and provision themselves like plugins did. Modules are initialized pre-configured and have the ability to validate the configuration and perform provisioning steps if necessary.
- Can specify different storage mechanisms in different parts of the configuration, if more than one is needed.
- "Top-level" Caddy modules are simply called "apps" because literally any long-lived application can be served by Caddy 2.
- Even more of Caddy is made of modules, allowing for unparalleled extensibility, flexibility, and control. Caddy 2 is arguably the most flexible, extensible, programmable web server ever made.
- TLS improvements!
- TLS configuration is now centralized and decoupled from specific sites
- A single certificate cache is used process-wide, reducing duplication and improving memory use
- Customize how to manage each certificate ("automation policies") based on the hostname
- Automation policy doesn't have to be limited to just ACME - could be any way to manage certificates
- Fine-grained control over TLS handshakes
- If an ACME challenge fails, other enabled challenges will be tried (no other web server does this)
- Enterprise: TLS Session Ticket Ephemeral Keys (STEKs) can be rotated in a cluster for increased performance (no other web server does this either!)
- Enterprise: Ability to select a specific certificate per ClientHello given multiple qualifying certificates
- Enterprise: Provide TLS certificates without persisting them to disk; keep private keys entirely in memory
- All-new HTTP server core
- Listeners can be configured for any network type, address, and port range
- Customizable TLS connection policies
- HTTP handlers are configured by "routes" which consist of matcher and handler components. Match matches an HTTP request, and handle defines the list of handlers to invoke as a result of the match.
- Some matchers are regular expressions, which expose capture groups to placeholders.
- New matchers include negation and matching based on remote IP address / CIDR ranges.
- Placeholders are vastly improved generally
- Placeholders (variables) are more properly namespaced.
- Multiple routes may match an HTTP request, creating a "composite route" quickly on the fly.
- The actual handler for any given request is its composite route.
- User defines the order of middlewares (careful! easy to break things).
- Adding middlewares no longer requires changes to Caddy's code base (there is no authoritative list).
- Routes may be marked as terminal, meaning no more routes will be matched.
- Routes may be grouped so that only the first matching route in a group is applied.
- Requests may be "re-handled" if they are modified and need to be sent through the chain again (internal redirect).
- Vastly more powerful static file server, with native content-negotiation abilities
- Done away with URL-rewriting hacks often needed in Caddy 1
- Highly descriptive/traceable errors
- Very flexible error handling, with the ability to specify a whole list of routes just for error cases
- More control over automatic HTTPS: disable entirely, disable only HTTP->HTTPS redirects, disable only cert management, and for certain names, etc.
- Enterprise: Use Starlark to build custom, dynamic HTTP handlers at request-time
- We are finding that -- on average -- Caddy 2's Starlark handlers are ~1.25-2x faster than NGINX+Lua.
And a few major features still being worked on:
- Logging
- Kubernetes ingress controller (mostly done, just polishing it -- and it's amazing)
- More config adapters. Caddy's native JSON config structure is powerful and complex. Config adapters upsample various formats to Caddy's native config. There are already adapters for Caddyfile, JSON 5, and JSON-C. Planned are NGINX config, YAML, and TOML. The community might be interested in building Traefik and Apache config adapters!
## FAQ
### How do I configure Caddy 2?
Caddy's primary mode of configuration is a REST API, which accepts a JSON document. The JSON structure is described [in the wiki](https://github.com/caddyserver/caddy/wiki/v2:-Documentation). The advantages of exposing this low-level structure are 1) it has near-parity with actual memory initialization, 2) it allows us to offer wrappers over this configuration to any degree of convenience that is needed, and 3) it performs very well under rapid config changes.
Basically, you will [start Caddy](https://github.com/caddyserver/caddy/wiki/v2:-Documentation#start), then [POST a JSON config to its API endpoint](https://github.com/caddyserver/caddy/wiki/v2:-Documentation#post-load).
Although this makes Caddy 2 highly programmable, not everyone will want to configure Caddy via JSON with an API. Sometimes we just want to give Caddy a simple, static config file and have it do its thing. That's what **[config adapters](https://github.com/caddyserver/caddy/wiki/v2:-Documentation#config-adapters)** are for! You can configure Caddy more ways than one, depending on your needs and preferences. See the next questions that explain this more.
### Caddy 2 feels harder to use. How is this an improvement over Caddy 1?
Caddy's ease of use is one of the main reasons it is special. We are not taking that away in Caddy 2, but first we had to be sure to tackle the fundamental design limitations with Caddy 1. Usability can then be layered on top. This approach has several advantages which we discuss in the next question.
### What about the Caddyfile; are there easier ways to configure Caddy 2?
Yes! Caddy's native JSON configuration via API is nice when you are automating config changes at scale, but if you just have a simple, static configuration in a file, you can do that too with the [Caddyfile](https://github.com/caddyserver/caddy/wiki/v2:-Documentation#caddyfile-adapter).
The v2 Caddyfile is very similar to the v1 Caddyfile, but they are not compatible. Several improvements have been made to request matching and directives in v2, giving you more power with less complexity and fewer inconsistencies.
Caddy's default _config adapter_ is the Caddyfile adapter. This takes a Caddyfile as input and [outputs the JSON config](https://github.com/caddyserver/caddy/wiki/v2:-Documentation#adapt-config). You can even run Caddy directly without having to see or think about the underlying JSON config.
The following _config adapters_ are already being built or plan to be built:
- Caddyfile
- JSON 5
- JSON-C
- nginx
- YAML
- TOML
- any others that the community would like to contribute
Config adapters allow you to configure Caddy not just one way but _any_ of these ways. For example, you'll be able to bring your existing NGINX config to Caddy and it will spit out the Caddy config JSON you need (to the best of its ability). How cool is that! You can then easily tweak the resulting config by hand, if necessary.
All config adapters vary in their theoretical expressiveness; that is, if you need more advanced configuration you'll have to drop down to the JSON config, because the Caddyfile or an nginx config may not be expressive enough.
However, we expect that most users will be able to use the Caddyfile (or another easy config adapter) exclusively for their sites.
### Why JSON for configuration? Why not _&lt;any other serialization format&gt;_?
We know there might be strong opinions on this one. Regardless, for Caddy 2, we've decided to go with JSON. If that proves to be a fatal mistake, then Caddy 3 probably won't use JSON.
JSON may not be the fastest, the most compact, the easiest to write, serialization format that exists. But those aren't our goals. It has withstood the test of time and checks all our boxes.
- It is almost entirely ubiquitous. JSON works natively in web browsers and has mature libraries in pretty much every language.
- It is human-readable (as opposed to a binary format).
- It is easy to tweak by hand. Although composing raw JSON by hand is not awesome, this will not be mainstream once our config adapters are done.
- It is generally easy to convert other serializations or config formats into JSON, as opposed to the other way around.
- Even though JSON deserialization is not fast per-se, that kind of performance is not really a concern since config reloads are not the server's hottest path like HTTP request handling or TLS handshakes are. Even with JSON, Caddy 2 can handle dozens of config changes per second, which is probably plenty for now.
- It maps almost 1:1 to the actual, in-memory values that power your HTTP handlers and other parts of the server (no need to parse a config file with some arbitrary DSL and do a bunch of extra pre-processing).
Ultimately, we think all these properties are appropriate -- if not ideal -- for a web server configuration.
If you're still not happy with the choice of JSON, feel free to contribute a config adapter of your own choice!
Or just use YAML or TOML, which seamlessly translate to JSON.
### JSON is declarative; what if I need more programmability (i.e. imperative syntax)?
NGINX also realized the need for imperative logic in declarative configs, so they tried "if" statements, [but it was a bad idea](https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/).
We have good news. Caddy 2 can give you the power of imperative logic without the perils of mixing declarative and imperative config such as befell NGINX. We do this by allowing embedded imperative syntax awithin the Caddy's declarative config.
Caddy 2's configuration is declarative because configuration is very much declarative in nature. Configuration is a tricky medium, as it is read and written by both computers and humans. Computers use it, but humans constantly refer to it and update it. Declarative syntaxes are fairly straightforward to make sense of, whereas it is difficult to reason about imperative logic.
However, sometimes computation is useful, and in some cases, the only way to express what you need. This can be illustrated really well in the simple case of trying to decide whether a particular HTTP middleware should be invoked as part of an HTTP request. A lot of the time, such logic is as simple as: "GET requests for any path starting with /foo/bar", which can be expressed declaratively in JSON:
```json
{
"method": "GET",
"path": "/foo/bar"
}
```
But what if you need to match /foo/bar OR /topaz? How do you express that OR clause? Maybe an array:
```json
{
"method": ["GET"],
"path": ["/foo/bar", "/topaz"]
}
```
Now what if you need add a NOT or AND clause? JSON quickly tires out. As you learn about Caddy 2's request matching, you will see how we handled this. Caddy 2's JSON gives you the ability to express moderately-complex logic such as:
```js
// this is not actual Caddy config, just logic pseudocode
IF (Host = "example.com")
OR (Host = "sub.example.com" AND Path != "/foo/bar")
```
Already, this is more expressive power than most web servers offer with their native config, yet Caddy 2 offers this in JSON.
But in most web servers, to make logic this complex feasible, you'll generally call out to Lua or some extra DSL. For example, in NGINX you could use a Lua module to express this logic. Traefik 2.0 has [yet another kind of clunky-looking custom DSL](https://blog.containo.us/back-to-traefik-2-0-2f9aa17be305#d22e) just for this.
Caddy 2 solves this in a novel way with [Starlark expressions](https://godoc.org/go.starlark.net/starlark#Eval). Starlark is a familiar dialect of Python! So, no new DSLs to learn and no VMs to slow things down:
```python
req.host == 'example.com' ||
(req.host == 'sub.example.com' && req.path != '/foo/bar')
```
Starlark performs at least as well as NGINX+Lua (more performance tests ongoing, as well as optimizations to make it even faster!) and because it's basically Python, it's familiar and easy to use.
In summary: Caddy 2 config is declarative, but can be imperative where that is useful.
### What will Caddy 2 be licensed as?
Caddy 2 is licensed under the Apache 2.0 open source license. There are no official Caddy 2 distributions that are proprietary.
### What is Caddy Enterprise?
Caddy Enterprise is a collection of plugins for Caddy 2 which provide features and performance that are crucial in business settings. Caddy Enterprise is not a separate web server and does not even use a separate code base from Caddy 2; it is not even a separate branch that merges the open source core in every once in a while. In other words, open source users aren't missing out on a "better" web server, but Enterprise provides features that are used by businesses.
Caddy Enterprise is for businesses that need more advanced features for higher scalability and easier management of clusters. It includes:
- a web UI
- performance improvements within a cluster
- advanced TLS controls
- fine-grained config changes (i.e. ability to change only certain parts of the configuration)
- training and support
- advanced HTTP handlers for authentication, metrics, debugging, and more
- dynamic HTTP handlers and TLS handshakes with Starlark
Caddy Enterprise can be customized for each customer according to their needs.
Caddy 2 and Caddy Enterprise offer equal levels of security and, as mentioned, share the same open source code base.
### Does Caddy 2 have telemetry?
No. There was not enough academic interest to continue supporting it. If telemetry does get added later, it will not be on by default or will be vastly reduced in its scope.
## Does Caddy 2 use HTTPS by default?
Yes. HTTPS is automatic and enabled by default when possible, just like in Caddy 1. Basically, if your HTTP routes specify a `host` matcher with qualifying domain names, those names will be enabled for automatic HTTPS. Automatic HTTPS is disabled for domains which match certificates that are manually loaded by your config.
## How do I avoid Let's Encrypt rate limits with Caddy 2?
As you are testing and developing with Caddy 2, you may wish to use test ("staging") certificates from Let's Encrypt to avoid rate limits. By default, Caddy 2 uses Let's Encrypt's production endpoint to get real certificates for your domains, but their [rate limits](https://letsencrypt.org/docs/rate-limits/) forbid testing and development use of this endpoint for good reasons. You can switch to their [staging endpoint](https://letsencrypt.org/docs/staging-environment/) by adding the staging CA to your automation policy in the `tls` app:
```json
"tls": {
"automation": {
"policies": [
{
"management": {
"module": "acme",
"ca": "https://acme-staging-v02.api.letsencrypt.org/directory"
}
}
]
}
}
```
Or with the Caddyfile:
If you don't want to type your password for `setcap`, use `sudo visudo` to edit your sudoers file and allow your user account to run that command without a password, for example:
```
tls {
ca https://acme-staging-v02.api.letsencrypt.org/directory
}
username ALL=(ALL:ALL) NOPASSWD: /usr/sbin/setcap
```
## Can we get some access controls on the admin endpoint?
replacing `username` with your actual username. Please be careful and only do this if you know what you are doing! We are only qualified to document how to use Caddy, not Go tooling or your computer, and we are providing these instructions for convenience only; please learn how to use your own computer at your own risk and make any needful adjustments.
Yeah, that's coming. For now, you can use a unix socket that is properly permissioned for some basic security.
### With version information and/or plugins
Using [our builder tool, `xcaddy`](https://github.com/caddyserver/xcaddy)...
```
$ xcaddy build
```
...the following steps are automated:
1. Create a new folder: `mkdir caddy`
2. Change into it: `cd caddy`
3. Copy [Caddy's main.go](https://github.com/caddyserver/caddy/blob/master/cmd/caddy/main.go) into the empty folder. Add imports for any custom plugins you want to add.
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.
6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
7. Compile: `go build`
## Quick start
The [Caddy website](https://caddyserver.com/docs/) has documentation that includes tutorials, quick-start guides, reference, and more.
**We recommend that all users -- regardless of experience level -- do our [Getting Started](https://caddyserver.com/docs/getting-started) guide to become familiar with using Caddy.**
If you've only got a minute, [the website has several quick-start tutorials](https://caddyserver.com/docs/quick-starts) to choose from! However, after finishing a quick-start tutorial, please read more documentation to understand how the software works. 🙂
## Overview
Caddy is most often used as an HTTPS server, but it is suitable for any long-running Go program. First and foremost, it is a platform to run Go applications. Caddy "apps" are just Go programs that are implemented as Caddy modules. Two apps -- `tls` and `http` -- ship standard with Caddy.
Caddy apps instantly benefit from [automated documentation](https://caddyserver.com/docs/json/), graceful on-line [config changes via API](https://caddyserver.com/docs/api), and unification with other Caddy apps.
Although [JSON](https://caddyserver.com/docs/json/) is Caddy's native config language, Caddy can accept input from [config adapters](https://caddyserver.com/docs/config-adapters) which can essentially convert any config format of your choice into JSON: Caddyfile, JSON 5, YAML, TOML, NGINX config, and more.
The primary way to configure Caddy is through [its API](https://caddyserver.com/docs/api), but if you prefer config files, the [command-line interface](https://caddyserver.com/docs/command-line) supports those too.
Caddy exposes an unprecedented level of control compared to any web server in existence. In Caddy, you are usually setting the actual values of the initialized types in memory that power everything from your HTTP handlers and TLS handshakes to your storage medium. Caddy is also ridiculously extensible, with a powerful plugin system that makes vast improvements over other web servers.
To wield the power of this design, you need to know how the config document is structured. Please see [our documentation site](https://caddyserver.com/docs/) for details about [Caddy's config structure](https://caddyserver.com/docs/json/).
Nearly all of Caddy's configuration is contained in a single config document, rather than being scattered across CLI flags and env variables and a configuration file as with other web servers. This makes managing your server config more straightforward and reduces hidden variables/factors.
## Full documentation
Our website has complete documentation:
**https://caddyserver.com/docs/**
The docs are also open source. You can contribute to them here: https://github.com/caddyserver/website
## 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.
- 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!
- Individuals can exchange help for free on our community forum at https://caddy.community. Remember that people give help out of their spare time and good will. The best way to get help is to give it first!
Please use our [issue tracker](https://github.com/caddyserver/caddy/issues) only for bug reports and feature requests, i.e. actionable development items (support questions will usually be referred to the forums).
## About
Matthew Holt began developing Caddy in 2014 while studying computer science at Brigham Young University. (The name "Caddy" was chosen because this software helps with the tedious, mundane tasks of serving the Web, and is also a single place for multiple things to be organized together.) It soon became the first web server to use HTTPS automatically and by default, and now has hundreds of contributors and has served trillions of HTTPS requests.
**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)_
- _Author on Twitter: [@mholt6](https://twitter.com/mholt6)_
Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company.
Debian package repository hosting is graciously provided by [Cloudsmith](https://cloudsmith.com). Cloudsmith is the only fully hosted, cloud-native, universal package management solution, that enables your organization to create, store and share packages in any format, to any place, with total confidence.
+1230 -171
View File
File diff suppressed because it is too large Load Diff
+161 -6
View File
@@ -15,14 +15,15 @@
package caddy
import (
"strings"
"encoding/json"
"fmt"
"net/http"
"reflect"
"sync"
"testing"
)
func BenchmarkLoad(b *testing.B) {
for i := 0; i < b.N; i++ {
r := strings.NewReader(`{
"testval": "Yippee!",
var testCfg = []byte(`{
"apps": {
"http": {
"servers": {
@@ -39,6 +40,160 @@ func BenchmarkLoad(b *testing.B) {
}
}
`)
Load(r)
func TestUnsyncedConfigAccess(t *testing.T) {
// each test is performed in sequence, so
// each change builds on the previous ones;
// the config is not reset between tests
for i, tc := range []struct {
method string
path string // rawConfigKey will be prepended
payload string
expect string // JSON representation of what the whole config is expected to be after the request
shouldErr bool
}{
{
method: "POST",
path: "",
payload: `{"foo": "bar", "list": ["a", "b", "c"]}`, // starting value
expect: `{"foo": "bar", "list": ["a", "b", "c"]}`,
},
{
method: "POST",
path: "/foo",
payload: `"jet"`,
expect: `{"foo": "jet", "list": ["a", "b", "c"]}`,
},
{
method: "POST",
path: "/bar",
payload: `{"aa": "bb", "qq": "zz"}`,
expect: `{"foo": "jet", "bar": {"aa": "bb", "qq": "zz"}, "list": ["a", "b", "c"]}`,
},
{
method: "DELETE",
path: "/bar/qq",
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`,
},
{
method: "POST",
path: "/list",
payload: `"e"`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "e"]}`,
},
{
method: "PUT",
path: "/list/3",
payload: `"d"`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d", "e"]}`,
},
{
method: "DELETE",
path: "/list/3",
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "e"]}`,
},
{
method: "PATCH",
path: "/list/3",
payload: `"d"`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d"]}`,
},
{
method: "POST",
path: "/list/...",
payload: `["e", "f", "g"]`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d", "e", "f", "g"]}`,
},
} {
err := unsyncedConfigAccess(tc.method, rawConfigKey+tc.path, []byte(tc.payload), nil)
if tc.shouldErr && err == nil {
t.Fatalf("Test %d: Expected error return value, but got: %v", i, err)
}
if !tc.shouldErr && err != nil {
t.Fatalf("Test %d: Should not have had error return value, but got: %v", i, err)
}
// decode the expected config so we can do a convenient DeepEqual
var expectedDecoded any
err = json.Unmarshal([]byte(tc.expect), &expectedDecoded)
if err != nil {
t.Fatalf("Test %d: Unmarshaling expected config: %v", i, err)
}
// make sure the resulting config is as we expect it
if !reflect.DeepEqual(rawCfg[rawConfigKey], expectedDecoded) {
t.Fatalf("Test %d:\nExpected:\n\t%#v\nActual:\n\t%#v",
i, expectedDecoded, rawCfg[rawConfigKey])
}
}
}
// TestLoadConcurrent exercises Load under concurrent conditions
// and is most useful under test with `-race` enabled.
func TestLoadConcurrent(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
_ = Load(testCfg, true)
wg.Done()
}()
}
wg.Wait()
}
type fooModule struct {
IntField int
StrField string
}
func (fooModule) CaddyModule() ModuleInfo {
return ModuleInfo{
ID: "foo",
New: func() Module { return new(fooModule) },
}
}
func (fooModule) Start() error { return nil }
func (fooModule) Stop() error { return nil }
func TestETags(t *testing.T) {
RegisterModule(fooModule{})
if err := Load([]byte(`{"admin": {"listen": "localhost:2999"}, "apps": {"foo": {"strField": "abc", "intField": 0}}}`), true); err != nil {
t.Fatalf("loading: %s", err)
}
const key = "/" + rawConfigKey + "/apps/foo"
// try update the config with the wrong etag
err := changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}}`), fmt.Sprintf(`"/%s not_an_etag"`, rawConfigKey), false)
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
t.Fatalf("expected precondition failed; got %v", err)
}
// get the etag
hash := etagHasher()
if err := readConfig(key, hash); err != nil {
t.Fatalf("reading: %s", err)
}
// do the same update with the correct key
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}`), makeEtag(key, hash), false)
if err != nil {
t.Fatalf("expected update to work; got %v", err)
}
// now try another update. The hash should no longer match and we should get precondition failed
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 2}`), makeEtag(key, hash), false)
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
t.Fatalf("expected precondition failed; got %v", err)
}
}
func BenchmarkLoad(b *testing.B) {
for i := 0; i < b.N; i++ {
Load(testCfg, true)
}
}
+896 -143
View File
File diff suppressed because it is too large Load Diff
+74
View File
@@ -0,0 +1,74 @@
// 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 (
"testing"
"time"
)
func TestParseDuration(t *testing.T) {
const day = 24 * time.Hour
for i, tc := range []struct {
input string
expect time.Duration
}{
{
input: "3h",
expect: 3 * time.Hour,
},
{
input: "1d",
expect: day,
},
{
input: "1d30m",
expect: day + 30*time.Minute,
},
{
input: "1m2d",
expect: time.Minute + day*2,
},
{
input: "1m2d30s",
expect: time.Minute + day*2 + 30*time.Second,
},
{
input: "1d2d",
expect: 3 * day,
},
{
input: "1.5d",
expect: time.Duration(1.5 * float64(day)),
},
{
input: "4m1.25d",
expect: 4*time.Minute + time.Duration(1.25*float64(day)),
},
{
input: "-1.25d12h",
expect: time.Duration(-1.25*float64(day)) - 12*time.Hour,
},
} {
actual, err := ParseDuration(tc.input)
if err != nil {
t.Errorf("Test %d ('%s'): Got error: %v", i, tc.input, err)
continue
}
if actual != tc.expect {
t.Errorf("Test %d ('%s'): Expected=%s Actual=%s", i, tc.input, tc.expect, actual)
}
}
}
+70 -8
View File
@@ -29,12 +29,12 @@ type Adapter struct {
}
// Adapt converts the Caddyfile config in body to Caddy JSON.
func (a Adapter) Adapt(body []byte, options map[string]interface{}) ([]byte, []caddyconfig.Warning, error) {
func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, []caddyconfig.Warning, error) {
if a.ServerType == nil {
return nil, nil, fmt.Errorf("no server type")
}
if options == nil {
options = make(map[string]interface{})
options = make(map[string]any)
}
filename, _ := options["filename"].(string)
@@ -42,7 +42,7 @@ func (a Adapter) Adapt(body []byte, options map[string]interface{}) ([]byte, []c
filename = "Caddyfile"
}
serverBlocks, err := Parse(filename, bytes.NewReader(body))
serverBlocks, err := Parse(filename, body)
if err != nil {
return nil, nil, err
}
@@ -52,15 +52,46 @@ func (a Adapter) Adapt(body []byte, options map[string]interface{}) ([]byte, []c
return nil, warnings, err
}
marshalFunc := json.Marshal
if options["pretty"] == "true" {
marshalFunc = caddyconfig.JSONIndent
// lint check: see if input was properly formatted; sometimes messy files files parse
// successfully but result in logical errors (the Caddyfile is a bad format, I'm sorry)
if warning, different := formattingDifference(filename, body); different {
warnings = append(warnings, warning)
}
result, err := marshalFunc(cfg)
result, err := json.Marshal(cfg)
return result, warnings, err
}
// formattingDifference returns a warning and true if the formatted version
// is any different from the input; empty warning and false otherwise.
// TODO: also perform this check on imported files
func formattingDifference(filename string, body []byte) (caddyconfig.Warning, bool) {
// replace windows-style newlines to normalize comparison
normalizedBody := bytes.Replace(body, []byte("\r\n"), []byte("\n"), -1)
formatted := Format(normalizedBody)
if bytes.Equal(formatted, normalizedBody) {
return caddyconfig.Warning{}, false
}
// find where the difference is
line := 1
for i, ch := range normalizedBody {
if i >= len(formatted) || ch != formatted[i] {
break
}
if ch == '\n' {
line++
}
}
return caddyconfig.Warning{
File: filename,
Line: line,
Message: "Caddyfile input is not formatted; run the 'caddy fmt' command to fix inconsistencies",
}, true
}
// Unmarshaler is a type that can unmarshal
// Caddyfile tokens to set itself up for a
// JSON encoding. The goal of an unmarshaler
@@ -69,6 +100,11 @@ func (a Adapter) Adapt(body []byte, options map[string]interface{}) ([]byte, []c
// into JSON. Caddyfile-unmarshaled values
// will not be used directly; they will be
// encoded as JSON and then used from that.
// Implementations must be able to support
// multiple segments (instances of their
// directive or batch of tokens); typically
// this means wrapping all token logic in
// a loop: `for d.Next() { ... }`.
type Unmarshaler interface {
UnmarshalCaddyfile(d *Dispenser) error
}
@@ -80,7 +116,33 @@ type ServerType interface {
// (e.g. CLI flags) and creates a Caddy
// config, along with any warnings or
// an error.
Setup([]ServerBlock, map[string]interface{}) (*caddy.Config, []caddyconfig.Warning, error)
Setup([]ServerBlock, map[string]any) (*caddy.Config, []caddyconfig.Warning, error)
}
// UnmarshalModule instantiates a module with the given ID and invokes
// UnmarshalCaddyfile on the new value using the immediate next segment
// of d as input. In other words, d's next token should be the first
// token of the module's Caddyfile input.
//
// This function is used when the next segment of Caddyfile tokens
// belongs to another Caddy module. The returned value is often
// type-asserted to the module's associated type for practical use
// when setting up a config.
func UnmarshalModule(d *Dispenser, moduleID string) (Unmarshaler, error) {
mod, err := caddy.GetModule(moduleID)
if err != nil {
return nil, d.Errf("getting module named '%s': %v", moduleID, err)
}
inst := mod.New()
unm, ok := inst.(Unmarshaler)
if !ok {
return nil, d.Errf("module %s is not a Caddyfile unmarshaler; is %T", mod.ID, inst)
}
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
if err != nil {
return nil, err
}
return unm, nil
}
// Interface guard
+166 -20
View File
@@ -17,6 +17,9 @@ package caddyfile
import (
"errors"
"fmt"
"io"
"log"
"strconv"
"strings"
)
@@ -37,6 +40,16 @@ func NewDispenser(tokens []Token) *Dispenser {
}
}
// NewTestDispenser parses input into tokens and creates a new
// Dispenser for test purposes only; any errors are fatal.
func NewTestDispenser(input string) *Dispenser {
tokens, err := allTokens("Testfile", []byte(input))
if err != nil && err != io.EOF {
log.Fatalf("getting all tokens from input: %v", err)
}
return NewDispenser(tokens)
}
// Next loads the next token. Returns true if a token
// was loaded; false otherwise. If false, all tokens
// have been consumed.
@@ -133,15 +146,15 @@ func (d *Dispenser) NextLine() bool {
//
// Proper use of this method looks like this:
//
// for nesting := d.Nesting(); d.NextBlock(nesting); {
// }
// for nesting := d.Nesting(); d.NextBlock(nesting); {
// }
//
// However, in simple cases where it is known that the
// Dispenser is new and has not already traversed state
// by a loop over NextBlock(), this will do:
//
// for d.NextBlock(0) {
// }
// for d.NextBlock(0) {
// }
//
// As with other token parsing logic, a loop over
// NextBlock() should be contained within a loop over
@@ -152,8 +165,10 @@ func (d *Dispenser) NextBlock(initialNestingLevel int) bool {
if !d.Next() {
return false // should be EOF error
}
if d.Val() == "}" {
if d.Val() == "}" && !d.nextOnSameLine() {
d.nesting--
} else if d.Val() == "{" && !d.nextOnSameLine() {
d.nesting++
}
return d.nesting > initialNestingLevel
}
@@ -187,6 +202,43 @@ func (d *Dispenser) Val() string {
return d.tokens[d.cursor].Text
}
// ValRaw gets the raw text of the current token (including quotes).
// If there is no token loaded, it returns empty string.
func (d *Dispenser) ValRaw() string {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return ""
}
quote := d.tokens[d.cursor].wasQuoted
if quote > 0 {
return string(quote) + d.tokens[d.cursor].Text + string(quote) // string literal
}
return d.tokens[d.cursor].Text
}
// ScalarVal gets value of the current token, converted to the closest
// scalar type. If there is no token loaded, it returns nil.
func (d *Dispenser) ScalarVal() any {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return nil
}
quote := d.tokens[d.cursor].wasQuoted
text := d.tokens[d.cursor].Text
if quote > 0 {
return text // string literal
}
if num, err := strconv.Atoi(text); err == nil {
return num
}
if num, err := strconv.ParseFloat(text, 64); err == nil {
return num
}
if bool, err := strconv.ParseBool(text); err == nil {
return bool
}
return text
}
// Line gets the line number of the current token.
// If there is no token loaded, it returns 0.
func (d *Dispenser) Line() int {
@@ -235,6 +287,19 @@ func (d *Dispenser) AllArgs(targets ...*string) bool {
return true
}
// CountRemainingArgs counts the amount of remaining arguments
// (tokens on the same line) without consuming the tokens.
func (d *Dispenser) CountRemainingArgs() int {
count := 0
for d.NextArg() {
count++
}
for i := 0; i < count; i++ {
d.Prev()
}
return count
}
// RemainingArgs loads any more arguments (tokens on the same line)
// into a slice and returns them. Open curly brace tokens also indicate
// the end of arguments, and the curly brace is not included in
@@ -247,13 +312,32 @@ func (d *Dispenser) RemainingArgs() []string {
return args
}
// NewFromNextTokens returns a new dispenser with a copy of
// RemainingArgsRaw loads any more arguments (tokens on the same line,
// retaining quotes) into a slice and returns them. Open curly brace
// tokens also indicate the end of arguments, and the curly brace is
// not included in the return value nor is it loaded.
func (d *Dispenser) RemainingArgsRaw() []string {
var args []string
for d.NextArg() {
args = append(args, d.ValRaw())
}
return args
}
// NewFromNextSegment returns a new dispenser with a copy of
// the tokens from the current token until the end of the
// "directive" whether that be to the end of the line or
// the end of a block that starts at the end of the line;
// in other words, until the end of the segment.
func (d *Dispenser) NewFromNextTokens() *Dispenser {
tkns := []Token{d.Token()}
func (d *Dispenser) NewFromNextSegment() *Dispenser {
return NewDispenser(d.NextSegment())
}
// NextSegment returns a copy of the tokens from the current
// token until the end of the line or block that starts at
// the end of the line.
func (d *Dispenser) NextSegment() Segment {
tkns := Segment{d.Token()}
for d.NextArg() {
tkns = append(tkns, d.Token())
}
@@ -262,9 +346,9 @@ func (d *Dispenser) NewFromNextTokens() *Dispenser {
if !openedBlock {
// because NextBlock() consumes the initial open
// curly brace, we rewind here to append it, since
// our case is special in that we want to include
// all the tokens including surrounding curly braces
// for a new dispenser to have
// our case is special in that we want the new
// dispenser to have all the tokens including
// surrounding curly braces
d.Prev()
tkns = append(tkns, d.Token())
d.Next()
@@ -273,10 +357,14 @@ func (d *Dispenser) NewFromNextTokens() *Dispenser {
tkns = append(tkns, d.Token())
}
if openedBlock {
// include closing brace accordingly
// include closing brace
tkns = append(tkns, d.Token())
// do not consume the closing curly brace; the
// next iteration of the enclosing loop will
// call Next() and consume it
}
return NewDispenser(tkns)
return tkns
}
// Token returns the current token.
@@ -320,13 +408,17 @@ func (d *Dispenser) EOFErr() error {
// Err generates a custom parse-time error with a message of msg.
func (d *Dispenser) Err(msg string) error {
msg = fmt.Sprintf("%s:%d - Error during parsing: %s", d.File(), d.Line(), msg)
return errors.New(msg)
return d.Errf(msg)
}
// Errf is like Err, but for formatted error messages
func (d *Dispenser) Errf(format string, args ...interface{}) error {
return d.Err(fmt.Sprintf(format, args...))
func (d *Dispenser) Errf(format string, args ...any) error {
return d.WrapErr(fmt.Errorf(format, args...))
}
// WrapErr takes an existing error and adds the Caddyfile file and line number.
func (d *Dispenser) WrapErr(err error) error {
return fmt.Errorf("%s:%d - Error during parsing: %w", d.File(), d.Line(), err)
}
// Delete deletes the current token and returns the updated slice
@@ -339,7 +431,7 @@ func (d *Dispenser) Errf(format string, args ...interface{}) error {
// array will become apparent (or worse, hide from you like they
// did me for 3 and a half freaking hours late one night).
func (d *Dispenser) Delete() []Token {
if d.cursor >= 0 && d.cursor < len(d.tokens)-1 {
if d.cursor >= 0 && d.cursor <= len(d.tokens)-1 {
d.tokens = append(d.tokens[:d.cursor], d.tokens[d.cursor+1:]...)
d.cursor--
}
@@ -366,6 +458,60 @@ func (d *Dispenser) isNewLine() bool {
if d.cursor > len(d.tokens)-1 {
return false
}
return d.tokens[d.cursor-1].File != d.tokens[d.cursor].File ||
d.tokens[d.cursor-1].Line+d.numLineBreaks(d.cursor-1) < d.tokens[d.cursor].Line
prev := d.tokens[d.cursor-1]
curr := d.tokens[d.cursor]
// If the previous token is from a different file,
// we can assume it's from a different line
if prev.File != curr.File {
return true
}
// The previous token may contain line breaks if
// it was quoted and spanned multiple lines. e.g:
//
// dir "foo
// bar
// baz"
prevLineBreaks := d.numLineBreaks(d.cursor - 1)
// If the previous token (incl line breaks) ends
// on a line earlier than the current token,
// then the current token is on a new line
return prev.Line+prevLineBreaks < curr.Line
}
// isNextOnNewLine determines whether the current token is on a different
// line (higher line number) than the next token. It handles imported
// tokens correctly. If there isn't a next token, it returns true.
func (d *Dispenser) isNextOnNewLine() bool {
if d.cursor < 0 {
return false
}
if d.cursor >= len(d.tokens)-1 {
return true
}
curr := d.tokens[d.cursor]
next := d.tokens[d.cursor+1]
// If the next token is from a different file,
// we can assume it's from a different line
if curr.File != next.File {
return true
}
// The current token may contain line breaks if
// it was quoted and spanned multiple lines. e.g:
//
// dir "foo
// bar
// baz"
currLineBreaks := d.numLineBreaks(d.cursor)
// If the current token (incl line breaks) ends
// on a line earlier than the next token,
// then the next token is on a new line
return curr.Line+currLineBreaks < next.Line
}
+12 -15
View File
@@ -15,8 +15,7 @@
package caddyfile
import (
"io"
"log"
"errors"
"reflect"
"strings"
"testing"
@@ -27,7 +26,7 @@ func TestDispenser_Val_Next(t *testing.T) {
dir1 arg1
dir2 arg2 arg3
dir3`
d := newTestDispenser(input)
d := NewTestDispenser(input)
if val := d.Val(); val != "" {
t.Fatalf("Val(): Should return empty string when no token loaded; got '%s'", val)
@@ -65,7 +64,7 @@ func TestDispenser_NextArg(t *testing.T) {
input := `dir1 arg1
dir2 arg2 arg3
dir3`
d := newTestDispenser(input)
d := NewTestDispenser(input)
assertNext := func(shouldLoad bool, expectedVal string, expectedCursor int) {
if d.Next() != shouldLoad {
@@ -112,7 +111,7 @@ func TestDispenser_NextLine(t *testing.T) {
input := `host:port
dir1 arg1
dir2 arg2 arg3`
d := newTestDispenser(input)
d := NewTestDispenser(input)
assertNextLine := func(shouldLoad bool, expectedVal string, expectedCursor int) {
if d.NextLine() != shouldLoad {
@@ -145,7 +144,7 @@ func TestDispenser_NextBlock(t *testing.T) {
}
foobar2 {
}`
d := newTestDispenser(input)
d := NewTestDispenser(input)
assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) {
if loaded := d.NextBlock(0); loaded != shouldLoad {
@@ -175,7 +174,7 @@ func TestDispenser_Args(t *testing.T) {
dir2 arg4 arg5
dir3 arg6 arg7
dir4`
d := newTestDispenser(input)
d := NewTestDispenser(input)
d.Next() // dir1
@@ -242,7 +241,7 @@ func TestDispenser_RemainingArgs(t *testing.T) {
dir2 arg4 arg5
dir3 arg6 { arg7
dir4`
d := newTestDispenser(input)
d := NewTestDispenser(input)
d.Next() // dir1
@@ -279,7 +278,7 @@ func TestDispenser_ArgErr_Err(t *testing.T) {
input := `dir1 {
}
dir2 arg1 arg2`
d := newTestDispenser(input)
d := NewTestDispenser(input)
d.cursor = 1 // {
@@ -305,12 +304,10 @@ func TestDispenser_ArgErr_Err(t *testing.T) {
if !strings.Contains(err.Error(), "foobar") {
t.Errorf("Expected error message with custom message in it ('foobar'); got '%v'", err)
}
}
func newTestDispenser(input string) *Dispenser {
tokens, err := allTokens("Testfile", strings.NewReader(input))
if err != nil && err != io.EOF {
log.Fatalf("getting all tokens from input: %v", err)
var ErrBarIsFull = errors.New("bar is full")
bookingError := d.Errf("unable to reserve: %w", ErrBarIsFull)
if !errors.Is(bookingError, ErrBarIsFull) {
t.Errorf("Errf(): should be able to unwrap the error chain")
}
return NewDispenser(tokens)
}
+219
View File
@@ -0,0 +1,219 @@
// 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 caddyfile
import (
"bytes"
"io"
"unicode"
)
// Format formats the input Caddyfile to a standard, nice-looking
// appearance. It works by reading each rune of the input and taking
// control over all the bracing and whitespace that is written; otherwise,
// words, comments, placeholders, and escaped characters are all treated
// literally and written as they appear in the input.
func Format(input []byte) []byte {
input = bytes.TrimSpace(input)
out := new(bytes.Buffer)
rdr := bytes.NewReader(input)
var (
last rune // the last character that was written to the result
space = true // whether current/previous character was whitespace (beginning of input counts as space)
beginningOfLine = true // whether we are at beginning of line
openBrace bool // whether current word/token is or started with open curly brace
openBraceWritten bool // if openBrace, whether that brace was written or not
openBraceSpace bool // whether there was a non-newline space before open brace
newLines int // count of newlines consumed
comment bool // whether we're in a comment
quoted bool // whether we're in a quoted segment
escaped bool // whether current char is escaped
nesting int // indentation level
)
write := func(ch rune) {
out.WriteRune(ch)
last = ch
}
indent := func() {
for tabs := nesting; tabs > 0; tabs-- {
write('\t')
}
}
nextLine := func() {
write('\n')
beginningOfLine = true
}
for {
ch, _, err := rdr.ReadRune()
if err != nil {
if err == io.EOF {
break
}
panic(err)
}
if comment {
if ch == '\n' {
comment = false
space = true
nextLine()
continue
} else {
write(ch)
continue
}
}
if !escaped && ch == '\\' {
if space {
write(' ')
space = false
}
write(ch)
escaped = true
continue
}
if escaped {
write(ch)
escaped = false
continue
}
if quoted {
if ch == '"' {
quoted = false
}
write(ch)
continue
}
if space && ch == '"' {
quoted = true
}
if unicode.IsSpace(ch) {
space = true
if ch == '\n' {
newLines++
}
continue
}
spacePrior := space
space = false
//////////////////////////////////////////////////////////
// I find it helpful to think of the formatting loop in two
// main sections; by the time we reach this point, we
// know we are in a "regular" part of the file: we know
// the character is not a space, not in a literal segment
// like a comment or quoted, it's not escaped, etc.
//////////////////////////////////////////////////////////
if ch == '#' {
comment = true
}
if openBrace && spacePrior && !openBraceWritten {
if nesting == 0 && last == '}' {
nextLine()
nextLine()
}
openBrace = false
if beginningOfLine {
indent()
} else if !openBraceSpace {
write(' ')
}
write('{')
openBraceWritten = true
nextLine()
newLines = 0
// prevent infinite nesting from ridiculous inputs (issue #4169)
if nesting < 10 {
nesting++
}
}
switch {
case ch == '{':
openBrace = true
openBraceWritten = false
openBraceSpace = spacePrior && !beginningOfLine
if openBraceSpace {
write(' ')
}
continue
case ch == '}' && (spacePrior || !openBrace):
if last != '\n' {
nextLine()
}
if nesting > 0 {
nesting--
}
indent()
write('}')
newLines = 0
continue
}
if newLines > 2 {
newLines = 2
}
for i := 0; i < newLines; i++ {
nextLine()
}
newLines = 0
if beginningOfLine {
indent()
}
if nesting == 0 && last == '}' && beginningOfLine {
nextLine()
nextLine()
}
if !beginningOfLine && spacePrior {
write(' ')
}
if openBrace && !openBraceWritten {
write('{')
openBraceWritten = true
}
write(ch)
beginningOfLine = false
}
// the Caddyfile does not need any leading or trailing spaces, but...
trimmedResult := bytes.TrimSpace(out.Bytes())
// ...Caddyfiles should, however, end with a newline because
// newlines are significant to the syntax of the file
return append(trimmedResult, '\n')
}
+27
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.
//go:build gofuzz
package caddyfile
import "bytes"
func FuzzFormat(input []byte) int {
formatted := Format(input)
if bytes.Equal(formatted, Format(formatted)) {
return 1
}
return 0
}
+381
View File
@@ -0,0 +1,381 @@
// 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 caddyfile
import (
"strings"
"testing"
)
func TestFormatter(t *testing.T) {
for i, tc := range []struct {
description string
input string
expect string
}{
{
description: "very simple",
input: `abc def
g hi jkl
mn`,
expect: `abc def
g hi jkl
mn`,
},
{
description: "basic indentation, line breaks, and nesting",
input: ` a
b
c {
d
}
e { f
}
g {
h {
i
}
}
j { k {
l
}
}
m {
n { o
}
p { q r
s }
}
{
{ t
u
v
w
}
}`,
expect: `a
b
c {
d
}
e {
f
}
g {
h {
i
}
}
j {
k {
l
}
}
m {
n {
o
}
p {
q r
s
}
}
{
{
t
u
v
w
}
}`,
},
{
description: "block spacing",
input: `a{
b
}
c{ d
}`,
expect: `a {
b
}
c {
d
}`,
},
{
description: "advanced spacing",
input: `abc {
def
}ghi{
jkl mno
pqr}`,
expect: `abc {
def
}
ghi {
jkl mno
pqr
}`,
},
{
description: "env var placeholders",
input: `{$A}
b {
{$C}
}
d { {$E}
}
{ {$F}
}
`,
expect: `{$A}
b {
{$C}
}
d {
{$E}
}
{
{$F}
}`,
},
{
description: "env var placeholders with port",
input: `:{$PORT}`,
expect: `:{$PORT}`,
},
{
description: "comments",
input: `#a "\n"
#b {
c
}
d {
e#f
# g
}
h { # i
}`,
expect: `#a "\n"
#b {
c
}
d {
e#f
# g
}
h {
# i
}`,
},
{
description: "quotes and escaping",
input: `"a \"b\" "#c
d
e {
"f"
}
g { "h"
}
i {
"foo
bar"
}
j {
"\"k\" l m"
}`,
expect: `"a \"b\" "#c
d
e {
"f"
}
g {
"h"
}
i {
"foo
bar"
}
j {
"\"k\" l m"
}`,
},
{
description: "bad nesting (too many open)",
input: `a
{
{
}`,
expect: `a {
{
}
`,
},
{
description: "bad nesting (too many close)",
input: `a
{
{
}}}`,
expect: `a {
{
}
}
}
`,
},
{
description: "json",
input: `foo
bar "{\"key\":34}"
`,
expect: `foo
bar "{\"key\":34}"`,
},
{
description: "escaping after spaces",
input: `foo \"literal\"`,
expect: `foo \"literal\"`,
},
{
description: "simple placeholders as standalone tokens",
input: `foo {bar}`,
expect: `foo {bar}`,
},
{
description: "simple placeholders within tokens",
input: `foo{bar} foo{bar}baz`,
expect: `foo{bar} foo{bar}baz`,
},
{
description: "placeholders and malformed braces",
input: `foo{bar} foo{ bar}baz`,
expect: `foo{bar} foo {
bar
}
baz`,
},
{
description: "hash within string is not a comment",
input: `redir / /some/#/path`,
expect: `redir / /some/#/path`,
},
{
description: "brace does not fold into comment above",
input: `# comment
{
foo
}`,
expect: `# comment
{
foo
}`,
},
{
description: "matthewpi/vscode-caddyfile-support#13",
input: `{
email {$ACMEEMAIL}
#debug
}
block {
}
`,
expect: `{
email {$ACMEEMAIL}
#debug
}
block {
}
`,
},
{
description: "matthewpi/vscode-caddyfile-support#13 - bad formatting",
input: `{
email {$ACMEEMAIL}
#debug
}
block {
}
`,
expect: `{
email {$ACMEEMAIL}
#debug
}
block {
}
`,
},
} {
// the formatter should output a trailing newline,
// even if the tests aren't written to expect that
if !strings.HasSuffix(tc.expect, "\n") {
tc.expect += "\n"
}
actual := Format([]byte(tc.input))
if string(actual) != tc.expect {
t.Errorf("\n[TEST %d: %s]\n====== EXPECTED ======\n%s\n====== ACTUAL ======\n%s^^^^^^^^^^^^^^^^^^^^^",
i, tc.description, string(tc.expect), string(actual))
}
}
}
+127
View File
@@ -0,0 +1,127 @@
// 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 caddyfile
import (
"fmt"
)
type adjacency map[string][]string
type importGraph struct {
nodes map[string]bool
edges adjacency
}
func (i *importGraph) addNode(name string) {
if i.nodes == nil {
i.nodes = make(map[string]bool)
}
if _, exists := i.nodes[name]; exists {
return
}
i.nodes[name] = true
}
func (i *importGraph) addNodes(names []string) {
for _, name := range names {
i.addNode(name)
}
}
func (i *importGraph) removeNode(name string) {
delete(i.nodes, name)
}
func (i *importGraph) removeNodes(names []string) {
for _, name := range names {
i.removeNode(name)
}
}
func (i *importGraph) addEdge(from, to string) error {
if !i.exists(from) || !i.exists(to) {
return fmt.Errorf("one of the nodes does not exist")
}
if i.willCycle(to, from) {
return fmt.Errorf("a cycle of imports exists between %s and %s", from, to)
}
if i.areConnected(from, to) {
// if connected, there's nothing to do
return nil
}
if i.nodes == nil {
i.nodes = make(map[string]bool)
}
if i.edges == nil {
i.edges = make(adjacency)
}
i.edges[from] = append(i.edges[from], to)
return nil
}
func (i *importGraph) addEdges(from string, tos []string) error {
for _, to := range tos {
err := i.addEdge(from, to)
if err != nil {
return err
}
}
return nil
}
func (i *importGraph) areConnected(from, to string) bool {
al, ok := i.edges[from]
if !ok {
return false
}
for _, v := range al {
if v == to {
return true
}
}
return false
}
func (i *importGraph) willCycle(from, to string) bool {
collector := make(map[string]bool)
var visit func(string)
visit = func(start string) {
if !collector[start] {
collector[start] = true
for _, v := range i.edges[start] {
visit(v)
}
}
}
for _, v := range i.edges[from] {
visit(v)
}
for k := range collector {
if to == k {
return true
}
}
return false
}
func (i *importGraph) exists(key string) bool {
_, exists := i.nodes[key]
return exists
}
Executable → Regular
+77 -30
View File
@@ -1,4 +1,4 @@
// Copyright 2015 Light Code Labs, LLC
// 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.
@@ -16,6 +16,7 @@ package caddyfile
import (
"bufio"
"bytes"
"io"
"unicode"
)
@@ -26,16 +27,20 @@ type (
// are separated by whitespace. A word can be enclosed
// in quotes if it contains whitespace.
lexer struct {
reader *bufio.Reader
token Token
line int
reader *bufio.Reader
token Token
line int
skippedLines int
}
// Token represents a single parsable unit.
Token struct {
File string
Line int
Text string
File string
Line int
Text string
wasQuoted rune // enclosing quote character, if any
inSnippet bool
snippetName string
}
)
@@ -72,10 +77,11 @@ func (l *lexer) load(input io.Reader) error {
// a token was loaded; false otherwise.
func (l *lexer) next() bool {
var val []rune
var comment, quoted, escaped bool
var comment, quoted, btQuoted, escaped bool
makeToken := func() bool {
makeToken := func(quoted rune) bool {
l.token.Text = string(val)
l.token.wasQuoted = quoted
return true
}
@@ -83,7 +89,7 @@ func (l *lexer) next() bool {
ch, _, err := l.reader.ReadRune()
if err != nil {
if len(val) > 0 {
return makeToken()
return makeToken(0)
}
if err == io.EOF {
return false
@@ -91,27 +97,32 @@ func (l *lexer) next() bool {
panic(err)
}
if quoted {
if !escaped {
if ch == '\\' {
escaped = true
continue
} else if ch == '"' {
quoted = false
return makeToken()
if !escaped && !btQuoted && ch == '\\' {
escaped = true
continue
}
if quoted || btQuoted {
if quoted && escaped {
// all is literal in quoted area,
// so only escape quotes
if ch != '"' {
val = append(val, '\\')
}
escaped = false
} else {
if quoted && ch == '"' {
return makeToken('"')
}
if btQuoted && ch == '`' {
return makeToken('`')
}
}
if ch == '\n' {
l.line++
}
if escaped {
// only escape quotes and newlines
if ch != '"' && ch != '\n' {
val = append(val, '\\')
}
l.line += 1 + l.skippedLines
l.skippedLines = 0
}
val = append(val, ch)
escaped = false
continue
}
@@ -120,19 +131,24 @@ func (l *lexer) next() bool {
continue
}
if ch == '\n' {
l.line++
if escaped {
l.skippedLines++
escaped = false
} else {
l.line += 1 + l.skippedLines
l.skippedLines = 0
}
comment = false
}
if len(val) > 0 {
return makeToken()
return makeToken(0)
}
continue
}
if ch == '#' {
if ch == '#' && len(val) == 0 {
comment = true
}
if comment {
continue
}
@@ -143,8 +159,39 @@ func (l *lexer) next() bool {
quoted = true
continue
}
if ch == '`' {
btQuoted = true
continue
}
}
if escaped {
val = append(val, '\\')
escaped = false
}
val = append(val, ch)
}
}
// Tokenize takes bytes as input and lexes it into
// a list of tokens that can be parsed as a Caddyfile.
// Also takes a filename to fill the token's File as
// the source of the tokens, which is important to
// determine relative paths for `import` directives.
func Tokenize(input []byte, filename string) ([]Token, error) {
l := lexer{}
if err := l.load(bytes.NewReader(input)); err != nil {
return nil, err
}
var tokens []Token
for l.next() {
l.token.File = filename
tokens = append(tokens, l.token)
}
return tokens, nil
}
func (t Token) Quoted() bool {
return t.wasQuoted > 0
}
@@ -12,32 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package json5adapter
//go:build gofuzz
import (
"encoding/json"
package caddyfile
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/ilibs/json5"
)
func init() {
caddyconfig.RegisterAdapter("json5", Adapter{})
}
// Adapter adapts JSON5 to Caddy JSON.
type Adapter struct{}
// Adapt converts the JSON5 config in body to Caddy JSON.
func (a Adapter) Adapt(body []byte, options map[string]interface{}) (result []byte, warnings []caddyconfig.Warning, err error) {
var decoded interface{}
err = json5.Unmarshal(body, &decoded)
func FuzzTokenize(input []byte) int {
tokens, err := Tokenize(input, "Caddyfile")
if err != nil {
return
return 0
}
result, err = json.Marshal(decoded)
return
if len(tokens) == 0 {
return -1
}
return 1
}
// Interface guard
var _ caddyconfig.Adapter = (*Adapter)(nil)
+122 -38
View File
@@ -1,4 +1,4 @@
// Copyright 2015 Light Code Labs, LLC
// 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.
@@ -15,37 +15,35 @@
package caddyfile
import (
"log"
"strings"
"testing"
)
type lexerTestCase struct {
input string
input []byte
expected []Token
}
func TestLexer(t *testing.T) {
testCases := []lexerTestCase{
{
input: `host:123`,
input: []byte(`host:123`),
expected: []Token{
{Line: 1, Text: "host:123"},
},
},
{
input: `host:123
input: []byte(`host:123
directive`,
directive`),
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 3, Text: "directive"},
},
},
{
input: `host:123 {
input: []byte(`host:123 {
directive
}`,
}`),
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 1, Text: "{"},
@@ -54,7 +52,7 @@ func TestLexer(t *testing.T) {
},
},
{
input: `host:123 { directive }`,
input: []byte(`host:123 { directive }`),
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 1, Text: "{"},
@@ -63,12 +61,12 @@ func TestLexer(t *testing.T) {
},
},
{
input: `host:123 {
input: []byte(`host:123 {
#comment
directive
# comment
foobar # another comment
}`,
}`),
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 1, Text: "{"},
@@ -78,8 +76,28 @@ func TestLexer(t *testing.T) {
},
},
{
input: `a "quoted value" b
foobar`,
input: []byte(`host:123 {
# hash inside string is not a comment
redir / /some/#/path
}`),
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 1, Text: "{"},
{Line: 3, Text: "redir"},
{Line: 3, Text: "/"},
{Line: 3, Text: "/some/#/path"},
{Line: 4, Text: "}"},
},
},
{
input: []byte("# comment at beginning of file\n# comment at beginning of line\nhost:123"),
expected: []Token{
{Line: 3, Text: "host:123"},
},
},
{
input: []byte(`a "quoted value" b
foobar`),
expected: []Token{
{Line: 1, Text: "a"},
{Line: 1, Text: "quoted value"},
@@ -88,7 +106,7 @@ func TestLexer(t *testing.T) {
},
},
{
input: `A "quoted \"value\" inside" B`,
input: []byte(`A "quoted \"value\" inside" B`),
expected: []Token{
{Line: 1, Text: "A"},
{Line: 1, Text: `quoted "value" inside`},
@@ -96,30 +114,72 @@ func TestLexer(t *testing.T) {
},
},
{
input: "A \"newline \\\ninside\" quotes",
input: []byte("An escaped \"newline\\\ninside\" quotes"),
expected: []Token{
{Line: 1, Text: "A"},
{Line: 1, Text: "newline \ninside"},
{Line: 1, Text: "An"},
{Line: 1, Text: "escaped"},
{Line: 1, Text: "newline\\\ninside"},
{Line: 2, Text: "quotes"},
},
},
{
input: `"don't\escape"`,
input: []byte("An escaped newline\\\noutside quotes"),
expected: []Token{
{Line: 1, Text: "An"},
{Line: 1, Text: "escaped"},
{Line: 1, Text: "newline"},
{Line: 1, Text: "outside"},
{Line: 1, Text: "quotes"},
},
},
{
input: []byte("line1\\\nescaped\nline2\nline3"),
expected: []Token{
{Line: 1, Text: "line1"},
{Line: 1, Text: "escaped"},
{Line: 3, Text: "line2"},
{Line: 4, Text: "line3"},
},
},
{
input: []byte("line1\\\nescaped1\\\nescaped2\nline4\nline5"),
expected: []Token{
{Line: 1, Text: "line1"},
{Line: 1, Text: "escaped1"},
{Line: 1, Text: "escaped2"},
{Line: 4, Text: "line4"},
{Line: 5, Text: "line5"},
},
},
{
input: []byte(`"unescapable\ in quotes"`),
expected: []Token{
{Line: 1, Text: `unescapable\ in quotes`},
},
},
{
input: []byte(`"don't\escape"`),
expected: []Token{
{Line: 1, Text: `don't\escape`},
},
},
{
input: `"don't\\escape"`,
input: []byte(`"don't\\escape"`),
expected: []Token{
{Line: 1, Text: `don't\\escape`},
},
},
{
input: `A "quoted value with line
input: []byte(`un\escapable`),
expected: []Token{
{Line: 1, Text: `un\escapable`},
},
},
{
input: []byte(`A "quoted value with line
break inside" {
foobar
}`,
}`),
expected: []Token{
{Line: 1, Text: "A"},
{Line: 1, Text: "quoted value with line\n\t\t\t\t\tbreak inside"},
@@ -129,13 +189,13 @@ func TestLexer(t *testing.T) {
},
},
{
input: `"C:\php\php-cgi.exe"`,
input: []byte(`"C:\php\php-cgi.exe"`),
expected: []Token{
{Line: 1, Text: `C:\php\php-cgi.exe`},
},
},
{
input: `empty "" string`,
input: []byte(`empty "" string`),
expected: []Token{
{Line: 1, Text: `empty`},
{Line: 1, Text: ``},
@@ -143,7 +203,7 @@ func TestLexer(t *testing.T) {
},
},
{
input: "skip those\r\nCR characters",
input: []byte("skip those\r\nCR characters"),
expected: []Token{
{Line: 1, Text: "skip"},
{Line: 1, Text: "those"},
@@ -152,30 +212,54 @@ func TestLexer(t *testing.T) {
},
},
{
input: "\xEF\xBB\xBF:8080", // test with leading byte order mark
input: []byte("\xEF\xBB\xBF:8080"), // test with leading byte order mark
expected: []Token{
{Line: 1, Text: ":8080"},
},
},
{
input: []byte("simple `backtick quoted` string"),
expected: []Token{
{Line: 1, Text: `simple`},
{Line: 1, Text: `backtick quoted`},
{Line: 1, Text: `string`},
},
},
{
input: []byte("multiline `backtick\nquoted\n` string"),
expected: []Token{
{Line: 1, Text: `multiline`},
{Line: 1, Text: "backtick\nquoted\n"},
{Line: 3, Text: `string`},
},
},
{
input: []byte("nested `\"quotes inside\" backticks` string"),
expected: []Token{
{Line: 1, Text: `nested`},
{Line: 1, Text: `"quotes inside" backticks`},
{Line: 1, Text: `string`},
},
},
{
input: []byte("reverse-nested \"`backticks` inside\" quotes"),
expected: []Token{
{Line: 1, Text: `reverse-nested`},
{Line: 1, Text: "`backticks` inside"},
{Line: 1, Text: `quotes`},
},
},
}
for i, testCase := range testCases {
actual := tokenize(testCase.input)
actual, err := Tokenize(testCase.input, "")
if err != nil {
t.Errorf("%v", err)
}
lexerCompare(t, i, testCase.expected, actual)
}
}
func tokenize(input string) (tokens []Token) {
l := lexer{}
if err := l.load(strings.NewReader(input)); err != nil {
log.Printf("[ERROR] load failed: %v", err)
}
for l.next() {
tokens = append(tokens, l.token)
}
return
}
func lexerCompare(t *testing.T, n int, expected, actual []Token) {
if len(expected) != len(actual) {
t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))
Executable → Regular
+173 -59
View File
@@ -1,4 +1,4 @@
// Copyright 2015 Light Code Labs, LLC
// 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.
@@ -15,11 +15,16 @@
package caddyfile
import (
"bytes"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/caddyserver/caddy/v2"
"go.uber.org/zap"
)
// Parse parses the input just enough to group tokens, in
@@ -28,38 +33,98 @@ import (
// Directives that do not appear in validDirectives will cause
// an error. If you do not want to check for valid directives,
// pass in nil instead.
func Parse(filename string, input io.Reader) ([]ServerBlock, error) {
tokens, err := allTokens(filename, input)
//
// Environment variables in {$ENVIRONMENT_VARIABLE} notation
// will be replaced before parsing begins.
func Parse(filename string, input []byte) ([]ServerBlock, error) {
// unfortunately, we must copy the input because parsing must
// remain a read-only operation, but we have to expand environment
// variables before we parse, which changes the underlying array (#4422)
inputCopy := make([]byte, len(input))
copy(inputCopy, input)
tokens, err := allTokens(filename, inputCopy)
if err != nil {
return nil, err
}
p := parser{Dispenser: NewDispenser(tokens)}
p := parser{
Dispenser: NewDispenser(tokens),
importGraph: importGraph{
nodes: make(map[string]bool),
edges: make(adjacency),
},
}
return p.parseAll()
}
// allTokens lexes the entire input, but does not parse it.
// It returns all the tokens from the input, unstructured
// and in order.
func allTokens(filename string, input io.Reader) ([]Token, error) {
l := new(lexer)
err := l.load(input)
// and in order. It may mutate input as it expands env vars.
func allTokens(filename string, input []byte) ([]Token, error) {
inputCopy, err := replaceEnvVars(input)
if err != nil {
return nil, err
}
var tokens []Token
for l.next() {
l.token.File = filename
tokens = append(tokens, l.token)
tokens, err := Tokenize(inputCopy, filename)
if err != nil {
return nil, err
}
return tokens, nil
}
// replaceEnvVars replaces all occurrences of environment variables.
// It mutates the underlying array and returns the updated slice.
func replaceEnvVars(input []byte) ([]byte, error) {
var offset int
for {
begin := bytes.Index(input[offset:], spanOpen)
if begin < 0 {
break
}
begin += offset // make beginning relative to input, not offset
end := bytes.Index(input[begin+len(spanOpen):], spanClose)
if end < 0 {
break
}
end += begin + len(spanOpen) // make end relative to input, not begin
// get the name; if there is no name, skip it
envString := input[begin+len(spanOpen) : end]
if len(envString) == 0 {
offset = end + len(spanClose)
continue
}
// split the string into a key and an optional default
envParts := strings.SplitN(string(envString), envVarDefaultDelimiter, 2)
// do a lookup for the env var, replace with the default if not found
envVarValue, found := os.LookupEnv(envParts[0])
if !found && len(envParts) == 2 {
envVarValue = envParts[1]
}
// get the value of the environment variable
// note that this causes one-level deep chaining
envVarBytes := []byte(envVarValue)
// splice in the value
input = append(input[:begin],
append(envVarBytes, input[end+len(spanClose):]...)...)
// continue at the end of the replacement
offset = begin + len(envVarBytes)
}
return input, nil
}
type parser struct {
*Dispenser
block ServerBlock // current server block being parsed
eof bool // if we encounter a valid EOF in a hard place
definedSnippets map[string][]Token
nesting int
importGraph importGraph
}
func (p *parser) parseAll() ([]ServerBlock, error) {
@@ -115,6 +180,15 @@ func (p *parser) begin() error {
if err != nil {
return err
}
// Just as we need to track which file the token comes from, we need to
// keep track of which snippets do the tokens come from. This is helpful
// in tracking import cycles across files/snippets by namespacing them. Without
// this we end up with false-positives in cycle-detection.
for k, v := range tokens {
v.inSnippet = true
v.snippetName = name
tokens[k] = v
}
p.definedSnippets[name] = tokens
// empty block keys so we don't save this block as a real server.
p.block.Keys = nil
@@ -128,7 +202,7 @@ func (p *parser) addresses() error {
var expectingAnother bool
for {
tkn := replaceEnvVars(p.Val())
tkn := p.Val()
// special case: import directive replaces tokens during parse-time
if tkn == "import" && p.isNewLine() {
@@ -144,9 +218,20 @@ func (p *parser) addresses() error {
if expectingAnother {
return p.Errf("Expected another address but had '%s' - check for extra comma", tkn)
}
// Mark this server block as being defined with braces.
// This is used to provide a better error message when
// the user may have tried to define two server blocks
// without having used braces, which are required in
// that case.
p.block.HasBraces = true
break
}
// Users commonly forget to place a space between the address and the '{'
if strings.HasSuffix(tkn, "{") {
return p.Errf("Site addresses cannot end with a curly brace: '%s' - put a space between the token and the brace", tkn)
}
if tkn != "" { // empty token possible if user typed ""
// Trailing comma indicates another address will follow, which
// may possibly be on the next line
@@ -157,6 +242,13 @@ func (p *parser) addresses() error {
expectingAnother = false // but we may still see another one on this line
}
// If there's a comma here, it's probably because they didn't use a space
// between their two domains, e.g. "foo.com,bar.com", which would not be
// parsed as two separate site addresses.
if strings.Contains(tkn, ",") {
return p.Errf("Site addresses cannot contain a comma ',': '%s' - put a space after the comma to separate site addresses", tkn)
}
p.block.Keys = append(p.block.Keys, tkn)
}
@@ -245,21 +337,34 @@ func (p *parser) doImport() error {
if !p.NextArg() {
return p.ArgErr()
}
importPattern := replaceEnvVars(p.Val())
importPattern := p.Val()
if importPattern == "" {
return p.Err("Import requires a non-empty filepath")
}
if p.NextArg() {
return p.Err("Import takes only one argument (glob pattern or file)")
// grab remaining args as placeholder replacements
args := p.RemainingArgs()
// add args to the replacer
repl := caddy.NewEmptyReplacer()
for index, arg := range args {
repl.Set("args."+strconv.Itoa(index), arg)
}
// splice out the import directive and its argument (2 tokens total)
tokensBefore := p.tokens[:p.cursor-1]
// splice out the import directive and its arguments
// (2 tokens, plus the length of args)
tokensBefore := p.tokens[:p.cursor-1-len(args)]
tokensAfter := p.tokens[p.cursor+1:]
var importedTokens []Token
var nodes []string
// first check snippets. That is a simple, non-recursive replacement
if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil {
importedTokens = p.definedSnippets[importPattern]
if len(importedTokens) > 0 {
// just grab the first one
nodes = append(nodes, fmt.Sprintf("%s:%s", importedTokens[0].File, importedTokens[0].snippetName))
}
} else {
// make path relative to the file of the _token_ being processed rather
// than current working directory (issue #867) and then use glob to get
@@ -288,14 +393,13 @@ func (p *parser) doImport() error {
}
if len(matches) == 0 {
if strings.ContainsAny(globPattern, "*?[]") {
log.Printf("[WARNING] No files matching import glob pattern: %s", importPattern)
caddy.Log().Warn("No files matching import glob pattern", zap.String("pattern", importPattern))
} else {
return p.Errf("File to import not found: %s", importPattern)
}
}
// collect all the imported tokens
for _, importFile := range matches {
newTokens, err := p.doSingleImport(importFile)
if err != nil {
@@ -303,12 +407,34 @@ func (p *parser) doImport() error {
}
importedTokens = append(importedTokens, newTokens...)
}
nodes = matches
}
nodeName := p.File()
if p.Token().inSnippet {
nodeName += fmt.Sprintf(":%s", p.Token().snippetName)
}
p.importGraph.addNode(nodeName)
p.importGraph.addNodes(nodes)
if err := p.importGraph.addEdges(nodeName, nodes); err != nil {
p.importGraph.removeNodes(nodes)
return err
}
// copy the tokens so we don't overwrite p.definedSnippets
tokensCopy := make([]Token, len(importedTokens))
copy(tokensCopy, importedTokens)
// run the argument replacer on the tokens
for index, token := range tokensCopy {
token.Text = repl.ReplaceKnown(token.Text, "")
tokensCopy[index] = token
}
// splice the imported tokens in the place of the import statement
// and rewind cursor so Next() will land on first imported token
p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...)
p.cursor--
p.tokens = append(tokensBefore, append(tokensCopy, tokensAfter...)...)
p.cursor -= len(args) + 1
return nil
}
@@ -328,7 +454,12 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
return nil, p.Errf("Could not import %s: is a directory", importFile)
}
importedTokens, err := allTokens(importFile, file)
input, err := io.ReadAll(file)
if err != nil {
return nil, p.Errf("Could not read imported file %s: %v", importFile, err)
}
importedTokens, err := allTokens(importFile, input)
if err != nil {
return nil, p.Errf("Could not read tokens while importing %s: %v", importFile, err)
}
@@ -353,8 +484,6 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
// are loaded into the current server block for later use
// by directive setup functions.
func (p *parser) directive() error {
// evaluate any env vars in directive token
p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
// a segment is a list of tokens associated with this directive
var segment Segment
@@ -365,6 +494,13 @@ func (p *parser) directive() error {
for p.Next() {
if p.Val() == "{" {
p.nesting++
if !p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
return p.Err("Unexpected next token after '{' on same line")
}
} else if p.Val() == "{}" {
if p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
return p.Err("Unexpected '{}' at end of line")
}
} else if p.isNewLine() && p.nesting == 0 {
p.cursor-- // read too far
break
@@ -379,7 +515,7 @@ func (p *parser) directive() error {
p.cursor-- // cursor is advanced when we continue, so roll back one more
continue
}
p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
segment = append(segment, p.Token())
}
@@ -414,36 +550,6 @@ func (p *parser) closeCurlyBrace() error {
return nil
}
// replaceEnvVars replaces environment variables that appear in the token
// and understands both the $UNIX and %WINDOWS% syntaxes.
func replaceEnvVars(s string) string {
s = replaceEnvReferences(s, "{%", "%}")
s = replaceEnvReferences(s, "{$", "}")
return s
}
// replaceEnvReferences performs the actual replacement of env variables
// in s, given the placeholder start and placeholder end strings.
func replaceEnvReferences(s, refStart, refEnd string) string {
index := strings.Index(s, refStart)
for index != -1 {
endIndex := strings.Index(s[index:], refEnd)
if endIndex == -1 {
break
}
endIndex += index
if endIndex > index+len(refStart) {
ref := s[index : endIndex+len(refEnd)]
s = strings.Replace(s, ref, os.Getenv(ref[len(refStart):len(ref)-len(refEnd)]), -1)
} else {
return s
}
index = strings.Index(s, refStart)
}
return s
}
func (p *parser) isSnippet() (bool, string) {
keys := p.block.Keys
// A snippet block is a single key with parens. Nothing else qualifies.
@@ -485,8 +591,9 @@ func (p *parser) snippetTokens() ([]Token, error) {
// head of the server block with tokens, which are
// grouped by segments.
type ServerBlock struct {
Keys []string
Segments []Segment
HasBraces bool
Keys []string
Segments []Segment
}
// DispenseDirective returns a dispenser that contains
@@ -514,3 +621,10 @@ func (s Segment) Directive() string {
}
return ""
}
// spanOpen and spanClose are used to bound spans that
// contain the name of an environment variable.
var (
spanOpen, spanClose = []byte{'{', '$'}, []byte{'}'}
envVarDefaultDelimiter = ":"
)
+150 -109
View File
@@ -1,4 +1,4 @@
// Copyright 2015 Light Code Labs, LLC
// 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.
@@ -15,17 +15,14 @@
package caddyfile
import (
"io/ioutil"
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)
// TODO: re-enable all tests
func TestAllTokens(t *testing.T) {
input := strings.NewReader("a b c\nd e")
input := []byte("a b c\nd e")
expected := []string{"a", "b", "c", "d", "e"}
tokens, err := allTokens("TestAllTokens", input)
@@ -162,6 +159,10 @@ func TestParseOneAndImport(t *testing.T) {
"localhost",
}, []int{}},
{`localhost{
dir1
}`, true, []string{}, []int{}},
{`localhost
dir1 {
nested {
@@ -184,14 +185,31 @@ func TestParseOneAndImport(t *testing.T) {
"host1",
}, []int{1, 2}},
{`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, []int{}},
{`import testdata/not_found.txt`, true, []string{}, []int{}},
{`""`, false, []string{}, []int{}},
{``, false, []string{}, []int{}},
// Unexpected next token after '{' on same line
{`localhost
dir1 { a b }`, true, []string{"localhost"}, []int{}},
// Workaround with quotes
{`localhost
dir1 "{" a b "}"`, false, []string{"localhost"}, []int{5}},
// Unexpected '{}' at end of line
{`localhost
dir1 {}`, true, []string{"localhost"}, []int{}},
// Workaround with quotes
{`localhost
dir1 "{}"`, false, []string{"localhost"}, []int{2}},
// import with args
{`import testdata/import_args0.txt a`, false, []string{"a"}, []int{}},
{`import testdata/import_args1.txt a b`, false, []string{"a", "b"}, []int{}},
{`import testdata/import_args*.txt a b`, false, []string{"a"}, []int{2}},
// test cases found by fuzzing!
{`import }{$"`, true, []string{}, []int{}},
{`import /*/*.txt`, true, []string{}, []int{}},
@@ -212,6 +230,7 @@ func TestParseOneAndImport(t *testing.T) {
t.Errorf("Test %d: Expected no error, but got: %v", i, err)
}
// t.Logf("%+v\n", result)
if len(result.Keys) != len(test.keys) {
t.Errorf("Test %d: Expected %d keys, got %d",
i, len(test.keys), len(result.Keys))
@@ -258,7 +277,7 @@ func TestRecursiveImport(t *testing.T) {
return false
}
if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 2 {
t.Errorf("got unexpect tokens: %v", got.Segments)
t.Errorf("got unexpected tokens: %v", got.Segments)
return false
}
return true
@@ -274,7 +293,7 @@ func TestRecursiveImport(t *testing.T) {
}
// test relative recursive import
err = ioutil.WriteFile(recursiveFile1, []byte(
err = os.WriteFile(recursiveFile1, []byte(
`localhost
dir1
import recursive_import_test2`), 0644)
@@ -283,7 +302,7 @@ func TestRecursiveImport(t *testing.T) {
}
defer os.Remove(recursiveFile1)
err = ioutil.WriteFile(recursiveFile2, []byte("dir2 1"), 0644)
err = os.WriteFile(recursiveFile2, []byte("dir2 1"), 0644)
if err != nil {
t.Fatal(err)
}
@@ -308,7 +327,7 @@ func TestRecursiveImport(t *testing.T) {
}
// test absolute recursive import
err = ioutil.WriteFile(recursiveFile1, []byte(
err = os.WriteFile(recursiveFile1, []byte(
`localhost
dir1
import `+recursiveFile2), 0644)
@@ -353,7 +372,7 @@ func TestDirectiveImport(t *testing.T) {
return false
}
if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 8 {
t.Errorf("got unexpect tokens: %v", got.Segments)
t.Errorf("got unexpected tokens: %v", got.Segments)
return false
}
return true
@@ -364,7 +383,7 @@ func TestDirectiveImport(t *testing.T) {
t.Fatal(err)
}
err = ioutil.WriteFile(directiveFile, []byte(`prop1 1
err = os.WriteFile(directiveFile, []byte(`prop1 1
prop2 2`), 0644)
if err != nil {
t.Fatal(err)
@@ -442,6 +461,28 @@ func TestParseAll(t *testing.T) {
{`import notfound/*`, false, [][]string{}}, // glob needn't error with no matches
{`import notfound/file.conf`, true, [][]string{}}, // but a specific file should
// recursive self-import
{`import testdata/import_recursive0.txt`, true, [][]string{}},
{`import testdata/import_recursive3.txt
import testdata/import_recursive1.txt`, true, [][]string{}},
// cyclic imports
{`(A) {
import A
}
:80
import A
`, true, [][]string{}},
{`(A) {
import B
}
(B) {
import A
}
:80
import A
`, true, [][]string{}},
} {
p := testParser(test.input)
blocks, err := p.parseAll()
@@ -475,89 +516,101 @@ func TestParseAll(t *testing.T) {
}
func TestEnvironmentReplacement(t *testing.T) {
os.Setenv("PORT", "8080")
os.Setenv("ADDRESS", "servername.com")
os.Setenv("FOOBAR", "foobar")
os.Setenv("PARTIAL_DIR", "r1")
os.Setenv("CHAINED", "$FOOBAR")
// basic test; unix-style env vars
p := testParser(`{$ADDRESS}`)
blocks, _ := p.parseAll()
if actual, expected := blocks[0].Keys[0], "servername.com"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// basic test; unix-style env vars
p = testParser(`di{$PARTIAL_DIR}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], "dir1"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// multiple vars per token
p = testParser(`{$ADDRESS}:{$PORT}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// windows-style var and unix style in same token
p = testParser(`{%ADDRESS%}:{$PORT}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// reverse order
p = testParser(`{$ADDRESS}:{%PORT%}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// env var in server block body as argument
p = testParser(":{%PORT%}\ndir1 {$FOOBAR}")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], ":8080"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
if actual, expected := blocks[0].Segments[0][1].Text, "foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
// combined windows env vars in argument
p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Segments[0][1].Text, "servername.com/foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
// malformed env var (windows)
p = testParser(":1234\ndir1 {%ADDRESS}")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Segments[0][1].Text, "{%ADDRESS}"; expected != actual {
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
}
// malformed (non-existent) env var (unix)
p = testParser(`:{$PORT$}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], ":"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// in quoted field
p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Segments[0][1].Text, "Test foobar test"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
// after end token
p = testParser(":1234\nanswer \"{{ .Name }} {$FOOBAR}\"")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Segments[0][1].Text, "{{ .Name }} foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
for i, test := range []struct {
input string
expect string
}{
{
input: "",
expect: "",
},
{
input: "foo",
expect: "foo",
},
{
input: "{$NOT_SET}",
expect: "",
},
{
input: "foo{$NOT_SET}bar",
expect: "foobar",
},
{
input: "{$FOOBAR}",
expect: "foobar",
},
{
input: "foo {$FOOBAR} bar",
expect: "foo foobar bar",
},
{
input: "foo{$FOOBAR}bar",
expect: "foofoobarbar",
},
{
input: "foo\n{$FOOBAR}\nbar",
expect: "foo\nfoobar\nbar",
},
{
input: "{$FOOBAR} {$FOOBAR}",
expect: "foobar foobar",
},
{
input: "{$FOOBAR}{$FOOBAR}",
expect: "foobarfoobar",
},
{
input: "{$CHAINED}",
expect: "$FOOBAR", // should not chain env expands
},
{
input: "{$FOO:default}",
expect: "default",
},
{
input: "foo{$BAR:bar}baz",
expect: "foobarbaz",
},
{
input: "foo{$BAR:$FOOBAR}baz",
expect: "foo$FOOBARbaz", // should not chain env expands
},
{
input: "{$FOOBAR",
expect: "{$FOOBAR",
},
{
input: "{$LONGER_NAME $FOOBAR}",
expect: "",
},
{
input: "{$}",
expect: "{$}",
},
{
input: "{$$}",
expect: "",
},
{
input: "{$",
expect: "{$",
},
{
input: "}{$",
expect: "}{$",
},
} {
actual, err := replaceEnvVars([]byte(test.input))
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(actual, []byte(test.expect)) {
t.Errorf("Test %d: Expected: '%s' but got '%s'", i, test.expect, actual)
}
}
}
@@ -575,10 +628,6 @@ func TestSnippets(t *testing.T) {
if err != nil {
t.Fatal(err)
}
for _, b := range blocks {
t.Log(b.Keys)
t.Log(b.Segments)
}
if len(blocks) != 1 {
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
}
@@ -597,7 +646,7 @@ func TestSnippets(t *testing.T) {
}
func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
file, err := ioutil.TempFile("", t.Name())
file, err := os.CreateTemp("", t.Name())
if err != nil {
panic(err) // get a stack trace so we know where this was called from.
}
@@ -623,10 +672,6 @@ func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) {
if err != nil {
t.Fatal(err)
}
for _, b := range blocks {
t.Log(b.Keys)
t.Log(b.Segments)
}
auth := blocks[0].Segments[0]
line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text
if line != "basicauth / import password" {
@@ -658,10 +703,6 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
if err != nil {
t.Fatal(err)
}
for _, b := range blocks {
t.Log(b.Keys)
t.Log(b.Segments)
}
if len(blocks) != 1 {
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
}
@@ -677,5 +718,5 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
}
func testParser(input string) parser {
return parser{Dispenser: newTestDispenser(input)}
return parser{Dispenser: NewTestDispenser(input)}
}
+1
View File
@@ -0,0 +1 @@
{args.0}
+1
View File
@@ -0,0 +1 @@
{args.0} {args.1}
View File
View File
View File
+1
View File
@@ -0,0 +1 @@
import import_recursive0.txt
+1
View File
@@ -0,0 +1 @@
import import_recursive2.txt
+1
View File
@@ -0,0 +1 @@
import import_recursive3.txt
+1
View File
@@ -0,0 +1 @@
import import_recursive1.txt
View File
View File
+47 -26
View File
@@ -17,20 +17,30 @@ package caddyconfig
import (
"encoding/json"
"fmt"
"github.com/caddyserver/caddy/v2"
)
// Adapter is a type which can adapt a configuration to Caddy JSON.
// It returns the results and any warnings, or an error.
type Adapter interface {
Adapt(body []byte, options map[string]interface{}) ([]byte, []Warning, error)
Adapt(body []byte, options map[string]any) ([]byte, []Warning, error)
}
// Warning represents a warning or notice related to conversion.
type Warning struct {
File string
Line int
Directive string
Message string
File string `json:"file,omitempty"`
Line int `json:"line,omitempty"`
Directive string `json:"directive,omitempty"`
Message string `json:"message,omitempty"`
}
func (w Warning) String() string {
var directive string
if w.Directive != "" {
directive = fmt.Sprintf(" (%s)", w.Directive)
}
return fmt.Sprintf("%s:%d%s: %s", w.File, w.Line, directive, w.Message)
}
// JSON encodes val as JSON, returning it as a json.RawMessage. Any
@@ -38,7 +48,7 @@ type Warning struct {
// are converted to warnings. This is convenient when filling config
// structs that require a json.RawMessage, without having to worry
// about errors.
func JSON(val interface{}, warnings *[]Warning) json.RawMessage {
func JSON(val any, warnings *[]Warning) json.RawMessage {
b, err := json.Marshal(val)
if err != nil {
if warnings != nil {
@@ -49,15 +59,14 @@ func JSON(val interface{}, warnings *[]Warning) json.RawMessage {
return b
}
// JSONModuleObject is like JSON, except it marshals val into a JSON object
// and then adds a key to that object named fieldName with the value fieldVal.
// This is useful for JSON-encoding module values where the module name has to
// be described within the object by a certain key; for example,
// "responder": "file_server" for a file server HTTP responder. The val must
// encode into a map[string]interface{} (i.e. it must be a struct or map),
// and any errors are converted into warnings, so this can be conveniently
// used when filling a struct. For correct code, there should be no errors.
func JSONModuleObject(val interface{}, fieldName, fieldVal string, warnings *[]Warning) json.RawMessage {
// JSONModuleObject is like JSON(), except it marshals val into a JSON object
// with an added key named fieldName with the value fieldVal. This is useful
// for encoding module values where the module name has to be described within
// the object by a certain key; for example, `"handler": "file_server"` for a
// file server HTTP handler (fieldName="handler" and fieldVal="file_server").
// The val parameter must encode into a map[string]any (i.e. it must be
// a struct or map). Any errors are converted into warnings.
func JSONModuleObject(val any, fieldName, fieldVal string, warnings *[]Warning) json.RawMessage {
// encode to a JSON object first
enc, err := json.Marshal(val)
if err != nil {
@@ -68,7 +77,7 @@ func JSONModuleObject(val interface{}, fieldName, fieldVal string, warnings *[]W
}
// then decode the object
var tmp map[string]interface{}
var tmp map[string]any
err = json.Unmarshal(enc, &tmp)
if err != nil {
if warnings != nil {
@@ -92,20 +101,15 @@ func JSONModuleObject(val interface{}, fieldName, fieldVal string, warnings *[]W
return result
}
// JSONIndent is used to JSON-marshal the final resulting Caddy
// configuration in a consistent, human-readable way.
func JSONIndent(val interface{}) ([]byte, error) {
return json.MarshalIndent(val, "", "\t")
}
// RegisterAdapter registers a config adapter with the given name.
// This should usually be done at init-time.
func RegisterAdapter(name string, adapter Adapter) error {
// This should usually be done at init-time. It panics if the
// adapter cannot be registered successfully.
func RegisterAdapter(name string, adapter Adapter) {
if _, ok := configAdapters[name]; ok {
return fmt.Errorf("%s: already registered", name)
panic(fmt.Errorf("%s: already registered", name))
}
configAdapters[name] = adapter
return nil
caddy.RegisterModule(adapterModule{name, adapter})
}
// GetAdapter returns the adapter with the given name,
@@ -114,4 +118,21 @@ func GetAdapter(name string) Adapter {
return configAdapters[name]
}
// adapterModule is a wrapper type that can turn any config
// adapter into a Caddy module, which has the benefit of being
// counted with other modules, even though they do not
// technically extend the Caddy configuration structure.
// See caddyserver/caddy#3132.
type adapterModule struct {
name string
Adapter
}
func (am adapterModule) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: caddy.ModuleID("caddy.adapters." + am.name),
New: func() caddy.Module { return am },
}
}
var configAdapters = make(map[string]Adapter)
+169 -90
View File
@@ -17,14 +17,17 @@ package httpcaddyfile
import (
"fmt"
"net"
"net/url"
"net/netip"
"reflect"
"sort"
"strconv"
"strings"
"unicode"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/mholt/certmagic"
"github.com/caddyserver/certmagic"
)
// mapAddressToServerBlocks returns a map of listener address to list of server
@@ -33,12 +36,12 @@ import (
// server block that share the same address stay grouped together so the config
// isn't repeated unnecessarily. For example, this Caddyfile:
//
// example.com {
// bind 127.0.0.1
// }
// www.example.com, example.net/path, localhost:9999 {
// bind 127.0.0.1 1.2.3.4
// }
// example.com {
// bind 127.0.0.1
// }
// www.example.com, example.net/path, localhost:9999 {
// bind 127.0.0.1 1.2.3.4
// }
//
// has two server blocks to start with. But expressed in this Caddyfile are
// actually 4 listener addresses: 127.0.0.1:443, 1.2.3.4:443, 127.0.0.1:9999,
@@ -46,7 +49,7 @@ import (
// key of its server block (specifying the host part), and each key may have
// a different port. And we definitely need to be sure that a site which is
// bound to be served on a specific interface is not served on others just
// beceause that is more convenient: it would be a potential security risk
// because that is more convenient: it would be a potential security risk
// if the difference between interfaces means private vs. public.
//
// So what this function does for the example above is iterate each server
@@ -73,7 +76,8 @@ import (
// repetition may be undesirable, so call consolidateAddrMappings() to map
// multiple addresses to the same lists of server blocks (a many:many mapping).
// (Doing this is essentially a map-reduce technique.)
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock) (map[string][]serverBlock, error) {
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
options map[string]any) (map[string][]serverBlock, error) {
sbmap := make(map[string][]serverBlock)
for i, sblock := range originalServerBlocks {
@@ -88,7 +92,7 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
// arguments to the 'bind' directive (although they will all have
// the same port, since the port is defined by the key or is implicit
// through automatic HTTPS)
addrs, err := st.listenerAddrsForServerBlockKey(sblock, key)
addrs, err := st.listenerAddrsForServerBlockKey(sblock, key, options)
if err != nil {
return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key, err)
}
@@ -99,18 +103,36 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
}
}
// make a slice of the map keys so we can iterate in sorted order
addrs := make([]string, 0, len(addrToKeys))
for k := range addrToKeys {
addrs = append(addrs, k)
}
sort.Strings(addrs)
// now that we know which addresses serve which keys of this
// server block, we iterate that mapping and create a list of
// new server blocks for each address where the keys of the
// server block are only the ones which use the address; but
// the contents (tokens) are of course the same
for addr, keys := range addrToKeys {
for _, addr := range addrs {
keys := addrToKeys[addr]
// parse keys so that we only have to do it once
parsedKeys := make([]Address, 0, len(keys))
for _, key := range keys {
addr, err := ParseAddress(key)
if err != nil {
return nil, fmt.Errorf("parsing key '%s': %v", key, err)
}
parsedKeys = append(parsedKeys, addr.Normalize())
}
sbmap[addr] = append(sbmap[addr], serverBlock{
block: caddyfile.ServerBlock{
Keys: keys,
Segments: sblock.block.Segments,
},
pile: sblock.pile,
keys: parsedKeys,
})
}
}
@@ -127,7 +149,7 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
// association from multiple addresses to multiple server blocks; i.e. each element of
// the returned slice) becomes a server definition in the output JSON.
func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
var sbaddrs []sbAddrAssociation
sbaddrs := make([]sbAddrAssociation, 0, len(addrToServerBlocks))
for addr, sblocks := range addrToServerBlocks {
// we start with knowing that at least this address
// maps to these server blocks
@@ -148,49 +170,99 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]se
delete(addrToServerBlocks, otherAddr)
}
}
sort.Strings(a.addresses)
sbaddrs = append(sbaddrs, a)
}
// sort them by their first address (we know there will always be at least one)
// to avoid problems with non-deterministic ordering (makes tests flaky)
sort.Slice(sbaddrs, func(i, j int) bool {
return sbaddrs[i].addresses[0] < sbaddrs[j].addresses[0]
})
return sbaddrs
}
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string) ([]string, error) {
// listenerAddrsForServerBlockKey essentially converts the Caddyfile
// site addresses to Caddy listener addresses for each server block.
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
options map[string]any) ([]string, error) {
addr, err := ParseAddress(key)
if err != nil {
return nil, fmt.Errorf("parsing key: %v", err)
}
addr = addr.Normalize()
lnPort := defaultPort
// figure out the HTTP and HTTPS ports; either
// use defaults, or override with user config
httpPort, httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPPort), strconv.Itoa(caddyhttp.DefaultHTTPSPort)
if hport, ok := options["http_port"]; ok {
httpPort = strconv.Itoa(hport.(int))
}
if hsport, ok := options["https_port"]; ok {
httpsPort = strconv.Itoa(hsport.(int))
}
// default port is the HTTPS port
lnPort := httpsPort
if addr.Port != "" {
// port explicitly defined
lnPort = addr.Port
} else if certmagic.HostQualifies(addr.Host) {
// automatic HTTPS
lnPort = strconv.Itoa(certmagic.HTTPSPort)
} else if addr.Scheme == "http" {
// port inferred from scheme
lnPort = httpPort
}
// the bind directive specifies hosts, but is optional
var lnHosts []string
// error if scheme and port combination violate convention
if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) {
return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
}
// the bind directive specifies hosts (and potentially network), but is optional
lnHosts := make([]string, 0, len(sblock.pile["bind"]))
for _, cfgVal := range sblock.pile["bind"] {
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
}
if len(lnHosts) == 0 {
lnHosts = []string{""}
if defaultBind, ok := options["default_bind"].([]string); ok {
lnHosts = defaultBind
} else {
lnHosts = []string{""}
}
}
// use a map to prevent duplication
listeners := make(map[string]struct{})
for _, host := range lnHosts {
listeners[net.JoinHostPort(host, lnPort)] = struct{}{}
for _, lnHost := range lnHosts {
// normally we would simply append the port,
// but if lnHost is IPv6, we need to ensure it
// is enclosed in [ ]; net.JoinHostPort does
// this for us, but lnHost 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
network, host, ok := strings.Cut(lnHost, "/")
if !ok {
host = network
network = ""
}
host = strings.Trim(host, "[]") // IPv6
networkAddr := caddy.JoinNetworkAddress(network, host, lnPort)
addr, err := caddy.ParseNetworkAddress(networkAddr)
if err != nil {
return nil, fmt.Errorf("parsing network address: %v", err)
}
listeners[addr.String()] = struct{}{}
}
// now turn map into list
var listenersList []string
listenersList := make([]string, 0, len(listeners))
for lnStr := range listeners {
listenersList = append(listenersList, lnStr)
}
// sort.Strings(listenersList) // TODO: is sorting necessary?
sort.Strings(listenersList)
return listenersList, nil
}
@@ -209,48 +281,55 @@ type Address struct {
// ParseAddress parses an address string into a structured format with separate
// scheme, host, port, and path portions, as well as the original input string.
func ParseAddress(str string) (Address, error) {
httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
const maxLen = 4096
if len(str) > maxLen {
str = str[:maxLen]
}
remaining := strings.TrimSpace(str)
a := Address{Original: remaining}
input := str
// Split input into components (prepend with // to force host portion by default)
if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") {
str = "//" + str
// extract scheme
splitScheme := strings.SplitN(remaining, "://", 2)
switch len(splitScheme) {
case 0:
return a, nil
case 1:
remaining = splitScheme[0]
case 2:
a.Scheme = splitScheme[0]
remaining = splitScheme[1]
}
u, err := url.Parse(str)
if err != nil {
return Address{}, err
}
// separate host and port
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
host, port, err = net.SplitHostPort(u.Host + ":")
// extract host and port
hostSplit := strings.SplitN(remaining, "/", 2)
if len(hostSplit) > 0 {
host, port, err := net.SplitHostPort(hostSplit[0])
if err != nil {
host = u.Host
host, port, err = net.SplitHostPort(hostSplit[0] + ":")
if err != nil {
host = hostSplit[0]
}
}
a.Host = host
a.Port = port
}
if len(hostSplit) == 2 {
// all that remains is the path
a.Path = "/" + hostSplit[1]
}
// make sure port is valid
if a.Port != "" {
if portNum, err := strconv.Atoi(a.Port); err != nil {
return Address{}, fmt.Errorf("invalid port '%s': %v", a.Port, err)
} else if portNum < 0 || portNum > 65535 {
return Address{}, fmt.Errorf("port %d is out of range", portNum)
}
}
// see if we can set port based off scheme
if port == "" {
if u.Scheme == "http" {
port = httpPort
} else if u.Scheme == "https" {
port = httpsPort
}
}
// error if scheme and port combination violate convention
if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) {
return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
}
return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
return a, nil
}
// TODO: which of the methods on Address are even used?
// String returns a human-readable form of a. It will
// be a cleaned-up and filled-out URL string.
func (a Address) String() string {
@@ -285,48 +364,48 @@ func (a Address) String() string {
// Normalize returns a normalized version of a.
func (a Address) Normalize() Address {
path := a.Path
if !caseSensitivePath {
path = strings.ToLower(path)
}
// ensure host is normalized if it's an IP address
host := a.Host
if ip := net.ParseIP(host); ip != nil {
host = ip.String()
host := strings.TrimSpace(a.Host)
if ip, err := netip.ParseAddr(host); err == nil {
if ip.Is6() && !ip.Is4() && !ip.Is4In6() {
host = ip.String()
}
}
return Address{
Original: a.Original,
Scheme: strings.ToLower(a.Scheme),
Host: strings.ToLower(host),
Scheme: lowerExceptPlaceholders(a.Scheme),
Host: lowerExceptPlaceholders(host),
Port: a.Port,
Path: path,
}
}
// Key returns a string form of a, much like String() does, but this
// method doesn't add anything default that wasn't in the original.
func (a Address) Key() string {
res := ""
if a.Scheme != "" {
res += a.Scheme + "://"
// lowerExceptPlaceholders lowercases s except within
// placeholders (substrings in non-escaped '{ }' spans).
// See https://github.com/caddyserver/caddy/issues/3264
func lowerExceptPlaceholders(s string) string {
var sb strings.Builder
var escaped, inPlaceholder bool
for _, ch := range s {
if ch == '\\' && !escaped {
escaped = true
sb.WriteRune(ch)
continue
}
if ch == '{' && !escaped {
inPlaceholder = true
}
if ch == '}' && inPlaceholder && !escaped {
inPlaceholder = false
}
if inPlaceholder {
sb.WriteRune(ch)
} else {
sb.WriteRune(unicode.ToLower(ch))
}
escaped = false
}
if a.Host != "" {
res += a.Host
}
// insert port only if the original has its own explicit port
if a.Port != "" &&
len(a.Original) >= len(res) &&
strings.HasPrefix(a.Original[len(res):], ":"+a.Port) {
res += ":" + a.Port
}
if a.Path != "" {
res += a.Path
}
return res
return sb.String()
}
const (
defaultPort = "2015"
caseSensitivePath = false // TODO: Used?
)
@@ -0,0 +1,28 @@
// 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 gofuzz
package httpcaddyfile
func FuzzParseAddress(data []byte) int {
addr, err := ParseAddress(string(data))
if err != nil {
if addr == (Address{}) {
return 1
}
return 0
}
return 1
}
+130 -43
View File
@@ -1,7 +1,6 @@
package httpcaddyfile
import (
"strings"
"testing"
)
@@ -11,6 +10,7 @@ func TestParseAddress(t *testing.T) {
scheme, host, port, path string
shouldErr bool
}{
{``, "", "", "", "", false},
{`localhost`, "", "localhost", "", "", false},
{`localhost:1234`, "", "localhost", "1234", "", false},
{`localhost:`, "", "localhost", "", "", false},
@@ -27,25 +27,29 @@ func TestParseAddress(t *testing.T) {
{`http://localhost:https`, "", "", "", "", true}, // conflict
{`http://localhost:http`, "", "", "", "", true}, // repeated scheme
{`host:https/path`, "", "", "", "", true},
{`http://localhost:443`, "", "", "", "", true}, // not conventional
{`https://localhost:80`, "", "", "", "", true}, // not conventional
{`http://localhost`, "http", "localhost", "80", "", false},
{`https://localhost`, "https", "localhost", "443", "", false},
{`http://127.0.0.1`, "http", "127.0.0.1", "80", "", false},
{`https://127.0.0.1`, "https", "127.0.0.1", "443", "", false},
{`http://[::1]`, "http", "::1", "80", "", false},
{`http://localhost:443`, "http", "localhost", "443", "", false}, // NOTE: not conventional
{`https://localhost:80`, "https", "localhost", "80", "", false}, // NOTE: not conventional
{`http://localhost`, "http", "localhost", "", "", false},
{`https://localhost`, "https", "localhost", "", "", false},
{`http://{env.APP_DOMAIN}`, "http", "{env.APP_DOMAIN}", "", "", false},
{`{env.APP_DOMAIN}:80`, "", "{env.APP_DOMAIN}", "80", "", false},
{`{env.APP_DOMAIN}/path`, "", "{env.APP_DOMAIN}", "", "/path", false},
{`example.com/{env.APP_PATH}`, "", "example.com", "", "/{env.APP_PATH}", false},
{`http://127.0.0.1`, "http", "127.0.0.1", "", "", false},
{`https://127.0.0.1`, "https", "127.0.0.1", "", "", false},
{`http://[::1]`, "http", "::1", "", "", false},
{`http://localhost:1234`, "http", "localhost", "1234", "", false},
{`https://127.0.0.1:1234`, "https", "127.0.0.1", "1234", "", false},
{`http://[::1]:1234`, "http", "::1", "1234", "", false},
{``, "", "", "", "", false},
{`::1`, "", "::1", "", "", true},
{`localhost::`, "", "localhost::", "", "", true},
{`#$%@`, "", "", "", "", true},
{`::1`, "", "::1", "", "", false},
{`localhost::`, "", "localhost::", "", "", false},
{`#$%@`, "", "#$%@", "", "", false}, // don't want to presume what the hostname could be
{`host/path`, "", "host", "", "/path", false},
{`http://host/`, "http", "host", "80", "/", false},
{`//asdf`, "", "asdf", "", "", false},
{`http://host/`, "http", "host", "", "/", false},
{`//asdf`, "", "", "", "//asdf", false},
{`:1234/asdf`, "", "", "1234", "/asdf", false},
{`http://host/path`, "http", "host", "80", "/path", false},
{`http://host/path`, "http", "host", "", "/path", false},
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
{`host:80/path`, "", "host", "80", "/path", false},
{`/path`, "", "", "", "/path", false},
@@ -56,7 +60,7 @@ func TestParseAddress(t *testing.T) {
t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
}
if err == nil && test.shouldErr {
t.Errorf("Test %d (%s): Expected error, but had none", i, test.input)
t.Errorf("Test %d (%s): Expected error, but had none (%#v)", i, test.input, actual)
}
if !test.shouldErr && actual.Original != test.input {
@@ -102,47 +106,128 @@ func TestAddressString(t *testing.T) {
func TestKeyNormalization(t *testing.T) {
testCases := []struct {
input string
expect string
expect Address
}{
{
input: "http://host:1234/path",
expect: "http://host:1234/path",
input: "example.com",
expect: Address{
Host: "example.com",
},
},
{
input: "HTTP://A/ABCDEF",
expect: "http://a/ABCDEF",
input: "http://host:1234/path",
expect: Address{
Scheme: "http",
Host: "host",
Port: "1234",
Path: "/path",
},
},
{
input: "A/ABCDEF",
expect: "a/ABCDEF",
input: "HTTP://A/ABCDEF",
expect: Address{
Scheme: "http",
Host: "a",
Path: "/ABCDEF",
},
},
{
input: "A:2015/Path",
expect: "a:2015/Path",
input: "A/ABCDEF",
expect: Address{
Host: "a",
Path: "/ABCDEF",
},
},
{
input: ":80",
expect: ":80",
input: "A:2015/Path",
expect: Address{
Host: "a",
Port: "2015",
Path: "/Path",
},
},
{
input: ":443",
expect: ":443",
input: "sub.{env.MY_DOMAIN}",
expect: Address{
Host: "sub.{env.MY_DOMAIN}",
},
},
{
input: ":1234",
expect: ":1234",
input: "sub.ExAmPle",
expect: Address{
Host: "sub.example",
},
},
{
input: "sub.\\{env.MY_DOMAIN\\}",
expect: Address{
Host: "sub.\\{env.my_domain\\}",
},
},
{
input: "sub.{env.MY_DOMAIN}.com",
expect: Address{
Host: "sub.{env.MY_DOMAIN}.com",
},
},
{
input: ":80",
expect: Address{
Port: "80",
},
},
{
input: ":443",
expect: Address{
Port: "443",
},
},
{
input: ":1234",
expect: Address{
Port: "1234",
},
},
{
input: "",
expect: "",
expect: Address{},
},
{
input: ":",
expect: "",
expect: Address{},
},
{
input: "[::]",
expect: "::",
input: "[::]",
expect: Address{
Host: "::",
},
},
{
input: "127.0.0.1",
expect: Address{
Host: "127.0.0.1",
},
},
{
input: "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:1234",
expect: Address{
Host: "2001:db8:85a3:8d3:1319:8a2e:370:7348",
Port: "1234",
},
},
{
// IPv4 address in IPv6 form (#4381)
input: "[::ffff:cff4:e77d]:1234",
expect: Address{
Host: "::ffff:cff4:e77d",
Port: "1234",
},
},
{
input: "::ffff:cff4:e77d",
expect: Address{
Host: "::ffff:cff4:e77d",
},
},
}
for i, tc := range testCases {
@@ -151,16 +236,18 @@ func TestKeyNormalization(t *testing.T) {
t.Errorf("Test %d: Parsing address '%s': %v", i, tc.input, err)
continue
}
expect := tc.expect
if !caseSensitivePath {
// every other part of the address should be lowercased when normalized,
// so simply lower-case the whole thing to do case-insensitive comparison
// of the path as well
expect = strings.ToLower(expect)
actual := addr.Normalize()
if actual.Scheme != tc.expect.Scheme {
t.Errorf("Test %d: Input '%s': Expected Scheme='%s' but got Scheme='%s'", i, tc.input, tc.expect.Scheme, actual.Scheme)
}
if actual := addr.Normalize().Key(); actual != expect {
t.Errorf("Test %d: Normalized key for address '%s' was '%s' but expected '%s'", i, tc.input, actual, expect)
if actual.Host != tc.expect.Host {
t.Errorf("Test %d: Input '%s': Expected Host='%s' but got Host='%s'", i, tc.input, tc.expect.Host, actual.Host)
}
if actual.Port != tc.expect.Port {
t.Errorf("Test %d: Input '%s': Expected Port='%s' but got Port='%s'", i, tc.input, tc.expect.Port, actual.Port)
}
if actual.Path != tc.expect.Path {
t.Errorf("Test %d: Input '%s': Expected Path='%s' but got Path='%s'", i, tc.input, tc.expect.Path, actual.Path)
}
}
}
+702 -81
View File
@@ -15,24 +15,45 @@
package httpcaddyfile
import (
"encoding/json"
"encoding/base64"
"encoding/pem"
"fmt"
"html"
"net/http"
"os"
"reflect"
"strconv"
"strings"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls"
"github.com/caddyserver/certmagic"
"github.com/mholt/acmez/acme"
"go.uber.org/zap/zapcore"
)
func init() {
RegisterDirective("bind", parseBind)
RegisterDirective("root", parseRoot)
RegisterDirective("tls", parseTLS)
RegisterHandlerDirective("root", parseRoot)
RegisterHandlerDirective("vars", parseVars)
RegisterHandlerDirective("redir", parseRedir)
RegisterHandlerDirective("respond", parseRespond)
RegisterHandlerDirective("abort", parseAbort)
RegisterHandlerDirective("error", parseError)
RegisterHandlerDirective("route", parseRoute)
RegisterHandlerDirective("handle", parseHandle)
RegisterDirective("handle_errors", parseHandleErrors)
RegisterDirective("log", parseLog)
RegisterHandlerDirective("skip_log", parseSkipLog)
}
// parseBind parses the bind directive. Syntax:
//
// bind <addresses...>
func parseBind(h Helper) ([]ConfigValue, error) {
var lnHosts []string
for h.Next() {
@@ -41,49 +62,41 @@ func parseBind(h Helper) ([]ConfigValue, error) {
return h.NewBindAddresses(lnHosts), nil
}
func parseRoot(h Helper) ([]ConfigValue, error) {
if !h.Next() {
return nil, h.ArgErr()
}
matcherSet, ok, err := h.MatcherToken()
if err != nil {
return nil, err
}
if !ok {
// no matcher token; oops
h.Dispenser.Prev()
}
if !h.NextArg() {
return nil, h.ArgErr()
}
root := h.Val()
if h.NextArg() {
return nil, h.ArgErr()
}
varsHandler := caddyhttp.VarsMiddleware{"root": root}
route := caddyhttp.Route{
HandlersRaw: []json.RawMessage{
caddyconfig.JSONModuleObject(varsHandler, "handler", "vars", nil),
},
}
if matcherSet != nil {
route.MatcherSetsRaw = []map[string]json.RawMessage{matcherSet}
}
return h.NewVarsRoute(route), nil
}
// parseTLS parses the tls directive. Syntax:
//
// tls [<email>|internal]|[<cert_file> <key_file>] {
// protocols <min> [<max>]
// ciphers <cipher_suites...>
// curves <curves...>
// client_auth {
// mode [request|require|verify_if_given|require_and_verify]
// trusted_ca_cert <base64_der>
// trusted_ca_cert_file <filename>
// trusted_leaf_cert <base64_der>
// trusted_leaf_cert_file <filename>
// }
// alpn <values...>
// load <paths...>
// ca <acme_ca_endpoint>
// ca_root <pem_file>
// dns <provider_name> [...]
// on_demand
// eab <key_id> <mac_key>
// issuer <module_name> [...]
// get_certificate <module_name> [...]
// insecure_secrets_log <log_file>
// }
func parseTLS(h Helper) ([]ConfigValue, error) {
var configVals []ConfigValue
cp := new(caddytls.ConnectionPolicy)
var fileLoader caddytls.FileLoader
var folderLoader caddytls.FolderLoader
var mgr caddytls.ACMEManagerMaker
var off bool
var certSelector caddytls.CustomCertSelectionPolicy
var acmeIssuer *caddytls.ACMEIssuer
var keyType string
var internalIssuer *caddytls.InternalIssuer
var issuers []certmagic.Issuer
var certManagers []certmagic.Manager
var onDemand bool
for h.Next() {
// file certificate loader
@@ -91,28 +104,71 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
switch len(firstLine) {
case 0:
case 1:
if firstLine[0] == "off" {
off = true
if firstLine[0] == "internal" {
internalIssuer = new(caddytls.InternalIssuer)
} else if !strings.Contains(firstLine[0], "@") {
return nil, h.Err("single argument must either be 'internal' or an email address")
} else {
mgr.Email = firstLine[0]
if acmeIssuer == nil {
acmeIssuer = new(caddytls.ACMEIssuer)
}
acmeIssuer.Email = firstLine[0]
}
case 2:
fileLoader = append(fileLoader, caddytls.CertKeyFilePair{
Certificate: firstLine[0],
Key: firstLine[1],
// TODO: add tags, for enterprise module's certificate selection
})
certFilename := firstLine[0]
keyFilename := firstLine[1]
// tag this certificate so if multiple certs match, specifically
// this one that the user has provided will be used, see #2588:
// https://github.com/caddyserver/caddy/issues/2588 ... but we
// must be careful about how we do this; being careless will
// lead to failed handshakes
//
// we need to remember which cert files we've seen, since we
// must load each cert only once; otherwise, they each get a
// different tag... since a cert loaded twice has the same
// bytes, it will overwrite the first one in the cache, and
// only the last cert (and its tag) will survive, so any conn
// policy that is looking for any tag other than the last one
// to be loaded won't find it, and TLS handshakes will fail
// (see end of issue #3004)
//
// tlsCertTags maps certificate filenames to their tag.
// This is used to remember which tag is used for each
// certificate files, since we need to avoid loading
// the same certificate files more than once, overwriting
// previous tags
tlsCertTags, ok := h.State["tlsCertTags"].(map[string]string)
if !ok {
tlsCertTags = make(map[string]string)
h.State["tlsCertTags"] = tlsCertTags
}
tag, ok := tlsCertTags[certFilename]
if !ok {
// haven't seen this cert file yet, let's give it a tag
// and add a loader for it
tag = fmt.Sprintf("cert%d", len(tlsCertTags))
fileLoader = append(fileLoader, caddytls.CertKeyFilePair{
Certificate: certFilename,
Key: keyFilename,
Tags: []string{tag},
})
// remember this for next time we see this cert file
tlsCertTags[certFilename] = tag
}
certSelector.AnyTag = append(certSelector.AnyTag, tag)
default:
return nil, h.ArgErr()
}
var hasBlock bool
for h.NextBlock(0) {
for nesting := h.Nesting(); h.NextBlock(nesting); {
hasBlock = true
switch h.Val() {
// connection policy
case "protocols":
args := h.RemainingArgs()
if len(args) == 0 {
@@ -130,13 +186,15 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
}
cp.ProtocolMax = args[1]
}
case "ciphers":
for h.NextArg() {
if _, ok := caddytls.SupportedCipherSuites[h.Val()]; !ok {
if !caddytls.CipherSuiteNameSupported(h.Val()) {
return nil, h.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
}
cp.CipherSuites = append(cp.CipherSuites, h.Val())
}
case "curves":
for h.NextArg() {
if _, ok := caddytls.SupportedCurves[h.Val()]; !ok {
@@ -144,6 +202,58 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
}
cp.Curves = append(cp.Curves, h.Val())
}
case "client_auth":
cp.ClientAuthentication = &caddytls.ClientAuthentication{}
for nesting := h.Nesting(); h.NextBlock(nesting); {
subdir := h.Val()
switch subdir {
case "mode":
if !h.Args(&cp.ClientAuthentication.Mode) {
return nil, h.ArgErr()
}
if h.NextArg() {
return nil, h.ArgErr()
}
case "trusted_ca_cert",
"trusted_leaf_cert":
if !h.NextArg() {
return nil, h.ArgErr()
}
if subdir == "trusted_ca_cert" {
cp.ClientAuthentication.TrustedCACerts = append(cp.ClientAuthentication.TrustedCACerts, h.Val())
} else {
cp.ClientAuthentication.TrustedLeafCerts = append(cp.ClientAuthentication.TrustedLeafCerts, h.Val())
}
case "trusted_ca_cert_file",
"trusted_leaf_cert_file":
if !h.NextArg() {
return nil, h.ArgErr()
}
filename := h.Val()
certDataPEM, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
block, _ := pem.Decode(certDataPEM)
if block == nil || block.Type != "CERTIFICATE" {
return nil, h.Errf("no CERTIFICATE pem block found in %s", h.Val())
}
if subdir == "trusted_ca_cert_file" {
cp.ClientAuthentication.TrustedCACerts = append(cp.ClientAuthentication.TrustedCACerts,
base64.StdEncoding.EncodeToString(block.Bytes))
} else {
cp.ClientAuthentication.TrustedLeafCerts = append(cp.ClientAuthentication.TrustedLeafCerts,
base64.StdEncoding.EncodeToString(block.Bytes))
}
default:
return nil, h.Errf("unknown subdirective for client_auth: %s", subdir)
}
}
case "alpn":
args := h.RemainingArgs()
if len(args) == 0 {
@@ -151,19 +261,148 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
}
cp.ALPN = args
// certificate folder loader
case "load":
folderLoader = append(folderLoader, h.RemainingArgs()...)
// automation policy
case "ca":
arg := h.RemainingArgs()
if len(arg) != 1 {
return nil, h.ArgErr()
}
mgr.CA = arg[0]
if acmeIssuer == nil {
acmeIssuer = new(caddytls.ACMEIssuer)
}
acmeIssuer.CA = arg[0]
// TODO: other properties for automation manager
case "key_type":
arg := h.RemainingArgs()
if len(arg) != 1 {
return nil, h.ArgErr()
}
keyType = arg[0]
case "eab":
arg := h.RemainingArgs()
if len(arg) != 2 {
return nil, h.ArgErr()
}
if acmeIssuer == nil {
acmeIssuer = new(caddytls.ACMEIssuer)
}
acmeIssuer.ExternalAccount = &acme.EAB{
KeyID: arg[0],
MACKey: arg[1],
}
case "issuer":
if !h.NextArg() {
return nil, h.ArgErr()
}
modName := h.Val()
modID := "tls.issuance." + modName
unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
if err != nil {
return nil, err
}
issuer, ok := unm.(certmagic.Issuer)
if !ok {
return nil, h.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm)
}
issuers = append(issuers, issuer)
case "get_certificate":
if !h.NextArg() {
return nil, h.ArgErr()
}
modName := h.Val()
modID := "tls.get_certificate." + modName
unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
if err != nil {
return nil, err
}
certManager, ok := unm.(certmagic.Manager)
if !ok {
return nil, h.Errf("module %s (%T) is not a certmagic.CertificateManager", modID, unm)
}
certManagers = append(certManagers, certManager)
case "dns":
if !h.NextArg() {
return nil, h.ArgErr()
}
provName := h.Val()
if acmeIssuer == nil {
acmeIssuer = new(caddytls.ACMEIssuer)
}
if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
}
if acmeIssuer.Challenges.DNS == nil {
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
}
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)
case "resolvers":
args := h.RemainingArgs()
if len(args) == 0 {
return nil, h.ArgErr()
}
if acmeIssuer == nil {
acmeIssuer = new(caddytls.ACMEIssuer)
}
if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
}
if acmeIssuer.Challenges.DNS == nil {
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
}
acmeIssuer.Challenges.DNS.Resolvers = args
case "dns_challenge_override_domain":
arg := h.RemainingArgs()
if len(arg) != 1 {
return nil, h.ArgErr()
}
if acmeIssuer == nil {
acmeIssuer = new(caddytls.ACMEIssuer)
}
if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
}
if acmeIssuer.Challenges.DNS == nil {
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
}
acmeIssuer.Challenges.DNS.OverrideDomain = arg[0]
case "ca_root":
arg := h.RemainingArgs()
if len(arg) != 1 {
return nil, h.ArgErr()
}
if acmeIssuer == nil {
acmeIssuer = new(caddytls.ACMEIssuer)
}
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, arg[0])
case "on_demand":
if h.NextArg() {
return nil, h.ArgErr()
}
onDemand = true
case "insecure_secrets_log":
if !h.NextArg() {
return nil, h.ArgErr()
}
cp.InsecureSecretsLog = h.Val()
default:
return nil, h.Errf("unknown subdirective: %s", h.Val())
}
}
@@ -173,42 +412,146 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
}
}
// connection policy
configVals = append(configVals, ConfigValue{
Class: "tls.connection_policy",
Value: cp,
})
// begin building the final config values
configVals := []ConfigValue{}
// certificate loaders
if len(fileLoader) > 0 {
configVals = append(configVals, ConfigValue{
Class: "tls.certificate_loader",
Class: "tls.cert_loader",
Value: fileLoader,
})
}
if len(folderLoader) > 0 {
configVals = append(configVals, ConfigValue{
Class: "tls.certificate_loader",
Class: "tls.cert_loader",
Value: folderLoader,
})
}
// automation policy
if off {
// some tls subdirectives are shortcuts that implicitly configure issuers, and the
// user can also configure issuers explicitly using the issuer subdirective; the
// logic to support both would likely be complex, or at least unintuitive
if len(issuers) > 0 && (acmeIssuer != nil || internalIssuer != nil) {
return nil, h.Err("cannot mix issuer subdirective (explicit issuers) with other issuer-specific subdirectives (implicit issuers)")
}
if acmeIssuer != nil && internalIssuer != nil {
return nil, h.Err("cannot create both ACME and internal certificate issuers")
}
// now we should either have: explicitly-created issuers, or an implicitly-created
// ACME or internal issuer, or no issuers at all
switch {
case len(issuers) > 0:
for _, issuer := range issuers {
configVals = append(configVals, ConfigValue{
Class: "tls.cert_issuer",
Value: issuer,
})
}
case acmeIssuer != nil:
// implicit ACME issuers (from various subdirectives) - use defaults; there might be more than one
defaultIssuers := caddytls.DefaultIssuers()
// if a CA endpoint was set, override multiple implicit issuers since it's a specific one
if acmeIssuer.CA != "" {
defaultIssuers = []certmagic.Issuer{acmeIssuer}
}
for _, issuer := range defaultIssuers {
switch iss := issuer.(type) {
case *caddytls.ACMEIssuer:
issuer = acmeIssuer
case *caddytls.ZeroSSLIssuer:
iss.ACMEIssuer = acmeIssuer
}
configVals = append(configVals, ConfigValue{
Class: "tls.cert_issuer",
Value: issuer,
})
}
case internalIssuer != nil:
configVals = append(configVals, ConfigValue{
Class: "tls.off",
Value: true,
})
} else if !reflect.DeepEqual(mgr, caddytls.ACMEManagerMaker{}) {
configVals = append(configVals, ConfigValue{
Class: "tls.automation_manager",
Value: mgr,
Class: "tls.cert_issuer",
Value: internalIssuer,
})
}
// certificate key type
if keyType != "" {
configVals = append(configVals, ConfigValue{
Class: "tls.key_type",
Value: keyType,
})
}
// on-demand TLS
if onDemand {
configVals = append(configVals, ConfigValue{
Class: "tls.on_demand",
Value: true,
})
}
for _, certManager := range certManagers {
configVals = append(configVals, ConfigValue{
Class: "tls.cert_manager",
Value: certManager,
})
}
// custom certificate selection
if len(certSelector.AnyTag) > 0 {
cp.CertSelection = &certSelector
}
// connection policy -- always add one, to ensure that TLS
// is enabled, because this directive was used (this is
// needed, for instance, when a site block has a key of
// just ":5000" - i.e. no hostname, and only on-demand TLS
// is enabled)
configVals = append(configVals, ConfigValue{
Class: "tls.connection_policy",
Value: cp,
})
return configVals, nil
}
// parseRoot parses the root directive. Syntax:
//
// root [<matcher>] <path>
func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
var root string
for h.Next() {
if !h.NextArg() {
return nil, h.ArgErr()
}
root = h.Val()
if h.NextArg() {
return nil, h.ArgErr()
}
}
return caddyhttp.VarsMiddleware{"root": root}, nil
}
// parseVars parses the vars directive. See its UnmarshalCaddyfile method for syntax.
func parseVars(h Helper) (caddyhttp.MiddlewareHandler, error) {
v := new(caddyhttp.VarsMiddleware)
err := v.UnmarshalCaddyfile(h.Dispenser)
return v, err
}
// parseRedir parses the redir directive. Syntax:
//
// redir [<matcher>] <to> [<code>]
//
// <code> can be "permanent" for 301, "temporary" for 302 (default),
// a placeholder, or any number in the 3xx range or 401. The special
// code "html" can be used to redirect only browser clients (will
// respond with HTTP 200 and no Location header; redirect is performed
// with JS and a meta tag).
func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
if !h.Next() {
return nil, h.ArgErr()
@@ -223,14 +566,15 @@ func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
if h.NextArg() {
code = h.Val()
}
if code == "permanent" {
code = "301"
}
if code == "temporary" || code == "" {
code = "307"
}
var body string
if code == "meta" {
var hdr http.Header
switch code {
case "permanent":
code = "301"
case "temporary", "":
code = "302"
case "html":
// Script tag comes first since that will better imitate a redirect in the browser's
// history, but the meta tag is a fallback for most non-JS clients.
const metaRedir = `<!DOCTYPE html>
@@ -245,11 +589,288 @@ func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
`
safeTo := html.EscapeString(to)
body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
code = "200" // don't redirect non-browser clients
default:
// Allow placeholders for the code
if strings.HasPrefix(code, "{") {
break
}
// Try to validate as an integer otherwise
codeInt, err := strconv.Atoi(code)
if err != nil {
return nil, h.Errf("Not a supported redir code type or not valid integer: '%s'", code)
}
// Sometimes, a 401 with Location header is desirable because
// requests made with XHR will "eat" the 3xx redirect; so if
// the intent was to redirect to an auth page, a 3xx won't
// work. Responding with 401 allows JS code to read the
// Location header and do a window.location redirect manually.
// see https://stackoverflow.com/a/2573589/846934
// see https://github.com/oauth2-proxy/oauth2-proxy/issues/1522
if codeInt < 300 || (codeInt > 399 && codeInt != 401) {
return nil, h.Errf("Redir code not in the 3xx range or 401: '%v'", codeInt)
}
}
// don't redirect non-browser clients
if code != "200" {
hdr = http.Header{"Location": []string{to}}
}
return caddyhttp.StaticResponse{
StatusCode: caddyhttp.WeakString(code),
Headers: http.Header{"Location": []string{to}},
Headers: hdr,
Body: body,
}, nil
}
// parseRespond parses the respond directive.
func parseRespond(h Helper) (caddyhttp.MiddlewareHandler, error) {
sr := new(caddyhttp.StaticResponse)
err := sr.UnmarshalCaddyfile(h.Dispenser)
if err != nil {
return nil, err
}
return sr, nil
}
// parseAbort parses the abort directive.
func parseAbort(h Helper) (caddyhttp.MiddlewareHandler, error) {
h.Next() // consume directive
for h.Next() || h.NextBlock(0) {
return nil, h.ArgErr()
}
return &caddyhttp.StaticResponse{Abort: true}, nil
}
// parseError parses the error directive.
func parseError(h Helper) (caddyhttp.MiddlewareHandler, error) {
se := new(caddyhttp.StaticError)
err := se.UnmarshalCaddyfile(h.Dispenser)
if err != nil {
return nil, err
}
return se, nil
}
// parseRoute parses the route directive.
func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
sr := new(caddyhttp.Subroute)
allResults, err := parseSegmentAsConfig(h)
if err != nil {
return nil, err
}
for _, result := range allResults {
switch handler := result.Value.(type) {
case caddyhttp.Route:
sr.Routes = append(sr.Routes, handler)
case caddyhttp.Subroute:
// directives which return a literal subroute instead of a route
// means they intend to keep those handlers together without
// them being reordered; we're doing that anyway since we're in
// the route directive, so just append its handlers
sr.Routes = append(sr.Routes, handler.Routes...)
default:
return nil, h.Errf("%s directive returned something other than an HTTP route or subroute: %#v (only handler directives can be used in routes)", result.directive, result.Value)
}
}
return sr, nil
}
func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
return ParseSegmentAsSubroute(h)
}
func parseHandleErrors(h Helper) ([]ConfigValue, error) {
subroute, err := ParseSegmentAsSubroute(h)
if err != nil {
return nil, err
}
return []ConfigValue{
{
Class: "error_route",
Value: subroute,
},
}, nil
}
// parseLog parses the log directive. Syntax:
//
// log {
// output <writer_module> ...
// format <encoder_module> ...
// level <level>
// }
func parseLog(h Helper) ([]ConfigValue, error) {
return parseLogHelper(h, nil)
}
// parseLogHelper is used both for the parseLog directive within Server Blocks,
// as well as the global "log" option for configuring loggers at the global
// level. The parseAsGlobalOption parameter is used to distinguish any differing logic
// between the two.
func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue, error) {
// When the globalLogNames parameter is passed in, we make
// modifications to the parsing behavior.
parseAsGlobalOption := globalLogNames != nil
var configValues []ConfigValue
for h.Next() {
// Logic below expects that a name is always present when a
// global option is being parsed.
var globalLogName string
if parseAsGlobalOption {
if h.NextArg() {
globalLogName = h.Val()
// Only a single argument is supported.
if h.NextArg() {
return nil, h.ArgErr()
}
} else {
// If there is no log name specified, we
// reference the default logger. See the
// setupNewDefault function in the logging
// package for where this is configured.
globalLogName = caddy.DefaultLoggerName
}
// Verify this name is unused.
_, used := globalLogNames[globalLogName]
if used {
return nil, h.Err("duplicate global log option for: " + globalLogName)
}
globalLogNames[globalLogName] = struct{}{}
} else {
// No arguments are supported for the server block log directive
if h.NextArg() {
return nil, h.ArgErr()
}
}
cl := new(caddy.CustomLog)
for h.NextBlock(0) {
switch h.Val() {
case "output":
if !h.NextArg() {
return nil, h.ArgErr()
}
moduleName := h.Val()
// can't use the usual caddyfile.Unmarshaler flow with the
// standard writers because they are in the caddy package
// (because they are the default) and implementing that
// interface there would unfortunately create circular import
var wo caddy.WriterOpener
switch moduleName {
case "stdout":
wo = caddy.StdoutWriter{}
case "stderr":
wo = caddy.StderrWriter{}
case "discard":
wo = caddy.DiscardWriter{}
default:
modID := "caddy.logging.writers." + moduleName
unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
if err != nil {
return nil, err
}
var ok bool
wo, ok = unm.(caddy.WriterOpener)
if !ok {
return nil, h.Errf("module %s (%T) is not a WriterOpener", modID, unm)
}
}
cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings)
case "format":
if !h.NextArg() {
return nil, h.ArgErr()
}
moduleName := h.Val()
moduleID := "caddy.logging.encoders." + moduleName
unm, err := caddyfile.UnmarshalModule(h.Dispenser, moduleID)
if err != nil {
return nil, err
}
enc, ok := unm.(zapcore.Encoder)
if !ok {
return nil, h.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm)
}
cl.EncoderRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, h.warnings)
case "level":
if !h.NextArg() {
return nil, h.ArgErr()
}
cl.Level = h.Val()
if h.NextArg() {
return nil, h.ArgErr()
}
case "include":
// This configuration is only allowed in the global options
if !parseAsGlobalOption {
return nil, h.ArgErr()
}
for h.NextArg() {
cl.Include = append(cl.Include, h.Val())
}
case "exclude":
// This configuration is only allowed in the global options
if !parseAsGlobalOption {
return nil, h.ArgErr()
}
for h.NextArg() {
cl.Exclude = append(cl.Exclude, h.Val())
}
default:
return nil, h.Errf("unrecognized subdirective: %s", h.Val())
}
}
var val namedCustomLog
// Skip handling of empty logging configs
if !reflect.DeepEqual(cl, new(caddy.CustomLog)) {
if parseAsGlobalOption {
// Use indicated name for global log options
val.name = globalLogName
val.log = cl
} else {
// Construct a log name for server log streams
logCounter, ok := h.State["logCounter"].(int)
if !ok {
logCounter = 0
}
val.name = fmt.Sprintf("log%d", logCounter)
cl.Include = []string{"http.log.access." + val.name}
val.log = cl
logCounter++
h.State["logCounter"] = logCounter
}
}
configValues = append(configValues, ConfigValue{
Class: "custom_log",
Value: val,
})
}
return configValues, nil
}
// parseSkipLog parses the skip_log directive. Syntax:
//
// skip_log [<matcher>]
func parseSkipLog(h Helper) (caddyhttp.MiddlewareHandler, error) {
for h.Next() {
if h.NextArg() {
return nil, h.ArgErr()
}
}
return caddyhttp.VarsMiddleware{"skip_log": true}, nil
}
+215
View File
@@ -0,0 +1,215 @@
package httpcaddyfile
import (
"testing"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
_ "github.com/caddyserver/caddy/v2/modules/logging"
)
func TestLogDirectiveSyntax(t *testing.T) {
for i, tc := range []struct {
input string
output string
expectError bool
}{
{
input: `:8080 {
log
}
`,
output: `{"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{}}}}}}`,
expectError: false,
},
{
input: `:8080 {
log {
output file foo.log
}
}
`,
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"writer":{"filename":"foo.log","output":"file"},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`,
expectError: false,
},
{
input: `:8080 {
log {
format filter {
wrap console
fields {
request>remote_ip ip_mask {
ipv4 24
ipv6 32
}
}
}
}
}
`,
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"encoder":{"fields":{"request\u003eremote_ip":{"filter":"ip_mask","ipv4_cidr":24,"ipv6_cidr":32}},"format":"filter","wrap":{"format":"console"}},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`,
expectError: false,
},
{
input: `:8080 {
log invalid {
output file foo.log
}
}
`,
expectError: true,
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
out, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil != tc.expectError {
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
continue
}
if string(out) != tc.output {
t.Errorf("Test %d error output mismatch Expected: %s, got %s", i, tc.output, out)
}
}
}
func TestRedirDirectiveSyntax(t *testing.T) {
for i, tc := range []struct {
input string
expectError bool
}{
{
input: `:8080 {
redir :8081
}`,
expectError: false,
},
{
input: `:8080 {
redir * :8081
}`,
expectError: false,
},
{
input: `:8080 {
redir /api/* :8081 300
}`,
expectError: false,
},
{
input: `:8080 {
redir :8081 300
}`,
expectError: false,
},
{
input: `:8080 {
redir /api/* :8081 399
}`,
expectError: false,
},
{
input: `:8080 {
redir :8081 399
}`,
expectError: false,
},
{
input: `:8080 {
redir /old.html /new.html
}`,
expectError: false,
},
{
input: `:8080 {
redir /old.html /new.html temporary
}`,
expectError: false,
},
{
input: `:8080 {
redir https://example.com{uri} permanent
}`,
expectError: false,
},
{
input: `:8080 {
redir /old.html /new.html permanent
}`,
expectError: false,
},
{
input: `:8080 {
redir /old.html /new.html html
}`,
expectError: false,
},
{
// this is now allowed so a Location header
// can be written and consumed by JS
// in the case of XHR requests
input: `:8080 {
redir * :8081 401
}`,
expectError: false,
},
{
input: `:8080 {
redir * :8081 402
}`,
expectError: true,
},
{
input: `:8080 {
redir * :8081 {http.reverse_proxy.status_code}
}`,
expectError: false,
},
{
input: `:8080 {
redir /old.html /new.html htlm
}`,
expectError: true,
},
{
input: `:8080 {
redir * :8081 200
}`,
expectError: true,
},
{
input: `:8080 {
redir * :8081 temp
}`,
expectError: true,
},
{
input: `:8080 {
redir * :8081 perm
}`,
expectError: true,
},
{
input: `:8080 {
redir * :8081 php
}`,
expectError: true,
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
_, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil != tc.expectError {
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
continue
}
}
}
+412 -33
View File
@@ -16,6 +16,10 @@ package httpcaddyfile
import (
"encoding/json"
"net"
"sort"
"strconv"
"strings"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
@@ -23,21 +27,69 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
// defaultDirectiveOrder specifies the order
// directiveOrder specifies the order
// to apply directives in HTTP routes.
// TODO: finish the ability to customize this
var defaultDirectiveOrder = []string{
//
// The root directive goes first in case rewrites or
// redirects depend on existence of files, i.e. the
// file matcher, which must know the root first.
//
// The header directive goes second so that headers
// can be manipulated before doing redirects.
var directiveOrder = []string{
"tracing",
"map",
"vars",
"root",
"skip_log",
"header",
"copy_response_headers", // only in reverse_proxy's handle_response
"request_body",
"redir",
// incoming request manipulation
"method",
"rewrite",
"uri",
"try_files",
"headers",
// middleware handlers; some wrap responses
"basicauth",
"forward_auth",
"request_header",
"encode",
"push",
"templates",
"redir",
"static_response", // TODO: "reply" or "respond"?
// special routing & dispatching directives
"handle",
"handle_path",
"route",
// handlers that typically respond to requests
"abort",
"error",
"copy_response", // only in reverse_proxy's handle_response
"respond",
"metrics",
"reverse_proxy",
"php_fastcgi",
"file_server",
"acme_server",
}
// directiveIsOrdered returns true if dir is
// a known, ordered (sorted) directive.
func directiveIsOrdered(dir string) bool {
for _, d := range directiveOrder {
if d == dir {
return true
}
}
return false
}
// RegisterDirective registers a unique directive dir with an
@@ -53,21 +105,19 @@ func RegisterDirective(dir string, setupFunc UnmarshalFunc) {
// RegisterHandlerDirective is like RegisterDirective, but for
// directives which specifically output only an HTTP handler.
// Directives registered with this function will always have
// an optional matcher token as the first argument.
func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
RegisterDirective(dir, func(h Helper) ([]ConfigValue, error) {
if !h.Next() {
return nil, h.ArgErr()
}
matcherSet, ok, err := h.MatcherToken()
matcherSet, err := h.ExtractMatcherSet()
if err != nil {
return nil, err
}
if ok {
h.Dispenser.Delete() // strip matcher token
}
h.Dispenser.Reset() // pretend this lookahead never happened
val, err := setupFunc(h)
if err != nil {
return nil, err
@@ -77,13 +127,33 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
})
}
// RegisterGlobalOption registers a unique global option opt with
// an associated unmarshaling (setup) function. When the global
// option opt is encountered in a Caddyfile, setupFunc will be
// called to unmarshal its tokens.
func RegisterGlobalOption(opt string, setupFunc UnmarshalGlobalFunc) {
if _, ok := registeredGlobalOptions[opt]; ok {
panic("global option " + opt + " already registered")
}
registeredGlobalOptions[opt] = setupFunc
}
// Helper is a type which helps setup a value from
// Caddyfile tokens.
type Helper struct {
*caddyfile.Dispenser
warnings *[]caddyconfig.Warning
matcherDefs map[string]map[string]json.RawMessage
parentBlock caddyfile.ServerBlock
// State stores intermediate variables during caddyfile adaptation.
State map[string]any
options map[string]any
warnings *[]caddyconfig.Warning
matcherDefs map[string]caddy.ModuleMap
parentBlock caddyfile.ServerBlock
groupCounter counter
}
// Option gets the option keyed by name.
func (h Helper) Option(name string) any {
return h.options[name]
}
// Caddyfiles returns the list of config files from
@@ -106,29 +176,56 @@ func (h Helper) Caddyfiles() []string {
}
// JSON converts val into JSON. Any errors are added to warnings.
func (h Helper) JSON(val interface{}, warnings *[]caddyconfig.Warning) json.RawMessage {
func (h Helper) JSON(val any) json.RawMessage {
return caddyconfig.JSON(val, h.warnings)
}
// MatcherToken assumes the current token is (possibly) a matcher, and
// if so, returns the matcher set along with a true value. If the current
// MatcherToken assumes the next argument token is (possibly) a matcher,
// and if so, returns the matcher set along with a true value. If the next
// token is not a matcher, nil and false is returned. Note that a true
// value may be returned with a nil matcher set if it is a catch-all.
func (h Helper) MatcherToken() (map[string]json.RawMessage, bool, error) {
func (h Helper) MatcherToken() (caddy.ModuleMap, bool, error) {
if !h.NextArg() {
return nil, false, nil
}
return matcherSetFromMatcherToken(h.Dispenser.Token(), h.matcherDefs, h.warnings)
}
// NewRoute returns config values relevant to creating a new HTTP route.
func (h Helper) NewRoute(matcherSet map[string]json.RawMessage,
handler caddyhttp.MiddlewareHandler) []ConfigValue {
mod, err := caddy.GetModule(caddy.GetModuleName(handler))
// ExtractMatcherSet is like MatcherToken, except this is a higher-level
// method that returns the matcher set described by the matcher token,
// or nil if there is none, and deletes the matcher token from the
// dispenser and resets it as if this look-ahead never happened. Useful
// when wrapping a route (one or more handlers) in a user-defined matcher.
func (h Helper) ExtractMatcherSet() (caddy.ModuleMap, error) {
matcherSet, hasMatcher, err := h.MatcherToken()
if err != nil {
// TODO: append to warnings
return nil, err
}
var matcherSetsRaw []map[string]json.RawMessage
if hasMatcher {
// strip matcher token; we don't need to
// use the return value here because a
// new dispenser should have been made
// solely for this directive's tokens,
// with no other uses of same slice
h.Dispenser.Delete()
}
h.Dispenser.Reset() // pretend this lookahead never happened
return matcherSet, nil
}
// NewRoute returns config values relevant to creating a new HTTP route.
func (h Helper) NewRoute(matcherSet caddy.ModuleMap,
handler caddyhttp.MiddlewareHandler) []ConfigValue {
mod, err := caddy.GetModule(caddy.GetModuleID(handler))
if err != nil {
*h.warnings = append(*h.warnings, caddyconfig.Warning{
File: h.File(),
Line: h.Line(),
Message: err.Error(),
})
return nil
}
var matcherSetsRaw []caddy.ModuleMap
if matcherSet != nil {
matcherSetsRaw = append(matcherSetsRaw, matcherSet)
}
@@ -137,22 +234,131 @@ func (h Helper) NewRoute(matcherSet map[string]json.RawMessage,
Class: "route",
Value: caddyhttp.Route{
MatcherSetsRaw: matcherSetsRaw,
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", mod.ID(), h.warnings)},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", mod.ID.Name(), h.warnings)},
},
},
}
}
// GroupRoutes adds the routes (caddyhttp.Route type) in vals to the
// same group, if there is more than one route in vals.
func (h Helper) GroupRoutes(vals []ConfigValue) {
// ensure there's at least two routes; group of one is pointless
var count int
for _, v := range vals {
if _, ok := v.Value.(caddyhttp.Route); ok {
count++
if count > 1 {
break
}
}
}
if count < 2 {
return
}
// now that we know the group will have some effect, do it
groupName := h.groupCounter.nextGroup()
for i := range vals {
if route, ok := vals[i].Value.(caddyhttp.Route); ok {
route.Group = groupName
vals[i].Value = route
}
}
}
// NewBindAddresses returns config values relevant to adding
// listener bind addresses to the config.
func (h Helper) NewBindAddresses(addrs []string) []ConfigValue {
return []ConfigValue{{Class: "bind", Value: addrs}}
}
// NewVarsRoute returns config values relevant to adding a
// "vars" wrapper route to the config.
func (h Helper) NewVarsRoute(route caddyhttp.Route) []ConfigValue {
return []ConfigValue{{Class: "var", Value: route}}
// WithDispenser returns a new instance based on d. All others Helper
// fields are copied, so typically maps are shared with this new instance.
func (h Helper) WithDispenser(d *caddyfile.Dispenser) Helper {
h.Dispenser = d
return h
}
// ParseSegmentAsSubroute parses the segment such that its subdirectives
// are themselves treated as directives, from which a subroute is built
// and returned.
func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
allResults, err := parseSegmentAsConfig(h)
if err != nil {
return nil, err
}
return buildSubroute(allResults, h.groupCounter)
}
// parseSegmentAsConfig parses the segment such that its subdirectives
// are themselves treated as directives, including named matcher definitions,
// and the raw Config structs are returned.
func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
var allResults []ConfigValue
for h.Next() {
// don't allow non-matcher args on the first line
if h.NextArg() {
return nil, h.ArgErr()
}
// slice the linear list of tokens into top-level segments
var segments []caddyfile.Segment
for nesting := h.Nesting(); h.NextBlock(nesting); {
segments = append(segments, h.NextSegment())
}
// copy existing matcher definitions so we can augment
// new ones that are defined only in this scope
matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs))
for key, val := range h.matcherDefs {
matcherDefs[key] = val
}
// find and extract any embedded matcher definitions in this scope
for i := 0; i < len(segments); i++ {
seg := segments[i]
if strings.HasPrefix(seg.Directive(), matcherPrefix) {
// parse, then add the matcher to matcherDefs
err := parseMatcherDefinitions(caddyfile.NewDispenser(seg), matcherDefs)
if err != nil {
return nil, err
}
// remove the matcher segment (consumed), then step back the loop
segments = append(segments[:i], segments[i+1:]...)
i--
}
}
// with matchers ready to go, evaluate each directive's segment
for _, seg := range segments {
dir := seg.Directive()
dirFunc, ok := registeredDirectives[dir]
if !ok {
return nil, h.Errf("unrecognized directive: %s - are you sure your Caddyfile structure (nesting and braces) is correct?", dir)
}
subHelper := h
subHelper.Dispenser = caddyfile.NewDispenser(seg)
subHelper.matcherDefs = matcherDefs
results, err := dirFunc(subHelper)
if err != nil {
return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
}
dir = normalizeDirectiveName(dir)
for _, result := range results {
result.directive = dir
allResults = append(allResults, result)
}
}
}
return allResults, nil
}
// ConfigValue represents a value to be added to the final
@@ -170,17 +376,182 @@ type ConfigValue struct {
// The value to be used when building the config.
// Generally its type is associated with the
// name of the Class.
Value interface{}
Value any
directive string
}
// serverBlock pairs a Caddyfile server block
// with a "pile" of config values, keyed by class
// name.
func sortRoutes(routes []ConfigValue) {
dirPositions := make(map[string]int)
for i, dir := range directiveOrder {
dirPositions[dir] = i
}
sort.SliceStable(routes, func(i, j int) bool {
// if the directives are different, just use the established directive order
iDir, jDir := routes[i].directive, routes[j].directive
if iDir != jDir {
return dirPositions[iDir] < dirPositions[jDir]
}
// directives are the same; sub-sort by path matcher length if there's
// only one matcher set and one path (this is a very common case and
// usually -- but not always -- helpful/expected, oh well; user can
// always take manual control of order using handler or route blocks)
iRoute, ok := routes[i].Value.(caddyhttp.Route)
if !ok {
return false
}
jRoute, ok := routes[j].Value.(caddyhttp.Route)
if !ok {
return false
}
// decode the path matchers if there is just one matcher set
var iPM, jPM caddyhttp.MatchPath
if len(iRoute.MatcherSetsRaw) == 1 {
_ = json.Unmarshal(iRoute.MatcherSetsRaw[0]["path"], &iPM)
}
if len(jRoute.MatcherSetsRaw) == 1 {
_ = json.Unmarshal(jRoute.MatcherSetsRaw[0]["path"], &jPM)
}
// if there is only one path in the path matcher, sort by longer path
// (more specific) first; missing path matchers or multi-matchers are
// treated as zero-length paths
var iPathLen, jPathLen int
if len(iPM) == 1 {
iPathLen = len(iPM[0])
}
if len(jPM) == 1 {
jPathLen = len(jPM[0])
}
// some directives involve setting values which can overwrite
// each other, so it makes most sense to reverse the order so
// that the lease specific matcher is first; everything else
// has most-specific matcher first
if iDir == "vars" {
// we can only confidently compare path lengths if both
// directives have a single path to match (issue #5037)
if iPathLen > 0 && jPathLen > 0 {
// sort least-specific (shortest) path first
return iPathLen < jPathLen
}
// if both directives don't have a single path to compare,
// sort whichever one has no matcher first; if both have
// no matcher, sort equally (stable sort preserves order)
return len(iRoute.MatcherSetsRaw) == 0 && len(jRoute.MatcherSetsRaw) > 0
} else {
// we can only confidently compare path lengths if both
// directives have a single path to match (issue #5037)
if iPathLen > 0 && jPathLen > 0 {
// sort most-specific (longest) path first
return iPathLen > jPathLen
}
// if both directives don't have a single path to compare,
// sort whichever one has a matcher first; if both have
// a matcher, sort equally (stable sort preserves order)
return len(iRoute.MatcherSetsRaw) > 0 && len(jRoute.MatcherSetsRaw) == 0
}
})
}
// serverBlock pairs a Caddyfile server block with
// a "pile" of config values, keyed by class name,
// as well as its parsed keys for convenience.
type serverBlock struct {
block caddyfile.ServerBlock
pile map[string][]ConfigValue // config values obtained from directives
keys []Address
}
// hostsFromKeys returns a list of all the non-empty hostnames found in
// the keys of the server block sb. If logger mode is false, a key with
// an empty hostname portion will return an empty slice, since that
// server block is interpreted to effectively match all hosts. An empty
// string is never added to the slice.
//
// If loggerMode is true, then the non-standard ports of keys will be
// joined to the hostnames. This is to effectively match the Host
// header of requests that come in for that key.
//
// The resulting slice is not sorted but will never have duplicates.
func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
// ensure each entry in our list is unique
hostMap := make(map[string]struct{})
for _, addr := range sb.keys {
if addr.Host == "" {
if !loggerMode {
// server block contains a key like ":443", i.e. the host portion
// is empty / catch-all, which means to match all hosts
return []string{}
}
// never append an empty string
continue
}
if loggerMode &&
addr.Port != "" &&
addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPPort) &&
addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPSPort) {
hostMap[net.JoinHostPort(addr.Host, addr.Port)] = struct{}{}
} else {
hostMap[addr.Host] = struct{}{}
}
}
// convert map to slice
sblockHosts := make([]string, 0, len(hostMap))
for host := range hostMap {
sblockHosts = append(sblockHosts, host)
}
return sblockHosts
}
func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
// ensure each entry in our list is unique
hostMap := make(map[string]struct{})
for _, addr := range sb.keys {
if addr.Host == "" {
continue
}
if addr.Scheme != "http" && addr.Port != httpPort {
hostMap[addr.Host] = struct{}{}
}
}
// convert map to slice
sblockHosts := make([]string, 0, len(hostMap))
for host := range hostMap {
sblockHosts = append(sblockHosts, host)
}
return sblockHosts
}
// hasHostCatchAllKey returns true if sb has a key that
// omits a host portion, i.e. it "catches all" hosts.
func (sb serverBlock) hasHostCatchAllKey() bool {
for _, addr := range sb.keys {
if addr.Host == "" {
return true
}
}
return false
}
// isAllHTTP returns true if all sb keys explicitly specify
// the http:// scheme
func (sb serverBlock) isAllHTTP() bool {
for _, addr := range sb.keys {
if addr.Scheme != "http" {
return false
}
}
return true
}
type (
@@ -199,6 +570,14 @@ type (
// for you. These are passed to a call to
// RegisterHandlerDirective.
UnmarshalHandlerFunc func(h Helper) (caddyhttp.MiddlewareHandler, error)
// UnmarshalGlobalFunc is a function which can unmarshal Caddyfile
// tokens from a global option. It is passed the tokens to parse and
// existing value from the previous instance of this global option
// (if any). It returns the value to associate with this global option.
UnmarshalGlobalFunc func(d *caddyfile.Dispenser, existingVal any) (any, error)
)
var registeredDirectives = make(map[string]UnmarshalFunc)
var registeredGlobalOptions = make(map[string]UnmarshalGlobalFunc)
@@ -0,0 +1,94 @@
package httpcaddyfile
import (
"reflect"
"sort"
"testing"
)
func TestHostsFromKeys(t *testing.T) {
for i, tc := range []struct {
keys []Address
expectNormalMode []string
expectLoggerMode []string
}{
{
[]Address{
{Original: "foo", Host: "foo"},
},
[]string{"foo"},
[]string{"foo"},
},
{
[]Address{
{Original: "foo", Host: "foo"},
{Original: "bar", Host: "bar"},
},
[]string{"bar", "foo"},
[]string{"bar", "foo"},
},
{
[]Address{
{Original: ":2015", Port: "2015"},
},
[]string{}, []string{},
},
{
[]Address{
{Original: ":443", Port: "443"},
},
[]string{}, []string{},
},
{
[]Address{
{Original: "foo", Host: "foo"},
{Original: ":2015", Port: "2015"},
},
[]string{}, []string{"foo"},
},
{
[]Address{
{Original: "example.com:2015", Host: "example.com", Port: "2015"},
},
[]string{"example.com"},
[]string{"example.com:2015"},
},
{
[]Address{
{Original: "example.com:80", Host: "example.com", Port: "80"},
},
[]string{"example.com"},
[]string{"example.com"},
},
{
[]Address{
{Original: "https://:2015/foo", Scheme: "https", Port: "2015", Path: "/foo"},
},
[]string{},
[]string{},
},
{
[]Address{
{Original: "https://example.com:2015/foo", Scheme: "https", Host: "example.com", Port: "2015", Path: "/foo"},
},
[]string{"example.com"},
[]string{"example.com:2015"},
},
} {
sb := serverBlock{keys: tc.keys}
// test in normal mode
actual := sb.hostsFromKeys(false)
sort.Strings(actual)
if !reflect.DeepEqual(tc.expectNormalMode, actual) {
t.Errorf("Test %d (loggerMode=false): Expected: %v Actual: %v", i, tc.expectNormalMode, actual)
}
// test in logger mode
actual = sb.hostsFromKeys(true)
sort.Strings(actual)
if !reflect.DeepEqual(tc.expectLoggerMode, actual) {
t.Errorf("Test %d (loggerMode=true): Expected: %v Actual: %v", i, tc.expectLoggerMode, actual)
}
}
}
-56
View File
@@ -1,56 +0,0 @@
// 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 httpcaddyfile
import (
"encoding/json"
"fmt"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func (st *ServerType) parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]map[string]json.RawMessage, error) {
matchers := make(map[string]map[string]json.RawMessage)
for d.Next() {
definitionName := d.Val()
for nesting := d.Nesting(); d.NextBlock(nesting); {
matcherName := d.Val()
mod, err := caddy.GetModule("http.matchers." + matcherName)
if err != nil {
return nil, fmt.Errorf("getting matcher module '%s': %v", matcherName, err)
}
unm, ok := mod.New().(caddyfile.Unmarshaler)
if !ok {
return nil, fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
}
err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
if err != nil {
return nil, err
}
rm, ok := unm.(caddyhttp.RequestMatcher)
if !ok {
return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
}
if _, ok := matchers[definitionName]; !ok {
matchers[definitionName] = make(map[string]json.RawMessage)
}
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
}
}
return matchers, nil
}
File diff suppressed because it is too large Load Diff
+211
View File
@@ -0,0 +1,211 @@
package httpcaddyfile
import (
"testing"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)
func TestMatcherSyntax(t *testing.T) {
for i, tc := range []struct {
input string
expectError bool
}{
{
input: `http://localhost
@debug {
query showdebug=1
}
`,
expectError: false,
},
{
input: `http://localhost
@debug {
query bad format
}
`,
expectError: true,
},
{
input: `http://localhost
@debug {
not {
path /somepath*
}
}
`,
expectError: false,
},
{
input: `http://localhost
@debug {
not path /somepath*
}
`,
expectError: false,
},
{
input: `http://localhost
@debug not path /somepath*
`,
expectError: false,
},
{
input: `@matcher {
path /matcher-not-allowed/outside-of-site-block/*
}
http://localhost
`,
expectError: true,
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
_, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil != tc.expectError {
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
continue
}
}
}
func TestSpecificity(t *testing.T) {
for i, tc := range []struct {
input string
expect int
}{
{"", 0},
{"*", 0},
{"*.*", 1},
{"{placeholder}", 0},
{"/{placeholder}", 1},
{"foo", 3},
{"example.com", 11},
{"a.example.com", 13},
{"*.example.com", 12},
{"/foo", 4},
{"/foo*", 4},
{"{placeholder}.example.com", 12},
{"{placeholder.example.com", 24},
{"}.", 2},
{"}{", 2},
{"{}", 0},
{"{{{}}", 1},
} {
actual := specificity(tc.input)
if actual != tc.expect {
t.Errorf("Test %d (%s): Expected %d but got %d", i, tc.input, tc.expect, actual)
}
}
}
func TestGlobalOptions(t *testing.T) {
for i, tc := range []struct {
input string
expectError bool
}{
{
input: `
{
email test@example.com
}
:80
`,
expectError: false,
},
{
input: `
{
admin off
}
:80
`,
expectError: false,
},
{
input: `
{
admin 127.0.0.1:2020
}
:80
`,
expectError: false,
},
{
input: `
{
admin {
disabled false
}
}
:80
`,
expectError: true,
},
{
input: `
{
admin {
enforce_origin
origins 192.168.1.1:2020 127.0.0.1:2020
}
}
:80
`,
expectError: false,
},
{
input: `
{
admin 127.0.0.1:2020 {
enforce_origin
origins 192.168.1.1:2020 127.0.0.1:2020
}
}
:80
`,
expectError: false,
},
{
input: `
{
admin 192.168.1.1:2020 127.0.0.1:2020 {
enforce_origin
origins 192.168.1.1:2020 127.0.0.1:2020
}
}
:80
`,
expectError: true,
},
{
input: `
{
admin off {
enforce_origin
origins 192.168.1.1:2020 127.0.0.1:2020
}
}
:80
`,
expectError: true,
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
_, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil != tc.expectError {
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
continue
}
}
}
+412 -20
View File
@@ -17,10 +17,48 @@ package httpcaddyfile
import (
"strconv"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddytls"
"github.com/caddyserver/certmagic"
"github.com/mholt/acmez/acme"
)
func parseHTTPPort(d *caddyfile.Dispenser) (int, error) {
func init() {
RegisterGlobalOption("debug", parseOptTrue)
RegisterGlobalOption("http_port", parseOptHTTPPort)
RegisterGlobalOption("https_port", parseOptHTTPSPort)
RegisterGlobalOption("default_bind", parseOptStringList)
RegisterGlobalOption("grace_period", parseOptDuration)
RegisterGlobalOption("shutdown_delay", parseOptDuration)
RegisterGlobalOption("default_sni", parseOptSingleString)
RegisterGlobalOption("order", parseOptOrder)
RegisterGlobalOption("storage", parseOptStorage)
RegisterGlobalOption("storage_clean_interval", parseOptDuration)
RegisterGlobalOption("renew_interval", parseOptDuration)
RegisterGlobalOption("ocsp_interval", parseOptDuration)
RegisterGlobalOption("acme_ca", parseOptSingleString)
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
RegisterGlobalOption("acme_dns", parseOptACMEDNS)
RegisterGlobalOption("acme_eab", parseOptACMEEAB)
RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
RegisterGlobalOption("skip_install_trust", parseOptTrue)
RegisterGlobalOption("email", parseOptSingleString)
RegisterGlobalOption("admin", parseOptAdmin)
RegisterGlobalOption("on_demand_tls", parseOptOnDemand)
RegisterGlobalOption("local_certs", parseOptTrue)
RegisterGlobalOption("key_type", parseOptSingleString)
RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
RegisterGlobalOption("servers", parseServerOptions)
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
RegisterGlobalOption("log", parseLogOptions)
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
}
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
func parseOptHTTPPort(d *caddyfile.Dispenser, _ any) (any, error) {
var httpPort int
for d.Next() {
var httpPortStr string
@@ -36,7 +74,7 @@ func parseHTTPPort(d *caddyfile.Dispenser) (int, error) {
return httpPort, nil
}
func parseHTTPSPort(d *caddyfile.Dispenser) (int, error) {
func parseOptHTTPSPort(d *caddyfile.Dispenser, _ any) (any, error) {
var httpsPort int
for d.Next() {
var httpsPortStr string
@@ -52,29 +90,383 @@ func parseHTTPSPort(d *caddyfile.Dispenser) (int, error) {
return httpsPort, nil
}
func parseExperimentalHTTP3(d *caddyfile.Dispenser) (bool, error) {
return true, nil
}
func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
newOrder := directiveOrder
func parseHandlerOrder(d *caddyfile.Dispenser) ([]string, error) {
if !d.Next() {
return nil, d.ArgErr()
}
order := d.RemainingArgs()
if len(order) == 1 && order[0] == "appearance" {
return []string{"appearance"}, nil
}
if len(order) > 0 && d.NextBlock(0) {
return nil, d.Err("cannot open block if there are arguments")
}
for d.NextBlock(0) {
order = append(order, d.Val())
for d.Next() {
// get directive name
if !d.Next() {
return nil, d.ArgErr()
}
dirName := d.Val()
if _, ok := registeredDirectives[dirName]; !ok {
return nil, d.Errf("%s is not a registered directive", dirName)
}
// get positional token
if !d.Next() {
return nil, d.ArgErr()
}
pos := d.Val()
// if directive exists, first remove it
for i, d := range newOrder {
if d == dirName {
newOrder = append(newOrder[:i], newOrder[i+1:]...)
break
}
}
// act on the positional
switch pos {
case "first":
newOrder = append([]string{dirName}, newOrder...)
if d.NextArg() {
return nil, d.ArgErr()
}
directiveOrder = newOrder
return newOrder, nil
case "last":
newOrder = append(newOrder, dirName)
if d.NextArg() {
return nil, d.ArgErr()
}
directiveOrder = newOrder
return newOrder, nil
case "before":
case "after":
default:
return nil, d.Errf("unknown positional '%s'", pos)
}
// get name of other directive
if !d.NextArg() {
return nil, d.ArgErr()
}
otherDir := d.Val()
if d.NextArg() {
return nil, d.ArgErr()
}
// insert directive into proper position
for i, d := range newOrder {
if d == otherDir {
if pos == "before" {
newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...)
} else if pos == "after" {
newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...)
}
break
}
}
}
if len(order) == 0 {
directiveOrder = newOrder
return newOrder, nil
}
func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
if !d.Next() { // consume option name
return nil, d.ArgErr()
}
return order, nil
if !d.Next() { // get storage module name
return nil, d.ArgErr()
}
modID := "caddy.storage." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
storage, ok := unm.(caddy.StorageConverter)
if !ok {
return nil, d.Errf("module %s is not a caddy.StorageConverter", modID)
}
return storage, nil
}
func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
if !d.Next() { // consume option name
return nil, d.ArgErr()
}
if !d.Next() { // get duration value
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, err
}
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.ACMEDNSProvider)
if !ok {
return nil, d.Errf("module %s (%T) is not a certmagic.ACMEDNSProvider", modID, unm)
}
return prov, nil
}
func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
eab := new(acme.EAB)
for d.Next() {
if d.NextArg() {
return nil, d.ArgErr()
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "key_id":
if !d.NextArg() {
return nil, d.ArgErr()
}
eab.KeyID = d.Val()
case "mac_key":
if !d.NextArg() {
return nil, d.ArgErr()
}
eab.MACKey = d.Val()
default:
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
}
}
}
return eab, nil
}
func parseOptCertIssuer(d *caddyfile.Dispenser, existing any) (any, error) {
var issuers []certmagic.Issuer
if existing != nil {
issuers = existing.([]certmagic.Issuer)
}
for d.Next() { // consume option name
if !d.Next() { // get issuer module name
return nil, d.ArgErr()
}
modID := "tls.issuance." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
iss, ok := unm.(certmagic.Issuer)
if !ok {
return nil, d.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm)
}
issuers = append(issuers, iss)
}
return issuers, nil
}
func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume parameter name
if !d.Next() {
return "", d.ArgErr()
}
val := d.Val()
if d.Next() {
return "", d.ArgErr()
}
return val, nil
}
func parseOptStringList(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume parameter name
val := d.RemainingArgs()
if len(val) == 0 {
return "", d.ArgErr()
}
return val, nil
}
func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
adminCfg := new(caddy.AdminConfig)
for d.Next() {
if d.NextArg() {
listenAddress := d.Val()
if listenAddress == "off" {
adminCfg.Disabled = true
if d.Next() { // Do not accept any remaining options including block
return nil, d.Err("No more option is allowed after turning off admin config")
}
} else {
adminCfg.Listen = listenAddress
if d.NextArg() { // At most 1 arg is allowed
return nil, d.ArgErr()
}
}
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "enforce_origin":
adminCfg.EnforceOrigin = true
case "origins":
adminCfg.Origins = d.RemainingArgs()
default:
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
}
}
}
if adminCfg.Listen == "" && !adminCfg.Disabled {
adminCfg.Listen = caddy.DefaultAdminListen
}
return adminCfg, nil
}
func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
var ond *caddytls.OnDemandConfig
for d.Next() {
if d.NextArg() {
return nil, d.ArgErr()
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "ask":
if !d.NextArg() {
return nil, d.ArgErr()
}
if ond == nil {
ond = new(caddytls.OnDemandConfig)
}
ond.Ask = d.Val()
case "interval":
if !d.NextArg() {
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":
if !d.NextArg() {
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:
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
}
}
}
if ond == nil {
return nil, d.Err("expected at least one config parameter for on_demand_tls")
}
return ond, nil
}
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume parameter name
if !d.Next() {
return "", d.ArgErr()
}
val := d.Val()
if d.Next() {
return "", d.ArgErr()
}
if val != "off" && val != "disable_redirects" && val != "disable_certs" && val != "ignore_loaded_certs" {
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', or 'ignore_loaded_certs'")
}
return val, nil
}
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
return unmarshalCaddyfileServerOptions(d)
}
func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
var val string
if !d.AllArgs(&val) {
return nil, d.ArgErr()
}
if val != "off" {
return nil, d.Errf("invalid argument '%s'", val)
}
return certmagic.OCSPConfig{
DisableStapling: val == "off",
}, nil
}
// parseLogOptions parses the global log option. Syntax:
//
// log [name] {
// output <writer_module> ...
// format <encoder_module> ...
// level <level>
// include <namespaces...>
// exclude <namespaces...>
// }
//
// When the name argument is unspecified, this directive modifies the default
// logger.
func parseLogOptions(d *caddyfile.Dispenser, existingVal any) (any, error) {
currentNames := make(map[string]struct{})
if existingVal != nil {
innerVals, ok := existingVal.([]ConfigValue)
if !ok {
return nil, d.Errf("existing log values of unexpected type: %T", existingVal)
}
for _, rawVal := range innerVals {
val, ok := rawVal.Value.(namedCustomLog)
if !ok {
return nil, d.Errf("existing log value of unexpected type: %T", existingVal)
}
currentNames[val.name] = struct{}{}
}
}
var warnings []caddyconfig.Warning
// Call out the same parser that handles server-specific log configuration.
configValues, err := parseLogHelper(
Helper{
Dispenser: d,
warnings: &warnings,
},
currentNames,
)
if err != nil {
return nil, err
}
if len(warnings) > 0 {
return nil, d.Errf("warnings found in parsing global log options: %+v", warnings)
}
return configValues, nil
}
func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next()
return caddytls.ParseCaddyfilePreferredChainsOptions(d)
}
+64
View File
@@ -0,0 +1,64 @@
package httpcaddyfile
import (
"testing"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
_ "github.com/caddyserver/caddy/v2/modules/logging"
)
func TestGlobalLogOptionSyntax(t *testing.T) {
for i, tc := range []struct {
input string
output string
expectError bool
}{
// NOTE: Additional test cases of successful Caddyfile parsing
// are present in: caddytest/integration/caddyfile_adapt/
{
input: `{
log default
}
`,
output: `{}`,
expectError: false,
},
{
input: `{
log example {
output file foo.log
}
log example {
format json
}
}
`,
expectError: true,
},
{
input: `{
log example /foo {
output file foo.log
}
}
`,
expectError: true,
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
out, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil != tc.expectError {
t.Errorf("Test %d error expectation failed Expected: %v, got %v", i, tc.expectError, err)
continue
}
if string(out) != tc.output {
t.Errorf("Test %d error output mismatch Expected: %s, got %s", i, tc.output, out)
}
}
}
+218
View File
@@ -0,0 +1,218 @@
// 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 httpcaddyfile
import (
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddypki"
)
func init() {
RegisterGlobalOption("pki", parsePKIApp)
}
// parsePKIApp parses the global log option. Syntax:
//
// pki {
// ca [<id>] {
// name <name>
// root_cn <name>
// intermediate_cn <name>
// root {
// cert <path>
// key <path>
// format <format>
// }
// intermediate {
// cert <path>
// key <path>
// format <format>
// }
// }
// }
//
// When the CA ID is unspecified, 'local' is assumed.
func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
pki := &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}
for d.Next() {
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "ca":
pkiCa := new(caddypki.CA)
if d.NextArg() {
pkiCa.ID = d.Val()
if d.NextArg() {
return nil, d.ArgErr()
}
}
if pkiCa.ID == "" {
pkiCa.ID = caddypki.DefaultCAID
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "name":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Name = d.Val()
case "root_cn":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.RootCommonName = d.Val()
case "intermediate_cn":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.IntermediateCommonName = d.Val()
case "root":
if pkiCa.Root == nil {
pkiCa.Root = new(caddypki.KeyPair)
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "cert":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Root.Certificate = d.Val()
case "key":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Root.PrivateKey = d.Val()
case "format":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Root.Format = d.Val()
default:
return nil, d.Errf("unrecognized pki ca root option '%s'", d.Val())
}
}
case "intermediate":
if pkiCa.Intermediate == nil {
pkiCa.Intermediate = new(caddypki.KeyPair)
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "cert":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Intermediate.Certificate = d.Val()
case "key":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Intermediate.PrivateKey = d.Val()
case "format":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Intermediate.Format = d.Val()
default:
return nil, d.Errf("unrecognized pki ca intermediate option '%s'", d.Val())
}
}
default:
return nil, d.Errf("unrecognized pki ca option '%s'", d.Val())
}
}
pki.CAs[pkiCa.ID] = pkiCa
default:
return nil, d.Errf("unrecognized pki option '%s'", d.Val())
}
}
}
return pki, nil
}
func (st ServerType) buildPKIApp(
pairings []sbAddrAssociation,
options map[string]any,
warnings []caddyconfig.Warning,
) (*caddypki.PKI, []caddyconfig.Warning, error) {
skipInstallTrust := false
if _, ok := options["skip_install_trust"]; ok {
skipInstallTrust = true
}
falseBool := false
// Load the PKI app configured via global options
var pkiApp *caddypki.PKI
unwrappedPki, ok := options["pki"].(*caddypki.PKI)
if ok {
pkiApp = unwrappedPki
} else {
pkiApp = &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}
}
for _, ca := range pkiApp.CAs {
if skipInstallTrust {
ca.InstallTrust = &falseBool
}
pkiApp.CAs[ca.ID] = ca
}
// Add in the CAs configured via directives
for _, p := range pairings {
for _, sblock := range p.serverBlocks {
// find all the CAs that were defined and add them to the app config
// i.e. from any "acme_server" directives
for _, caCfgValue := range sblock.pile["pki.ca"] {
ca := caCfgValue.Value.(*caddypki.CA)
if skipInstallTrust {
ca.InstallTrust = &falseBool
}
// the CA might already exist from global options, so
// don't overwrite it in that case
if _, ok := pkiApp.CAs[ca.ID]; !ok {
pkiApp.CAs[ca.ID] = ca
}
}
}
}
// if there was no CAs defined in any of the servers,
// and we were requested to not install trust, then
// add one for the default/local CA to do so
if len(pkiApp.CAs) == 0 && skipInstallTrust {
ca := new(caddypki.CA)
ca.ID = caddypki.DefaultCAID
ca.InstallTrust = &falseBool
pkiApp.CAs[ca.ID] = ca
}
return pkiApp, warnings, nil
}
+282
View File
@@ -0,0 +1,282 @@
// 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 httpcaddyfile
import (
"encoding/json"
"fmt"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/dustin/go-humanize"
)
// serverOptions collects server config overrides parsed from Caddyfile global options
type serverOptions struct {
// If set, will only apply these options to servers that contain a
// listener address that matches exactly. If empty, will apply to all
// servers that were not already matched by another serverOptions.
ListenerAddress string
// These will all map 1:1 to the caddyhttp.Server struct
ListenerWrappersRaw []json.RawMessage
ReadTimeout caddy.Duration
ReadHeaderTimeout caddy.Duration
WriteTimeout caddy.Duration
IdleTimeout caddy.Duration
KeepAliveInterval caddy.Duration
MaxHeaderBytes int
Protocols []string
StrictSNIHost *bool
ShouldLogCredentials bool
Metrics *caddyhttp.Metrics
}
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
serverOpts := serverOptions{}
for d.Next() {
if d.NextArg() {
serverOpts.ListenerAddress = d.Val()
if d.NextArg() {
return nil, d.ArgErr()
}
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "listener_wrappers":
for nesting := d.Nesting(); d.NextBlock(nesting); {
modID := "caddy.listeners." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
listenerWrapper, ok := unm.(caddy.ListenerWrapper)
if !ok {
return nil, fmt.Errorf("module %s (%T) is not a listener wrapper", modID, unm)
}
jsonListenerWrapper := caddyconfig.JSONModuleObject(
listenerWrapper,
"wrapper",
listenerWrapper.(caddy.Module).CaddyModule().ID.Name(),
nil,
)
serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper)
}
case "timeouts":
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "read_body":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing read_body timeout duration: %v", err)
}
serverOpts.ReadTimeout = caddy.Duration(dur)
case "read_header":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing read_header timeout duration: %v", err)
}
serverOpts.ReadHeaderTimeout = caddy.Duration(dur)
case "write":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing write timeout duration: %v", err)
}
serverOpts.WriteTimeout = caddy.Duration(dur)
case "idle":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing idle timeout duration: %v", err)
}
serverOpts.IdleTimeout = caddy.Duration(dur)
default:
return nil, d.Errf("unrecognized timeouts option '%s'", d.Val())
}
}
case "keepalive_interval":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing keepalive interval duration: %v", err)
}
serverOpts.KeepAliveInterval = caddy.Duration(dur)
case "max_header_size":
var sizeStr string
if !d.AllArgs(&sizeStr) {
return nil, d.ArgErr()
}
size, err := humanize.ParseBytes(sizeStr)
if err != nil {
return nil, d.Errf("parsing max_header_size: %v", err)
}
serverOpts.MaxHeaderBytes = int(size)
case "log_credentials":
if d.NextArg() {
return nil, d.ArgErr()
}
serverOpts.ShouldLogCredentials = true
case "protocols":
protos := d.RemainingArgs()
for _, proto := range protos {
if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" {
return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto)
}
if sliceContains(serverOpts.Protocols, proto) {
return nil, d.Errf("protocol %s specified more than once", proto)
}
serverOpts.Protocols = append(serverOpts.Protocols, proto)
}
if nesting := d.Nesting(); d.NextBlock(nesting) {
return nil, d.ArgErr()
}
case "strict_sni_host":
if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" {
return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val())
}
boolVal := true
if d.Val() == "insecure_off" {
boolVal = false
}
serverOpts.StrictSNIHost = &boolVal
case "metrics":
if d.NextArg() {
return nil, d.ArgErr()
}
if nesting := d.Nesting(); d.NextBlock(nesting) {
return nil, d.ArgErr()
}
serverOpts.Metrics = new(caddyhttp.Metrics)
// TODO: DEPRECATED. (August 2022)
case "protocol":
caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol sub-option will be removed soon")
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "allow_h2c":
caddy.Log().Named("caddyfile").Warn("DEPRECATED: allow_h2c will be removed soon; use protocols option instead")
if d.NextArg() {
return nil, d.ArgErr()
}
if sliceContains(serverOpts.Protocols, "h2c") {
return nil, d.Errf("protocol h2c already specified")
}
serverOpts.Protocols = append(serverOpts.Protocols, "h2c")
case "strict_sni_host":
caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol > strict_sni_host in this position will be removed soon; move up to the servers block instead")
if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" {
return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val())
}
boolVal := true
if d.Val() == "insecure_off" {
boolVal = false
}
serverOpts.StrictSNIHost = &boolVal
default:
return nil, d.Errf("unrecognized protocol option '%s'", d.Val())
}
}
default:
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
}
}
}
return serverOpts, nil
}
// applyServerOptions sets the server options on the appropriate servers
func applyServerOptions(
servers map[string]*caddyhttp.Server,
options map[string]any,
warnings *[]caddyconfig.Warning,
) error {
serverOpts, ok := options["servers"].([]serverOptions)
if !ok {
return nil
}
for _, server := range servers {
// find the options that apply to this server
opts := func() *serverOptions {
for _, entry := range serverOpts {
if entry.ListenerAddress == "" {
return &entry
}
for _, listener := range server.Listen {
if entry.ListenerAddress == listener {
return &entry
}
}
}
return nil
}()
// if none apply, then move to the next server
if opts == nil {
continue
}
// set all the options
server.ListenerWrappersRaw = opts.ListenerWrappersRaw
server.ReadTimeout = opts.ReadTimeout
server.ReadHeaderTimeout = opts.ReadHeaderTimeout
server.WriteTimeout = opts.WriteTimeout
server.IdleTimeout = opts.IdleTimeout
server.KeepAliveInterval = opts.KeepAliveInterval
server.MaxHeaderBytes = opts.MaxHeaderBytes
server.Protocols = opts.Protocols
server.StrictSNIHost = opts.StrictSNIHost
server.Metrics = opts.Metrics
if opts.ShouldLogCredentials {
if server.Logs == nil {
server.Logs = &caddyhttp.ServerLogConfig{}
}
server.Logs.ShouldLogCredentials = opts.ShouldLogCredentials
}
}
return nil
}
+671
View File
@@ -0,0 +1,671 @@
// 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 httpcaddyfile
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls"
"github.com/caddyserver/certmagic"
"github.com/mholt/acmez/acme"
)
func (st ServerType) buildTLSApp(
pairings []sbAddrAssociation,
options map[string]any,
warnings []caddyconfig.Warning,
) (*caddytls.TLS, []caddyconfig.Warning, error) {
tlsApp := &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
var certLoaders []caddytls.CertificateLoader
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
if hp, ok := options["http_port"].(int); ok {
httpPort = strconv.Itoa(hp)
}
autoHTTPS := "on"
if ah, ok := options["auto_https"].(string); ok {
autoHTTPS = ah
}
// find all hosts that share a server block with a hostless
// key, so that they don't get forgotten/omitted by auto-HTTPS
// (since they won't appear in route matchers)
httpsHostsSharedWithHostlessKey := make(map[string]struct{})
if autoHTTPS != "off" {
for _, pair := range pairings {
for _, sb := range pair.serverBlocks {
for _, addr := range sb.keys {
if addr.Host == "" {
// this server block has a hostless key, now
// go through and add all the hosts to the set
for _, otherAddr := range sb.keys {
if otherAddr.Original == addr.Original {
continue
}
if otherAddr.Host != "" && otherAddr.Scheme != "http" && otherAddr.Port != httpPort {
httpsHostsSharedWithHostlessKey[otherAddr.Host] = struct{}{}
}
}
break
}
}
}
}
}
// a catch-all automation policy is used as a "default" for all subjects that
// don't have custom configuration explicitly associated with them; this
// is only to add if the global settings or defaults are non-empty
catchAllAP, err := newBaseAutomationPolicy(options, warnings, false)
if err != nil {
return nil, warnings, err
}
if catchAllAP != nil {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
}
for _, p := range pairings {
// avoid setting up TLS automation policies for a server that is HTTP-only
if !listenersUseAnyPortOtherThan(p.addresses, httpPort) {
continue
}
for _, sblock := range p.serverBlocks {
// check the scheme of all the site addresses,
// skip building AP if they all had http://
if sblock.isAllHTTP() {
continue
}
// get values that populate an automation policy for this block
ap, err := newBaseAutomationPolicy(options, warnings, true)
if err != nil {
return nil, warnings, err
}
sblockHosts := sblock.hostsFromKeys(false)
if len(sblockHosts) == 0 && catchAllAP != nil {
ap = catchAllAP
}
// on-demand tls
if _, ok := sblock.pile["tls.on_demand"]; ok {
ap.OnDemand = true
}
if keyTypeVals, ok := sblock.pile["tls.key_type"]; ok {
ap.KeyType = keyTypeVals[0].Value.(string)
}
// certificate issuers
if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
var issuers []certmagic.Issuer
for _, issuerVal := range issuerVals {
issuers = append(issuers, issuerVal.Value.(certmagic.Issuer))
}
if ap == catchAllAP && !reflect.DeepEqual(ap.Issuers, issuers) {
// this more correctly implements an error check that was removed
// below; try it with this config:
//
// :443 {
// bind 127.0.0.1
// }
//
// :443 {
// bind ::1
// tls {
// issuer acme
// }
// }
return nil, warnings, fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v", ap.Issuers, issuers)
}
ap.Issuers = issuers
}
// certificate managers
if certManagerVals, ok := sblock.pile["tls.cert_manager"]; ok {
for _, certManager := range certManagerVals {
certGetterName := certManager.Value.(caddy.Module).CaddyModule().ID.Name()
ap.ManagersRaw = append(ap.ManagersRaw, caddyconfig.JSONModuleObject(certManager.Value, "via", certGetterName, &warnings))
}
}
// custom bind host
for _, cfgVal := range sblock.pile["bind"] {
for _, iss := range ap.Issuers {
// if an issuer was already configured and it is NOT an ACME issuer,
// skip, since we intend to adjust only ACME issuers; ensure we
// include any issuer that embeds/wraps an underlying ACME issuer
var acmeIssuer *caddytls.ACMEIssuer
if acmeWrapper, ok := iss.(acmeCapable); ok {
acmeIssuer = acmeWrapper.GetACMEIssuer()
}
if acmeIssuer == nil {
continue
}
// proceed to configure the ACME issuer's bind host, without
// overwriting any existing settings
if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
}
if acmeIssuer.Challenges.BindHost == "" {
// only binding to one host is supported
var bindHost string
if bindHosts, ok := cfgVal.Value.([]string); ok && len(bindHosts) > 0 {
bindHost = bindHosts[0]
}
acmeIssuer.Challenges.BindHost = bindHost
}
}
}
// we used to ensure this block is allowed to create an automation policy;
// doing so was forbidden if it has a key with no host (i.e. ":443")
// and if there is a different server block that also has a key with no
// host -- since a key with no host matches any host, we need its
// associated automation policy to have an empty Subjects list, i.e. no
// host filter, which is indistinguishable between the two server blocks
// because automation is not done in the context of a particular server...
// this is an example of a poor mapping from Caddyfile to JSON but that's
// the least-leaky abstraction I could figure out -- however, this check
// was preventing certain listeners, like those provided by plugins, from
// being used as desired (see the Tailscale listener plugin), so I removed
// the check: and I think since I originally wrote the check I added a new
// check above which *properly* detects this ambiguity without breaking the
// listener plugin; see the check above with a commented example config
if len(sblockHosts) == 0 && catchAllAP == nil {
// this server block has a key with no hosts, but there is not yet
// a catch-all automation policy (probably because no global options
// were set), so this one becomes it
catchAllAP = ap
}
// associate our new automation policy with this server block's hosts
ap.Subjects = sblock.hostsFromKeysNotHTTP(httpPort)
sort.Strings(ap.Subjects) // solely for deterministic test results
// if a combination of public and internal names were given
// for this same server block and no issuer was specified, we
// need to separate them out in the automation policies so
// that the internal names can use the internal issuer and
// the other names can use the default/public/ACME issuer
var ap2 *caddytls.AutomationPolicy
if len(ap.Issuers) == 0 {
var internal, external []string
for _, s := range ap.Subjects {
if !certmagic.SubjectQualifiesForCert(s) {
return nil, warnings, fmt.Errorf("subject does not qualify for certificate: '%s'", s)
}
// we don't use certmagic.SubjectQualifiesForPublicCert() because of one nuance:
// names like *.*.tld that may not qualify for a public certificate are actually
// fine when used with OnDemand, since OnDemand (currently) does not obtain
// wildcards (if it ever does, there will be a separate config option to enable
// it that we would need to check here) since the hostname is known at handshake;
// and it is unexpected to switch to internal issuer when the user wants to get
// regular certificates on-demand for a class of certs like *.*.tld.
if subjectQualifiesForPublicCert(ap, s) {
external = append(external, s)
} else {
internal = append(internal, s)
}
}
if len(external) > 0 && len(internal) > 0 {
ap.Subjects = external
apCopy := *ap
ap2 = &apCopy
ap2.Subjects = internal
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
}
}
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap)
if ap2 != nil {
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap2)
}
// certificate loaders
if clVals, ok := sblock.pile["tls.cert_loader"]; ok {
for _, clVal := range clVals {
certLoaders = append(certLoaders, clVal.Value.(caddytls.CertificateLoader))
}
}
}
}
// group certificate loaders by module name, then add to config
if len(certLoaders) > 0 {
loadersByName := make(map[string]caddytls.CertificateLoader)
for _, cl := range certLoaders {
name := caddy.GetModuleName(cl)
// ugh... technically, we may have multiple FileLoader and FolderLoader
// modules (because the tls directive returns one per occurrence), but
// the config structure expects only one instance of each kind of loader
// module, so we have to combine them... instead of enumerating each
// possible cert loader module in a type switch, we can use reflection,
// which works on any cert loaders that are slice types
if reflect.TypeOf(cl).Kind() == reflect.Slice {
combined := reflect.ValueOf(loadersByName[name])
if !combined.IsValid() {
combined = reflect.New(reflect.TypeOf(cl)).Elem()
}
clVal := reflect.ValueOf(cl)
for i := 0; i < clVal.Len(); i++ {
combined = reflect.Append(combined, clVal.Index(i))
}
loadersByName[name] = combined.Interface().(caddytls.CertificateLoader)
}
}
for certLoaderName, loaders := range loadersByName {
tlsApp.CertificatesRaw[certLoaderName] = caddyconfig.JSON(loaders, &warnings)
}
}
// set any of the on-demand options, for if/when on-demand TLS is enabled
if onDemand, ok := options["on_demand_tls"].(*caddytls.OnDemandConfig); ok {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.OnDemand = onDemand
}
// set the storage clean interval if configured
if storageCleanInterval, ok := options["storage_clean_interval"].(caddy.Duration); ok {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.StorageCleanInterval = storageCleanInterval
}
// set the expired certificates renew interval if configured
if renewCheckInterval, ok := options["renew_interval"].(caddy.Duration); ok {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.RenewCheckInterval = renewCheckInterval
}
// set the OCSP check interval if configured
if ocspCheckInterval, ok := options["ocsp_interval"].(caddy.Duration); ok {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.OCSPCheckInterval = ocspCheckInterval
}
// set whether OCSP stapling should be disabled for manually-managed certificates
if ocspConfig, ok := options["ocsp_stapling"].(certmagic.OCSPConfig); ok {
tlsApp.DisableOCSPStapling = ocspConfig.DisableStapling
}
// if any hostnames appear on the same server block as a key with
// no host, they will not be used with route matchers because the
// hostless key matches all hosts, therefore, it wouldn't be
// considered for auto-HTTPS, so we need to make sure those hosts
// are manually considered for managed certificates; we also need
// to make sure that any of these names which are internal-only
// get internal certificates by default rather than ACME
var al caddytls.AutomateLoader
internalAP := &caddytls.AutomationPolicy{
IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
}
if autoHTTPS != "off" {
for h := range httpsHostsSharedWithHostlessKey {
al = append(al, h)
if !certmagic.SubjectQualifiesForPublicCert(h) {
internalAP.Subjects = append(internalAP.Subjects, h)
}
}
}
if len(al) > 0 {
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
}
if len(internalAP.Subjects) > 0 {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, internalAP)
}
// if there are any global options set for issuers (ACME ones in particular), make sure they
// take effect in every automation policy that does not have any issuers
if tlsApp.Automation != nil {
globalEmail := options["email"]
globalACMECA := options["acme_ca"]
globalACMECARoot := options["acme_ca_root"]
globalACMEDNS := options["acme_dns"]
globalACMEEAB := options["acme_eab"]
globalPreferredChains := options["preferred_chains"]
hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil
if hasGlobalACMEDefaults {
for i := 0; i < len(tlsApp.Automation.Policies); i++ {
ap := tlsApp.Automation.Policies[i]
if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) {
// for public names, create default issuers which will later be filled in with configured global defaults
// (internal names will implicitly use the internal issuer at auto-https time)
ap.Issuers = caddytls.DefaultIssuers()
// if a specific endpoint is configured, can't use multiple default issuers
if globalACMECA != nil {
if strings.Contains(globalACMECA.(string), "zerossl") {
ap.Issuers = []certmagic.Issuer{&caddytls.ZeroSSLIssuer{ACMEIssuer: new(caddytls.ACMEIssuer)}}
} else {
ap.Issuers = []certmagic.Issuer{new(caddytls.ACMEIssuer)}
}
}
}
}
}
}
// finalize and verify policies; do cleanup
if tlsApp.Automation != nil {
for i, ap := range tlsApp.Automation.Policies {
// ensure all issuers have global defaults filled in
for j, issuer := range ap.Issuers {
err := fillInGlobalACMEDefaults(issuer, options)
if err != nil {
return nil, warnings, fmt.Errorf("filling in global issuer defaults for AP %d, issuer %d: %v", i, j, err)
}
}
// encode all issuer values we created, so they will be rendered in the output
if len(ap.Issuers) > 0 && ap.IssuersRaw == nil {
for _, iss := range ap.Issuers {
issuerName := iss.(caddy.Module).CaddyModule().ID.Name()
ap.IssuersRaw = append(ap.IssuersRaw, caddyconfig.JSONModuleObject(iss, "module", issuerName, &warnings))
}
}
}
// consolidate automation policies that are the exact same
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
// ensure automation policies don't overlap subjects (this should be
// an error at provision-time as well, but catch it in the adapt phase
// for convenience)
automationHostSet := make(map[string]struct{})
for _, ap := range tlsApp.Automation.Policies {
for _, s := range ap.Subjects {
if _, ok := automationHostSet[s]; ok {
return nil, warnings, fmt.Errorf("hostname appears in more than one automation policy, making certificate management ambiguous: %s", s)
}
automationHostSet[s] = struct{}{}
}
}
// if nothing remains, remove any excess values to clean up the resulting config
if len(tlsApp.Automation.Policies) == 0 {
tlsApp.Automation.Policies = nil
}
if reflect.DeepEqual(tlsApp.Automation, new(caddytls.AutomationConfig)) {
tlsApp.Automation = nil
}
}
return tlsApp, warnings, nil
}
type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) error {
acmeWrapper, ok := issuer.(acmeCapable)
if !ok {
return nil
}
acmeIssuer := acmeWrapper.GetACMEIssuer()
if acmeIssuer == nil {
return nil
}
globalEmail := options["email"]
globalACMECA := options["acme_ca"]
globalACMECARoot := options["acme_ca_root"]
globalACMEDNS := options["acme_dns"]
globalACMEEAB := options["acme_eab"]
globalPreferredChains := options["preferred_chains"]
if globalEmail != nil && acmeIssuer.Email == "" {
acmeIssuer.Email = globalEmail.(string)
}
if globalACMECA != nil && acmeIssuer.CA == "" {
acmeIssuer.CA = globalACMECA.(string)
}
if globalACMECARoot != nil && !sliceContains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
}
if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
acmeIssuer.Challenges = &caddytls.ChallengesConfig{
DNS: &caddytls.DNSChallengeConfig{
ProviderRaw: caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil),
},
}
}
if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
acmeIssuer.ExternalAccount = globalACMEEAB.(*acme.EAB)
}
if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil {
acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference)
}
return nil
}
// newBaseAutomationPolicy returns a new TLS automation policy that gets
// its values from the global options map. It should be used as the base
// for any other automation policies. A nil policy (and no error) will be
// returned if there are no default/global options. However, if always is
// true, a non-nil value will always be returned (unless there is an error).
func newBaseAutomationPolicy(options map[string]any, warnings []caddyconfig.Warning, always bool) (*caddytls.AutomationPolicy, error) {
issuers, hasIssuers := options["cert_issuer"]
_, hasLocalCerts := options["local_certs"]
keyType, hasKeyType := options["key_type"]
ocspStapling, hasOCSPStapling := options["ocsp_stapling"]
hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling
// if there are no global options related to automation policies
// set, then we can just return right away
if !hasGlobalAutomationOpts {
if always {
return new(caddytls.AutomationPolicy), nil
}
return nil, nil
}
ap := new(caddytls.AutomationPolicy)
if hasKeyType {
ap.KeyType = keyType.(string)
}
if hasIssuers && hasLocalCerts {
return nil, fmt.Errorf("global options are ambiguous: local_certs is confusing when combined with cert_issuer, because local_certs is also a specific kind of issuer")
}
if hasIssuers {
ap.Issuers = issuers.([]certmagic.Issuer)
} else if hasLocalCerts {
ap.Issuers = []certmagic.Issuer{new(caddytls.InternalIssuer)}
}
if hasOCSPStapling {
ocspConfig := ocspStapling.(certmagic.OCSPConfig)
ap.DisableOCSPStapling = ocspConfig.DisableStapling
ap.OCSPOverrides = ocspConfig.ResponderOverrides
}
return ap, nil
}
// consolidateAutomationPolicies combines automation policies that are the same,
// for a cleaner overall output.
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {
// sort from most specific to least specific; we depend on this ordering
sort.SliceStable(aps, func(i, j int) bool {
if automationPolicyIsSubset(aps[i], aps[j]) {
return true
}
if automationPolicyIsSubset(aps[j], aps[i]) {
return false
}
return len(aps[i].Subjects) > len(aps[j].Subjects)
})
emptyAPCount := 0
origLenAPs := len(aps)
// compute the number of empty policies (disregarding subjects) - see #4128
emptyAP := new(caddytls.AutomationPolicy)
for i := 0; i < len(aps); i++ {
emptyAP.Subjects = aps[i].Subjects
if reflect.DeepEqual(aps[i], emptyAP) {
emptyAPCount++
if !automationPolicyHasAllPublicNames(aps[i]) {
// if this automation policy has internal names, we might as well remove it
// so auto-https can implicitly use the internal issuer
aps = append(aps[:i], aps[i+1:]...)
i--
}
}
}
// If all policies are empty, we can return nil, as there is no need to set any policy
if emptyAPCount == origLenAPs {
return nil
}
// remove or combine duplicate policies
outer:
for i := 0; i < len(aps); i++ {
// compare only with next policies; we sorted by specificity so we must not delete earlier policies
for j := i + 1; j < len(aps); j++ {
// if they're exactly equal in every way, just keep one of them
if reflect.DeepEqual(aps[i], aps[j]) {
aps = append(aps[:j], aps[j+1:]...)
// must re-evaluate current i against next j; can't skip it!
// even if i decrements to -1, will be incremented to 0 immediately
i--
continue outer
}
// if the policy is the same, we can keep just one, but we have
// to be careful which one we keep; if only one has any hostnames
// defined, then we need to keep the one without any hostnames,
// otherwise the one without any subjects (a catch-all) would be
// eaten up by the one with subjects; and if both have subjects, we
// need to combine their lists
if reflect.DeepEqual(aps[i].IssuersRaw, aps[j].IssuersRaw) &&
bytes.Equal(aps[i].StorageRaw, aps[j].StorageRaw) &&
aps[i].MustStaple == aps[j].MustStaple &&
aps[i].KeyType == aps[j].KeyType &&
aps[i].OnDemand == aps[j].OnDemand &&
aps[i].RenewalWindowRatio == aps[j].RenewalWindowRatio {
if len(aps[i].Subjects) > 0 && len(aps[j].Subjects) == 0 {
// later policy (at j) has no subjects ("catch-all"), so we can
// remove the identical-but-more-specific policy that comes first
// AS LONG AS it is not shadowed by another policy before it; e.g.
// if policy i is for example.com, policy i+1 is '*.com', and policy
// j is catch-all, we cannot remove policy i because that would
// cause example.com to be served by the less specific policy for
// '*.com', which might be different (yes we've seen this happen)
if automationPolicyShadows(i, aps) >= j {
aps = append(aps[:i], aps[i+1:]...)
i--
continue outer
}
} else {
// avoid repeated subjects
for _, subj := range aps[j].Subjects {
if !sliceContains(aps[i].Subjects, subj) {
aps[i].Subjects = append(aps[i].Subjects, subj)
}
}
aps = append(aps[:j], aps[j+1:]...)
j--
}
}
}
}
return aps
}
// automationPolicyIsSubset returns true if a's subjects are a subset
// of b's subjects.
func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
if len(b.Subjects) == 0 {
return true
}
if len(a.Subjects) == 0 {
return false
}
for _, aSubj := range a.Subjects {
var inSuperset bool
for _, bSubj := range b.Subjects {
if certmagic.MatchWildcard(aSubj, bSubj) {
inSuperset = true
break
}
}
if !inSuperset {
return false
}
}
return true
}
// automationPolicyShadows returns the index of a policy that aps[i] shadows;
// in other words, for all policies after position i, if that policy covers
// the same subjects but is less specific, that policy's position is returned,
// or -1 if no shadowing is found. For example, if policy i is for
// "foo.example.com" and policy i+2 is for "*.example.com", then i+2 will be
// returned, since that policy is shadowed by i, which is in front.
func automationPolicyShadows(i int, aps []*caddytls.AutomationPolicy) int {
for j := i + 1; j < len(aps); j++ {
if automationPolicyIsSubset(aps[i], aps[j]) {
return j
}
}
return -1
}
// subjectQualifiesForPublicCert is like certmagic.SubjectQualifiesForPublicCert() except
// that this allows domains with multiple wildcard levels like '*.*.example.com' to qualify
// if the automation policy has OnDemand enabled (i.e. this function is more lenient).
func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) bool {
return !certmagic.SubjectIsIP(subj) &&
!certmagic.SubjectIsInternal(subj) &&
(strings.Count(subj, "*.") < 2 || ap.OnDemand)
}
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
for _, subj := range ap.Subjects {
if !subjectQualifiesForPublicCert(ap, subj) {
return false
}
}
return true
}
+56
View File
@@ -0,0 +1,56 @@
package httpcaddyfile
import (
"testing"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
func TestAutomationPolicyIsSubset(t *testing.T) {
for i, test := range []struct {
a, b []string
expect bool
}{
{
a: []string{"example.com"},
b: []string{},
expect: true,
},
{
a: []string{},
b: []string{"example.com"},
expect: false,
},
{
a: []string{"foo.example.com"},
b: []string{"*.example.com"},
expect: true,
},
{
a: []string{"foo.example.com"},
b: []string{"foo.example.com"},
expect: true,
},
{
a: []string{"foo.example.com"},
b: []string{"example.com"},
expect: false,
},
{
a: []string{"example.com", "foo.example.com"},
b: []string{"*.com", "*.*.com"},
expect: true,
},
{
a: []string{"example.com", "foo.example.com"},
b: []string{"*.com"},
expect: false,
},
} {
apA := &caddytls.AutomationPolicy{Subjects: test.a}
apB := &caddytls.AutomationPolicy{Subjects: test.b}
if actual := automationPolicyIsSubset(apA, apB); actual != test.expect {
t.Errorf("Test %d: Expected %t but got %t (A: %v B: %v)", i, test.expect, actual, test.a, test.b)
}
}
}
+204
View File
@@ -0,0 +1,204 @@
// 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 caddyconfig
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/caddyserver/caddy/v2"
)
func init() {
caddy.RegisterModule(HTTPLoader{})
}
// HTTPLoader can load Caddy configs over HTTP(S). It can adapt the config
// based on the Content-Type header of the HTTP response.
type HTTPLoader struct {
// The method for the request. Default: GET
Method string `json:"method,omitempty"`
// The URL of the request.
URL string `json:"url,omitempty"`
// HTTP headers to add to the request.
Headers http.Header `json:"header,omitempty"`
// Maximum time allowed for a complete connection and request.
Timeout caddy.Duration `json:"timeout,omitempty"`
TLS *struct {
// Present this instance's managed remote identity credentials to the server.
UseServerIdentity bool `json:"use_server_identity,omitempty"`
// PEM-encoded client certificate filename to present to the server.
ClientCertificateFile string `json:"client_certificate_file,omitempty"`
// PEM-encoded key to use with the client certificate.
ClientCertificateKeyFile string `json:"client_certificate_key_file,omitempty"`
// List of PEM-encoded CA certificate files to add to the same trust
// store as RootCAPool (or root_ca_pool in the JSON).
RootCAPEMFiles []string `json:"root_ca_pem_files,omitempty"`
} `json:"tls,omitempty"`
}
// CaddyModule returns the Caddy module information.
func (HTTPLoader) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "caddy.config_loaders.http",
New: func() caddy.Module { return new(HTTPLoader) },
}
}
// LoadConfig loads a Caddy config.
func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
repl := caddy.NewReplacer()
client, err := hl.makeClient(ctx)
if err != nil {
return nil, err
}
method := repl.ReplaceAll(hl.Method, "")
if method == "" {
method = http.MethodGet
}
url := repl.ReplaceAll(hl.URL, "")
req, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
for key, vals := range hl.Headers {
for _, val := range vals {
req.Header.Add(repl.ReplaceAll(key, ""), repl.ReplaceKnown(val, ""))
}
}
resp, err := doHttpCallWithRetries(ctx, client, req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("server responded with HTTP %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
result, warnings, err := adaptByContentType(resp.Header.Get("Content-Type"), body)
if err != nil {
return nil, err
}
for _, warn := range warnings {
ctx.Logger().Warn(warn.String())
}
return result, nil
}
func attemptHttpCall(client *http.Client, request *http.Request) (*http.Response, error) {
resp, err := client.Do(request)
if err != nil {
return nil, fmt.Errorf("problem calling http loader url: %v", err)
} else if resp.StatusCode < 200 || resp.StatusCode > 499 {
return nil, fmt.Errorf("bad response status code from http loader url: %v", resp.StatusCode)
}
return resp, nil
}
func doHttpCallWithRetries(ctx caddy.Context, client *http.Client, request *http.Request) (*http.Response, error) {
var resp *http.Response
var err error
const maxAttempts = 10
// attempt up to 10 times
for i := 0; i < maxAttempts; i++ {
resp, err = attemptHttpCall(client, request)
if err != nil && i < maxAttempts-1 {
// wait 500ms before reattempting, or until context is done
select {
case <-time.After(time.Millisecond * 500):
case <-ctx.Done():
return resp, ctx.Err()
}
}
}
return resp, err
}
func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, error) {
client := &http.Client{
Timeout: time.Duration(hl.Timeout),
}
if hl.TLS != nil {
var tlsConfig *tls.Config
// client authentication
if hl.TLS.UseServerIdentity {
certs, err := ctx.IdentityCredentials(ctx.Logger())
if err != nil {
return nil, fmt.Errorf("getting server identity credentials: %v", err)
}
if tlsConfig == nil {
tlsConfig = new(tls.Config)
}
tlsConfig.Certificates = certs
} else if hl.TLS.ClientCertificateFile != "" && hl.TLS.ClientCertificateKeyFile != "" {
cert, err := tls.LoadX509KeyPair(hl.TLS.ClientCertificateFile, hl.TLS.ClientCertificateKeyFile)
if err != nil {
return nil, err
}
if tlsConfig == nil {
tlsConfig = new(tls.Config)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
// trusted server certs
if len(hl.TLS.RootCAPEMFiles) > 0 {
rootPool := x509.NewCertPool()
for _, pemFile := range hl.TLS.RootCAPEMFiles {
pemData, err := os.ReadFile(pemFile)
if err != nil {
return nil, fmt.Errorf("failed reading ca cert: %v", err)
}
rootPool.AppendCertsFromPEM(pemData)
}
if tlsConfig == nil {
tlsConfig = new(tls.Config)
}
tlsConfig.RootCAs = rootPool
}
client.Transport = &http.Transport{TLSClientConfig: tlsConfig}
}
return client, nil
}
var _ caddy.ConfigLoader = (*HTTPLoader)(nil)
-49
View File
@@ -1,49 +0,0 @@
// 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 jsoncadapter
import (
"encoding/json"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/muhammadmuzzammil1998/jsonc"
)
func init() {
caddyconfig.RegisterAdapter("jsonc", Adapter{})
}
// Adapter adapts JSON-C to Caddy JSON.
type Adapter struct{}
// Adapt converts the JSON-C config in body to Caddy JSON.
func (a Adapter) Adapt(body []byte, options map[string]interface{}) (result []byte, warnings []caddyconfig.Warning, err error) {
result = jsonc.ToJSON(body)
// any errors in the JSON will be
// reported during config load, but
// we can at least warn here that
// it is not valid JSON
if !json.Valid(result) {
warnings = append(warnings, caddyconfig.Warning{
Message: "Resulting JSON is invalid.",
})
}
return
}
// Interface guard
var _ caddyconfig.Adapter = (*Adapter)(nil)
+214
View File
@@ -0,0 +1,214 @@
// 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 caddyconfig
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime"
"net/http"
"strings"
"sync"
"github.com/caddyserver/caddy/v2"
)
func init() {
caddy.RegisterModule(adminLoad{})
}
// adminLoad is a module that provides the /load endpoint
// for the Caddy admin API. The only reason it's not baked
// into the caddy package directly is because of the import
// of the caddyconfig package for its GetAdapter function.
// If the caddy package depends on the caddyconfig package,
// then the caddyconfig package will not be able to import
// the caddy package, and it can more easily cause backward
// edges in the dependency tree (i.e. import cycle).
// Fortunately, the admin API has first-class support for
// adding endpoints from modules.
type adminLoad struct{}
// CaddyModule returns the Caddy module information.
func (adminLoad) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "admin.api.load",
New: func() caddy.Module { return new(adminLoad) },
}
}
// Routes returns a route for the /load endpoint.
func (al adminLoad) Routes() []caddy.AdminRoute {
return []caddy.AdminRoute{
{
Pattern: "/load",
Handler: caddy.AdminHandlerFunc(al.handleLoad),
},
{
Pattern: "/adapt",
Handler: caddy.AdminHandlerFunc(al.handleAdapt),
},
}
}
// handleLoad replaces the entire current configuration with
// a new one provided in the response body. It supports config
// adapters through the use of the Content-Type header. A
// config that is identical to the currently-running config
// will be a no-op unless Cache-Control: must-revalidate is set.
func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
if r.Method != http.MethodPost {
return caddy.APIError{
HTTPStatus: http.StatusMethodNotAllowed,
Err: fmt.Errorf("method not allowed"),
}
}
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
_, err := io.Copy(buf, r.Body)
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("reading request body: %v", err),
}
}
body := buf.Bytes()
// if the config is formatted other than Caddy's native
// JSON, we need to adapt it before loading it
if ctHeader := r.Header.Get("Content-Type"); ctHeader != "" {
result, warnings, err := adaptByContentType(ctHeader, body)
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: err,
}
}
if len(warnings) > 0 {
respBody, err := json.Marshal(warnings)
if err != nil {
caddy.Log().Named("admin.api.load").Error(err.Error())
}
_, _ = w.Write(respBody)
}
body = result
}
forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
err = caddy.Load(body, forceReload)
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("loading config: %v", err),
}
}
caddy.Log().Named("admin.api").Info("load complete")
return nil
}
// handleAdapt adapts the given Caddy config to JSON and responds with the result.
func (adminLoad) handleAdapt(w http.ResponseWriter, r *http.Request) error {
if r.Method != http.MethodPost {
return caddy.APIError{
HTTPStatus: http.StatusMethodNotAllowed,
Err: fmt.Errorf("method not allowed"),
}
}
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
_, err := io.Copy(buf, r.Body)
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("reading request body: %v", err),
}
}
result, warnings, err := adaptByContentType(r.Header.Get("Content-Type"), buf.Bytes())
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: err,
}
}
out := struct {
Warnings []Warning `json:"warnings,omitempty"`
Result json.RawMessage `json:"result"`
}{
Warnings: warnings,
Result: result,
}
w.Header().Set("Content-Type", "application/json")
return json.NewEncoder(w).Encode(out)
}
// adaptByContentType adapts body to Caddy JSON using the adapter specified by contentType.
// If contentType is empty or ends with "/json", the input will be returned, as a no-op.
func adaptByContentType(contentType string, body []byte) ([]byte, []Warning, error) {
// assume JSON as the default
if contentType == "" {
return body, nil, nil
}
ct, _, err := mime.ParseMediaType(contentType)
if err != nil {
return nil, nil, caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("invalid Content-Type: %v", err),
}
}
// if already JSON, no need to adapt
if strings.HasSuffix(ct, "/json") {
return body, nil, nil
}
// adapter name should be suffix of MIME type
_, adapterName, slashFound := strings.Cut(ct, "/")
if !slashFound {
return nil, nil, fmt.Errorf("malformed Content-Type")
}
cfgAdapter := GetAdapter(adapterName)
if cfgAdapter == nil {
return nil, nil, fmt.Errorf("unrecognized config adapter '%s'", adapterName)
}
result, warnings, err := cfgAdapter.Adapt(body, nil)
if err != nil {
return nil, nil, fmt.Errorf("adapting config using %s adapter: %v", adapterName, err)
}
return result, warnings, nil
}
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
+23
View File
@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID5zCCAs8CFG4+w/pqR5AZQ+aVB330uRRRKMF0MA0GCSqGSIb3DQEBCwUAMIGv
MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZBgNVBAoMEkxvY2FsIERldmVs
b3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxvcGVtZW50MRowGAYDVQQDDBFh
LmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9jYWwgRGV2ZWxvcGVtZW50MSAw
HgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2NhbDAeFw0yMDAzMTMxODUwMTda
Fw0zMDAzMTExODUwMTdaMIGvMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZ
BgNVBAoMEkxvY2FsIERldmVsb3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxv
cGVtZW50MRowGAYDVQQDDBFhLmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9j
YWwgRGV2ZWxvcGVtZW50MSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2Nh
bDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMd9pC9wF7j0459FndPs
Deud/rq41jEZFsVOVtjQgjS1A5ct6NfeMmSlq8i1F7uaTMPZjbOHzY6y6hzLc9+y
/VWNgyUC543HjXnNTnp9Xug6tBBxOxvRMw5mv2nAyzjBGDePPgN84xKhOXG2Wj3u
fOZ+VPVISefRNvjKfN87WLJ0B0HI9wplG5ASVdPQsWDY1cndrZgt2sxQ/3fjIno4
VvrgRWC9Penizgps/a0ZcFZMD/6HJoX/mSZVa1LjopwbMTXvyHCpXkth21E+rBt6
I9DMHerdioVQcX25CqPmAwePxPZSNGEQo/Qu32kzcmscmYxTtYBhDa+yLuHgGggI
j7ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAP/94KPtkpYtkWADnhtzDmgQ6Q1pH
SubTUZdCwQtm6/LrvpT+uFNsOj4L3Mv3TVUnIQDmKd5VvR42W2MRBiTN2LQptgEn
C7g9BB+UA9kjL3DPk1pJMjzxLHohh0uNLi7eh4mAj8eNvjz9Z4qMWPQoVS0y7/ZK
cCBRKh2GkIqKm34ih6pX7xmMpPEQsFoTVPRHYJfYD1SZ8Iui+EN+7WqLuJWPsPXw
JM1HuZKn7pZmJU2MZZBsrupHGUvNMbBg2mFJcxt4D1VvU+p+a67PSjpFQ6dJG2re
pZoF+N1vMGAFkxe6UqhcC/bXDX+ILVQHJ+RNhzDO6DcWf8dRrC2LaJk3WA==
-----END CERTIFICATE-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAx32kL3AXuPTjn0Wd0+wN653+urjWMRkWxU5W2NCCNLUDly3o
194yZKWryLUXu5pMw9mNs4fNjrLqHMtz37L9VY2DJQLnjceNec1Oen1e6Dq0EHE7
G9EzDma/acDLOMEYN48+A3zjEqE5cbZaPe585n5U9UhJ59E2+Mp83ztYsnQHQcj3
CmUbkBJV09CxYNjVyd2tmC3azFD/d+MiejhW+uBFYL096eLOCmz9rRlwVkwP/ocm
hf+ZJlVrUuOinBsxNe/IcKleS2HbUT6sG3oj0Mwd6t2KhVBxfbkKo+YDB4/E9lI0
YRCj9C7faTNyaxyZjFO1gGENr7Iu4eAaCAiPsQIDAQABAoIBAQDD/YFIBeWYlifn
e9risQDAIrp3sk7lb9O6Rwv1+Wxi4hBEABvJsYhq74VFK/3EF4UhyWR5JIvkjYyK
e6w887oGyoA05ZSe65XoO7fFidSrbbkoikZbPv3dQT7/ZCWEfdkQBNAVVyY0UGeC
e3hPbjYRsb5AOSQ694X9idqC6uhqcOrBDjITFrctUoP4S6l9A6a+mLSUIwiICcuh
mrNl+j0lzy7DMXRp/Z5Hyo5kuUlrC0dCLa1UHqtrrK7MR55AVEOihSNp1w+OC+vw
f0VjE4JUtO7RQEQUmD1tDfLXwNfMFeWaobB2W0WMvRg0IqoitiqPxsPHRm56OxfM
SRo/Q7QBAoGBAP8DapzBMuaIcJ7cE8Yl07ZGndWWf8buIKIItGF8rkEO3BXhrIke
EmpOi+ELtpbMOG0APhORZyQ58f4ZOVrqZfneNKtDiEZV4mJZaYUESm1pU+2Y6+y5
g4bpQSVKN0ow0xR+MH7qDYtSlsmBU7qAOz775L7BmMA1Bnu72aN/H1JBAoGBAMhD
OzqCSakHOjUbEd22rPwqWmcIyVyo04gaSmcVVT2dHbqR4/t0gX5a9D9U2qwyO6xi
/R+PXyMd32xIeVR2D/7SQ0x6dK68HXICLV8ofHZ5UQcHbxy5og4v/YxSZVTkN374
cEsUeyB0s/UPOHLktFU5hpIlON72/Rp7b+pNIwFxAoGAczpq+Qu/YTWzlcSh1r4O
7OT5uqI3eH7vFehTAV3iKxl4zxZa7NY+wfRd9kFhrr/2myIp6pOgBFl+hC+HoBIc
JAyIxf5M3GNAWOpH6MfojYmzV7/qktu8l8BcJGplk0t+hVsDtMUze4nFAqZCXBpH
Kw2M7bjyuZ78H/rgu6TcVUECgYEAo1M5ldE2U/VCApeuLX1TfWDpU8i1uK0zv3d5
oLKkT1i5KzTak3SEO9HgC1qf8PoS8tfUio26UICHe99rnHehOfivzEq+qNdgyF+A
M3BoeZMdgzcL5oh640k+Zte4LtDlddcWdhUhCepD7iPYrNNbQ3pkBwL2a9lRuOxc
7OC2IPECgYBH8f3OrwXjDltIG1dDvuDPNljxLZbFEFbQyVzMePYNftgZknAyGEdh
NW/LuWeTzstnmz/s6RE3jN5ZrrMa4sW77VA9+yU9QW2dkHqFyukQ4sfuNg6kDDNZ
+lqZYMCLw0M5P9fIbmnIYwey7tXkHfmzoCpnYHGQDN6hL0Bh0zGwmg==
-----END RSA PRIVATE KEY-----
+20
View File
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkw
ODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU
7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl0
3WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45t
wOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNx
tdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTU
ApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAd
BgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS
2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5u
NY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkq
hkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfK
D66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEO
fG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnk
oNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZ
ks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdle
Ih6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
-----END CERTIFICATE-----
+23
View File
@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID5zCCAs8CFFmAAFKV79uhzxc5qXbUw3oBNsYXMA0GCSqGSIb3DQEBCwUAMIGv
MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZBgNVBAoMEkxvY2FsIERldmVs
b3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxvcGVtZW50MRowGAYDVQQDDBEq
LmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9jYWwgRGV2ZWxvcGVtZW50MSAw
HgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2NhbDAeFw0yMDAzMDIwODAxMTZa
Fw0zMDAyMjgwODAxMTZaMIGvMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZ
BgNVBAoMEkxvY2FsIERldmVsb3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxv
cGVtZW50MRowGAYDVQQDDBEqLmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9j
YWwgRGV2ZWxvcGVtZW50MSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2Nh
bDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJngfeirQkWaU8ihgIC5
SKpRQX/3koRjljDK/oCbhLs+wg592kIwVv06l7+mn7NSaNBloabjuA1GqyLRsNLL
ptrv0HvXa5qLx28+icsb2Ny3dJnQaj9w9PwjxQ1qZqEJfWRH1D8Vz9AmB+QSV/Gu
8e8alGFewlYZVfH1kbxoTT6QorF37TeA3bh1fgKFtzsGYKswcaZNdDBBHzLunCKZ
HU6U6L45hm+yLADj3mmDLafUeiVOt6MRLLoSD1eLRVSXGrNo+brJ87zkZntI9+W1
JxOBoXtZCwka7k2DlAtLihsrmBZA2ZC9yVeu/SQy3qb3iCNnTFTCyAnWeTCr6Tcq
6w8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAOWfXqpAmD4C3wGiMeZAeaaS4hDAR
+JmN+avPDA6F6Bq7DB4NJuIwVUlaDL2s07w5VJJtW52aZVKoBlgHR5yG/XUli6J7
YUJRmdQJvHUSu26cmKvyoOaTrEYbmvtGICWtZc8uTlMf9wQZbJA4KyxTgEQJDXsZ
B2XFe+wVdhAgEpobYDROi+l/p8TL5z3U24LpwVTcJy5sEZVv7Wfs886IyxU8ORt8
VZNcDiH6V53OIGeiufIhia/mPe6jbLntfGZfIFxtCcow4IA/lTy1ned7K5fmvNNb
ZilxOQUk+wVK8genjdrZVAnAxsYLHJIb5yf9O7rr6fWciVMF3a0k5uNK1w==
-----END CERTIFICATE-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAmeB96KtCRZpTyKGAgLlIqlFBf/eShGOWMMr+gJuEuz7CDn3a
QjBW/TqXv6afs1Jo0GWhpuO4DUarItGw0sum2u/Qe9drmovHbz6JyxvY3Ld0mdBq
P3D0/CPFDWpmoQl9ZEfUPxXP0CYH5BJX8a7x7xqUYV7CVhlV8fWRvGhNPpCisXft
N4DduHV+AoW3OwZgqzBxpk10MEEfMu6cIpkdTpTovjmGb7IsAOPeaYMtp9R6JU63
oxEsuhIPV4tFVJcas2j5usnzvORme0j35bUnE4Ghe1kLCRruTYOUC0uKGyuYFkDZ
kL3JV679JDLepveII2dMVMLICdZ5MKvpNyrrDwIDAQABAoIBAFcPK01zb6hfm12c
+k5aBiHOnUdgc/YRPg1XHEz5MEycQkDetZjTLrRQ7UBSbnKPgpu9lIsOtbhVLkgh
6XAqJroiCou2oruqr+hhsqZGmBiwdvj7cNF6ADGTr05az7v22YneFdinZ481pStF
sZocx+bm2+KHMV5zMSwXKyA0xtdJLxs2yklniDBxSZRppgppq1pDPprP5DkgKPfe
3ekUmbQd5bHmivhW8ItbJLuf82XSsMBZ9ZhKiKIlWlbKAgiSV3SqnUQb5fi7l8hG
yYZxbuCUIGFwKmEpUBBt/nyxrOlMiNtDh9JhrPmijTV3slq70pCLwLL/Ai2aeear
EVA5VhkCgYEAyAmxfPqc2P7BsDAp67/sA7OEPso9qM4WyuWiVdlX2gb9TLNLYbPX
Kk/UmpAIVzpoTAGY5Zp3wkvdD/ou8uUQsE8ioNn4S1a4G9XURH1wVhcEbUiAKI1S
QVBH9B/Pj3eIp5OTKwob0Wj7DNdxoH7ed/Eok0EaTWzOA8pCWADKv/MCgYEAxOzY
YsX7Nl+eyZr2+9unKyeAK/D1DCT/o99UUAHx72/xaBVP/06cfzpvKBNcF9iYc+fq
R1yIUIrDRoSmYKBq+Kb3+nOg1nrqih/NBTokbTiI4Q+/30OQt0Al1e7y9iNKqV8H
jYZItzluGNrWKedZbATwBwbVCY2jnNl6RMDnS3UCgYBxj3cwQUHLuoyQjjcuO80r
qLzZvIxWiXDNDKIk5HcIMlGYOmz/8U2kGp/SgxQJGQJeq8V2C0QTjGfaCyieAcaA
oNxCvptDgd6RBsoze5bLeNOtiqwe2WOp6n5+q5R0mOJ+Z7vzghCayGNFPgWmnH+F
TeW/+wSIkc0+v5L8TK7NWwKBgBrlWlyLO9deUfqpHqihhICBYaEexOlGuF+yZfqT
eW7BdFBJ8OYm33sFCR+JHV/oZlIWT8o1Wizd9vPPtEWoQ1P4wg/D8Si6GwSIeWEI
YudD/HX4x7T/rmlI6qIAg9CYW18sqoRq3c2gm2fro6qPfYgiWIItLbWjUcBfd7Ki
QjTtAoGARKdRv3jMWL84rlEx1nBRgL3pe9Dt+Uxzde2xT3ZeF+5Hp9NfU01qE6M6
1I6H64smqpetlsXmCEVKwBemP3pJa6avLKgIYiQvHAD/v4rs9mqgy1RTqtYyGNhR
1A/6dKkbiZ6wzePLLPasXVZxSKEviXf5gJooqumQVSVhCswyCZ0=
-----END RSA PRIVATE KEY-----
+552
View File
@@ -0,0 +1,552 @@
package caddytest
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/http/cookiejar"
"os"
"path"
"reflect"
"regexp"
"runtime"
"strings"
"testing"
"time"
"github.com/aryann/difflib"
"github.com/caddyserver/caddy/v2/caddyconfig"
caddycmd "github.com/caddyserver/caddy/v2/cmd"
// plug in Caddy modules here
_ "github.com/caddyserver/caddy/v2/modules/standard"
)
// Defaults store any configuration required to make the tests run
type Defaults struct {
// Port we expect caddy to listening on
AdminPort int
// Certificates we expect to be loaded before attempting to run the tests
Certifcates []string
// TestRequestTimeout is the time to wait for a http request to
TestRequestTimeout time.Duration
// LoadRequestTimeout is the time to wait for the config to be loaded against the caddy server
LoadRequestTimeout time.Duration
}
// Default testing values
var Default = Defaults{
AdminPort: 2999, // different from what a real server also running on a developer's machine might be
Certifcates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
TestRequestTimeout: 5 * time.Second,
LoadRequestTimeout: 5 * time.Second,
}
var (
matchKey = regexp.MustCompile(`(/[\w\d\.]+\.key)`)
matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`)
)
// Tester represents an instance of a test client.
type Tester struct {
Client *http.Client
configLoaded bool
t *testing.T
}
// NewTester will create a new testing client with an attached cookie jar
func NewTester(t *testing.T) *Tester {
jar, err := cookiejar.New(nil)
if err != nil {
t.Fatalf("failed to create cookiejar: %s", err)
}
return &Tester{
Client: &http.Client{
Transport: CreateTestingTransport(),
Jar: jar,
Timeout: Default.TestRequestTimeout,
},
configLoaded: false,
t: t,
}
}
type configLoadError struct {
Response string
}
func (e configLoadError) Error() string { return e.Response }
func timeElapsed(start time.Time, name string) {
elapsed := time.Since(start)
log.Printf("%s took %s", name, elapsed)
}
// InitServer this will configure the server with a configurion of a specific
// type. The configType must be either "json" or the adapter type.
func (tc *Tester) InitServer(rawConfig string, configType string) {
if err := tc.initServer(rawConfig, configType); err != nil {
tc.t.Logf("failed to load config: %s", err)
tc.t.Fail()
}
if err := tc.ensureConfigRunning(rawConfig, configType); err != nil {
tc.t.Logf("failed ensuring config is running: %s", err)
tc.t.Fail()
}
}
// InitServer this will configure the server with a configurion of a specific
// type. The configType must be either "json" or the adapter type.
func (tc *Tester) initServer(rawConfig string, configType string) error {
if testing.Short() {
tc.t.SkipNow()
return nil
}
err := validateTestPrerequisites()
if err != nil {
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
return nil
}
tc.t.Cleanup(func() {
if tc.t.Failed() && tc.configLoaded {
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
if err != nil {
tc.t.Log("unable to read the current config")
return
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
var out bytes.Buffer
_ = json.Indent(&out, body, "", " ")
tc.t.Logf("----------- failed with config -----------\n%s", out.String())
}
})
rawConfig = prependCaddyFilePath(rawConfig)
client := &http.Client{
Timeout: Default.LoadRequestTimeout,
}
start := time.Now()
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig))
if err != nil {
tc.t.Errorf("failed to create request. %s", err)
return err
}
if configType == "json" {
req.Header.Add("Content-Type", "application/json")
} else {
req.Header.Add("Content-Type", "text/"+configType)
}
res, err := client.Do(req)
if err != nil {
tc.t.Errorf("unable to contact caddy server. %s", err)
return err
}
timeElapsed(start, "caddytest: config load time")
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
tc.t.Errorf("unable to read response. %s", err)
return err
}
if res.StatusCode != 200 {
return configLoadError{Response: string(body)}
}
tc.configLoaded = true
return nil
}
func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error {
expectedBytes := []byte(prependCaddyFilePath(rawConfig))
if configType != "json" {
adapter := caddyconfig.GetAdapter(configType)
if adapter == nil {
return fmt.Errorf("adapter of config type is missing: %s", configType)
}
expectedBytes, _, _ = adapter.Adapt([]byte(rawConfig), nil)
}
var expected any
err := json.Unmarshal(expectedBytes, &expected)
if err != nil {
return err
}
client := &http.Client{
Timeout: Default.LoadRequestTimeout,
}
fetchConfig := func(client *http.Client) any {
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
if err != nil {
return nil
}
defer resp.Body.Close()
actualBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil
}
var actual any
err = json.Unmarshal(actualBytes, &actual)
if err != nil {
return nil
}
return actual
}
for retries := 10; retries > 0; retries-- {
if reflect.DeepEqual(expected, fetchConfig(client)) {
return nil
}
time.Sleep(10 * time.Millisecond)
}
tc.t.Errorf("POSTed configuration isn't active")
return errors.New("EnsureConfigRunning: POSTed configuration isn't active")
}
// validateTestPrerequisites ensures the certificates are available in the
// designated path and Caddy sub-process is running.
func validateTestPrerequisites() error {
// check certificates are found
for _, certName := range Default.Certifcates {
if _, err := os.Stat(getIntegrationDir() + certName); os.IsNotExist(err) {
return fmt.Errorf("caddy integration test certificates (%s) not found", certName)
}
}
if isCaddyAdminRunning() != nil {
// start inprocess caddy server
os.Args = []string{"caddy", "run", "--config", "./test.init.config", "--adapter", "caddyfile"}
go func() {
caddycmd.Main()
}()
// wait for caddy to start serving the initial config
for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
time.Sleep(10 * time.Millisecond)
}
}
// one more time to return the error
return isCaddyAdminRunning()
}
func isCaddyAdminRunning() error {
// assert that caddy is running
client := &http.Client{
Timeout: Default.LoadRequestTimeout,
}
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
if err != nil {
return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", Default.AdminPort)
}
resp.Body.Close()
return nil
}
func getIntegrationDir() string {
_, filename, _, ok := runtime.Caller(1)
if !ok {
panic("unable to determine the current file path")
}
return path.Dir(filename)
}
// use the convention to replace /[certificatename].[crt|key] with the full path
// this helps reduce the noise in test configurations and also allow this
// to run in any path
func prependCaddyFilePath(rawConfig string) string {
r := matchKey.ReplaceAllString(rawConfig, getIntegrationDir()+"$1")
r = matchCert.ReplaceAllString(r, getIntegrationDir()+"$1")
return r
}
// CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally
func CreateTestingTransport() *http.Transport {
dialer := net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 5 * time.Second,
DualStack: true,
}
dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
parts := strings.Split(addr, ":")
destAddr := fmt.Sprintf("127.0.0.1:%s", parts[1])
log.Printf("caddytest: redirecting the dialer from %s to %s", addr, destAddr)
return dialer.DialContext(ctx, network, destAddr)
}
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec
}
}
// AssertLoadError will load a config and expect an error
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
tc := NewTester(t)
err := tc.initServer(rawConfig, configType)
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
}
}
// AssertRedirect makes a request and asserts the redirection happens
func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
// using the existing client, we override the check redirect policy for this test
old := tc.Client.CheckRedirect
tc.Client.CheckRedirect = redirectPolicyFunc
defer func() { tc.Client.CheckRedirect = old }()
resp, err := tc.Client.Get(requestURI)
if err != nil {
tc.t.Errorf("failed to call server %s", err)
return nil
}
if expectedStatusCode != resp.StatusCode {
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode)
}
loc, err := resp.Location()
if err != nil {
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err)
}
if loc == nil && expectedToLocation != "" {
tc.t.Errorf("requesting \"%s\" expected a Location header, but didn't get one", requestURI)
}
if loc != nil {
if expectedToLocation != loc.String() {
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got \"%s\"", requestURI, expectedToLocation, loc.String())
}
}
return resp
}
// CompareAdapt adapts a config and then compares it against an expected result
func CompareAdapt(t *testing.T, filename, rawConfig string, adapterName string, expectedResponse string) bool {
cfgAdapter := caddyconfig.GetAdapter(adapterName)
if cfgAdapter == nil {
t.Logf("unrecognized config adapter '%s'", adapterName)
return false
}
options := make(map[string]any)
result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options)
if err != nil {
t.Logf("adapting config using %s adapter: %v", adapterName, err)
return false
}
// prettify results to keep tests human-manageable
var prettyBuf bytes.Buffer
err = json.Indent(&prettyBuf, result, "", "\t")
if err != nil {
return false
}
result = prettyBuf.Bytes()
if len(warnings) > 0 {
for _, w := range warnings {
t.Logf("warning: %s:%d: %s: %s", filename, w.Line, w.Directive, w.Message)
}
}
diff := difflib.Diff(
strings.Split(expectedResponse, "\n"),
strings.Split(string(result), "\n"))
// scan for failure
failed := false
for _, d := range diff {
if d.Delta != difflib.Common {
failed = true
break
}
}
if failed {
for _, d := range diff {
switch d.Delta {
case difflib.Common:
fmt.Printf(" %s\n", d.Payload)
case difflib.LeftOnly:
fmt.Printf(" - %s\n", d.Payload)
case difflib.RightOnly:
fmt.Printf(" + %s\n", d.Payload)
}
}
return false
}
return true
}
// AssertAdapt adapts a config and then tests it against an expected result
func AssertAdapt(t *testing.T, rawConfig string, adapterName string, expectedResponse string) {
ok := CompareAdapt(t, "Caddyfile", rawConfig, adapterName, expectedResponse)
if !ok {
t.Fail()
}
}
// Generic request functions
func applyHeaders(t *testing.T, req *http.Request, requestHeaders []string) {
requestContentType := ""
for _, requestHeader := range requestHeaders {
arr := strings.SplitAfterN(requestHeader, ":", 2)
k := strings.TrimRight(arr[0], ":")
v := strings.TrimSpace(arr[1])
if k == "Content-Type" {
requestContentType = v
}
t.Logf("Request header: %s => %s", k, v)
req.Header.Set(k, v)
}
if requestContentType == "" {
t.Logf("Content-Type header not provided")
}
}
// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
resp, err := tc.Client.Do(req)
if err != nil {
tc.t.Fatalf("failed to call server %s", err)
}
if expectedStatusCode != resp.StatusCode {
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.RequestURI, expectedStatusCode, resp.StatusCode)
}
return resp
}
// AssertResponse request a URI and assert the status code and the body contains a string
func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
resp := tc.AssertResponseCode(req, expectedStatusCode)
defer resp.Body.Close()
bytes, err := io.ReadAll(resp.Body)
if err != nil {
tc.t.Fatalf("unable to read the response body %s", err)
}
body := string(bytes)
if body != expectedBody {
tc.t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
}
return resp, body
}
// Verb specific test functions
// AssertGetResponse GET a URI and expect a statusCode and body text
func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("GET", requestURI, nil)
if err != nil {
tc.t.Fatalf("unable to create request %s", err)
}
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertDeleteResponse request a URI and expect a statusCode and body text
func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("DELETE", requestURI, nil)
if err != nil {
tc.t.Fatalf("unable to create request %s", err)
}
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPostResponseBody POST to a URI and assert the response code and body
func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("POST", requestURI, requestBody)
if err != nil {
tc.t.Errorf("failed to create request %s", err)
return nil, ""
}
applyHeaders(tc.t, req, requestHeaders)
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPutResponseBody PUT to a URI and assert the response code and body
func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("PUT", requestURI, requestBody)
if err != nil {
tc.t.Errorf("failed to create request %s", err)
return nil, ""
}
applyHeaders(tc.t, req, requestHeaders)
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
// AssertPatchResponseBody PATCH to a URI and assert the response code and body
func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
req, err := http.NewRequest("PATCH", requestURI, requestBody)
if err != nil {
tc.t.Errorf("failed to create request %s", err)
return nil, ""
}
applyHeaders(tc.t, req, requestHeaders)
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
}
+33
View File
@@ -0,0 +1,33 @@
package caddytest
import (
"strings"
"testing"
)
func TestReplaceCertificatePaths(t *testing.T) {
rawConfig := `a.caddy.localhost:9443 {
tls /caddy.localhost.crt /caddy.localhost.key {
}
redir / https://b.caddy.localhost:9443/version 301
respond /version 200 {
body "hello from a.caddy.localhost"
}
}`
r := prependCaddyFilePath(rawConfig)
if !strings.Contains(r, getIntegrationDir()+"/caddy.localhost.crt") {
t.Error("expected the /caddy.localhost.crt to be expanded to include the full path")
}
if !strings.Contains(r, getIntegrationDir()+"/caddy.localhost.key") {
t.Error("expected the /caddy.localhost.crt to be expanded to include the full path")
}
if !strings.Contains(r, "https://b.caddy.localhost:9443/version") {
t.Error("expected redirect uri to be unchanged")
}
}
+145
View File
@@ -0,0 +1,145 @@
package integration
import (
"net/http"
"testing"
"github.com/caddyserver/caddy/v2/caddytest"
)
func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
admin localhost:2999
skip_install_trust
http_port 9080
https_port 9443
}
localhost
respond "Yahaha! You found me!"
`, "caddyfile")
tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect)
}
func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
}
localhost:9443
respond "Yahaha! You found me!"
`, "caddyfile")
tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect)
}
func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
}
localhost:1234
respond "Yahaha! You found me!"
`, "caddyfile")
tester.AssertRedirect("http://localhost:9080/", "https://localhost:1234/", http.StatusPermanentRedirect)
}
func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
"admin": {
"listen": "localhost:2999"
},
"apps": {
"http": {
"http_port": 9080,
"https_port": 9443,
"servers": {
"ingress_server": {
"listen": [
":9080",
":9443"
],
"routes": [
{
"match": [
{
"host": ["localhost"]
}
]
}
]
}
}
},
"pki": {
"certificate_authorities": {
"local": {
"install_trust": false
}
}
}
}
}
`, "json")
tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect)
}
func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
local_certs
}
http://:9080 {
respond "Foo"
}
http://baz.localhost:9080 {
respond "Baz"
}
bar.localhost {
respond "Bar"
}
`, "caddyfile")
tester.AssertRedirect("http://bar.localhost:9080/", "https://bar.localhost/", http.StatusPermanentRedirect)
tester.AssertGetResponse("http://foo.localhost:9080/", 200, "Foo")
tester.AssertGetResponse("http://baz.localhost:9080/", 200, "Baz")
}
func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSite(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
skip_install_trust
admin localhost:2999
http_port 9080
https_port 9443
local_certs
}
http://:9080 {
respond "Foo"
}
bar.localhost {
respond "Bar"
}
`, "caddyfile")
tester.AssertRedirect("http://bar.localhost:9080/", "https://bar.localhost/", http.StatusPermanentRedirect)
tester.AssertGetResponse("http://foo.localhost:9080/", 200, "Foo")
tester.AssertGetResponse("http://baz.localhost:9080/", 200, "Foo")
}
@@ -0,0 +1,34 @@
{
auto_https disable_redirects
}
localhost
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"localhost"
]
}
],
"terminal": true
}
],
"automatic_https": {
"disable_redirects": true
}
}
}
}
}
}
@@ -0,0 +1,34 @@
{
auto_https ignore_loaded_certs
}
localhost
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"localhost"
]
}
],
"terminal": true
}
],
"automatic_https": {
"ignore_loaded_certificates": true
}
}
}
}
}
}
@@ -0,0 +1,37 @@
{
auto_https off
}
localhost
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"localhost"
]
}
],
"terminal": true
}
],
"tls_connection_policies": [
{}
],
"automatic_https": {
"disable": true
}
}
}
}
}
}
@@ -0,0 +1,29 @@
example.com {
bind tcp6/[::]
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
"tcp6/[::]:443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"terminal": true
}
]
}
}
}
}
}
@@ -0,0 +1,85 @@
:80
# All the options
encode gzip zstd {
minimum_length 256
match {
status 2xx 4xx 500
header Content-Type text/*
header Content-Type application/json*
header Content-Type application/javascript*
header Content-Type application/xhtml+xml*
header Content-Type application/atom+xml*
header Content-Type application/rss+xml*
header Content-Type image/svg+xml*
}
}
# Long way with a block for each encoding
encode {
zstd
gzip 5
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"handle": [
{
"encodings": {
"gzip": {},
"zstd": {}
},
"handler": "encode",
"match": {
"headers": {
"Content-Type": [
"text/*",
"application/json*",
"application/javascript*",
"application/xhtml+xml*",
"application/atom+xml*",
"application/rss+xml*",
"image/svg+xml*"
]
},
"status_code": [
2,
4,
500
]
},
"minimum_length": 256,
"prefer": [
"gzip",
"zstd"
]
},
{
"encodings": {
"gzip": {
"level": 5
},
"zstd": {}
},
"handler": "encode",
"prefer": [
"zstd",
"gzip"
]
}
]
}
]
}
}
}
}
}
@@ -0,0 +1,138 @@
example.com {
root * /srv
# Trigger errors for certain paths
error /private* "Unauthorized" 403
error /hidden* "Not found" 404
# Handle the error by serving an HTML page
handle_errors {
rewrite * /{http.error.status_code}.html
file_server
}
file_server
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "vars",
"root": "/srv"
}
]
},
{
"handle": [
{
"error": "Unauthorized",
"handler": "error",
"status_code": 403
}
],
"match": [
{
"path": [
"/private*"
]
}
]
},
{
"handle": [
{
"error": "Not found",
"handler": "error",
"status_code": 404
}
],
"match": [
{
"path": [
"/hidden*"
]
}
]
},
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile"
]
}
]
}
]
}
],
"terminal": true
}
],
"errors": {
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"group": "group0",
"handle": [
{
"handler": "rewrite",
"uri": "/{http.error.status_code}.html"
}
]
},
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile"
]
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
}
}
@@ -0,0 +1,114 @@
example.com
@a expression {http.error.status_code} == 400
abort @a
@b expression {http.error.status_code} == "401"
abort @b
@c expression {http.error.status_code} == `402`
abort @c
@d expression "{http.error.status_code} == 403"
abort @d
@e expression `{http.error.status_code} == 404`
abort @e
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"abort": true,
"handler": "static_response"
}
],
"match": [
{
"expression": "{http.error.status_code} == 400"
}
]
},
{
"handle": [
{
"abort": true,
"handler": "static_response"
}
],
"match": [
{
"expression": "{http.error.status_code} == \"401\""
}
]
},
{
"handle": [
{
"abort": true,
"handler": "static_response"
}
],
"match": [
{
"expression": "{http.error.status_code} == `402`"
}
]
},
{
"handle": [
{
"abort": true,
"handler": "static_response"
}
],
"match": [
{
"expression": "{http.error.status_code} == 403"
}
]
},
{
"handle": [
{
"abort": true,
"handler": "static_response"
}
],
"match": [
{
"expression": "{http.error.status_code} == 404"
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
}
@@ -0,0 +1,32 @@
:80
file_server {
disable_canonical_uris
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"handle": [
{
"canonical_uris": false,
"handler": "file_server",
"hide": [
"./Caddyfile"
]
}
]
}
]
}
}
}
}
}
@@ -0,0 +1,32 @@
:80
file_server {
pass_thru
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile"
],
"pass_thru": true
}
]
}
]
}
}
}
}
}
@@ -0,0 +1,41 @@
:80
file_server {
precompressed zstd br gzip
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile"
],
"precompressed": {
"br": {},
"gzip": {},
"zstd": {}
},
"precompressed_order": [
"zstd",
"br",
"gzip"
]
}
]
}
]
}
}
}
}
}
@@ -0,0 +1,112 @@
localhost
root * /srv
handle /nope* {
file_server {
status 403
}
}
handle /custom-status* {
file_server {
status {env.CUSTOM_STATUS}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"localhost"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "vars",
"root": "/srv"
}
]
},
{
"group": "group2",
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile"
],
"status_code": "{env.CUSTOM_STATUS}"
}
]
}
]
}
],
"match": [
{
"path": [
"/custom-status*"
]
}
]
},
{
"group": "group2",
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile"
],
"status_code": 403
}
]
}
]
}
],
"match": [
{
"path": [
"/nope*"
]
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
}
@@ -0,0 +1,111 @@
app.example.com {
forward_auth authelia:9091 {
uri /api/verify?rd=https://authelia.example.com
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}
reverse_proxy backend:8080
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"app.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handle_response": [
{
"match": {
"status_code": [
2
]
},
"routes": [
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"Remote-Email": [
"{http.reverse_proxy.header.Remote-Email}"
],
"Remote-Groups": [
"{http.reverse_proxy.header.Remote-Groups}"
],
"Remote-Name": [
"{http.reverse_proxy.header.Remote-Name}"
],
"Remote-User": [
"{http.reverse_proxy.header.Remote-User}"
]
}
}
}
]
}
]
}
],
"handler": "reverse_proxy",
"headers": {
"request": {
"set": {
"X-Forwarded-Method": [
"{http.request.method}"
],
"X-Forwarded-Uri": [
"{http.request.uri}"
]
}
}
},
"rewrite": {
"method": "GET",
"uri": "/api/verify?rd=https://authelia.example.com"
},
"upstreams": [
{
"dial": "authelia:9091"
}
]
},
{
"handler": "reverse_proxy",
"upstreams": [
{
"dial": "backend:8080"
}
]
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
}
@@ -0,0 +1,90 @@
:8881
forward_auth localhost:9000 {
uri /auth
copy_headers A>1 B C>3 {
D
E>5
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8881"
],
"routes": [
{
"handle": [
{
"handle_response": [
{
"match": {
"status_code": [
2
]
},
"routes": [
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"1": [
"{http.reverse_proxy.header.A}"
],
"3": [
"{http.reverse_proxy.header.C}"
],
"5": [
"{http.reverse_proxy.header.E}"
],
"B": [
"{http.reverse_proxy.header.B}"
],
"D": [
"{http.reverse_proxy.header.D}"
]
}
}
}
]
}
]
}
],
"handler": "reverse_proxy",
"headers": {
"request": {
"set": {
"X-Forwarded-Method": [
"{http.request.method}"
],
"X-Forwarded-Uri": [
"{http.request.uri}"
]
}
}
},
"rewrite": {
"method": "GET",
"uri": "/auth"
},
"upstreams": [
{
"dial": "localhost:9000"
}
]
}
]
}
]
}
}
}
}
}
@@ -0,0 +1,82 @@
{
debug
http_port 8080
https_port 8443
grace_period 5s
shutdown_delay 10s
default_sni localhost
order root first
storage file_system {
root /data
}
acme_ca https://example.com
acme_ca_root /path/to/ca.crt
ocsp_stapling off
email test@example.com
admin off
on_demand_tls {
ask https://example.com
interval 30s
burst 20
}
local_certs
key_type ed25519
}
:80
----------
{
"admin": {
"disabled": true
},
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
},
"storage": {
"module": "file_system",
"root": "/data"
},
"apps": {
"http": {
"http_port": 8080,
"https_port": 8443,
"grace_period": 5000000000,
"shutdown_delay": 10000000000,
"servers": {
"srv0": {
"listen": [
":80"
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"issuers": [
{
"module": "internal"
}
],
"key_type": "ed25519",
"disable_ocsp_stapling": true
}
],
"on_demand": {
"rate_limit": {
"interval": 30000000000,
"burst": 20
},
"ask": "https://example.com"
}
},
"disable_ocsp_stapling": true
}
}
}
@@ -0,0 +1,93 @@
{
debug
http_port 8080
https_port 8443
default_sni localhost
order root first
storage file_system {
root /data
}
acme_ca https://example.com
acme_eab {
key_id 4K2scIVbBpNd-78scadB2g
mac_key abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh
}
acme_ca_root /path/to/ca.crt
email test@example.com
admin off
on_demand_tls {
ask https://example.com
interval 30s
burst 20
}
storage_clean_interval 7d
renew_interval 1d
ocsp_interval 2d
key_type ed25519
}
:80
----------
{
"admin": {
"disabled": true
},
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
},
"storage": {
"module": "file_system",
"root": "/data"
},
"apps": {
"http": {
"http_port": 8080,
"https_port": 8443,
"servers": {
"srv0": {
"listen": [
":80"
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"issuers": [
{
"ca": "https://example.com",
"email": "test@example.com",
"external_account": {
"key_id": "4K2scIVbBpNd-78scadB2g",
"mac_key": "abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
},
"module": "acme",
"trusted_roots_pem_files": [
"/path/to/ca.crt"
]
}
],
"key_type": "ed25519"
}
],
"on_demand": {
"rate_limit": {
"interval": 30000000000,
"burst": 20
},
"ask": "https://example.com"
},
"ocsp_interval": 172800000000000,
"renew_interval": 86400000000000,
"storage_clean_interval": 604800000000000
}
}
}
}
@@ -0,0 +1,83 @@
{
debug
http_port 8080
https_port 8443
default_sni localhost
order root first
storage file_system {
root /data
}
acme_ca https://example.com
acme_ca_root /path/to/ca.crt
email test@example.com
admin {
origins localhost:2019 [::1]:2019 127.0.0.1:2019 192.168.10.128
}
on_demand_tls {
ask https://example.com
interval 30s
burst 20
}
local_certs
key_type ed25519
}
:80
----------
{
"admin": {
"listen": "localhost:2019",
"origins": [
"localhost:2019",
"[::1]:2019",
"127.0.0.1:2019",
"192.168.10.128"
]
},
"logging": {
"logs": {
"default": {
"level": "DEBUG"
}
}
},
"storage": {
"module": "file_system",
"root": "/data"
},
"apps": {
"http": {
"http_port": 8080,
"https_port": 8443,
"servers": {
"srv0": {
"listen": [
":80"
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"issuers": [
{
"module": "internal"
}
],
"key_type": "ed25519"
}
],
"on_demand": {
"rate_limit": {
"interval": 30000000000,
"burst": 20
},
"ask": "https://example.com"
}
}
}
}
}
@@ -0,0 +1,45 @@
{
debug
}
:8881 {
log {
format console
}
}
----------
{
"logging": {
"logs": {
"default": {
"level": "DEBUG",
"exclude": [
"http.log.access.log0"
]
},
"log0": {
"encoder": {
"format": "console"
},
"level": "DEBUG",
"include": [
"http.log.access.log0"
]
}
}
},
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8881"
],
"logs": {
"default_logger_name": "log0"
}
}
}
}
}
}
@@ -0,0 +1,54 @@
{
default_bind tcp4/0.0.0.0 tcp6/[::]
}
example.com {
}
example.org:12345 {
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
"tcp4/0.0.0.0:12345",
"tcp6/[::]:12345"
],
"routes": [
{
"match": [
{
"host": [
"example.org"
]
}
],
"terminal": true
}
]
},
"srv1": {
"listen": [
"tcp4/0.0.0.0:443",
"tcp6/[::]:443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"terminal": true
}
]
}
}
}
}
}
@@ -0,0 +1,77 @@
{
log {
output file caddy.log
include some-log-source
exclude admin.api admin2.api
}
log custom-logger {
output file caddy.log
level WARN
include custom-log-source
}
}
:8884 {
log {
format json
output file access.log
}
}
----------
{
"logging": {
"logs": {
"custom-logger": {
"writer": {
"filename": "caddy.log",
"output": "file"
},
"level": "WARN",
"include": [
"custom-log-source"
]
},
"default": {
"writer": {
"filename": "caddy.log",
"output": "file"
},
"include": [
"some-log-source"
],
"exclude": [
"admin.api",
"admin2.api",
"custom-log-source",
"http.log.access.log0"
]
},
"log0": {
"writer": {
"filename": "access.log",
"output": "file"
},
"encoder": {
"format": "json"
},
"include": [
"http.log.access.log0"
]
}
}
},
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"logs": {
"default_logger_name": "log0"
}
}
}
}
}
}
@@ -0,0 +1,18 @@
{
log {
output file foo.log
}
}
----------
{
"logging": {
"logs": {
"default": {
"writer": {
"filename": "foo.log",
"output": "file"
}
}
}
}
}
@@ -0,0 +1,35 @@
{
log custom-logger {
format filter {
wrap console
fields {
request>remote_ip ip_mask {
ipv4 24
ipv6 32
}
}
}
}
}
----------
{
"logging": {
"logs": {
"custom-logger": {
"encoder": {
"fields": {
"request\u003eremote_ip": {
"filter": "ip_mask",
"ipv4_cidr": 24,
"ipv6_cidr": 32
}
},
"format": "filter",
"wrap": {
"format": "console"
}
}
}
}
}
}
@@ -0,0 +1,26 @@
{
log first {
output file foo.log
}
log second {
format json
}
}
----------
{
"logging": {
"logs": {
"first": {
"writer": {
"filename": "foo.log",
"output": "file"
}
},
"second": {
"encoder": {
"format": "json"
}
}
}
}
}
@@ -0,0 +1,56 @@
{
preferred_chains smallest
}
example.com
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"example.com"
]
}
],
"terminal": true
}
]
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"example.com"
],
"issuers": [
{
"module": "acme",
"preferred_chains": {
"smallest": true
}
},
{
"module": "zerossl",
"preferred_chains": {
"smallest": true
}
}
]
}
]
}
}
}
}
@@ -0,0 +1,168 @@
{
skip_install_trust
pki {
ca {
name "Local"
root_cn "Custom Local Root Name"
intermediate_cn "Custom Local Intermediate Name"
root {
cert /path/to/cert.pem
key /path/to/key.pem
format pem_file
}
intermediate {
cert /path/to/cert.pem
key /path/to/key.pem
format pem_file
}
}
ca foo {
name "Foo"
root_cn "Custom Foo Root Name"
intermediate_cn "Custom Foo Intermediate Name"
}
}
}
a.example.com {
tls internal
}
acme.example.com {
acme_server {
ca foo
}
}
acme-bar.example.com {
acme_server {
ca bar
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"acme-bar.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "bar",
"handler": "acme_server"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"acme.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "foo",
"handler": "acme_server"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"a.example.com"
]
}
],
"terminal": true
}
]
}
}
},
"pki": {
"certificate_authorities": {
"bar": {
"install_trust": false
},
"foo": {
"name": "Foo",
"root_common_name": "Custom Foo Root Name",
"intermediate_common_name": "Custom Foo Intermediate Name",
"install_trust": false
},
"local": {
"name": "Local",
"root_common_name": "Custom Local Root Name",
"intermediate_common_name": "Custom Local Intermediate Name",
"install_trust": false,
"root": {
"certificate": "/path/to/cert.pem",
"private_key": "/path/to/key.pem",
"format": "pem_file"
},
"intermediate": {
"certificate": "/path/to/cert.pem",
"private_key": "/path/to/key.pem",
"format": "pem_file"
}
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"acme-bar.example.com",
"acme.example.com"
]
},
{
"subjects": [
"a.example.com"
],
"issuers": [
{
"module": "internal"
}
]
}
]
}
}
}
}
@@ -0,0 +1,82 @@
{
servers {
timeouts {
idle 90s
}
strict_sni_host insecure_off
}
servers :80 {
timeouts {
idle 60s
}
}
servers :443 {
timeouts {
idle 30s
}
strict_sni_host
}
}
foo.com {
}
http://bar.com {
}
:8080 {
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"idle_timeout": 30000000000,
"routes": [
{
"match": [
{
"host": [
"foo.com"
]
}
],
"terminal": true
}
],
"strict_sni_host": true
},
"srv1": {
"listen": [
":80"
],
"idle_timeout": 60000000000,
"routes": [
{
"match": [
{
"host": [
"bar.com"
]
}
],
"terminal": true
}
]
},
"srv2": {
"listen": [
":8080"
],
"idle_timeout": 90000000000,
"strict_sni_host": false
}
}
}
}
}
@@ -0,0 +1,71 @@
{
servers {
listener_wrappers {
http_redirect
tls
}
timeouts {
read_body 30s
read_header 30s
write 30s
idle 30s
}
max_header_size 100MB
log_credentials
protocols h1 h2 h2c h3
strict_sni_host
}
}
foo.com {
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"listener_wrappers": [
{
"wrapper": "http_redirect"
},
{
"wrapper": "tls"
}
],
"read_timeout": 30000000000,
"read_header_timeout": 30000000000,
"write_timeout": 30000000000,
"idle_timeout": 30000000000,
"max_header_bytes": 100000000,
"routes": [
{
"match": [
{
"host": [
"foo.com"
]
}
],
"terminal": true
}
],
"strict_sni_host": true,
"logs": {
"should_log_credentials": true
},
"protocols": [
"h1",
"h2",
"h2c",
"h3"
]
}
}
}
}
}
@@ -0,0 +1,52 @@
:80
handle_path /api/v1/* {
respond "API v1"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"match": [
{
"path": [
"/api/v1/*"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "rewrite",
"strip_path_prefix": "/api/v1"
}
]
},
{
"handle": [
{
"body": "API v1",
"handler": "static_response"
}
]
}
]
}
]
}
]
}
}
}
}
}
@@ -0,0 +1,105 @@
:80 {
handle /api/* {
respond "api"
}
handle_path /static/* {
respond "static"
}
handle {
respond "handle"
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"group": "group3",
"match": [
{
"path": [
"/static/*"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "rewrite",
"strip_path_prefix": "/static"
}
]
},
{
"handle": [
{
"body": "static",
"handler": "static_response"
}
]
}
]
}
]
},
{
"group": "group3",
"match": [
{
"path": [
"/api/*"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "api",
"handler": "static_response"
}
]
}
]
}
]
},
{
"group": "group3",
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "handle",
"handler": "static_response"
}
]
}
]
}
]
}
]
}
}
}
}
}
@@ -0,0 +1,147 @@
:80 {
header Denis "Ritchie"
header +Edsger "Dijkstra"
header ?John "von Neumann"
header -Wolfram
header {
Grace: "Hopper" # some users habitually suffix field names with a colon
+Ray "Solomonoff"
?Tim "Berners-Lee"
defer
}
@images path /images/*
header @images {
Cache-Control "public, max-age=3600, stale-while-revalidate=86400"
}
header {
+Link "Foo"
+Link "Bar"
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"match": [
{
"path": [
"/images/*"
]
}
],
"handle": [
{
"handler": "headers",
"response": {
"set": {
"Cache-Control": [
"public, max-age=3600, stale-while-revalidate=86400"
]
}
}
}
]
},
{
"handle": [
{
"handler": "headers",
"response": {
"set": {
"Denis": [
"Ritchie"
]
}
}
},
{
"handler": "headers",
"response": {
"add": {
"Edsger": [
"Dijkstra"
]
}
}
},
{
"handler": "headers",
"response": {
"require": {
"headers": {
"John": null
}
},
"set": {
"John": [
"von Neumann"
]
}
}
},
{
"handler": "headers",
"response": {
"deferred": true,
"delete": [
"Wolfram"
]
}
},
{
"handler": "headers",
"response": {
"add": {
"Ray": [
"Solomonoff"
]
},
"deferred": true,
"set": {
"Grace": [
"Hopper"
]
}
}
},
{
"handler": "headers",
"response": {
"require": {
"headers": {
"Tim": null
}
},
"set": {
"Tim": [
"Berners-Lee"
]
}
}
},
{
"handler": "headers",
"response": {
"add": {
"Link": [
"Foo",
"Bar"
]
}
}
}
]
}
]
}
}
}
}
}
@@ -0,0 +1,45 @@
# https://github.com/caddyserver/caddy/issues/3977
http://* {
respond "Hello, world!"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"match": [
{
"host": [
"*"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Hello, world!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
}
@@ -0,0 +1,37 @@
:80 {
respond /version 200 {
body "hello from localhost"
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"routes": [
{
"match": [
{
"path": [
"/version"
]
}
],
"handle": [
{
"body": "hello from localhost",
"handler": "static_response",
"status_code": 200
}
]
}
]
}
}
}
}
}

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