mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-25 16:22:36 -04:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e43b6d8178 | |||
| bffc258732 | |||
| 616418281b | |||
| 74547f5bed | |||
| 258071d857 | |||
| b6cec37893 | |||
| 48d723c07c | |||
| f1f7a22674 | |||
| 49b7a25264 | |||
| e6c58fdc08 | |||
| 2dc747cf2d | |||
| e338648fed | |||
| 9ad0ebc956 | |||
| a1ad20e472 | |||
| 62b0685375 | |||
| 0b3161aeea |
@@ -763,8 +763,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++ {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ var directiveOrder = []string{
|
||||
"map",
|
||||
"vars",
|
||||
"root",
|
||||
"skip_log",
|
||||
|
||||
"header",
|
||||
"copy_response_headers", // only in reverse_proxy's handle_response
|
||||
|
||||
@@ -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()},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -43,6 +43,7 @@ type serverOptions struct {
|
||||
Protocols []string
|
||||
StrictSNIHost *bool
|
||||
ShouldLogCredentials bool
|
||||
Metrics *caddyhttp.Metrics
|
||||
}
|
||||
|
||||
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
@@ -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{}
|
||||
|
||||
@@ -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,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": [
|
||||
{
|
||||
|
||||
+1
-1
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
@@ -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, "%") {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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()},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user