Compare commits

..

16 Commits

Author SHA1 Message Date
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
51 changed files with 608 additions and 409 deletions
+5 -1
View File
@@ -763,8 +763,12 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
// ParseDuration parses a duration string, adding // ParseDuration parses a duration string, adding
// support for the "d" unit meaning number of days, // 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) { 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 inNumber bool
var numStart int var numStart int
for i := 0; i < len(s); i++ { for i := 0; i < len(s); i++ {
+4 -1
View File
@@ -153,7 +153,10 @@ func Format(input []byte) []byte {
openBraceWritten = true openBraceWritten = true
nextLine() nextLine()
newLines = 0 newLines = 0
nesting++ // prevent infinite nesting from ridiculous inputs (issue #4169)
if nesting < 10 {
nesting++
}
} }
switch { switch {
+24 -12
View File
@@ -36,12 +36,12 @@ import (
// server block that share the same address stay grouped together so the config // server block that share the same address stay grouped together so the config
// isn't repeated unnecessarily. For example, this Caddyfile: // isn't repeated unnecessarily. For example, this Caddyfile:
// //
// example.com { // example.com {
// bind 127.0.0.1 // bind 127.0.0.1
// } // }
// www.example.com, example.net/path, localhost:9999 { // www.example.com, example.net/path, localhost:9999 {
// bind 127.0.0.1 1.2.3.4 // bind 127.0.0.1 1.2.3.4
// } // }
// //
// has two server blocks to start with. But expressed in this Caddyfile are // 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, // 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) 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"])) lnHosts := make([]string, 0, len(sblock.pile["bind"]))
for _, cfgVal := range sblock.pile["bind"] { for _, cfgVal := range sblock.pile["bind"] {
lnHosts = append(lnHosts, cfgVal.Value.([]string)...) lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
@@ -234,11 +234,23 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
// use a map to prevent duplication // use a map to prevent duplication
listeners := make(map[string]struct{}) listeners := make(map[string]struct{})
for _, host := range lnHosts { for _, lnHost := range lnHosts {
// host can have network + host (e.g. "tcp6/localhost") but // normally we would simply append the port,
// will/should not have port information because this usually // but if lnHost is IPv6, we need to ensure it
// comes from the bind directive, so we append the port // is enclosed in [ ]; net.JoinHostPort does
addr, err := caddy.ParseNetworkAddress(host + ":" + lnPort) // 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 { if err != nil {
return nil, fmt.Errorf("parsing network address: %v", err) return nil, fmt.Errorf("parsing network address: %v", err)
} }
+48 -32
View File
@@ -48,12 +48,12 @@ func init() {
RegisterHandlerDirective("handle", parseHandle) RegisterHandlerDirective("handle", parseHandle)
RegisterDirective("handle_errors", parseHandleErrors) RegisterDirective("handle_errors", parseHandleErrors)
RegisterDirective("log", parseLog) RegisterDirective("log", parseLog)
RegisterHandlerDirective("skip_log", parseSkipLog)
} }
// parseBind parses the bind directive. Syntax: // parseBind parses the bind directive. Syntax:
// //
// bind <addresses...> // bind <addresses...>
//
func parseBind(h Helper) ([]ConfigValue, error) { func parseBind(h Helper) ([]ConfigValue, error) {
var lnHosts []string var lnHosts []string
for h.Next() { for h.Next() {
@@ -64,28 +64,28 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// parseTLS parses the tls directive. Syntax: // parseTLS parses the tls directive. Syntax:
// //
// tls [<email>|internal]|[<cert_file> <key_file>] { // tls [<email>|internal]|[<cert_file> <key_file>] {
// protocols <min> [<max>] // protocols <min> [<max>]
// ciphers <cipher_suites...> // ciphers <cipher_suites...>
// curves <curves...> // curves <curves...>
// client_auth { // client_auth {
// mode [request|require|verify_if_given|require_and_verify] // mode [request|require|verify_if_given|require_and_verify]
// trusted_ca_cert <base64_der> // trusted_ca_cert <base64_der>
// trusted_ca_cert_file <filename> // trusted_ca_cert_file <filename>
// trusted_leaf_cert <base64_der> // trusted_leaf_cert <base64_der>
// trusted_leaf_cert_file <filename> // trusted_leaf_cert_file <filename>
// } // }
// alpn <values...> // alpn <values...>
// load <paths...> // load <paths...>
// ca <acme_ca_endpoint> // ca <acme_ca_endpoint>
// ca_root <pem_file> // ca_root <pem_file>
// dns <provider_name> [...] // dns <provider_name> [...]
// on_demand // on_demand
// eab <key_id> <mac_key> // eab <key_id> <mac_key>
// issuer <module_name> [...] // issuer <module_name> [...]
// get_certificate <module_name> [...] // get_certificate <module_name> [...]
// } // insecure_secrets_log <log_file>
// // }
func parseTLS(h Helper) ([]ConfigValue, error) { func parseTLS(h Helper) ([]ConfigValue, error) {
cp := new(caddytls.ConnectionPolicy) cp := new(caddytls.ConnectionPolicy)
var fileLoader caddytls.FileLoader var fileLoader caddytls.FileLoader
@@ -395,6 +395,12 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
} }
onDemand = true onDemand = true
case "insecure_secrets_log":
if !h.NextArg() {
return nil, h.ArgErr()
}
cp.InsecureSecretsLog = h.Val()
default: default:
return nil, h.Errf("unknown subdirective: %s", h.Val()) 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: // parseRoot parses the root directive. Syntax:
// //
// root [<matcher>] <path> // root [<matcher>] <path>
//
func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) { func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
var root string var root string
for h.Next() { for h.Next() {
@@ -694,12 +699,11 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
// parseLog parses the log directive. Syntax: // parseLog parses the log directive. Syntax:
// //
// log { // log {
// output <writer_module> ... // output <writer_module> ...
// format <encoder_module> ... // format <encoder_module> ...
// level <level> // level <level>
// } // }
//
func parseLog(h Helper) ([]ConfigValue, error) { func parseLog(h Helper) ([]ConfigValue, error) {
return parseLogHelper(h, nil) return parseLogHelper(h, nil)
} }
@@ -858,3 +862,15 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
} }
return configValues, nil 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", "map",
"vars", "vars",
"root", "root",
"skip_log",
"header", "header",
"copy_response_headers", // only in reverse_proxy's handle_response "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 hasDefaultLog = true
} }
if _, ok := options["debug"]; ok && ncl.log.Level == "" { if _, ok := options["debug"]; ok && ncl.log.Level == "" {
ncl.log.Level = "DEBUG" ncl.log.Level = zap.DebugLevel.CapitalString()
} }
customLogs = append(customLogs, ncl) customLogs = append(customLogs, ncl)
} }
@@ -241,7 +241,7 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
if _, ok := options["debug"]; ok { if _, ok := options["debug"]; ok {
customLogs = append(customLogs, namedCustomLog{ customLogs = append(customLogs, namedCustomLog{
name: "default", 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: // parseLogOptions parses the global log option. Syntax:
// //
// log [name] { // log [name] {
// output <writer_module> ... // output <writer_module> ...
// format <encoder_module> ... // format <encoder_module> ...
// level <level> // level <level>
// include <namespaces...> // include <namespaces...>
// exclude <namespaces...> // exclude <namespaces...>
// } // }
// //
// When the name argument is unspecified, this directive modifies the default // When the name argument is unspecified, this directive modifies the default
// logger. // logger.
@@ -43,6 +43,7 @@ type serverOptions struct {
Protocols []string Protocols []string
StrictSNIHost *bool StrictSNIHost *bool
ShouldLogCredentials bool ShouldLogCredentials bool
Metrics *caddyhttp.Metrics
} }
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) { func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
@@ -175,6 +176,15 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
} }
serverOpts.StrictSNIHost = &boolVal 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) // TODO: DEPRECATED. (August 2022)
case "protocol": case "protocol":
caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol sub-option will be removed soon") caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol sub-option will be removed soon")
@@ -259,6 +269,7 @@ func applyServerOptions(
server.MaxHeaderBytes = opts.MaxHeaderBytes server.MaxHeaderBytes = opts.MaxHeaderBytes
server.Protocols = opts.Protocols server.Protocols = opts.Protocols
server.StrictSNIHost = opts.StrictSNIHost server.StrictSNIHost = opts.StrictSNIHost
server.Metrics = opts.Metrics
if opts.ShouldLogCredentials { if opts.ShouldLogCredentials {
if server.Logs == nil { if server.Logs == nil {
server.Logs = &caddyhttp.ServerLogConfig{} server.Logs = &caddyhttp.ServerLogConfig{}
+2 -2
View File
@@ -113,7 +113,7 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
return nil, err return nil, err
} }
for _, warn := range warnings { for _, warn := range warnings {
ctx.Logger(hl).Warn(warn.String()) ctx.Logger().Warn(warn.String())
} }
return result, nil return result, nil
@@ -129,7 +129,7 @@ func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, error) {
// client authentication // client authentication
if hl.TLS.UseServerIdentity { if hl.TLS.UseServerIdentity {
certs, err := ctx.IdentityCredentials(ctx.Logger(hl)) certs, err := ctx.IdentityCredentials(ctx.Logger())
if err != nil { if err != nil {
return nil, fmt.Errorf("getting server identity credentials: %v", err) return nil, fmt.Errorf("getting server identity credentials: %v", err)
} }
@@ -1,5 +1,7 @@
http://localhost:2020 { http://localhost:2020 {
log log
skip_log /first-hidden*
skip_log /second-hidden*
respond 200 respond 200
} }
@@ -28,6 +30,36 @@ http://localhost:2020 {
{ {
"handler": "subroute", "handler": "subroute",
"routes": [ "routes": [
{
"handle": [
{
"handler": "vars",
"skip_log": true
}
],
"match": [
{
"path": [
"/second-hidden*"
]
}
]
},
{
"handle": [
{
"handler": "vars",
"skip_log": true
}
],
"match": [
{
"path": [
"/first-hidden*"
]
}
]
},
{ {
"handle": [ "handle": [
{ {
+1 -1
View File
@@ -679,7 +679,7 @@ func DetermineAdminAPIAddress(address string, config []byte, configFile, configA
return "", err return "", err
} }
if loadedConfigFile == "" { 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 // set a fitting User-Agent for ACME requests
version, _ := caddy.Version() version, _ := caddy.Version()
cleanModVersion := strings.TrimPrefix(version, "v") 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 // 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 certmagic.DefaultACME.Agreed = true
} }
+25 -18
View File
@@ -442,10 +442,27 @@ func (ctx Context) Storage() certmagic.Storage {
return ctx.cfg.storage return ctx.cfg.storage
} }
// TODO: aw man, can I please change this? // Logger returns a logger that is intended for use by the most
// Logger returns a logger that can be used by mod. // recent module associated with the context. Callers should not
func (ctx Context) Logger(mod Module) *zap.Logger { // pass in any arguments unless they want to associate with a
// TODO: if mod is nil, use ctx.Module() instead... // 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 { if ctx.cfg == nil {
// often the case in tests; just use a dev logger // often the case in tests; just use a dev logger
l, err := zap.NewDevelopment() l, err := zap.NewDevelopment()
@@ -454,23 +471,13 @@ func (ctx Context) Logger(mod Module) *zap.Logger {
} }
return l return l
} }
mod := ctx.Module()
if len(module) > 0 {
mod = module[0]
}
return ctx.cfg.Logging.Logger(mod) 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, // Modules returns the lineage of modules that this context provisioned,
// with the most recent/current module being last in the list. // with the most recent/current module being last in the list.
func (ctx Context) Modules() []Module { 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) { func ListenTimeout(network, addr string, keepAlivePeriod time.Duration) (net.Listener, error) {
// check to see if plugin provides listener // check to see if plugin provides listener
if ln, err := getListenerFromPlugin(network, addr); err != nil || ln != nil { if ln, err := getListenerFromPlugin(network, addr); err != nil || ln != nil {
return pipeable(ln), err return ln, err
} }
lnKey := listenerKey(network, addr) lnKey := listenerKey(network, addr)
@@ -29,7 +29,7 @@ func ListenTimeout(network, addr string, keepAlivePeriod time.Duration) (net.Lis
} }
return nil, err return nil, err
} }
return &sharedListener{Listener: pipeable(ln), key: lnKey}, nil return &sharedListener{Listener: ln, key: lnKey}, nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
+2 -3
View File
@@ -14,12 +14,11 @@ import (
func ListenTimeout(network, addr string, keepalivePeriod time.Duration) (net.Listener, error) { func ListenTimeout(network, addr string, keepalivePeriod time.Duration) (net.Listener, error) {
// check to see if plugin provides listener // check to see if plugin provides listener
if ln, err := getListenerFromPlugin(network, addr); err != nil || ln != nil { if ln, err := getListenerFromPlugin(network, addr); err != nil || ln != nil {
return pipeable(ln), err return ln, err
} }
config := &net.ListenConfig{Control: reusePort, KeepAlive: keepalivePeriod} config := &net.ListenConfig{Control: reusePort, KeepAlive: keepalivePeriod}
ln, err := config.Listen(context.Background(), network, addr) return config.Listen(context.Background(), network, addr)
return pipeable(ln), err
} }
func reusePort(network, address string, conn syscall.RawConn) error { 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) 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 // getListenerFromPlugin returns a listener on the given network and address
// if a plugin has registered the network name. It may return (nil, nil) if // if a plugin has registered the network name. It may return (nil, nil) if
// no plugin can provide a listener. // no plugin can provide a listener.
+1 -1
View File
@@ -119,7 +119,7 @@ func (App) CaddyModule() caddy.ModuleInfo {
// Provision sets up the app. // Provision sets up the app.
func (app *App) Provision(ctx caddy.Context) error { 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) app.subscriptions = make(map[string]map[caddy.ModuleID][]Handler)
for _, sub := range app.Subscriptions { for _, sub := range app.Subscriptions {
+6 -3
View File
@@ -160,7 +160,7 @@ func (app *App) Provision(ctx caddy.Context) error {
} }
app.tlsApp = tlsAppIface.(*caddytls.TLS) app.tlsApp = tlsAppIface.(*caddytls.TLS)
app.ctx = ctx app.ctx = ctx
app.logger = ctx.Logger(app) app.logger = ctx.Logger()
eventsAppIface, err := ctx.App("events") eventsAppIface, err := ctx.App("events")
if err != nil { if err != nil {
@@ -178,7 +178,9 @@ func (app *App) Provision(ctx caddy.Context) error {
} }
// prepare each server // prepare each server
oldContext := ctx.Context
for srvName, srv := range app.Servers { for srvName, srv := range app.Servers {
ctx.Context = context.WithValue(oldContext, ServerCtxKey, srv)
srv.name = srvName srv.name = srvName
srv.tlsApp = app.tlsApp srv.tlsApp = app.tlsApp
srv.events = eventsAppIface.(*caddyevents.App) 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. // route handler so that important security checks are done, etc.
primaryRoute := emptyHandler primaryRoute := emptyHandler
if srv.Routes != nil { if srv.Routes != nil {
err := srv.Routes.ProvisionHandlers(ctx) err := srv.Routes.ProvisionHandlers(ctx, srv.Metrics)
if err != nil { if err != nil {
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err) return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
} }
@@ -293,7 +295,7 @@ func (app *App) Provision(ctx caddy.Context) error {
srv.IdleTimeout = defaultIdleTimeout srv.IdleTimeout = defaultIdleTimeout
} }
} }
ctx.Context = oldContext
return nil 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 // 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 // by looking through the connection policies to find the first one that matches
tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx) tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
srv.configureServer(srv.server)
// enable H2C if configured // enable H2C if configured
if srv.protocol("h2c") { if srv.protocol("h2c") {
+1 -1
View File
@@ -56,7 +56,7 @@ func (Authentication) CaddyModule() caddy.ModuleInfo {
// Provision sets up a. // Provision sets up a.
func (a *Authentication) Provision(ctx caddy.Context) error { func (a *Authentication) Provision(ctx caddy.Context) error {
a.logger = ctx.Logger(a) a.logger = ctx.Logger()
a.Providers = make(map[string]Authenticator) a.Providers = make(map[string]Authenticator)
mods, err := ctx.LoadModule(a, "ProvidersRaw") mods, err := ctx.LoadModule(a, "ProvidersRaw")
if err != nil { if err != nil {
+1 -1
View File
@@ -92,7 +92,7 @@ func (ScryptHash) CaddyModule() caddy.ModuleInfo {
// Provision sets up s. // Provision sets up s.
func (s *ScryptHash) Provision(ctx caddy.Context) error { func (s *ScryptHash) Provision(ctx caddy.Context) error {
s.SetDefaults() 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 return nil
} }
+8 -8
View File
@@ -90,7 +90,7 @@ func (m *MatchExpression) UnmarshalJSON(data []byte) error {
// Provision sets ups m. // Provision sets ups m.
func (m *MatchExpression) Provision(ctx caddy.Context) error { 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 // replace placeholders with a function call - this is just some
// light (and possibly naïve) syntactic sugar // 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 // CELMatcherImpl creates a new cel.Library based on the following pieces of
// data: // data:
// //
// - macroName: the function name to be used within CEL. This will be a macro // - macroName: the function name to be used within CEL. This will be a macro
// and not a function proper. // and not a function proper.
// - funcName: the function overload name generated by the CEL macro used to // - funcName: the function overload name generated by the CEL macro used to
// represent the matcher. // represent the matcher.
// - matcherDataTypes: the argument types to the macro. // - matcherDataTypes: the argument types to the macro.
// - fac: a matcherFactory implementation which converts from CEL constant // - fac: a matcherFactory implementation which converts from CEL constant
// values to a Matcher instance. // values to a Matcher instance.
// //
// Note, macro names and function names must not collide with other macros or // Note, macro names and function names must not collide with other macros or
// functions exposed within CEL expressions, or an error will be produced // functions exposed within CEL expressions, or an error will be produced
+52 -76
View File
@@ -20,7 +20,6 @@
package encode package encode
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"math" "math"
@@ -160,13 +159,12 @@ func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter
// initResponseWriter initializes the responseWriter instance // initResponseWriter initializes the responseWriter instance
// allocated in openResponseWriter, enabling mid-stack inlining. // allocated in openResponseWriter, enabling mid-stack inlining.
func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter { func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter {
buf := bufPool.Get().(*bytes.Buffer) if httpInterfaces, ok := wrappedRW.(caddyhttp.HTTPInterfaces); ok {
buf.Reset() rw.HTTPInterfaces = httpInterfaces
} else {
// The allocation of ResponseWriterWrapper might be optimized as well. rw.HTTPInterfaces = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW}
rw.ResponseWriterWrapper = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW} }
rw.encodingName = encodingName rw.encodingName = encodingName
rw.buf = buf
rw.config = enc rw.config = enc
return rw return rw
@@ -176,10 +174,9 @@ func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, w
// using the encoding represented by encodingName and // using the encoding represented by encodingName and
// configured by config. // configured by config.
type responseWriter struct { type responseWriter struct {
*caddyhttp.ResponseWriterWrapper caddyhttp.HTTPInterfaces
encodingName string encodingName string
w Encoder w Encoder
buf *bytes.Buffer
config *Encode config *Encode
statusCode int statusCode int
wroteHeader bool wroteHeader bool
@@ -206,28 +203,33 @@ func (rw *responseWriter) Flush() {
// to rw.Write (see bug in #4314) // to rw.Write (see bug in #4314)
return return
} }
rw.ResponseWriterWrapper.Flush() rw.HTTPInterfaces.Flush()
} }
// Write writes to the response. If the response qualifies, // Write writes to the response. If the response qualifies,
// it is encoded using the encoder, which is initialized // it is encoded using the encoder, which is initialized
// if not done so already. // if not done so already.
func (rw *responseWriter) Write(p []byte) (int, error) { func (rw *responseWriter) Write(p []byte) (int, error) {
var n, written int // ignore zero data writes, probably head request
var err error if len(p) == 0 {
return 0, nil
}
if rw.buf != nil && rw.config.MinLength > 0 { // sniff content-type and determine content-length
written = rw.buf.Len() if !rw.wroteHeader && rw.config.MinLength > 0 {
_, err := rw.buf.Write(p) var gtMinLength bool
if err != nil { if len(p) > rw.config.MinLength {
return 0, err 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 // 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 // and if so, that means we haven't written the
// header OR the default status code will be written // header OR the default status code will be written
// by the standard library // by the standard library
if rw.statusCode > 0 { if !rw.wroteHeader {
rw.ResponseWriter.WriteHeader(rw.statusCode) if rw.statusCode != 0 {
rw.statusCode = 0 rw.HTTPInterfaces.WriteHeader(rw.statusCode)
} else {
rw.HTTPInterfaces.WriteHeader(http.StatusOK)
}
rw.wroteHeader = true rw.wroteHeader = true
} }
switch { if rw.w != nil {
case rw.w != nil: return rw.w.Write(p)
n, err = rw.w.Write(p) } else {
default: return rw.HTTPInterfaces.Write(p)
n, err = rw.ResponseWriter.Write(p)
} }
n -= written
if n < 0 {
n = 0
}
return n, err
} }
// Close writes any remaining buffered response and // Close writes any remaining buffered response and
// deallocates any active resources. // deallocates any active resources.
func (rw *responseWriter) Close() error { func (rw *responseWriter) Close() error {
var err error // didn't write, probably head request
// only attempt to write the remaining buffered response if !rw.wroteHeader {
// if there are any bytes left to write; otherwise, if cl, err := strconv.Atoi(rw.Header().Get("Content-Length"))
// the handler above us returned an error without writing if err == nil && cl > rw.config.MinLength {
// anything, we'd write to the response when we instead rw.init()
// 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 { if rw.statusCode != 0 {
rw.init() rw.HTTPInterfaces.WriteHeader(rw.statusCode)
p := rw.buf.Bytes() } else {
defer func() { rw.HTTPInterfaces.WriteHeader(http.StatusOK)
bufPool.Put(rw.buf)
rw.buf = nil
}()
switch {
case rw.w != nil:
_, err = rw.w.Write(p)
default:
_, err = rw.ResponseWriter.Write(p)
} }
} 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 rw.wroteHeader = true
} }
var err error
if rw.w != nil { if rw.w != nil {
err2 := rw.w.Close() err = rw.w.Close()
if err2 != nil && err == nil { rw.w.Reset(nil)
err = err2
}
rw.config.writerPools[rw.encodingName].Put(rw.w) rw.config.writerPools[rw.encodingName].Put(rw.w)
rw.w = nil 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. // init should be called before we write a response, if rw.buf has contents.
func (rw *responseWriter) init() { func (rw *responseWriter) init() {
if rw.Header().Get("Content-Encoding") == "" && if rw.Header().Get("Content-Encoding") == "" &&
rw.buf.Len() >= rw.config.MinLength &&
rw.config.Match(rw) { rw.config.Match(rw) {
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder) 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().Del("Content-Length") // https://github.com/golang/go/issues/14975
rw.Header().Set("Content-Encoding", rw.encodingName) rw.Header().Set("Content-Encoding", rw.encodingName)
rw.Header().Add("Vary", "Accept-Encoding") 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 // AcceptedEncodings returns the list of encodings that the
@@ -417,12 +399,6 @@ type Precompressed interface {
Suffix() string Suffix() string
} }
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
// defaultMinLength is the minimum length at which to compress content. // defaultMinLength is the minimum length at which to compress content.
const defaultMinLength = 512 const defaultMinLength = 512
+10
View File
@@ -27,6 +27,7 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
caddytpl "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates" caddytpl "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"go.uber.org/zap"
) )
func init() { func init() {
@@ -70,6 +71,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
browse := fs.Bool("browse") browse := fs.Bool("browse")
templates := fs.Bool("templates") templates := fs.Bool("templates")
accessLog := fs.Bool("access-log") accessLog := fs.Bool("access-log")
debug := fs.Bool("debug")
var handlers []json.RawMessage 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) err := caddy.Run(cfg)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, err return caddy.ExitCodeFailedStartup, err
+1 -1
View File
@@ -256,7 +256,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander {
// Provision sets up m's defaults. // Provision sets up m's defaults.
func (m *MatchFile) Provision(ctx caddy.Context) error { func (m *MatchFile) Provision(ctx caddy.Context) error {
m.logger = ctx.Logger(m) m.logger = ctx.Logger()
// establish the file system to use // establish the file system to use
if len(m.FileSystemRaw) > 0 { 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. // Provision sets up the static files responder.
func (fsrv *FileServer) Provision(ctx caddy.Context) error { 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 // establish which file system (possibly a virtual one) we'll be using
if len(fsrv.FileSystemRaw) > 0 { 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. // Provision parses m's IP ranges, either from IP or CIDR expressions.
func (m *MatchRemoteIP) Provision(ctx caddy.Context) error { func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
m.logger = ctx.Logger(m) m.logger = ctx.Logger()
for _, str := range m.Ranges { for _, str := range m.Ranges {
// Exclude the zone_id from the IP // Exclude the zone_id from the IP
if strings.Contains(str, "%") { if strings.Contains(str, "%") {
+4
View File
@@ -11,6 +11,10 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
) )
// Metrics configures metrics observations.
// EXPERIMENTAL and subject to change or removal.
type Metrics struct{}
var httpMetrics = struct { var httpMetrics = struct {
init sync.Once init sync.Once
requestInFlight *prometheus.GaugeVec requestInFlight *prometheus.GaugeVec
+1 -1
View File
@@ -61,7 +61,7 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
// Provision sets up h. // Provision sets up h.
func (h *Handler) Provision(ctx caddy.Context) error { func (h *Handler) Provision(ctx caddy.Context) error {
h.logger = ctx.Logger(h) h.logger = ctx.Logger()
if h.Headers != nil { if h.Headers != nil {
err := h.Headers.Provision(ctx) err := h.Headers.Provision(ctx)
if err != nil { if err != nil {
+43 -8
View File
@@ -28,6 +28,7 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
"github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/caddyserver/caddy/v2/modules/caddytls"
"go.uber.org/zap"
) )
func init() { func init() {
@@ -41,6 +42,7 @@ A simple but production-ready reverse proxy. Useful for quick deployments,
demos, and development. demos, and development.
Simply shuttles HTTP(S) traffic from the --from address to the --to address. 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 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 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 { Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("reverse-proxy", flag.ExitOnError) fs := flag.NewFlagSet("reverse-proxy", flag.ExitOnError)
fs.String("from", "localhost", "Address on which to receive traffic") 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("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("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("internal-certs", false, "Use internal CA for issuing certs")
fs.Bool("debug", false, "Enable verbose debug logs")
return fs return fs
}(), }(),
}) })
@@ -70,15 +73,15 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
caddy.TrapSignals() caddy.TrapSignals()
from := fs.String("from") from := fs.String("from")
to := fs.String("to")
changeHost := fs.Bool("change-host-header") changeHost := fs.Bool("change-host-header")
insecure := fs.Bool("insecure") insecure := fs.Bool("insecure")
internalCerts := fs.Bool("internal-certs") internalCerts := fs.Bool("internal-certs")
debug := fs.Bool("debug")
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort) httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort) httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
if to == "" { if len(reverseProxyCmdTo) == 0 {
return caddy.ExitCodeFailedStartup, fmt.Errorf("--to is required") 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 // set up the upstream address; assume missing information from given parts
toAddr, toScheme, err := parseUpstreamDialAddress(to) // mixing schemes isn't supported, so use first defined (if available)
if err != nil { toAddresses := make([]string, len(reverseProxyCmdTo))
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", to, err) 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 // 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{ handler := Handler{
TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil), TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil),
Upstreams: UpstreamPool{{Dial: toAddr}}, Upstreams: upstreamPool,
} }
if changeHost { if changeHost {
@@ -182,12 +201,28 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
AppsRaw: appsRaw, AppsRaw: appsRaw,
} }
if debug {
cfg.Logging = &caddy.Logging{
Logs: map[string]*caddy.CustomLog{
"default": {Level: zap.DebugLevel.CapitalString()},
},
}
}
err = caddy.Run(cfg) err = caddy.Run(cfg)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, err 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 {} 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. // Provision sets up t.
func (t *Transport) Provision(ctx caddy.Context) error { func (t *Transport) Provision(ctx caddy.Context) error {
t.logger = ctx.Logger(t) t.logger = ctx.Logger()
if t.Root == "" { if t.Root == "" {
t.Root = "{http.vars.root}" t.Root = "{http.vars.root}"
@@ -195,7 +195,7 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
TCPConn: tcpConn, TCPConn: tcpConn,
readTimeout: time.Duration(h.ReadTimeout), readTimeout: time.Duration(h.ReadTimeout),
writeTimeout: time.Duration(h.WriteTimeout), 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.events = eventAppIface.(*caddyevents.App)
h.ctx = ctx h.ctx = ctx
h.logger = ctx.Logger(h) h.logger = ctx.Logger()
h.connections = make(map[io.ReadWriteCloser]openConnection) h.connections = make(map[io.ReadWriteCloser]openConnection)
h.connectionsMu = new(sync.Mutex) 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 { func (su *SRVUpstreams) Provision(ctx caddy.Context) error {
su.logger = ctx.Logger(su) su.logger = ctx.Logger()
if su.Refresh == 0 { if su.Refresh == 0 {
su.Refresh = caddy.Duration(time.Minute) su.Refresh = caddy.Duration(time.Minute)
} }
@@ -383,7 +383,7 @@ func (MultiUpstreams) CaddyModule() caddy.ModuleInfo {
} }
func (mu *MultiUpstreams) Provision(ctx caddy.Context) error { func (mu *MultiUpstreams) Provision(ctx caddy.Context) error {
mu.logger = ctx.Logger(mu) mu.logger = ctx.Logger()
if mu.SourcesRaw != nil { if mu.SourcesRaw != nil {
mod, err := ctx.LoadModule(mu, "SourcesRaw") mod, err := ctx.LoadModule(mu, "SourcesRaw")
+1 -1
View File
@@ -101,7 +101,7 @@ func (Rewrite) CaddyModule() caddy.ModuleInfo {
// Provision sets up rewr. // Provision sets up rewr.
func (rewr *Rewrite) Provision(ctx caddy.Context) error { func (rewr *Rewrite) Provision(ctx caddy.Context) error {
rewr.logger = ctx.Logger(rewr) rewr.logger = ctx.Logger()
for i, rep := range rewr.PathRegexp { for i, rep := range rewr.PathRegexp {
if rep.Find == "" { if rep.Find == "" {
+10 -7
View File
@@ -130,7 +130,7 @@ func (routes RouteList) Provision(ctx caddy.Context) error {
if err != nil { if err != nil {
return err return err
} }
return routes.ProvisionHandlers(ctx) return routes.ProvisionHandlers(ctx, nil)
} }
// ProvisionMatchers sets up all the matchers by loading the // 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 // handler modules. Only call this method directly if you need
// to set up matchers and handlers separately without having // to set up matchers and handlers separately without having
// to provision a second time; otherwise use Provision instead. // 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 { for i := range routes {
handlersIface, err := ctx.LoadModule(&routes[i], "HandlersRaw") handlersIface, err := ctx.LoadModule(&routes[i], "HandlersRaw")
if err != nil { if err != nil {
@@ -168,7 +168,7 @@ func (routes RouteList) ProvisionHandlers(ctx caddy.Context) error {
// pre-compile the middleware handler chain // pre-compile the middleware handler chain
for _, midhandler := range routes[i].Handlers { 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 return nil
@@ -270,9 +270,12 @@ func wrapRoute(route Route) Middleware {
// we need to pull this particular MiddlewareHandler // we need to pull this particular MiddlewareHandler
// pointer into its own stack frame to preserve it so it // pointer into its own stack frame to preserve it so it
// won't be overwritten in future loop iterations. // won't be overwritten in future loop iterations.
func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler) Middleware { func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware {
// wrap the middleware with metrics instrumentation handlerToUse := mh
metricsHandler := newMetricsInstrumentedHandler(caddy.GetModuleName(mh), mh) if metrics != nil {
// wrap the middleware with metrics instrumentation
handlerToUse = newMetricsInstrumentedHandler(caddy.GetModuleName(mh), mh)
}
return func(next Handler) Handler { return func(next Handler) Handler {
// copy the next handler (it's an interface, so it's // 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 { return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
// TODO: This is where request tracing could be implemented // TODO: This is where request tracing could be implemented
// TODO: see what the std lib gives us in terms of stack tracing too // 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" "context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@@ -153,6 +152,10 @@ type Server struct {
// Default: `[h1 h2 h3]` // Default: `[h1 h2 h3]`
Protocols []string `json:"protocols,omitempty"` 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 name string
primaryHandlerChain Handler primaryHandlerChain Handler
@@ -173,6 +176,11 @@ type Server struct {
shutdownAt time.Time shutdownAt time.Time
shutdownAtMu *sync.RWMutex 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. // 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) accLog := s.accessLogger.With(loggableReq)
defer func() { 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.status", wrec.Status())
repl.Set("http.response.size", wrec.Size()) repl.Set("http.response.size", wrec.Size())
repl.Set("http.response.duration", duration) repl.Set("http.response.duration", duration)
@@ -505,6 +518,51 @@ func (s *Server) serveHTTP3(hostport string, tlsCfg *tls.Config) error {
return nil 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 // HTTPErrorConfig determines how to handle errors
// from the HTTP handlers. // from the HTTP handlers.
type HTTPErrorConfig struct { type HTTPErrorConfig struct {
@@ -592,96 +650,6 @@ func (s *Server) protocol(proto string) bool {
// EXPERIMENTAL: Subject to change or removal. // EXPERIMENTAL: Subject to change or removal.
func (s *Server) Listeners() []net.Listener { return s.listeners } 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 // 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. // 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 { 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 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 // originalRequest returns a partial, shallow copy of
// req, including: req.Method, deep copy of req.URL // req, including: req.Method, deep copy of req.URL
// (into the urlCopy parameter, which should be on the // (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"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
caddycmd "github.com/caddyserver/caddy/v2/cmd" caddycmd "github.com/caddyserver/caddy/v2/cmd"
"go.uber.org/zap"
) )
func init() { func init() {
@@ -403,7 +404,7 @@ func cmdRespond(fl caddycmd.Flags) (int, error) {
if debug { if debug {
cfg.Logging = &caddy.Logging{ cfg.Logging = &caddy.Logging{
Logs: map[string]*caddy.CustomLog{ 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. // Provision implements caddy.Provisioner.
func (ot *Tracing) Provision(ctx caddy.Context) error { func (ot *Tracing) Provision(ctx caddy.Context) error {
ot.logger = ctx.Logger(ot) ot.logger = ctx.Logger()
var err error var err error
ot.otel, err = newOpenTelemetryWrapper(ctx, ot.SpanName) 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: // UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
// //
// tracing { // tracing {
// [span <span_name>] // [span <span_name>]
// } // }
//
func (ot *Tracing) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (ot *Tracing) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
setParameter := func(d *caddyfile.Dispenser, val *string) error { setParameter := func(d *caddyfile.Dispenser, val *string) error {
if d.NextArg() { if d.NextArg() {
+1 -1
View File
@@ -87,7 +87,7 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
// Provision sets up the ACME server handler. // Provision sets up the ACME server handler.
func (ash *Handler) Provision(ctx caddy.Context) error { func (ash *Handler) Provision(ctx caddy.Context) error {
ash.logger = ctx.Logger(ash) ash.logger = ctx.Logger()
// set some defaults // set some defaults
if ash.CA == "" { if ash.CA == "" {
ash.CA = caddypki.DefaultCAID ash.CA = caddypki.DefaultCAID
+1 -1
View File
@@ -47,7 +47,7 @@ func (adminAPI) CaddyModule() caddy.ModuleInfo {
// Provision sets up the adminAPI module. // Provision sets up the adminAPI module.
func (a *adminAPI) Provision(ctx caddy.Context) error { func (a *adminAPI) Provision(ctx caddy.Context) error {
a.ctx = ctx 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 // First check if the PKI app was configured, because
// a.ctx.App() has the side effect of instantiating // 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. // Provision sets up the configuration for the PKI app.
func (p *PKI) Provision(ctx caddy.Context) error { func (p *PKI) Provision(ctx caddy.Context) error {
p.ctx = ctx p.ctx = ctx
p.log = ctx.Logger(p) p.log = ctx.Logger()
for caID, ca := range p.CAs { for caID, ca := range p.CAs {
err := ca.Provision(ctx, caID, p.log) err := ca.Provision(ctx, caID, p.log)
+1 -1
View File
@@ -103,7 +103,7 @@ func (ACMEIssuer) CaddyModule() caddy.ModuleInfo {
// Provision sets up iss. // Provision sets up iss.
func (iss *ACMEIssuer) Provision(ctx caddy.Context) error { func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
iss.logger = ctx.Logger(iss) iss.logger = ctx.Logger()
repl := caddy.NewReplacer() repl := caddy.NewReplacer()
+6 -6
View File
@@ -43,7 +43,7 @@ func (Tailscale) CaddyModule() caddy.ModuleInfo {
} }
func (ts *Tailscale) Provision(ctx caddy.Context) error { func (ts *Tailscale) Provision(ctx caddy.Context) error {
ts.logger = ctx.Logger(ts) ts.logger = ctx.Logger()
return nil return nil
} }
@@ -66,7 +66,9 @@ func (ts Tailscale) canHazCertificate(ctx context.Context, hello *tls.ClientHell
status, err := tscert.GetStatus(ctx) status, err := tscert.GetStatus(ctx)
if err != nil { if err != nil {
if ts.Optional { 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 return false, err
} }
@@ -80,8 +82,7 @@ func (ts Tailscale) canHazCertificate(ctx context.Context, hello *tls.ClientHell
// UnmarshalCaddyfile deserializes Caddyfile tokens into ts. // UnmarshalCaddyfile deserializes Caddyfile tokens into ts.
// //
// ... tailscale // ... tailscale
//
func (Tailscale) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (Tailscale) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {
if d.NextArg() { if d.NextArg() {
@@ -178,8 +179,7 @@ func (hcg HTTPCertGetter) GetCertificate(ctx context.Context, hello *tls.ClientH
// UnmarshalCaddyfile deserializes Caddyfile tokens into ts. // UnmarshalCaddyfile deserializes Caddyfile tokens into ts.
// //
// ... http <url> // ... http <url>
//
func (hcg *HTTPCertGetter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (hcg *HTTPCertGetter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {
if !d.NextArg() { if !d.NextArg() {
+45 -1
View File
@@ -20,11 +20,14 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/mholt/acmez" "github.com/mholt/acmez"
"go.uber.org/zap"
) )
func init() { func init() {
@@ -156,6 +159,16 @@ type ConnectionPolicy struct {
// is no policy configured for the empty SNI value. // is no policy configured for the empty SNI value.
DefaultSNI string `json:"default_sni,omitempty"` 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 // TLSConfig is the fully-formed, standard lib TLS config
// used to serve TLS connections. Provision all // used to serve TLS connections. Provision all
// ConnectionPolicies to populate this. It is exported only // 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) setDefaultTLSParams(cfg)
p.TLSConfig = cfg p.TLSConfig = cfg
@@ -297,7 +334,8 @@ func (p ConnectionPolicy) SettingsEmpty() bool {
p.ProtocolMin == "" && p.ProtocolMin == "" &&
p.ProtocolMax == "" && p.ProtocolMax == "" &&
p.ClientAuthentication == nil && p.ClientAuthentication == nil &&
p.DefaultSNI == "" p.DefaultSNI == "" &&
p.InsecureSecretsLog == ""
} }
// ClientAuthentication configures TLS client auth. // ClientAuthentication configures TLS client auth.
@@ -542,3 +580,9 @@ type ClientCertificateVerifier interface {
} }
var defaultALPN = []string{"h2", "http/1.1"} 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. // Provision sets up the issuer.
func (iss *InternalIssuer) Provision(ctx caddy.Context) error { func (iss *InternalIssuer) Provision(ctx caddy.Context) error {
iss.logger = ctx.Logger(iss) iss.logger = ctx.Logger()
// set some defaults // set some defaults
if iss.CA == "" { if iss.CA == "" {
@@ -148,12 +148,11 @@ func (iss InternalIssuer) Issue(ctx context.Context, csr *x509.CertificateReques
// UnmarshalCaddyfile deserializes Caddyfile tokens into iss. // UnmarshalCaddyfile deserializes Caddyfile tokens into iss.
// //
// ... internal { // ... internal {
// ca <name> // ca <name>
// lifetime <duration> // lifetime <duration>
// sign_with_root // sign_with_root
// } // }
//
func (iss *InternalIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (iss *InternalIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {
for d.NextBlock(0) { 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. // Provision parses m's IP ranges, either from IP or CIDR expressions.
func (m *MatchRemoteIP) Provision(ctx caddy.Context) error { func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
m.logger = ctx.Logger(m) m.logger = ctx.Logger()
for _, str := range m.Ranges { for _, str := range m.Ranges {
cidrs, err := m.parseIPRange(str) cidrs, err := m.parseIPRange(str)
if err != nil { 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.events = eventsAppIface.(*caddyevents.App)
t.ctx = ctx t.ctx = ctx
t.logger = ctx.Logger(t) t.logger = ctx.Logger()
repl := caddy.NewReplacer() repl := caddy.NewReplacer()
// set up a new certificate cache; this (re)loads all certificates // 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. // Provision sets up iss.
func (iss *ZeroSSLIssuer) Provision(ctx caddy.Context) error { func (iss *ZeroSSLIssuer) Provision(ctx caddy.Context) error {
iss.logger = ctx.Logger(iss) iss.logger = ctx.Logger()
if iss.ACMEIssuer == nil { if iss.ACMEIssuer == nil {
iss.ACMEIssuer = new(ACMEIssuer) iss.ACMEIssuer = new(ACMEIssuer)
} }
+4 -5
View File
@@ -62,7 +62,7 @@ func (l *zapLogger) Println(v ...any) {
// Provision sets up m. // Provision sets up m.
func (m *Metrics) Provision(ctx caddy.Context) error { func (m *Metrics) Provision(ctx caddy.Context) error {
log := ctx.Logger(m) log := ctx.Logger()
m.metricsHandler = createMetricsHandler(&zapLogger{log}, !m.DisableOpenMetrics) m.metricsHandler = createMetricsHandler(&zapLogger{log}, !m.DisableOpenMetrics)
return nil return nil
} }
@@ -75,10 +75,9 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: // UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
// //
// metrics [<matcher>] { // metrics [<matcher>] {
// disable_openmetrics // disable_openmetrics
// } // }
//
func (m *Metrics) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *Metrics) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {
args := d.RemainingArgs() 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 // iterate the input to find each placeholder
var lastWriteCursor int var lastWriteCursor int
// fail fast if too many placeholders are unclosed
var unclosedCount int
scan: scan:
for i := 0; i < len(input); i++ { for i := 0; i < len(input); i++ {
// check for escaped braces // check for escaped braces
if i > 0 && input[i-1] == phEscape && (input[i] == phClose || input[i] == phOpen) { if i > 0 && input[i-1] == phEscape && (input[i] == phClose || input[i] == phOpen) {
sb.WriteString(input[lastWriteCursor : i-1]) sb.WriteString(input[lastWriteCursor : i-1])
@@ -158,9 +160,17 @@ scan:
continue 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 // find the end of the placeholder
end := strings.Index(input[i:], string(phClose)) + i end := strings.Index(input[i:], string(phClose)) + i
if end < i { if end < i {
unclosedCount++
continue continue
} }
@@ -168,6 +178,7 @@ scan:
for end > 0 && end < len(input)-1 && input[end-1] == phEscape { for end > 0 && end < len(input)-1 && input[end-1] == phEscape {
nextEnd := strings.Index(input[end+1:], string(phClose)) nextEnd := strings.Index(input[end+1:], string(phClose))
if nextEnd < 0 { if nextEnd < 0 {
unclosedCount++
continue scan continue scan
} }
end += nextEnd + 1 end += nextEnd + 1