Compare commits

..

23 Commits

Author SHA1 Message Date
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
63 changed files with 746 additions and 451 deletions
+3 -4
View File
@@ -68,14 +68,13 @@ builds:
signs:
- cmd: cosign
signature: "${artifact}.sig"
certificate: '{{ trimsuffix .Env.artifact ".tar.gz" }}.pem'
certificate: '{{ trimsuffix (trimsuffix .Env.artifact ".zip") ".tar.gz" }}.pem'
args: ["sign-blob", "--output-signature=${signature}", "--output-certificate", "${certificate}", "${artifact}"]
artifacts: all
sboms:
- artifacts: binary
# defaults to
# documents:
# - "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"
documents:
- '{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{if .Arm}}v{{ .Arm }}{{end}}.sbom'
cmd: syft
args: ["$artifact", "--file", "${document}", "--output", "cyclonedx-json"]
archives:
+1 -1
View File
@@ -161,7 +161,7 @@ func (fooModule) Stop() error { return nil }
func TestETags(t *testing.T) {
RegisterModule(fooModule{})
if err := Load([]byte(`{"apps": {"foo": {"strField": "abc", "intField": 0}}}`), true); err != nil {
if err := Load([]byte(`{"admin": {"listen": "localhost:2999"}, "apps": {"foo": {"strField": "abc", "intField": 0}}}`), true); err != nil {
t.Fatalf("loading: %s", err)
}
+16 -1
View File
@@ -31,6 +31,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/caddyserver/caddy/v2/notify"
@@ -676,6 +677,10 @@ func Validate(cfg *Config) error {
// Errors are logged along the way, and an appropriate exit
// code is emitted.
func exitProcess(ctx context.Context, logger *zap.Logger) {
// let the rest of the program know we're quitting
atomic.StoreInt32(exiting, 1)
// give the OS or service/process manager our 2 weeks' notice: we quit
if err := notify.Stopping(); err != nil {
Log().Error("unable to notify service manager of stopping state", zap.Error(err))
}
@@ -739,6 +744,12 @@ func exitProcess(ctx context.Context, logger *zap.Logger) {
}()
}
var exiting = new(int32) // accessed atomically
// Exiting returns true if the process is exiting.
// EXPERIMENTAL API: subject to change or removal.
func Exiting() bool { return atomic.LoadInt32(exiting) == 1 }
// Duration can be an integer or a string. An integer is
// interpreted as nanoseconds. If a string, it is a Go
// time.Duration value such as `300ms`, `1.5h`, or `2h45m`;
@@ -763,8 +774,12 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
// ParseDuration parses a duration string, adding
// support for the "d" unit meaning number of days,
// where a day is assumed to be 24h.
// where a day is assumed to be 24h. The maximum
// input string length is 1024.
func ParseDuration(s string) (time.Duration, error) {
if len(s) > 1024 {
return 0, fmt.Errorf("parsing duration: input string too long")
}
var inNumber bool
var numStart int
for i := 0; i < len(s); i++ {
+4 -1
View File
@@ -153,7 +153,10 @@ func Format(input []byte) []byte {
openBraceWritten = true
nextLine()
newLines = 0
nesting++
// prevent infinite nesting from ridiculous inputs (issue #4169)
if nesting < 10 {
nesting++
}
}
switch {
+24 -12
View File
@@ -36,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,
@@ -219,7 +219,7 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
}
// the bind directive specifies hosts, but is optional
// 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)...)
@@ -234,11 +234,23 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
// use a map to prevent duplication
listeners := make(map[string]struct{})
for _, host := range lnHosts {
// host can have network + host (e.g. "tcp6/localhost") but
// will/should not have port information because this usually
// comes from the bind directive, so we append the port
addr, err := caddy.ParseNetworkAddress(host + ":" + lnPort)
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)
}
+48 -32
View File
@@ -48,12 +48,12 @@ func init() {
RegisterHandlerDirective("handle", parseHandle)
RegisterDirective("handle_errors", parseHandleErrors)
RegisterDirective("log", parseLog)
RegisterHandlerDirective("skip_log", parseSkipLog)
}
// parseBind parses the bind directive. Syntax:
//
// bind <addresses...>
//
// bind <addresses...>
func parseBind(h Helper) ([]ConfigValue, error) {
var lnHosts []string
for h.Next() {
@@ -64,28 +64,28 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// 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> [...]
// }
//
// 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) {
cp := new(caddytls.ConnectionPolicy)
var fileLoader caddytls.FileLoader
@@ -395,6 +395,12 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
}
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())
}
@@ -515,8 +521,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
// parseRoot parses the root directive. Syntax:
//
// root [<matcher>] <path>
//
// root [<matcher>] <path>
func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
var root string
for h.Next() {
@@ -694,12 +699,11 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
// parseLog parses the log directive. Syntax:
//
// log {
// output <writer_module> ...
// format <encoder_module> ...
// level <level>
// }
//
// log {
// output <writer_module> ...
// format <encoder_module> ...
// level <level>
// }
func parseLog(h Helper) ([]ConfigValue, error) {
return parseLogHelper(h, nil)
}
@@ -858,3 +862,15 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
}
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
}
+1
View File
@@ -42,6 +42,7 @@ var directiveOrder = []string{
"map",
"vars",
"root",
"skip_log",
"header",
"copy_response_headers", // only in reverse_proxy's handle_response
+2 -2
View File
@@ -223,7 +223,7 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
hasDefaultLog = true
}
if _, ok := options["debug"]; ok && ncl.log.Level == "" {
ncl.log.Level = "DEBUG"
ncl.log.Level = zap.DebugLevel.CapitalString()
}
customLogs = append(customLogs, ncl)
}
@@ -241,7 +241,7 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
if _, ok := options["debug"]; ok {
customLogs = append(customLogs, namedCustomLog{
name: "default",
log: &caddy.CustomLog{Level: "DEBUG"},
log: &caddy.CustomLog{Level: zap.DebugLevel.CapitalString()},
})
}
}
+7 -7
View File
@@ -421,13 +421,13 @@ func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
// parseLogOptions parses the global log option. Syntax:
//
// log [name] {
// output <writer_module> ...
// format <encoder_module> ...
// level <level>
// include <namespaces...>
// exclude <namespaces...>
// }
// 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.
+12 -1
View File
@@ -43,6 +43,7 @@ type serverOptions struct {
Protocols []string
StrictSNIHost *bool
ShouldLogCredentials bool
Metrics *caddyhttp.Metrics
}
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
@@ -161,7 +162,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
}
serverOpts.Protocols = append(serverOpts.Protocols, proto)
}
if d.NextBlock(0) {
if nesting := d.Nesting(); d.NextBlock(nesting) {
return nil, d.ArgErr()
}
@@ -175,6 +176,15 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
}
serverOpts.StrictSNIHost = &boolVal
case "metrics":
if d.NextArg() {
return nil, d.ArgErr()
}
if d.NextBlock(0) {
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")
@@ -259,6 +269,7 @@ func applyServerOptions(
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{}
+2 -2
View File
@@ -113,7 +113,7 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
return nil, err
}
for _, warn := range warnings {
ctx.Logger(hl).Warn(warn.String())
ctx.Logger().Warn(warn.String())
}
return result, nil
@@ -129,7 +129,7 @@ func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, error) {
// client authentication
if hl.TLS.UseServerIdentity {
certs, err := ctx.IdentityCredentials(ctx.Logger(hl))
certs, err := ctx.IdentityCredentials(ctx.Logger())
if err != nil {
return nil, fmt.Errorf("getting server identity credentials: %v", err)
}
+1 -1
View File
@@ -43,7 +43,7 @@ type Defaults struct {
// Default testing values
var Default = Defaults{
AdminPort: 2019,
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,
@@ -12,8 +12,8 @@
}
max_header_size 100MB
log_credentials
strict_sni_host
protocols h1 h2 h2c h3
strict_sni_host
}
}
@@ -1,5 +1,7 @@
http://localhost:2020 {
log
skip_log /first-hidden*
skip_log /second-hidden*
respond 200
}
@@ -28,6 +30,36 @@ http://localhost:2020 {
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "vars",
"skip_log": true
}
],
"match": [
{
"path": [
"/second-hidden*"
]
}
]
},
{
"handle": [
{
"handler": "vars",
"skip_log": true
}
],
"match": [
{
"path": [
"/first-hidden*"
]
}
]
},
{
"handle": [
{
+4
View File
@@ -16,6 +16,7 @@ func TestRespond(t *testing.T) {
{
http_port 9080
https_port 9443
grace_period 1
}
localhost:9080 {
@@ -37,6 +38,7 @@ func TestRedirect(t *testing.T) {
{
http_port 9080
https_port 9443
grace_period 1
}
localhost:9080 {
@@ -86,6 +88,7 @@ func TestReadCookie(t *testing.T) {
{
http_port 9080
https_port 9443
grace_period 1
}
localhost:9080 {
@@ -109,6 +112,7 @@ func TestReplIndex(t *testing.T) {
{
http_port 9080
https_port 9443
grace_period 1
}
localhost:9080 {
+1
View File
@@ -13,6 +13,7 @@ func TestBrowse(t *testing.T) {
{
http_port 9080
https_port 9443
grace_period 1
}
http://localhost:9080 {
file_server browse
+1
View File
@@ -13,6 +13,7 @@ func TestMap(t *testing.T) {
tester.InitServer(`{
http_port 9080
https_port 9443
grace_period 1
}
localhost:9080 {
@@ -18,6 +18,7 @@ func TestSRVReverseProxy(t *testing.T) {
{
"apps": {
"http": {
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
@@ -50,6 +51,7 @@ func TestSRVWithDial(t *testing.T) {
{
"apps": {
"http": {
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
@@ -115,6 +117,7 @@ func TestDialWithPlaceholderUnix(t *testing.T) {
{
"apps": {
"http": {
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
@@ -156,6 +159,7 @@ func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
{
"apps": {
"http": {
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
@@ -239,6 +243,7 @@ func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
{
"apps": {
"http": {
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
@@ -321,6 +326,7 @@ func TestSRVWithActiveHealthcheck(t *testing.T) {
{
"apps": {
"http": {
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
+3
View File
@@ -15,6 +15,7 @@ func TestDefaultSNI(t *testing.T) {
"http": {
"http_port": 9080,
"https_port": 9443,
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
@@ -112,6 +113,7 @@ func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
"http": {
"http_port": 9080,
"https_port": 9443,
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
@@ -212,6 +214,7 @@ func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
"http": {
"http_port": 9080,
"https_port": 9443,
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
+2
View File
@@ -27,6 +27,7 @@ func TestH2ToH2CStream(t *testing.T) {
"http": {
"http_port": 9080,
"https_port": 9443,
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
@@ -216,6 +217,7 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
"http": {
"http_port": 9080,
"https_port": 9443,
"grace_period": 1,
"servers": {
"srv0": {
"listen": [
+1 -1
View File
@@ -679,7 +679,7 @@ func DetermineAdminAPIAddress(address string, config []byte, configFile, configA
return "", err
}
if loadedConfigFile == "" {
return "", fmt.Errorf("no config file to load")
return "", fmt.Errorf("no config file to load; either use --config flag or ensure Caddyfile exists in current directory")
}
}
+7 -2
View File
@@ -41,10 +41,15 @@ func init() {
// set a fitting User-Agent for ACME requests
version, _ := caddy.Version()
cleanModVersion := strings.TrimPrefix(version, "v")
certmagic.UserAgent = "Caddy/" + cleanModVersion
ua := "Caddy/" + cleanModVersion
if uaEnv, ok := os.LookupEnv("USERAGENT"); ok {
ua = uaEnv + " " + ua
}
certmagic.UserAgent = ua
// by using Caddy, user indicates agreement to CA terms
// (very important, or ACME account creation will fail!)
// (very important, as Caddy is often non-interactive
// and thus ACME account creation will fail!)
certmagic.DefaultACME.Agreed = true
}
+25 -18
View File
@@ -442,10 +442,27 @@ func (ctx Context) Storage() certmagic.Storage {
return ctx.cfg.storage
}
// TODO: aw man, can I please change this?
// Logger returns a logger that can be used by mod.
func (ctx Context) Logger(mod Module) *zap.Logger {
// TODO: if mod is nil, use ctx.Module() instead...
// Logger returns a logger that is intended for use by the most
// recent module associated with the context. Callers should not
// pass in any arguments unless they want to associate with a
// different module; it panics if more than 1 value is passed in.
//
// Originally, this method's signature was `Logger(mod Module)`,
// requiring that an instance of a Caddy module be passsed in.
// However, that is no longer necessary, as the closest module
// most recently associated with the context will be automatically
// assumed. To prevent a sudden breaking change, this method's
// signature has been changed to be variadic, but we may remove
// the parameter altogether in the future. Callers should not
// pass in any argument. If there is valid need to specify a
// different module, please open an issue to discuss.
//
// PARTIALLY DEPRECATED: The Logger(module) form is deprecated and
// may be removed in the future. Do not pass in any arguments.
func (ctx Context) Logger(module ...Module) *zap.Logger {
if len(module) > 1 {
panic("more than 1 module passed in")
}
if ctx.cfg == nil {
// often the case in tests; just use a dev logger
l, err := zap.NewDevelopment()
@@ -454,23 +471,13 @@ func (ctx Context) Logger(mod Module) *zap.Logger {
}
return l
}
mod := ctx.Module()
if len(module) > 0 {
mod = module[0]
}
return ctx.cfg.Logging.Logger(mod)
}
// TODO: use this
// // Logger returns a logger that can be used by the current module.
// func (ctx Context) Log() *zap.Logger {
// if ctx.cfg == nil {
// // often the case in tests; just use a dev logger
// l, err := zap.NewDevelopment()
// if err != nil {
// panic("config missing, unable to create dev logger: " + err.Error())
// }
// return l
// }
// return ctx.cfg.Logging.Logger(ctx.Module())
// }
// Modules returns the lineage of modules that this context provisioned,
// with the most recent/current module being last in the list.
func (ctx Context) Modules() []Module {
+2 -2
View File
@@ -15,7 +15,7 @@ import (
func ListenTimeout(network, addr string, keepAlivePeriod time.Duration) (net.Listener, error) {
// check to see if plugin provides listener
if ln, err := getListenerFromPlugin(network, addr); err != nil || ln != nil {
return pipeable(ln), err
return ln, err
}
lnKey := listenerKey(network, addr)
@@ -29,7 +29,7 @@ func ListenTimeout(network, addr string, keepAlivePeriod time.Duration) (net.Lis
}
return nil, err
}
return &sharedListener{Listener: pipeable(ln), key: lnKey}, nil
return &sharedListener{Listener: ln, key: lnKey}, nil
})
if err != nil {
return nil, err
+2 -3
View File
@@ -14,12 +14,11 @@ import (
func ListenTimeout(network, addr string, keepalivePeriod time.Duration) (net.Listener, error) {
// check to see if plugin provides listener
if ln, err := getListenerFromPlugin(network, addr); err != nil || ln != nil {
return pipeable(ln), err
return ln, err
}
config := &net.ListenConfig{Control: reusePort, KeepAlive: keepalivePeriod}
ln, err := config.Listen(context.Background(), network, addr)
return pipeable(ln), err
return config.Listen(context.Background(), network, addr)
}
func reusePort(network, address string, conn syscall.RawConn) error {
-62
View File
@@ -45,68 +45,6 @@ func Listen(network, addr string) (net.Listener, error) {
return ListenTimeout(network, addr, 0)
}
// pipeableListener wraps an underlying listener so
// that connections can be given to a server that is
// calling Accept().
type pipeableListener struct {
net.Listener
bridge chan connAccept
done chan struct{}
closed *int32 // accessed atomically
}
func (pln pipeableListener) Accept() (net.Conn, error) {
accept := <-pln.bridge
return accept.conn, accept.err
}
func (pln pipeableListener) Close() error {
if atomic.CompareAndSwapInt32(pln.closed, 0, 1) {
close(pln.done)
}
return pln.Listener.Close()
}
// pump pipes real connections from the underlying listener's
// Accept() up to the callers of our own Accept().
func (pln pipeableListener) pump() {
for {
select {
case <-pln.done:
return
default:
pln.Pipe(pln.Listener.Accept())
}
}
}
// Pipe gives a connection (or an error) to an active Accept() call
// on this listener.
func (pln pipeableListener) Pipe(conn net.Conn, err error) {
pln.bridge <- connAccept{conn, err}
}
// pipeable wraps listener so that it can be given connections
// for its caller/server to Accept() and use.
func pipeable(listener net.Listener) net.Listener {
if listener == nil {
return listener // don't start a goroutine
}
pln := pipeableListener{
Listener: listener,
bridge: make(chan connAccept),
done: make(chan struct{}),
closed: new(int32),
}
go pln.pump()
return pln
}
type connAccept struct {
conn net.Conn
err error
}
// getListenerFromPlugin returns a listener on the given network and address
// if a plugin has registered the network name. It may return (nil, nil) if
// no plugin can provide a listener.
+29 -12
View File
@@ -119,7 +119,7 @@ func (App) CaddyModule() caddy.ModuleInfo {
// Provision sets up the app.
func (app *App) Provision(ctx caddy.Context) error {
app.logger = ctx.Logger(app)
app.logger = ctx.Logger()
app.subscriptions = make(map[string]map[caddy.ModuleID][]Handler)
for _, sub := range app.Subscriptions {
@@ -201,6 +201,9 @@ func (app *App) On(eventName string, handler Handler) error {
// Emit creates and dispatches an event named eventName to all relevant handlers with
// the metadata data. Events are emitted and propagated synchronously. The returned Event
// value will have any additional information from the invoked handlers.
//
// Note that the data map is not copied, for efficiency. After Emit() is called, the
// data passed in should not be changed in other goroutines.
func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) Event {
logger := app.logger.With(zap.String("name", eventName))
@@ -212,11 +215,11 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
eventName = strings.ToLower(eventName)
e := Event{
Data: data,
id: id,
ts: time.Now(),
name: eventName,
origin: ctx.Module(),
data: data,
}
logger = logger.With(
@@ -244,12 +247,12 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
case "event.module":
return e.origin.CaddyModule().ID, true
case "event.data":
return e.data, true
return e.Data, true
}
if strings.HasPrefix(key, "event.data.") {
key = strings.TrimPrefix(key, "event.data.")
if val, ok := data[key]; ok {
if val, ok := e.Data[key]; ok {
return val, true
}
}
@@ -257,7 +260,7 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
return nil, false
})
logger.Debug("event", zap.Any("data", e.data))
logger.Debug("event", zap.Any("data", e.Data))
// invoke handlers bound to the event by name and also all events; this for loop
// iterates twice at most: once for the event name, once for "" (all events)
@@ -314,26 +317,40 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
}
// Event represents something that has happened or is happening.
// An Event value is not synchronized, so it should be copied if
// being used in goroutines.
//
// EXPERIMENTAL: As with the rest of this package, events are
// subject to change.
type Event struct {
id uuid.UUID
ts time.Time
name string
origin caddy.Module
data map[string]any
// If non-nil, the event has been aborted, meaning
// propagation has stopped to other handlers and
// the code should stop what it was doing. Emitters
// may choose to use this as a signal to adjust their
// code path appropriately.
Aborted error
// The data associated with the event. Usually the
// original emitter will be the only one to set or
// change these values, but the field is exported
// so handlers can have full access if needed.
// However, this map is not synchronized, so
// handlers must not use this map directly in new
// goroutines; instead, copy the map to use it in a
// goroutine.
Data map[string]any
id uuid.UUID
ts time.Time
name string
origin caddy.Module
}
// CloudEvent exports event e as a structure that, when
// serialized as JSON, is compatible with the
// CloudEvents spec.
func (e Event) CloudEvent() CloudEvent {
dataJSON, _ := json.Marshal(e.data)
dataJSON, _ := json.Marshal(e.Data)
return CloudEvent{
ID: e.id.String(),
Source: e.origin.CaddyModule().String(),
+55 -12
View File
@@ -160,7 +160,7 @@ func (app *App) Provision(ctx caddy.Context) error {
}
app.tlsApp = tlsAppIface.(*caddytls.TLS)
app.ctx = ctx
app.logger = ctx.Logger(app)
app.logger = ctx.Logger()
eventsAppIface, err := ctx.App("events")
if err != nil {
@@ -178,7 +178,9 @@ func (app *App) Provision(ctx caddy.Context) error {
}
// prepare each server
oldContext := ctx.Context
for srvName, srv := range app.Servers {
ctx.Context = context.WithValue(oldContext, ServerCtxKey, srv)
srv.name = srvName
srv.tlsApp = app.tlsApp
srv.events = eventsAppIface.(*caddyevents.App)
@@ -263,7 +265,7 @@ func (app *App) Provision(ctx caddy.Context) error {
// route handler so that important security checks are done, etc.
primaryRoute := emptyHandler
if srv.Routes != nil {
err := srv.Routes.ProvisionHandlers(ctx)
err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics)
if err != nil {
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
}
@@ -293,7 +295,7 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.IdleTimeout = defaultIdleTimeout
}
}
ctx.Context = oldContext
return nil
}
@@ -365,6 +367,7 @@ func (app *App) Start() error {
// this TLS config is used by the std lib to choose the actual TLS config for connections
// by looking through the connection policies to find the first one that matches
tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
srv.configureServer(srv.server)
// enable H2C if configured
if srv.protocol("h2c") {
@@ -502,22 +505,62 @@ func (app *App) Stop() error {
app.logger.Debug("servers shutting down with eternal grace period")
}
// shut down servers
for _, server := range app.Servers {
// goroutines aren't guaranteed to be scheduled right away,
// so we'll use one WaitGroup to wait for all the goroutines
// to start their server shutdowns, and another to wait for
// them to finish; we'll always block for them to start so
// that when we return the caller can be confident* that the
// old servers are no longer accepting new connections
// (* the scheduler might still pause them right before
// calling Shutdown(), but it's unlikely)
var startedShutdown, finishedShutdown sync.WaitGroup
// these will run in goroutines
stopServer := func(server *Server) {
defer finishedShutdown.Done()
startedShutdown.Done()
if err := server.server.Shutdown(ctx); err != nil {
app.logger.Error("server shutdown",
zap.Error(err),
zap.Strings("addresses", server.Listen))
}
}
stopH3Server := func(server *Server) {
defer finishedShutdown.Done()
startedShutdown.Done()
if server.h3server != nil {
// TODO: CloseGracefully, once implemented upstream (see https://github.com/lucas-clemente/quic-go/issues/2103)
if err := server.h3server.Close(); err != nil {
app.logger.Error("HTTP/3 server shutdown",
zap.Error(err),
zap.Strings("addresses", server.Listen))
}
if server.h3server == nil {
return
}
// TODO: CloseGracefully, once implemented upstream (see https://github.com/lucas-clemente/quic-go/issues/2103)
if err := server.h3server.Close(); err != nil {
app.logger.Error("HTTP/3 server shutdown",
zap.Error(err),
zap.Strings("addresses", server.Listen))
}
}
for _, server := range app.Servers {
startedShutdown.Add(2)
finishedShutdown.Add(2)
go stopServer(server)
go stopH3Server(server)
}
// block until all the goroutines have been run by the scheduler;
// this means that they have likely called Shutdown() by now
startedShutdown.Wait()
// if the process is exiting, we need to block here and wait
// for the grace periods to complete, otherwise the process will
// terminate before the servers are finished shutting down; but
// we don't really need to wait for the grace period to finish
// if the process isn't exiting (but note that frequent config
// reloads with long grace periods for a sustained length of time
// may deplete resources)
if caddy.Exiting() {
finishedShutdown.Wait()
}
return nil
+22 -12
View File
@@ -378,19 +378,29 @@ redirServersLoop:
// we'll create a new server for all the listener addresses
// that are unused and serve the remaining redirects from it
for _, srv := range app.Servers {
if srv.hasListenerAddress(redirServerAddr) {
// find the index of the route after the last route with a host
// matcher, then insert the redirects there, but before any
// user-defined catch-all routes
// see https://github.com/caddyserver/caddy/issues/3212
insertIndex := srv.findLastRouteWithHostMatcher()
srv.Routes = append(srv.Routes[:insertIndex], append(routes, srv.Routes[insertIndex:]...)...)
// append our catch-all route in case the user didn't define their own
srv.Routes = appendCatchAll(srv.Routes)
continue redirServersLoop
// only look at servers which listen on an address which
// we want to add redirects to
if !srv.hasListenerAddress(redirServerAddr) {
continue
}
// find the index of the route after the last route with a host
// matcher, then insert the redirects there, but before any
// user-defined catch-all routes
// see https://github.com/caddyserver/caddy/issues/3212
insertIndex := srv.findLastRouteWithHostMatcher()
// add the redirects at the insert index, except for when
// we have a catch-all for HTTPS, in which case the user's
// defined catch-all should take precedence. See #4829
if len(uniqueDomainsForCerts) != 0 {
srv.Routes = append(srv.Routes[:insertIndex], append(routes, srv.Routes[insertIndex:]...)...)
}
// append our catch-all route in case the user didn't define their own
srv.Routes = appendCatchAll(srv.Routes)
continue redirServersLoop
}
// no server with this listener address exists;
+1 -1
View File
@@ -56,7 +56,7 @@ func (Authentication) CaddyModule() caddy.ModuleInfo {
// Provision sets up a.
func (a *Authentication) Provision(ctx caddy.Context) error {
a.logger = ctx.Logger(a)
a.logger = ctx.Logger()
a.Providers = make(map[string]Authenticator)
mods, err := ctx.LoadModule(a, "ProvidersRaw")
if err != nil {
+1 -1
View File
@@ -92,7 +92,7 @@ func (ScryptHash) CaddyModule() caddy.ModuleInfo {
// Provision sets up s.
func (s *ScryptHash) Provision(ctx caddy.Context) error {
s.SetDefaults()
ctx.Logger(s).Warn("use of 'scrypt' is deprecated, please use 'bcrypt' instead")
ctx.Logger().Warn("use of 'scrypt' is deprecated, please use 'bcrypt' instead")
return nil
}
+8 -8
View File
@@ -90,7 +90,7 @@ func (m *MatchExpression) UnmarshalJSON(data []byte) error {
// Provision sets ups m.
func (m *MatchExpression) Provision(ctx caddy.Context) error {
m.log = ctx.Logger(m)
m.log = ctx.Logger()
// replace placeholders with a function call - this is just some
// light (and possibly naïve) syntactic sugar
@@ -294,13 +294,13 @@ type CELLibraryProducer interface {
// CELMatcherImpl creates a new cel.Library based on the following pieces of
// data:
//
// - macroName: the function name to be used within CEL. This will be a macro
// and not a function proper.
// - funcName: the function overload name generated by the CEL macro used to
// represent the matcher.
// - matcherDataTypes: the argument types to the macro.
// - fac: a matcherFactory implementation which converts from CEL constant
// values to a Matcher instance.
// - macroName: the function name to be used within CEL. This will be a macro
// and not a function proper.
// - funcName: the function overload name generated by the CEL macro used to
// represent the matcher.
// - matcherDataTypes: the argument types to the macro.
// - fac: a matcherFactory implementation which converts from CEL constant
// values to a Matcher instance.
//
// Note, macro names and function names must not collide with other macros or
// functions exposed within CEL expressions, or an error will be produced
+52 -76
View File
@@ -20,7 +20,6 @@
package encode
import (
"bytes"
"fmt"
"io"
"math"
@@ -160,13 +159,12 @@ func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter
// initResponseWriter initializes the responseWriter instance
// allocated in openResponseWriter, enabling mid-stack inlining.
func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
// The allocation of ResponseWriterWrapper might be optimized as well.
rw.ResponseWriterWrapper = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW}
if httpInterfaces, ok := wrappedRW.(caddyhttp.HTTPInterfaces); ok {
rw.HTTPInterfaces = httpInterfaces
} else {
rw.HTTPInterfaces = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW}
}
rw.encodingName = encodingName
rw.buf = buf
rw.config = enc
return rw
@@ -176,10 +174,9 @@ func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, w
// using the encoding represented by encodingName and
// configured by config.
type responseWriter struct {
*caddyhttp.ResponseWriterWrapper
caddyhttp.HTTPInterfaces
encodingName string
w Encoder
buf *bytes.Buffer
config *Encode
statusCode int
wroteHeader bool
@@ -206,28 +203,33 @@ func (rw *responseWriter) Flush() {
// to rw.Write (see bug in #4314)
return
}
rw.ResponseWriterWrapper.Flush()
rw.HTTPInterfaces.Flush()
}
// Write writes to the response. If the response qualifies,
// it is encoded using the encoder, which is initialized
// if not done so already.
func (rw *responseWriter) Write(p []byte) (int, error) {
var n, written int
var err error
// ignore zero data writes, probably head request
if len(p) == 0 {
return 0, nil
}
if rw.buf != nil && rw.config.MinLength > 0 {
written = rw.buf.Len()
_, err := rw.buf.Write(p)
if err != nil {
return 0, err
// sniff content-type and determine content-length
if !rw.wroteHeader && rw.config.MinLength > 0 {
var gtMinLength bool
if len(p) > rw.config.MinLength {
gtMinLength = true
} else if cl, err := strconv.Atoi(rw.Header().Get("Content-Length")); err == nil && cl > rw.config.MinLength {
gtMinLength = true
}
if gtMinLength {
if rw.Header().Get("Content-Type") == "" {
rw.Header().Set("Content-Type", http.DetectContentType(p))
}
rw.init()
}
rw.init()
p = rw.buf.Bytes()
defer func() {
bufPool.Put(rw.buf)
rw.buf = nil
}()
}
// before we write to the response, we need to make
@@ -236,63 +238,44 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
// and if so, that means we haven't written the
// header OR the default status code will be written
// by the standard library
if rw.statusCode > 0 {
rw.ResponseWriter.WriteHeader(rw.statusCode)
rw.statusCode = 0
if !rw.wroteHeader {
if rw.statusCode != 0 {
rw.HTTPInterfaces.WriteHeader(rw.statusCode)
} else {
rw.HTTPInterfaces.WriteHeader(http.StatusOK)
}
rw.wroteHeader = true
}
switch {
case rw.w != nil:
n, err = rw.w.Write(p)
default:
n, err = rw.ResponseWriter.Write(p)
if rw.w != nil {
return rw.w.Write(p)
} else {
return rw.HTTPInterfaces.Write(p)
}
n -= written
if n < 0 {
n = 0
}
return n, err
}
// Close writes any remaining buffered response and
// deallocates any active resources.
func (rw *responseWriter) Close() error {
var err error
// only attempt to write the remaining buffered response
// if there are any bytes left to write; otherwise, if
// the handler above us returned an error without writing
// anything, we'd write to the response when we instead
// should simply let the error propagate back down; this
// is why the check for rw.buf.Len() > 0 is crucial
if rw.buf != nil && rw.buf.Len() > 0 {
rw.init()
p := rw.buf.Bytes()
defer func() {
bufPool.Put(rw.buf)
rw.buf = nil
}()
switch {
case rw.w != nil:
_, err = rw.w.Write(p)
default:
_, err = rw.ResponseWriter.Write(p)
// didn't write, probably head request
if !rw.wroteHeader {
cl, err := strconv.Atoi(rw.Header().Get("Content-Length"))
if err == nil && cl > rw.config.MinLength {
rw.init()
}
if rw.statusCode != 0 {
rw.HTTPInterfaces.WriteHeader(rw.statusCode)
} else {
rw.HTTPInterfaces.WriteHeader(http.StatusOK)
}
} else if rw.statusCode != 0 {
// it is possible that a body was not written, and
// a header was not even written yet, even though
// we are closing; ensure the proper status code is
// written exactly once, or we risk breaking requests
// that rely on If-None-Match, for example
rw.ResponseWriter.WriteHeader(rw.statusCode)
rw.statusCode = 0
rw.wroteHeader = true
}
var err error
if rw.w != nil {
err2 := rw.w.Close()
if err2 != nil && err == nil {
err = err2
}
err = rw.w.Close()
rw.w.Reset(nil)
rw.config.writerPools[rw.encodingName].Put(rw.w)
rw.w = nil
}
@@ -302,16 +285,15 @@ func (rw *responseWriter) Close() error {
// init should be called before we write a response, if rw.buf has contents.
func (rw *responseWriter) init() {
if rw.Header().Get("Content-Encoding") == "" &&
rw.buf.Len() >= rw.config.MinLength &&
rw.config.Match(rw) {
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
rw.w.Reset(rw.ResponseWriter)
rw.w.Reset(rw.HTTPInterfaces)
rw.Header().Del("Content-Length") // https://github.com/golang/go/issues/14975
rw.Header().Set("Content-Encoding", rw.encodingName)
rw.Header().Add("Vary", "Accept-Encoding")
rw.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-encoded content
}
rw.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-encoded content
}
// AcceptedEncodings returns the list of encodings that the
@@ -417,12 +399,6 @@ type Precompressed interface {
Suffix() string
}
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
// defaultMinLength is the minimum length at which to compress content.
const defaultMinLength = 512
+10
View File
@@ -27,6 +27,7 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
caddytpl "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
"github.com/caddyserver/certmagic"
"go.uber.org/zap"
)
func init() {
@@ -70,6 +71,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
browse := fs.Bool("browse")
templates := fs.Bool("templates")
accessLog := fs.Bool("access-log")
debug := fs.Bool("debug")
var handlers []json.RawMessage
@@ -130,6 +132,14 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
},
}
if debug {
cfg.Logging = &caddy.Logging{
Logs: map[string]*caddy.CustomLog{
"default": {Level: zap.DebugLevel.CapitalString()},
},
}
}
err := caddy.Run(cfg)
if err != nil {
return caddy.ExitCodeFailedStartup, err
+1 -1
View File
@@ -256,7 +256,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander {
// Provision sets up m's defaults.
func (m *MatchFile) Provision(ctx caddy.Context) error {
m.logger = ctx.Logger(m)
m.logger = ctx.Logger()
// establish the file system to use
if len(m.FileSystemRaw) > 0 {
+1 -1
View File
@@ -167,7 +167,7 @@ func (FileServer) CaddyModule() caddy.ModuleInfo {
// Provision sets up the static files responder.
func (fsrv *FileServer) Provision(ctx caddy.Context) error {
fsrv.logger = ctx.Logger(fsrv)
fsrv.logger = ctx.Logger()
// establish which file system (possibly a virtual one) we'll be using
if len(fsrv.FileSystemRaw) > 0 {
+144
View File
@@ -0,0 +1,144 @@
// 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 caddyhttp
import (
"errors"
"net"
"net/http"
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// ServerLogConfig describes a server's logging configuration. If
// enabled without customization, all requests to this server are
// logged to the default logger; logger destinations may be
// customized per-request-host.
type ServerLogConfig struct {
// The default logger name for all logs emitted by this server for
// hostnames that are not in the LoggerNames (logger_names) map.
DefaultLoggerName string `json:"default_logger_name,omitempty"`
// LoggerNames maps request hostnames to a custom logger name.
// For example, a mapping of "example.com" to "example" would
// cause access logs from requests with a Host of example.com
// to be emitted by a logger named "http.log.access.example".
LoggerNames map[string]string `json:"logger_names,omitempty"`
// By default, all requests to this server will be logged if
// access logging is enabled. This field lists the request
// hosts for which access logging should be disabled.
SkipHosts []string `json:"skip_hosts,omitempty"`
// If true, requests to any host not appearing in the
// LoggerNames (logger_names) map will not be logged.
SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"`
// If true, credentials that are otherwise omitted, will be logged.
// The definition of credentials is defined by https://fetch.spec.whatwg.org/#credentials,
// and this includes some request and response headers, i.e `Cookie`,
// `Set-Cookie`, `Authorization`, and `Proxy-Authorization`.
ShouldLogCredentials bool `json:"should_log_credentials,omitempty"`
}
// wrapLogger wraps logger in a logger named according to user preferences for the given host.
func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) *zap.Logger {
if loggerName := slc.getLoggerName(host); loggerName != "" {
return logger.Named(loggerName)
}
return logger
}
func (slc ServerLogConfig) getLoggerName(host string) string {
tryHost := func(key string) (string, bool) {
// first try exact match
if loggerName, ok := slc.LoggerNames[key]; ok {
return loggerName, ok
}
// strip port and try again (i.e. Host header of "example.com:1234" should
// match "example.com" if there is no "example.com:1234" in the map)
hostOnly, _, err := net.SplitHostPort(key)
if err != nil {
return "", false
}
loggerName, ok := slc.LoggerNames[hostOnly]
return loggerName, ok
}
// try the exact hostname first
if loggerName, ok := tryHost(host); ok {
return loggerName
}
// try matching wildcard domains if other non-specific loggers exist
labels := strings.Split(host, ".")
for i := range labels {
if labels[i] == "" {
continue
}
labels[i] = "*"
wildcardHost := strings.Join(labels, ".")
if loggerName, ok := tryHost(wildcardHost); ok {
return loggerName
}
}
return slc.DefaultLoggerName
}
func (slc *ServerLogConfig) clone() *ServerLogConfig {
clone := &ServerLogConfig{
DefaultLoggerName: slc.DefaultLoggerName,
LoggerNames: make(map[string]string),
SkipHosts: append([]string{}, slc.SkipHosts...),
SkipUnmappedHosts: slc.SkipUnmappedHosts,
ShouldLogCredentials: slc.ShouldLogCredentials,
}
for k, v := range slc.LoggerNames {
clone.LoggerNames[k] = v
}
return clone
}
// errLogValues inspects err and returns the status code
// to use, the error log message, and any extra fields.
// If err is a HandlerError, the returned values will
// have richer information.
func errLogValues(err error) (status int, msg string, fields []zapcore.Field) {
var handlerErr HandlerError
if errors.As(err, &handlerErr) {
status = handlerErr.StatusCode
if handlerErr.Err == nil {
msg = err.Error()
} else {
msg = handlerErr.Err.Error()
}
fields = []zapcore.Field{
zap.Int("status", handlerErr.StatusCode),
zap.String("err_id", handlerErr.ID),
zap.String("err_trace", handlerErr.Trace),
}
return
}
status = http.StatusInternalServerError
msg = err.Error()
return
}
// Variable name used to indicate that this request
// should be omitted from the access logs
const SkipLogVar = "skip_log"
+1 -1
View File
@@ -1326,7 +1326,7 @@ func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
// Provision parses m's IP ranges, either from IP or CIDR expressions.
func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
m.logger = ctx.Logger(m)
m.logger = ctx.Logger()
for _, str := range m.Ranges {
// Exclude the zone_id from the IP
if strings.Contains(str, "%") {
+4
View File
@@ -11,6 +11,10 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto"
)
// Metrics configures metrics observations.
// EXPERIMENTAL and subject to change or removal.
type Metrics struct{}
var httpMetrics = struct {
init sync.Once
requestInFlight *prometheus.GaugeVec
+1 -1
View File
@@ -61,7 +61,7 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
// Provision sets up h.
func (h *Handler) Provision(ctx caddy.Context) error {
h.logger = ctx.Logger(h)
h.logger = ctx.Logger()
if h.Headers != nil {
err := h.Headers.Provision(ctx)
if err != nil {
+4 -2
View File
@@ -170,9 +170,11 @@ func (rr *responseRecorder) WriteHeader(statusCode int) {
return
}
// save statusCode in case http middleware upgrading websocket
// connections by manually setting headers and writing status 101
rr.statusCode = statusCode
// 1xx responses aren't final; just informational
if statusCode < 100 || statusCode > 199 {
rr.statusCode = statusCode
rr.wroteHeader = true
// decide whether we should buffer the response
@@ -185,7 +187,7 @@ func (rr *responseRecorder) WriteHeader(statusCode int) {
// if informational or not buffered, immediately write header
if rr.stream || (100 <= statusCode && statusCode <= 199) {
rr.ResponseWriterWrapper.WriteHeader(rr.statusCode)
rr.ResponseWriterWrapper.WriteHeader(statusCode)
}
}
+43 -8
View File
@@ -28,6 +28,7 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
"github.com/caddyserver/caddy/v2/modules/caddytls"
"go.uber.org/zap"
)
func init() {
@@ -41,6 +42,7 @@ A simple but production-ready reverse proxy. Useful for quick deployments,
demos, and development.
Simply shuttles HTTP(S) traffic from the --from address to the --to address.
Multiple --to addresses may be specified by repeating the flag.
Unless otherwise specified in the addresses, the --from address will be
assumed to be HTTPS if a hostname is given, and the --to address will be
@@ -57,10 +59,11 @@ default, all incoming headers are passed through unmodified.)
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("reverse-proxy", flag.ExitOnError)
fs.String("from", "localhost", "Address on which to receive traffic")
fs.String("to", "", "Upstream address to which traffic should be sent")
fs.Var(&reverseProxyCmdTo, "to", "Upstream address(es) to which traffic should be sent")
fs.Bool("change-host-header", false, "Set upstream Host header to address of upstream")
fs.Bool("insecure", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING SSL CERTIFICATES!)")
fs.Bool("internal-certs", false, "Use internal CA for issuing certs")
fs.Bool("debug", false, "Enable verbose debug logs")
return fs
}(),
})
@@ -70,15 +73,15 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
caddy.TrapSignals()
from := fs.String("from")
to := fs.String("to")
changeHost := fs.Bool("change-host-header")
insecure := fs.Bool("insecure")
internalCerts := fs.Bool("internal-certs")
debug := fs.Bool("debug")
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
if to == "" {
if len(reverseProxyCmdTo) == 0 {
return caddy.ExitCodeFailedStartup, fmt.Errorf("--to is required")
}
@@ -106,9 +109,18 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
}
// set up the upstream address; assume missing information from given parts
toAddr, toScheme, err := parseUpstreamDialAddress(to)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", to, err)
// mixing schemes isn't supported, so use first defined (if available)
toAddresses := make([]string, len(reverseProxyCmdTo))
var toScheme string
for i, toLoc := range reverseProxyCmdTo {
addr, scheme, err := parseUpstreamDialAddress(toLoc)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", toLoc, err)
}
if scheme != "" && toScheme != "" {
toScheme = scheme
}
toAddresses[i] = addr
}
// proceed to build the handler and server
@@ -120,9 +132,16 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
}
}
upstreamPool := UpstreamPool{}
for _, toAddr := range toAddresses {
upstreamPool = append(upstreamPool, &Upstream{
Dial: toAddr,
})
}
handler := Handler{
TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil),
Upstreams: UpstreamPool{{Dial: toAddr}},
Upstreams: upstreamPool,
}
if changeHost {
@@ -182,12 +201,28 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
AppsRaw: appsRaw,
}
if debug {
cfg.Logging = &caddy.Logging{
Logs: map[string]*caddy.CustomLog{
"default": {Level: zap.DebugLevel.CapitalString()},
},
}
}
err = caddy.Run(cfg)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
fmt.Printf("Caddy proxying %s -> %s\n", fromAddr.String(), toAddr)
for _, to := range toAddresses {
fmt.Printf("Caddy proxying %s -> %s\n", fromAddr.String(), to)
}
if len(toAddresses) > 1 {
fmt.Println("Load balancing policy: random")
}
select {}
}
// reverseProxyCmdTo holds the parsed values from repeated use of the --to flag.
var reverseProxyCmdTo caddycmd.StringSlice
@@ -94,7 +94,7 @@ func (Transport) CaddyModule() caddy.ModuleInfo {
// Provision sets up t.
func (t *Transport) Provision(ctx caddy.Context) error {
t.logger = ctx.Logger(t)
t.logger = ctx.Logger()
if t.Root == "" {
t.Root = "{http.vars.root}"
@@ -195,7 +195,7 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
TCPConn: tcpConn,
readTimeout: time.Duration(h.ReadTimeout),
writeTimeout: time.Duration(h.WriteTimeout),
logger: caddyCtx.Logger(h),
logger: caddyCtx.Logger(),
}
}
@@ -217,7 +217,7 @@ func (h *Handler) Provision(ctx caddy.Context) error {
}
h.events = eventAppIface.(*caddyevents.App)
h.ctx = ctx
h.logger = ctx.Logger(h)
h.logger = ctx.Logger()
h.connections = make(map[io.ReadWriteCloser]openConnection)
h.connectionsMu = new(sync.Mutex)
+2 -2
View File
@@ -76,7 +76,7 @@ func (SRVUpstreams) CaddyModule() caddy.ModuleInfo {
}
func (su *SRVUpstreams) Provision(ctx caddy.Context) error {
su.logger = ctx.Logger(su)
su.logger = ctx.Logger()
if su.Refresh == 0 {
su.Refresh = caddy.Duration(time.Minute)
}
@@ -383,7 +383,7 @@ func (MultiUpstreams) CaddyModule() caddy.ModuleInfo {
}
func (mu *MultiUpstreams) Provision(ctx caddy.Context) error {
mu.logger = ctx.Logger(mu)
mu.logger = ctx.Logger()
if mu.SourcesRaw != nil {
mod, err := ctx.LoadModule(mu, "SourcesRaw")
+1 -1
View File
@@ -101,7 +101,7 @@ func (Rewrite) CaddyModule() caddy.ModuleInfo {
// Provision sets up rewr.
func (rewr *Rewrite) Provision(ctx caddy.Context) error {
rewr.logger = ctx.Logger(rewr)
rewr.logger = ctx.Logger()
for i, rep := range rewr.PathRegexp {
if rep.Find == "" {
+10 -7
View File
@@ -130,7 +130,7 @@ func (routes RouteList) Provision(ctx caddy.Context) error {
if err != nil {
return err
}
return routes.ProvisionHandlers(ctx)
return routes.ProvisionHandlers(ctx, nil)
}
// ProvisionMatchers sets up all the matchers by loading the
@@ -156,7 +156,7 @@ func (routes RouteList) ProvisionMatchers(ctx caddy.Context) error {
// handler modules. Only call this method directly if you need
// to set up matchers and handlers separately without having
// to provision a second time; otherwise use Provision instead.
func (routes RouteList) ProvisionHandlers(ctx caddy.Context) error {
func (routes RouteList) ProvisionHandlers(ctx caddy.Context, metrics *Metrics) error {
for i := range routes {
handlersIface, err := ctx.LoadModule(&routes[i], "HandlersRaw")
if err != nil {
@@ -168,7 +168,7 @@ func (routes RouteList) ProvisionHandlers(ctx caddy.Context) error {
// pre-compile the middleware handler chain
for _, midhandler := range routes[i].Handlers {
routes[i].middleware = append(routes[i].middleware, wrapMiddleware(ctx, midhandler))
routes[i].middleware = append(routes[i].middleware, wrapMiddleware(ctx, midhandler, metrics))
}
}
return nil
@@ -270,9 +270,12 @@ func wrapRoute(route Route) Middleware {
// we need to pull this particular MiddlewareHandler
// pointer into its own stack frame to preserve it so it
// won't be overwritten in future loop iterations.
func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler) Middleware {
// wrap the middleware with metrics instrumentation
metricsHandler := newMetricsInstrumentedHandler(caddy.GetModuleName(mh), mh)
func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware {
handlerToUse := mh
if metrics != nil {
// wrap the middleware with metrics instrumentation
handlerToUse = newMetricsInstrumentedHandler(caddy.GetModuleName(mh), mh)
}
return func(next Handler) Handler {
// copy the next handler (it's an interface, so it's
@@ -284,7 +287,7 @@ func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler) Middleware {
return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
// TODO: This is where request tracing could be implemented
// TODO: see what the std lib gives us in terms of stack tracing too
return metricsHandler.ServeHTTP(w, r, nextCopy)
return handlerToUse.ServeHTTP(w, r, nextCopy)
})
}
}
+59 -116
View File
@@ -18,7 +18,6 @@ import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
@@ -153,6 +152,10 @@ type Server struct {
// Default: `[h1 h2 h3]`
Protocols []string `json:"protocols,omitempty"`
// If set, metrics observations will be enabled.
// This setting is EXPERIMENTAL and subject to change.
Metrics *Metrics `json:"metrics,omitempty"`
name string
primaryHandlerChain Handler
@@ -173,6 +176,11 @@ type Server struct {
shutdownAt time.Time
shutdownAtMu *sync.RWMutex
// registered callback functions
connStateFuncs []func(net.Conn, http.ConnState)
connContextFuncs []func(ctx context.Context, c net.Conn) context.Context
onShutdownFuncs []func()
}
// ServeHTTP is the entry point for all HTTP requests.
@@ -226,6 +234,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
accLog := s.accessLogger.With(loggableReq)
defer func() {
// this request may be flagged as omitted from the logs
if skipLog, ok := GetVar(r.Context(), SkipLogVar).(bool); ok && skipLog {
return
}
repl.Set("http.response.status", wrec.Status())
repl.Set("http.response.size", wrec.Size())
repl.Set("http.response.duration", duration)
@@ -505,6 +518,51 @@ func (s *Server) serveHTTP3(hostport string, tlsCfg *tls.Config) error {
return nil
}
// configureServer applies/binds the registered callback functions to the server.
func (s *Server) configureServer(server *http.Server) {
for _, f := range s.connStateFuncs {
if server.ConnState != nil {
baseConnStateFunc := server.ConnState
server.ConnState = func(conn net.Conn, state http.ConnState) {
baseConnStateFunc(conn, state)
f(conn, state)
}
} else {
server.ConnState = f
}
}
for _, f := range s.connContextFuncs {
if server.ConnContext != nil {
baseConnContextFunc := server.ConnContext
server.ConnContext = func(ctx context.Context, c net.Conn) context.Context {
return f(baseConnContextFunc(ctx, c), c)
}
} else {
server.ConnContext = f
}
}
for _, f := range s.onShutdownFuncs {
server.RegisterOnShutdown(f)
}
}
// RegisterConnState registers f to be invoked on s.ConnState.
func (s *Server) RegisterConnState(f func(net.Conn, http.ConnState)) {
s.connStateFuncs = append(s.connStateFuncs, f)
}
// RegisterConnContext registers f to be invoked as part of s.ConnContext.
func (s *Server) RegisterConnContext(f func(ctx context.Context, c net.Conn) context.Context) {
s.connContextFuncs = append(s.connContextFuncs, f)
}
// RegisterOnShutdown registers f to be invoked on server shutdown.
func (s *Server) RegisterOnShutdown(f func()) {
s.onShutdownFuncs = append(s.onShutdownFuncs, f)
}
// HTTPErrorConfig determines how to handle errors
// from the HTTP handlers.
type HTTPErrorConfig struct {
@@ -592,96 +650,6 @@ func (s *Server) protocol(proto string) bool {
// EXPERIMENTAL: Subject to change or removal.
func (s *Server) Listeners() []net.Listener { return s.listeners }
// ServerLogConfig describes a server's logging configuration. If
// enabled without customization, all requests to this server are
// logged to the default logger; logger destinations may be
// customized per-request-host.
type ServerLogConfig struct {
// The default logger name for all logs emitted by this server for
// hostnames that are not in the LoggerNames (logger_names) map.
DefaultLoggerName string `json:"default_logger_name,omitempty"`
// LoggerNames maps request hostnames to a custom logger name.
// For example, a mapping of "example.com" to "example" would
// cause access logs from requests with a Host of example.com
// to be emitted by a logger named "http.log.access.example".
LoggerNames map[string]string `json:"logger_names,omitempty"`
// By default, all requests to this server will be logged if
// access logging is enabled. This field lists the request
// hosts for which access logging should be disabled.
SkipHosts []string `json:"skip_hosts,omitempty"`
// If true, requests to any host not appearing in the
// LoggerNames (logger_names) map will not be logged.
SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"`
// If true, credentials that are otherwise omitted, will be logged.
// The definition of credentials is defined by https://fetch.spec.whatwg.org/#credentials,
// and this includes some request and response headers, i.e `Cookie`,
// `Set-Cookie`, `Authorization`, and `Proxy-Authorization`.
ShouldLogCredentials bool `json:"should_log_credentials,omitempty"`
}
// wrapLogger wraps logger in a logger named according to user preferences for the given host.
func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) *zap.Logger {
if loggerName := slc.getLoggerName(host); loggerName != "" {
return logger.Named(loggerName)
}
return logger
}
func (slc ServerLogConfig) getLoggerName(host string) string {
tryHost := func(key string) (string, bool) {
// first try exact match
if loggerName, ok := slc.LoggerNames[key]; ok {
return loggerName, ok
}
// strip port and try again (i.e. Host header of "example.com:1234" should
// match "example.com" if there is no "example.com:1234" in the map)
hostOnly, _, err := net.SplitHostPort(key)
if err != nil {
return "", false
}
loggerName, ok := slc.LoggerNames[hostOnly]
return loggerName, ok
}
// try the exact hostname first
if loggerName, ok := tryHost(host); ok {
return loggerName
}
// try matching wildcard domains if other non-specific loggers exist
labels := strings.Split(host, ".")
for i := range labels {
if labels[i] == "" {
continue
}
labels[i] = "*"
wildcardHost := strings.Join(labels, ".")
if loggerName, ok := tryHost(wildcardHost); ok {
return loggerName
}
}
return slc.DefaultLoggerName
}
func (slc *ServerLogConfig) clone() *ServerLogConfig {
clone := &ServerLogConfig{
DefaultLoggerName: slc.DefaultLoggerName,
LoggerNames: make(map[string]string),
SkipHosts: append([]string{}, slc.SkipHosts...),
SkipUnmappedHosts: slc.SkipUnmappedHosts,
ShouldLogCredentials: slc.ShouldLogCredentials,
}
for k, v := range slc.LoggerNames {
clone.LoggerNames[k] = v
}
return clone
}
// PrepareRequest fills the request r for use in a Caddy HTTP handler chain. w and s can
// be nil, but the handlers will lose response placeholders and access to the server.
func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter, s *Server) *http.Request {
@@ -701,31 +669,6 @@ func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter
return r
}
// errLogValues inspects err and returns the status code
// to use, the error log message, and any extra fields.
// If err is a HandlerError, the returned values will
// have richer information.
func errLogValues(err error) (status int, msg string, fields []zapcore.Field) {
var handlerErr HandlerError
if errors.As(err, &handlerErr) {
status = handlerErr.StatusCode
if handlerErr.Err == nil {
msg = err.Error()
} else {
msg = handlerErr.Err.Error()
}
fields = []zapcore.Field{
zap.Int("status", handlerErr.StatusCode),
zap.String("err_id", handlerErr.ID),
zap.String("err_trace", handlerErr.Trace),
}
return
}
status = http.StatusInternalServerError
msg = err.Error()
return
}
// originalRequest returns a partial, shallow copy of
// req, including: req.Method, deep copy of req.URL
// (into the urlCopy parameter, which should be on the
+2 -1
View File
@@ -31,6 +31,7 @@ import (
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
caddycmd "github.com/caddyserver/caddy/v2/cmd"
"go.uber.org/zap"
)
func init() {
@@ -403,7 +404,7 @@ func cmdRespond(fl caddycmd.Flags) (int, error) {
if debug {
cfg.Logging = &caddy.Logging{
Logs: map[string]*caddy.CustomLog{
"default": {Level: "DEBUG"},
"default": {Level: zap.DebugLevel.CapitalString()},
},
}
}
+4 -5
View File
@@ -42,7 +42,7 @@ func (Tracing) CaddyModule() caddy.ModuleInfo {
// Provision implements caddy.Provisioner.
func (ot *Tracing) Provision(ctx caddy.Context) error {
ot.logger = ctx.Logger(ot)
ot.logger = ctx.Logger()
var err error
ot.otel, err = newOpenTelemetryWrapper(ctx, ot.SpanName)
@@ -66,10 +66,9 @@ func (ot *Tracing) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
//
// tracing {
// [span <span_name>]
// }
//
// tracing {
// [span <span_name>]
// }
func (ot *Tracing) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
setParameter := func(d *caddyfile.Dispenser, val *string) error {
if d.NextArg() {
+1 -1
View File
@@ -87,7 +87,7 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
// Provision sets up the ACME server handler.
func (ash *Handler) Provision(ctx caddy.Context) error {
ash.logger = ctx.Logger(ash)
ash.logger = ctx.Logger()
// set some defaults
if ash.CA == "" {
ash.CA = caddypki.DefaultCAID
+1 -1
View File
@@ -47,7 +47,7 @@ func (adminAPI) CaddyModule() caddy.ModuleInfo {
// Provision sets up the adminAPI module.
func (a *adminAPI) Provision(ctx caddy.Context) error {
a.ctx = ctx
a.log = ctx.Logger(a)
a.log = ctx.Logger(a) // TODO: passing in 'a' is a hack until the admin API is officially extensible (see #5032)
// First check if the PKI app was configured, because
// a.ctx.App() has the side effect of instantiating
+1 -1
View File
@@ -54,7 +54,7 @@ func (PKI) CaddyModule() caddy.ModuleInfo {
// Provision sets up the configuration for the PKI app.
func (p *PKI) Provision(ctx caddy.Context) error {
p.ctx = ctx
p.log = ctx.Logger(p)
p.log = ctx.Logger()
for caID, ca := range p.CAs {
err := ca.Provision(ctx, caID, p.log)
+1 -1
View File
@@ -103,7 +103,7 @@ func (ACMEIssuer) CaddyModule() caddy.ModuleInfo {
// Provision sets up iss.
func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
iss.logger = ctx.Logger(iss)
iss.logger = ctx.Logger()
repl := caddy.NewReplacer()
+6 -6
View File
@@ -43,7 +43,7 @@ func (Tailscale) CaddyModule() caddy.ModuleInfo {
}
func (ts *Tailscale) Provision(ctx caddy.Context) error {
ts.logger = ctx.Logger(ts)
ts.logger = ctx.Logger()
return nil
}
@@ -66,7 +66,9 @@ func (ts Tailscale) canHazCertificate(ctx context.Context, hello *tls.ClientHell
status, err := tscert.GetStatus(ctx)
if err != nil {
if ts.Optional {
return false, nil // ignore error if we don't expect/require it to work anyway
// ignore error if we don't expect/require it to work anyway, but log it for debugging
ts.logger.Debug("error getting tailscale status", zap.Error(err), zap.String("server_name", hello.ServerName))
return false, nil
}
return false, err
}
@@ -80,8 +82,7 @@ func (ts Tailscale) canHazCertificate(ctx context.Context, hello *tls.ClientHell
// UnmarshalCaddyfile deserializes Caddyfile tokens into ts.
//
// ... tailscale
//
// ... tailscale
func (Tailscale) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
if d.NextArg() {
@@ -178,8 +179,7 @@ func (hcg HTTPCertGetter) GetCertificate(ctx context.Context, hello *tls.ClientH
// UnmarshalCaddyfile deserializes Caddyfile tokens into ts.
//
// ... http <url>
//
// ... http <url>
func (hcg *HTTPCertGetter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
if !d.NextArg() {
+45 -1
View File
@@ -20,11 +20,14 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/caddyserver/caddy/v2"
"github.com/mholt/acmez"
"go.uber.org/zap"
)
func init() {
@@ -156,6 +159,16 @@ type ConnectionPolicy struct {
// is no policy configured for the empty SNI value.
DefaultSNI string `json:"default_sni,omitempty"`
// Also known as "SSLKEYLOGFILE", TLS secrets will be written to
// this file in NSS key log format which can then be parsed by
// Wireshark and other tools. This is INSECURE as it allows other
// programs or tools to decrypt TLS connections. However, this
// capability can be useful for debugging and troubleshooting.
// **ENABLING THIS LOG COMPROMISES SECURITY!**
//
// This feature is EXPERIMENTAL and subject to change or removal.
InsecureSecretsLog string `json:"insecure_secrets_log,omitempty"`
// TLSConfig is the fully-formed, standard lib TLS config
// used to serve TLS connections. Provision all
// ConnectionPolicies to populate this. It is exported only
@@ -280,6 +293,30 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
}
}
if p.InsecureSecretsLog != "" {
filename, err := caddy.NewReplacer().ReplaceOrErr(p.InsecureSecretsLog, true, true)
if err != nil {
return err
}
filename, err = filepath.Abs(filename)
if err != nil {
return err
}
logFile, _, err := secretsLogPool.LoadOrNew(filename, func() (caddy.Destructor, error) {
w, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
return destructableWriter{w}, err
})
if err != nil {
return err
}
ctx.OnCancel(func() { _, _ = secretsLogPool.Delete(filename) })
cfg.KeyLogWriter = logFile.(io.Writer)
tlsApp.logger.Warn("TLS SECURITY COMPROMISED: secrets logging is enabled!",
zap.String("log_filename", filename))
}
setDefaultTLSParams(cfg)
p.TLSConfig = cfg
@@ -297,7 +334,8 @@ func (p ConnectionPolicy) SettingsEmpty() bool {
p.ProtocolMin == "" &&
p.ProtocolMax == "" &&
p.ClientAuthentication == nil &&
p.DefaultSNI == ""
p.DefaultSNI == "" &&
p.InsecureSecretsLog == ""
}
// ClientAuthentication configures TLS client auth.
@@ -542,3 +580,9 @@ type ClientCertificateVerifier interface {
}
var defaultALPN = []string{"h2", "http/1.1"}
type destructableWriter struct{ *os.File }
func (d destructableWriter) Destruct() error { return d.Close() }
var secretsLogPool = caddy.NewUsagePool()
+6 -7
View File
@@ -65,7 +65,7 @@ func (InternalIssuer) CaddyModule() caddy.ModuleInfo {
// Provision sets up the issuer.
func (iss *InternalIssuer) Provision(ctx caddy.Context) error {
iss.logger = ctx.Logger(iss)
iss.logger = ctx.Logger()
// set some defaults
if iss.CA == "" {
@@ -148,12 +148,11 @@ func (iss InternalIssuer) Issue(ctx context.Context, csr *x509.CertificateReques
// UnmarshalCaddyfile deserializes Caddyfile tokens into iss.
//
// ... internal {
// ca <name>
// lifetime <duration>
// sign_with_root
// }
//
// ... internal {
// ca <name>
// lifetime <duration>
// sign_with_root
// }
func (iss *InternalIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
for d.NextBlock(0) {
+1 -1
View File
@@ -81,7 +81,7 @@ func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
// Provision parses m's IP ranges, either from IP or CIDR expressions.
func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
m.logger = ctx.Logger(m)
m.logger = ctx.Logger()
for _, str := range m.Ranges {
cidrs, err := m.parseIPRange(str)
if err != nil {
+1 -1
View File
@@ -94,7 +94,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
}
t.events = eventsAppIface.(*caddyevents.App)
t.ctx = ctx
t.logger = ctx.Logger(t)
t.logger = ctx.Logger()
repl := caddy.NewReplacer()
// set up a new certificate cache; this (re)loads all certificates
+1 -1
View File
@@ -66,7 +66,7 @@ func (*ZeroSSLIssuer) CaddyModule() caddy.ModuleInfo {
// Provision sets up iss.
func (iss *ZeroSSLIssuer) Provision(ctx caddy.Context) error {
iss.logger = ctx.Logger(iss)
iss.logger = ctx.Logger()
if iss.ACMEIssuer == nil {
iss.ACMEIssuer = new(ACMEIssuer)
}
+4 -5
View File
@@ -62,7 +62,7 @@ func (l *zapLogger) Println(v ...any) {
// Provision sets up m.
func (m *Metrics) Provision(ctx caddy.Context) error {
log := ctx.Logger(m)
log := ctx.Logger()
m.metricsHandler = createMetricsHandler(&zapLogger{log}, !m.DisableOpenMetrics)
return nil
}
@@ -75,10 +75,9 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
//
// metrics [<matcher>] {
// disable_openmetrics
// }
//
// metrics [<matcher>] {
// disable_openmetrics
// }
func (m *Metrics) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
args := d.RemainingArgs()
+12 -1
View File
@@ -144,9 +144,11 @@ func (r *Replacer) replace(input, empty string,
// iterate the input to find each placeholder
var lastWriteCursor int
// fail fast if too many placeholders are unclosed
var unclosedCount int
scan:
for i := 0; i < len(input); i++ {
// check for escaped braces
if i > 0 && input[i-1] == phEscape && (input[i] == phClose || input[i] == phOpen) {
sb.WriteString(input[lastWriteCursor : i-1])
@@ -158,9 +160,17 @@ scan:
continue
}
// our iterator is now on an unescaped open brace (start of placeholder)
// too many unclosed placeholders in absolutely ridiculous input can be extremely slow (issue #4170)
if unclosedCount > 100 {
return "", fmt.Errorf("too many unclosed placeholders")
}
// find the end of the placeholder
end := strings.Index(input[i:], string(phClose)) + i
if end < i {
unclosedCount++
continue
}
@@ -168,6 +178,7 @@ scan:
for end > 0 && end < len(input)-1 && input[end-1] == phEscape {
nextEnd := strings.Index(input[end+1:], string(phClose))
if nextEnd < 0 {
unclosedCount++
continue scan
}
end += nextEnd + 1