* caddyauth: isolate provider responses to prevent cross-provider clobbering
When multiple authentication providers are configured, each was handed the
real ResponseWriter, so a failing provider that wrote to the response (a
401 challenge or a login redirect) could clobber the response of another
provider or of the successful handler chain. Because provider map iteration
order is randomized, which provider's side effects won was nondeterministic.
A single provider now receives the real ResponseWriter unchanged — no
buffering, and Flusher/Hijacker/Pusher/ReaderFrom preserved exactly as
before. Only with multiple providers does each get its own buffered writer;
those writers embed caddyhttp.ResponseWriterWrapper so the underlying
capabilities remain type-assertable via http.ResponseController (Flush is
suppressed while buffering so a provider cannot prematurely commit the
response), and the buffered body is size-capped to avoid unbounded memory.
On success the winning provider's headers (e.g. a Set-Cookie) are copied to
the real writer and the chain proceeds. On total failure one provider's
challenge headers are applied (a redirect is sent as a full response),
otherwise the auth error is returned so handle_errors runs and a
header-only challenge (like basic auth) still returns 401.
Fixes#5190
* caddyauth: return total consumed bytes and drain error from ReadFrom
ReadFrom drained a source that exceeded the buffer cap but reported only
the bytes retained in the buffer and dropped any error from the drain,
violating the io.ReaderFrom contract: a caller such as io.Copy would see
fewer bytes than were actually consumed from the source, and a read
failure during the drain was silently swallowed. Return the
retained-plus-drained total and propagate the drain error.
* caddyauth: preserve Flusher and Hijacker on the buffered writer
The buffered writer used for multi-provider isolation embeds
caddyhttp.ResponseWriterWrapper, which promotes only Header, Write and
WriteHeader from the wrapped ResponseWriter and adds Push, ReadFrom and
Unwrap. Flush and Hijack were therefore reachable only through
http.ResponseController; a provider doing a plain w.(http.Flusher) or
w.(http.Hijacker) assertion silently lost them once a second provider was
configured.
Declare both on bufferedResponseWriter. Flush is a no-op so a provider
cannot prematurely commit a buffered response, and FlushError keeps the
same suppression for ResponseController, which prefers it over Flush.
Hijack delegates through the embedded wrapper, mirroring
responseRecorder.Hijack in the caddyhttp package.
The existing capability test only probed via http.ResponseController,
which is why this went unnoticed. It now asserts each capability both by
direct type assertion and through the controller, and uses two
non-authenticating providers so the probe is guaranteed to run — map
iteration order previously allowed a succeeding provider to break out of
the loop before the probe executed.
Also replace two header copy loops with maps.Copy, fixing the mapsloop
lint failures.
Header.Get/Set re-canonicalize and allocate whenever the passed key
isn't already in canonical MIME header form. Sec-WebSocket-Key,
WWW-Authenticate, and content-type all miss the fast path; switch to
their canonical forms (Sec-Websocket-Key, Www-Authenticate,
Content-Type).
Enable the canonicalheader linter to catch future regressions,
excluded in test files since those already use non-canonical casing
in several places without a perf-sensitive path behind them.
* feat: add bcrypt cost parameter to hash-password
* revert: typos
* refactor: take the cost out of interface
* fix: default bcrypt cost to 14
* fix: follow bcrypt library for min and max cost
* doc: mention defaultBcryptCost in cost parameter description
* chore: gci format
* fix: more specific bcrypt cost algorithm flag
* feat: bcrypt cost provisioning
* Revert "feat: bcrypt cost provisioning"
This reverts commit e09d4bd036.
* chore: gci format
* chore: gci format
* chore: gci format
* chore: golangcilint fmted
---------
Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* caddyauth: Set authentication provider error in placeholder for handle_errors directive
* caddyauth: Simplify error placeholder setting for authentication provider
* use gofmput to format code
* use gci to format imports
* reconfigure gci
* linter autofixes
* rearrange imports a little
* export GOOS=windows golangci-lint run ./... --fix
* update quic-go to v0.37.0
* Bump to Go 1.20
* Bump golangci-lint version, yml syntax consistency
* Use skip-pkg-cache workaround
* Workaround needed for both?
* Seeding weakrand is no longer necessary
---------
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
* cmd: Expand cobra support
* Convert commands to cobra, add short flags
* Fix version command typo
Co-authored-by: Emily Lange <git@indeednotjames.com>
* Apply suggestions from code review
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
---------
Co-authored-by: Emily Lange <git@indeednotjames.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
* caddyauth: Add singleflight for basic auth
* Fixes#5338
* it occurred the thunder herd problem like this https://medium.com/@mhrlife/avoid-duplicate-requests-while-filling-cache-98c687879f59
* Update modules/caddyhttp/caddyauth/basicauth.go
Fix comment
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>
* reverseproxy: Mask the WS close message when we're the client
* weakrand
* Bump golangci-lint version so path ignores work on Windows
* gofmt
* ugh, gofmt everything, I guess
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.
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"
* 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>
* 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
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!
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.
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>
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.
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.