diff options
Diffstat (limited to 'modules')
54 files changed, 771 insertions, 419 deletions
diff --git a/modules/caddyhttp/caddyauth/basicauth.go b/modules/caddyhttp/caddyauth/basicauth.go index 6412d36..8aa44f1 100644 --- a/modules/caddyhttp/caddyauth/basicauth.go +++ b/modules/caddyhttp/caddyauth/basicauth.go @@ -28,7 +28,7 @@ func init() { // HTTPBasicAuth facilitates HTTP basic authentication. type HTTPBasicAuth struct { - HashRaw json.RawMessage `json:"hash,omitempty"` + HashRaw json.RawMessage `json:"hash,omitempty" caddy:"namespace=http.authentication.hashes inline_key=algorithm"` AccountList []Account `json:"accounts,omitempty"` Realm string `json:"realm,omitempty"` @@ -39,8 +39,8 @@ type HTTPBasicAuth struct { // CaddyModule returns the Caddy module information. func (HTTPBasicAuth) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.authentication.providers.http_basic", - New: func() caddy.Module { return new(HTTPBasicAuth) }, + ID: "http.authentication.providers.http_basic", + New: func() caddy.Module { return new(HTTPBasicAuth) }, } } @@ -51,12 +51,11 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error { } // load password hasher - hashIface, err := ctx.LoadModuleInline("algorithm", "http.handlers.authentication.hashes", hba.HashRaw) + hasherIface, err := ctx.LoadModule(hba, "HashRaw") if err != nil { return fmt.Errorf("loading password hasher module: %v", err) } - hba.Hash = hashIface.(Comparer) - hba.HashRaw = nil // allow GC to deallocate + hba.Hash = hasherIface.(Comparer) if hba.Hash == nil { return fmt.Errorf("hash is required") diff --git a/modules/caddyhttp/caddyauth/caddyauth.go b/modules/caddyhttp/caddyauth/caddyauth.go index 48d4fba..c79d080 100644 --- a/modules/caddyhttp/caddyauth/caddyauth.go +++ b/modules/caddyhttp/caddyauth/caddyauth.go @@ -15,7 +15,6 @@ package caddyauth import ( - "encoding/json" "fmt" "log" "net/http" @@ -30,7 +29,7 @@ func init() { // Authentication is a middleware which provides user authentication. type Authentication struct { - ProvidersRaw map[string]json.RawMessage `json:"providers,omitempty"` + ProvidersRaw caddy.ModuleMap `json:"providers,omitempty" caddy:"namespace=http.authentication.providers"` Providers map[string]Authenticator `json:"-"` } @@ -38,23 +37,21 @@ type Authentication struct { // CaddyModule returns the Caddy module information. func (Authentication) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.authentication", - New: func() caddy.Module { return new(Authentication) }, + ID: "http.handlers.authentication", + New: func() caddy.Module { return new(Authentication) }, } } // Provision sets up a. func (a *Authentication) Provision(ctx caddy.Context) error { a.Providers = make(map[string]Authenticator) - for modName, rawMsg := range a.ProvidersRaw { - val, err := ctx.LoadModule("http.handlers.authentication.providers."+modName, rawMsg) - if err != nil { - return fmt.Errorf("loading authentication provider module '%s': %v", modName, err) - } - a.Providers[modName] = val.(Authenticator) + mods, err := ctx.LoadModule(a, "ProvidersRaw") + if err != nil { + return fmt.Errorf("loading authentication providers: %v", err) + } + for modName, modIface := range mods.(map[string]interface{}) { + a.Providers[modName] = modIface.(Authenticator) } - a.ProvidersRaw = nil // allow GC to deallocate - return nil } diff --git a/modules/caddyhttp/caddyauth/caddyfile.go b/modules/caddyhttp/caddyauth/caddyfile.go index 3600324..8a33e6f 100644 --- a/modules/caddyhttp/caddyauth/caddyfile.go +++ b/modules/caddyhttp/caddyauth/caddyfile.go @@ -16,8 +16,8 @@ package caddyauth import ( "encoding/base64" - "encoding/json" + "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -97,7 +97,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) } return Authentication{ - ProvidersRaw: map[string]json.RawMessage{ + ProvidersRaw: caddy.ModuleMap{ "http_basic": caddyconfig.JSON(ba, nil), }, }, nil diff --git a/modules/caddyhttp/caddyauth/hashes.go b/modules/caddyhttp/caddyauth/hashes.go index 13010db..3ca5116 100644 --- a/modules/caddyhttp/caddyauth/hashes.go +++ b/modules/caddyhttp/caddyauth/hashes.go @@ -33,8 +33,8 @@ type BcryptHash struct{} // CaddyModule returns the Caddy module information. func (BcryptHash) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.authentication.hashes.bcrypt", - New: func() caddy.Module { return new(BcryptHash) }, + ID: "http.authentication.hashes.bcrypt", + New: func() caddy.Module { return new(BcryptHash) }, } } @@ -61,8 +61,8 @@ type ScryptHash struct { // CaddyModule returns the Caddy module information. func (ScryptHash) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.authentication.hashes.scrypt", - New: func() caddy.Module { return new(ScryptHash) }, + ID: "http.authentication.hashes.scrypt", + New: func() caddy.Module { return new(ScryptHash) }, } } diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go index 064a963..756a6c3 100644 --- a/modules/caddyhttp/caddyhttp.go +++ b/modules/caddyhttp/caddyhttp.go @@ -44,12 +44,27 @@ func init() { } } -// App is the HTTP app for Caddy. +// App is a robust, flexible HTTP server for Caddy. type App struct { - HTTPPort int `json:"http_port,omitempty"` - HTTPSPort int `json:"https_port,omitempty"` - GracePeriod caddy.Duration `json:"grace_period,omitempty"` - Servers map[string]*Server `json:"servers,omitempty"` + // HTTPPort specifies the port to use for HTTP (as opposed to HTTPS), + // which is used when setting up HTTP->HTTPS redirects or ACME HTTP + // challenge solvers. Default: 80. + HTTPPort int `json:"http_port,omitempty"` + + // HTTPSPort specifies the port to use for HTTPS, which is used when + // solving the ACME TLS-ALPN challenges, or whenever HTTPS is needed + // but no specific port number is given. Default: 443. + HTTPSPort int `json:"https_port,omitempty"` + + // GracePeriod is how long to wait for active connections when shutting + // down the server. Once the grace period is over, connections will + // be forcefully closed. + GracePeriod caddy.Duration `json:"grace_period,omitempty"` + + // Servers is the list of servers, keyed by arbitrary names chosen + // at your discretion for your own convenience; the keys do not + // affect functionality. + Servers map[string]*Server `json:"servers,omitempty"` servers []*http.Server h3servers []*http3.Server @@ -62,8 +77,8 @@ type App struct { // CaddyModule returns the Caddy module information. func (App) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http", - New: func() caddy.Module { return new(App) }, + ID: "http", + New: func() caddy.Module { return new(App) }, } } @@ -561,8 +576,10 @@ var emptyHandler HandlerFunc = func(http.ResponseWriter, *http.Request) error { // WeakString is a type that unmarshals any JSON value // as a string literal, with the following exceptions: -// 1) actual string values are decoded as strings; and -// 2) null is decoded as empty string; +// +// 1. actual string values are decoded as strings; and +// 2. null is decoded as empty string; +// // and provides methods for getting the value as various // primitive types. However, using this type removes any // type safety as far as deserializing JSON is concerned. diff --git a/modules/caddyhttp/encode/brotli/brotli.go b/modules/caddyhttp/encode/brotli/brotli.go index cf055aa..52bb205 100644 --- a/modules/caddyhttp/encode/brotli/brotli.go +++ b/modules/caddyhttp/encode/brotli/brotli.go @@ -37,8 +37,8 @@ type Brotli struct { // CaddyModule returns the Caddy module information. func (Brotli) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.encoders.brotli", - New: func() caddy.Module { return new(Brotli) }, + ID: "http.encoders.brotli", + New: func() caddy.Module { return new(Brotli) }, } } diff --git a/modules/caddyhttp/encode/caddyfile.go b/modules/caddyhttp/encode/caddyfile.go index 4764e9b..dd12de2 100644 --- a/modules/caddyhttp/encode/caddyfile.go +++ b/modules/caddyhttp/encode/caddyfile.go @@ -15,7 +15,6 @@ package encode import ( - "encoding/json" "fmt" "github.com/caddyserver/caddy/v2" @@ -52,14 +51,14 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { for _, arg := range d.RemainingArgs() { mod, err := caddy.GetModule("http.encoders." + arg) if err != nil { - return fmt.Errorf("finding encoder module '%s': %v", mod.Name, err) + return fmt.Errorf("finding encoder module '%s': %v", mod, err) } encoding, ok := mod.New().(Encoding) if !ok { - return fmt.Errorf("module %s is not an HTTP encoding", mod.Name) + return fmt.Errorf("module %s is not an HTTP encoding", mod) } if enc.EncodingsRaw == nil { - enc.EncodingsRaw = make(map[string]json.RawMessage) + enc.EncodingsRaw = make(caddy.ModuleMap) } enc.EncodingsRaw[arg] = caddyconfig.JSON(encoding, nil) } @@ -72,7 +71,7 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } unm, ok := mod.New().(caddyfile.Unmarshaler) if !ok { - return fmt.Errorf("encoder module '%s' is not a Caddyfile unmarshaler", mod.Name) + return fmt.Errorf("encoder module '%s' is not a Caddyfile unmarshaler", mod) } err = unm.UnmarshalCaddyfile(d.NewFromNextTokens()) if err != nil { @@ -80,10 +79,10 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } encoding, ok := unm.(Encoding) if !ok { - return fmt.Errorf("module %s is not an HTTP encoding", mod.Name) + return fmt.Errorf("module %s is not an HTTP encoding", mod) } if enc.EncodingsRaw == nil { - enc.EncodingsRaw = make(map[string]json.RawMessage) + enc.EncodingsRaw = make(caddy.ModuleMap) } enc.EncodingsRaw[name] = caddyconfig.JSON(encoding, nil) } diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go index 3716fc6..c68f507 100644 --- a/modules/caddyhttp/encode/encode.go +++ b/modules/caddyhttp/encode/encode.go @@ -21,7 +21,6 @@ package encode import ( "bytes" - "encoding/json" "fmt" "io" "net/http" @@ -40,9 +39,9 @@ func init() { // Encode is a middleware which can encode responses. type Encode struct { - EncodingsRaw map[string]json.RawMessage `json:"encodings,omitempty"` - Prefer []string `json:"prefer,omitempty"` - MinLength int `json:"minimum_length,omitempty"` + EncodingsRaw caddy.ModuleMap `json:"encodings,omitempty" caddy:"namespace=http.encoders"` + Prefer []string `json:"prefer,omitempty"` + MinLength int `json:"minimum_length,omitempty"` writerPools map[string]*sync.Pool // TODO: these pools do not get reused through config reloads... } @@ -50,25 +49,23 @@ type Encode struct { // CaddyModule returns the Caddy module information. func (Encode) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.encode", - New: func() caddy.Module { return new(Encode) }, + ID: "http.handlers.encode", + New: func() caddy.Module { return new(Encode) }, } } // Provision provisions enc. func (enc *Encode) Provision(ctx caddy.Context) error { - for modName, rawMsg := range enc.EncodingsRaw { - val, err := ctx.LoadModule("http.encoders."+modName, rawMsg) - if err != nil { - return fmt.Errorf("loading encoder module '%s': %v", modName, err) - } - encoding := val.(Encoding) - err = enc.addEncoding(encoding) + mods, err := ctx.LoadModule(enc, "EncodingsRaw") + if err != nil { + return fmt.Errorf("loading encoder modules: %v", err) + } + for modName, modIface := range mods.(map[string]interface{}) { + err = enc.addEncoding(modIface.(Encoding)) if err != nil { - return err + return fmt.Errorf("adding encoding %s: %v", modName, err) } } - enc.EncodingsRaw = nil // allow GC to deallocate if enc.MinLength == 0 { enc.MinLength = defaultMinLength diff --git a/modules/caddyhttp/encode/gzip/gzip.go b/modules/caddyhttp/encode/gzip/gzip.go index d6d67f7..590f708 100644 --- a/modules/caddyhttp/encode/gzip/gzip.go +++ b/modules/caddyhttp/encode/gzip/gzip.go @@ -37,8 +37,8 @@ type Gzip struct { // CaddyModule returns the Caddy module information. func (Gzip) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.encoders.gzip", - New: func() caddy.Module { return new(Gzip) }, + ID: "http.encoders.gzip", + New: func() caddy.Module { return new(Gzip) }, } } diff --git a/modules/caddyhttp/encode/zstd/zstd.go b/modules/caddyhttp/encode/zstd/zstd.go index f2b4e85..5182fc4 100644 --- a/modules/caddyhttp/encode/zstd/zstd.go +++ b/modules/caddyhttp/encode/zstd/zstd.go @@ -31,8 +31,8 @@ type Zstd struct{} // CaddyModule returns the Caddy module information. func (Zstd) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.encoders.zstd", - New: func() caddy.Module { return new(Zstd) }, + ID: "http.encoders.zstd", + New: func() caddy.Module { return new(Zstd) }, } } diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go index 46bc5b7..fb931a1 100644 --- a/modules/caddyhttp/fileserver/caddyfile.go +++ b/modules/caddyhttp/fileserver/caddyfile.go @@ -15,8 +15,7 @@ package fileserver import ( - "encoding/json" - + "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" @@ -122,7 +121,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) URI: "{http.matchers.file.relative}{http.request.uri.query_string}", } - matcherSet := map[string]json.RawMessage{ + matcherSet := caddy.ModuleMap{ "file": h.JSON(MatchFile{ TryFiles: try, }, nil), diff --git a/modules/caddyhttp/fileserver/command.go b/modules/caddyhttp/fileserver/command.go index b861a99..e7a0ee3 100644 --- a/modules/caddyhttp/fileserver/command.go +++ b/modules/caddyhttp/fileserver/command.go @@ -75,8 +75,8 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) { }, } if domain != "" { - route.MatcherSetsRaw = []map[string]json.RawMessage{ - map[string]json.RawMessage{ + route.MatcherSetsRaw = []caddy.ModuleMap{ + caddy.ModuleMap{ "host": caddyconfig.JSON(caddyhttp.MatchHost{domain}, nil), }, } @@ -100,7 +100,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) { cfg := &caddy.Config{ Admin: &caddy.AdminConfig{Disabled: true}, - AppsRaw: map[string]json.RawMessage{ + AppsRaw: caddy.ModuleMap{ "http": caddyconfig.JSON(httpApp, nil), }, } diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index 4a7f657..13cb60a 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -54,8 +54,8 @@ type MatchFile struct { // CaddyModule returns the Caddy module information. func (MatchFile) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.file", - New: func() caddy.Module { return new(MatchFile) }, + ID: "http.matchers.file", + New: func() caddy.Module { return new(MatchFile) }, } } diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index 732894d..a9e6e1c 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -53,8 +53,8 @@ type FileServer struct { // CaddyModule returns the Caddy module information. func (FileServer) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.file_server", - New: func() caddy.Module { return new(FileServer) }, + ID: "http.handlers.file_server", + New: func() caddy.Module { return new(FileServer) }, } } diff --git a/modules/caddyhttp/headers/headers.go b/modules/caddyhttp/headers/headers.go index 813b9fe..f53e859 100644 --- a/modules/caddyhttp/headers/headers.go +++ b/modules/caddyhttp/headers/headers.go @@ -37,8 +37,8 @@ type Handler struct { // CaddyModule returns the Caddy module information. func (Handler) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.headers", - New: func() caddy.Module { return new(Handler) }, + ID: "http.handlers.headers", + New: func() caddy.Module { return new(Handler) }, } } diff --git a/modules/caddyhttp/httpcache/httpcache.go b/modules/caddyhttp/httpcache/httpcache.go index b5bc044..81f5816 100644 --- a/modules/caddyhttp/httpcache/httpcache.go +++ b/modules/caddyhttp/httpcache/httpcache.go @@ -43,8 +43,8 @@ type Cache struct { // CaddyModule returns the Caddy module information. func (Cache) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.cache", - New: func() caddy.Module { return new(Cache) }, + ID: "http.handlers.cache", + New: func() caddy.Module { return new(Cache) }, } } diff --git a/modules/caddyhttp/markdown/markdown.go b/modules/caddyhttp/markdown/markdown.go index 5ff18b8..acaa0c3 100644 --- a/modules/caddyhttp/markdown/markdown.go +++ b/modules/caddyhttp/markdown/markdown.go @@ -38,8 +38,8 @@ type Markdown struct { // CaddyModule returns the Caddy module information. func (Markdown) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.markdown", - New: func() caddy.Module { return new(Markdown) }, + ID: "http.handlers.markdown", + New: func() caddy.Module { return new(Markdown) }, } } diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 82fb04a..ea715c5 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -66,7 +66,7 @@ type ( // MatchNegate matches requests by negating its matchers' results. MatchNegate struct { - MatchersRaw map[string]json.RawMessage `json:"-"` + MatchersRaw caddy.ModuleMap `json:"-" caddy:"namespace=http.matchers"` Matchers MatcherSet `json:"-"` } @@ -95,8 +95,8 @@ func init() { // CaddyModule returns the Caddy module information. func (MatchHost) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.host", - New: func() caddy.Module { return new(MatchHost) }, + ID: "http.matchers.host", + New: func() caddy.Module { return new(MatchHost) }, } } @@ -149,8 +149,8 @@ outer: // CaddyModule returns the Caddy module information. func (MatchPath) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.path", - New: func() caddy.Module { return new(MatchPath) }, + ID: "http.matchers.path", + New: func() caddy.Module { return new(MatchPath) }, } } @@ -208,8 +208,8 @@ func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // CaddyModule returns the Caddy module information. func (MatchPathRE) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.path_regexp", - New: func() caddy.Module { return new(MatchPathRE) }, + ID: "http.matchers.path_regexp", + New: func() caddy.Module { return new(MatchPathRE) }, } } @@ -222,8 +222,8 @@ func (m MatchPathRE) Match(r *http.Request) bool { // CaddyModule returns the Caddy module information. func (MatchMethod) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.method", - New: func() caddy.Module { return new(MatchMethod) }, + ID: "http.matchers.method", + New: func() caddy.Module { return new(MatchMethod) }, } } @@ -248,8 +248,8 @@ func (m MatchMethod) Match(r *http.Request) bool { // CaddyModule returns the Caddy module information. func (MatchQuery) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.query", - New: func() caddy.Module { return new(MatchQuery) }, + ID: "http.matchers.query", + New: func() caddy.Module { return new(MatchQuery) }, } } @@ -291,8 +291,8 @@ func (m MatchQuery) Match(r *http.Request) bool { // CaddyModule returns the Caddy module information. func (MatchHeader) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.header", - New: func() caddy.Module { return new(MatchHeader) }, + ID: "http.matchers.header", + New: func() caddy.Module { return new(MatchHeader) }, } } @@ -349,8 +349,8 @@ func (m MatchHeader) Match(r *http.Request) bool { // CaddyModule returns the Caddy module information. func (MatchHeaderRE) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.header_regexp", - New: func() caddy.Module { return new(MatchHeaderRE) }, + ID: "http.matchers.header_regexp", + New: func() caddy.Module { return new(MatchHeaderRE) }, } } @@ -406,8 +406,8 @@ func (m MatchHeaderRE) Validate() error { // CaddyModule returns the Caddy module information. func (MatchProtocol) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.protocol", - New: func() caddy.Module { return new(MatchProtocol) }, + ID: "http.matchers.protocol", + New: func() caddy.Module { return new(MatchProtocol) }, } } @@ -439,8 +439,8 @@ func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // CaddyModule returns the Caddy module information. func (MatchNegate) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.not", - New: func() caddy.Module { return new(MatchNegate) }, + ID: "http.matchers.not", + New: func() caddy.Module { return new(MatchNegate) }, } } @@ -486,7 +486,7 @@ func (m *MatchNegate) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // we should now be functional, but we also need // to be able to marshal as JSON, otherwise config // adaptation won't work properly - m.MatchersRaw = make(map[string]json.RawMessage) + m.MatchersRaw = make(caddy.ModuleMap) for name, matchers := range matcherMap { jsonBytes, err := json.Marshal(matchers) if err != nil { @@ -500,14 +500,13 @@ func (m *MatchNegate) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // Provision loads the matcher modules to be negated. func (m *MatchNegate) Provision(ctx caddy.Context) error { - for modName, rawMsg := range m.MatchersRaw { - val, err := ctx.LoadModule("http.matchers."+modName, rawMsg) - if err != nil { - return fmt.Errorf("loading matcher module '%s': %v", modName, err) - } - m.Matchers = append(m.Matchers, val.(RequestMatcher)) + mods, err := ctx.LoadModule(m, "MatchersRaw") + if err != nil { + return fmt.Errorf("loading matchers: %v", err) + } + for _, modIface := range mods.(map[string]interface{}) { + m.Matchers = append(m.Matchers, modIface.(RequestMatcher)) } - m.MatchersRaw = nil // allow GC to deallocate return nil } @@ -520,8 +519,8 @@ func (m MatchNegate) Match(r *http.Request) bool { // CaddyModule returns the Caddy module information. func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.remote_ip", - New: func() caddy.Module { return new(MatchRemoteIP) }, + ID: "http.matchers.remote_ip", + New: func() caddy.Module { return new(MatchRemoteIP) }, } } @@ -597,8 +596,8 @@ func (m MatchRemoteIP) Match(r *http.Request) bool { // CaddyModule returns the Caddy module information. func (MatchStarlarkExpr) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.starlark_expr", // TODO: Rename to 'starlark'? - New: func() caddy.Module { return new(MatchStarlarkExpr) }, + ID: "http.matchers.starlark_expr", // TODO: Rename to 'starlark'? + New: func() caddy.Module { return new(MatchStarlarkExpr) }, } } diff --git a/modules/caddyhttp/requestbody/requestbody.go b/modules/caddyhttp/requestbody/requestbody.go index 9b16250..dd3f256 100644 --- a/modules/caddyhttp/requestbody/requestbody.go +++ b/modules/caddyhttp/requestbody/requestbody.go @@ -33,8 +33,8 @@ type RequestBody struct { // CaddyModule returns the Caddy module information. func (RequestBody) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.request_body", // TODO: better name for this? - New: func() caddy.Module { return new(RequestBody) }, + ID: "http.handlers.request_body", // TODO: better name for this? + New: func() caddy.Module { return new(RequestBody) }, } } diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go index c8cf26e..9dba769 100644 --- a/modules/caddyhttp/reverseproxy/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/caddyfile.go @@ -108,11 +108,11 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { name := d.Val() mod, err := caddy.GetModule("http.handlers.reverse_proxy.selection_policies." + name) if err != nil { - return d.Errf("getting load balancing policy module '%s': %v", mod.Name, err) + return d.Errf("getting load balancing policy module '%s': %v", mod, err) } unm, ok := mod.New().(caddyfile.Unmarshaler) if !ok { - return d.Errf("load balancing policy module '%s' is not a Caddyfile unmarshaler", mod.Name) + return d.Errf("load balancing policy module '%s' is not a Caddyfile unmarshaler", mod) } err = unm.UnmarshalCaddyfile(d.NewFromNextTokens()) if err != nil { @@ -120,7 +120,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } sel, ok := unm.(Selector) if !ok { - return d.Errf("module %s is not a Selector", mod.Name) + return d.Errf("module %s is not a Selector", mod) } if h.LoadBalancing == nil { h.LoadBalancing = new(LoadBalancing) @@ -391,11 +391,11 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { name := d.Val() mod, err := caddy.GetModule("http.handlers.reverse_proxy.transport." + name) if err != nil { - return d.Errf("getting transport module '%s': %v", mod.Name, err) + return d.Errf("getting transport module '%s': %v", mod, err) } unm, ok := mod.New().(caddyfile.Unmarshaler) if !ok { - return d.Errf("transport module '%s' is not a Caddyfile unmarshaler", mod.Name) + return d.Errf("transport module '%s' is not a Caddyfile unmarshaler", mod) } err = unm.UnmarshalCaddyfile(d.NewFromNextTokens()) if err != nil { @@ -403,7 +403,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } rt, ok := unm.(http.RoundTripper) if !ok { - return d.Errf("module %s is not a RoundTripper", mod.Name) + return d.Errf("module %s is not a RoundTripper", mod) } h.TransportRaw = caddyconfig.JSONModuleObject(rt, "protocol", name, nil) diff --git a/modules/caddyhttp/reverseproxy/circuitbreaker.go b/modules/caddyhttp/reverseproxy/circuitbreaker.go index de2a6f9..474f1c6 100644 --- a/modules/caddyhttp/reverseproxy/circuitbreaker.go +++ b/modules/caddyhttp/reverseproxy/circuitbreaker.go @@ -41,8 +41,8 @@ type localCircuitBreaker struct { // CaddyModule returns the Caddy module information. func (localCircuitBreaker) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.circuit_breakers.local", - New: func() caddy.Module { return new(localCircuitBreaker) }, + ID: "http.reverse_proxy.circuit_breakers.local", + New: func() caddy.Module { return new(localCircuitBreaker) }, } } diff --git a/modules/caddyhttp/reverseproxy/command.go b/modules/caddyhttp/reverseproxy/command.go index 0ddb8f2..e16c4f5 100644 --- a/modules/caddyhttp/reverseproxy/command.go +++ b/modules/caddyhttp/reverseproxy/command.go @@ -113,8 +113,8 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) { } urlHost := fromURL.Hostname() if urlHost != "" { - route.MatcherSetsRaw = []map[string]json.RawMessage{ - map[string]json.RawMessage{ + route.MatcherSetsRaw = []caddy.ModuleMap{ + caddy.ModuleMap{ "host": caddyconfig.JSON(caddyhttp.MatchHost{urlHost}, nil), }, } @@ -138,7 +138,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) { cfg := &caddy.Config{ Admin: &caddy.AdminConfig{Disabled: true}, - AppsRaw: map[string]json.RawMessage{ + AppsRaw: caddy.ModuleMap{ "http": caddyconfig.JSON(httpApp, nil), }, } diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go index ed97342..8e723b2 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go @@ -18,6 +18,7 @@ import ( "encoding/json" "net/http" + "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" @@ -121,12 +122,12 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error } // route to redirect to canonical path if index PHP file - redirMatcherSet := map[string]json.RawMessage{ + redirMatcherSet := caddy.ModuleMap{ "file": h.JSON(fileserver.MatchFile{ TryFiles: []string{"{http.request.uri.path}/index.php"}, }, nil), "not": h.JSON(caddyhttp.MatchNegate{ - MatchersRaw: map[string]json.RawMessage{ + MatchersRaw: caddy.ModuleMap{ "path": h.JSON(caddyhttp.MatchPath{"*/"}, nil), }, }, nil), @@ -136,12 +137,12 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error Headers: http.Header{"Location": []string{"{http.request.uri.path}/"}}, } redirRoute := caddyhttp.Route{ - MatcherSetsRaw: []map[string]json.RawMessage{redirMatcherSet}, + MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet}, HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)}, } // route to rewrite to PHP index file - rewriteMatcherSet := map[string]json.RawMessage{ + rewriteMatcherSet := caddy.ModuleMap{ "file": h.JSON(fileserver.MatchFile{ TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php", "index.php"}, }, nil), @@ -151,13 +152,13 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error Rehandle: true, } rewriteRoute := caddyhttp.Route{ - MatcherSetsRaw: []map[string]json.RawMessage{rewriteMatcherSet}, + MatcherSetsRaw: []caddy.ModuleMap{rewriteMatcherSet}, HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)}, } // route to actually reverse proxy requests to PHP files; // match only requests that are for PHP files - rpMatcherSet := map[string]json.RawMessage{ + rpMatcherSet := caddy.ModuleMap{ "path": h.JSON([]string{"*.php"}, nil), } @@ -193,7 +194,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error // create the final reverse proxy route which is // conditional on matching PHP files rpRoute := caddyhttp.Route{ - MatcherSetsRaw: []map[string]json.RawMessage{rpMatcherSet}, + MatcherSetsRaw: []caddy.ModuleMap{rpMatcherSet}, HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rpHandler, "handler", "reverse_proxy", nil)}, } @@ -207,7 +208,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error { Class: "route", Value: caddyhttp.Route{ - MatcherSetsRaw: []map[string]json.RawMessage{userMatcherSet}, + MatcherSetsRaw: []caddy.ModuleMap{userMatcherSet}, HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(subroute, "handler", "subroute", nil)}, }, }, diff --git a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go index 21aeb17..aff9a6e 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go @@ -73,8 +73,8 @@ type Transport struct { // CaddyModule returns the Caddy module information. func (Transport) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.transport.fastcgi", - New: func() caddy.Module { return new(Transport) }, + ID: "http.reverse_proxy.transport.fastcgi", + New: func() caddy.Module { return new(Transport) }, } } diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index 38a904e..1dd1d14 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -40,6 +40,7 @@ type HTTPTransport struct { // TODO: It's possible that other transports (like fastcgi) might be // able to borrow/use at least some of these config fields; if so, // maybe move them into a type called CommonTransport and embed it? + TLS *TLSConfig `json:"tls,omitempty"` KeepAlive *KeepAlive `json:"keep_alive,omitempty"` Compression *bool `json:"compression,omitempty"` @@ -59,8 +60,8 @@ type HTTPTransport struct { // CaddyModule returns the Caddy module information. func (HTTPTransport) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.transport.http", - New: func() caddy.Module { return new(HTTPTransport) }, + ID: "http.reverse_proxy.transport.http", + New: func() caddy.Module { return new(HTTPTransport) }, } } diff --git a/modules/caddyhttp/reverseproxy/ntlm.go b/modules/caddyhttp/reverseproxy/ntlm.go index e2d46b4..ea2bb85 100644 --- a/modules/caddyhttp/reverseproxy/ntlm.go +++ b/modules/caddyhttp/reverseproxy/ntlm.go @@ -30,12 +30,12 @@ func init() { caddy.RegisterModule(NTLMTransport{}) } -// NTLMTransport proxies HTTP+NTLM authentication is being used. +// NTLMTransport proxies HTTP with NTLM authentication. // It basically wraps HTTPTransport so that it is compatible with // NTLM's HTTP-hostile requirements. Specifically, it will use // HTTPTransport's single, default *http.Transport for all requests // (unless the client's connection is already mapped to a different -// transport) until a request comes in with Authorization header +// transport) until a request comes in with an Authorization header // that has "NTLM" or "Negotiate"; when that happens, NTLMTransport // maps the client's connection (by its address, req.RemoteAddr) // to a new transport that is used only by that downstream conn. @@ -56,8 +56,8 @@ type NTLMTransport struct { // CaddyModule returns the Caddy module information. func (NTLMTransport) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.transport.http_ntlm", - New: func() caddy.Module { return new(NTLMTransport) }, + ID: "http.reverse_proxy.transport.http_ntlm", + New: func() caddy.Module { return new(NTLMTransport) }, } } diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index 87895e2..132f222 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -42,8 +42,8 @@ func init() { // Handler implements a highly configurable and production-ready reverse proxy. type Handler struct { - TransportRaw json.RawMessage `json:"transport,omitempty"` - CBRaw json.RawMessage `json:"circuit_breaker,omitempty"` + TransportRaw json.RawMessage `json:"transport,omitempty" caddy:"namespace=http.reverse_proxy.transport inline_key=protocol"` + CBRaw json.RawMessage `json:"circuit_breaker,omitempty" caddy:"namespace=http.reverse_proxy.circuit_breakers inline_key=type"` LoadBalancing *LoadBalancing `json:"load_balancing,omitempty"` HealthChecks *HealthChecks `json:"health_checks,omitempty"` Upstreams UpstreamPool `json:"upstreams,omitempty"` @@ -60,8 +60,8 @@ type Handler struct { // CaddyModule returns the Caddy module information. func (Handler) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy", - New: func() caddy.Module { return new(Handler) }, + ID: "http.handlers.reverse_proxy", + New: func() caddy.Module { return new(Handler) }, } } @@ -71,30 +71,25 @@ func (h *Handler) Provision(ctx caddy.Context) error { // start by loading modules if h.TransportRaw != nil { - val, err := ctx.LoadModuleInline("protocol", "http.handlers.reverse_proxy.transport", h.TransportRaw) + mod, err := ctx.LoadModule(h, "TransportRaw") if err != nil { - return fmt.Errorf("loading transport module: %s", err) + return fmt.Errorf("loading transport: %v", err) } - h.Transport = val.(http.RoundTripper) - h.TransportRaw = nil // allow GC to deallocate + h.Transport = mod.(http.RoundTripper) } if h.LoadBalancing != nil && h.LoadBalancing.SelectionPolicyRaw != nil { - val, err := ctx.LoadModuleInline("policy", - "http.handlers.reverse_proxy.selection_policies", - h.LoadBalancing.SelectionPolicyRaw) + mod, err := ctx.LoadModule(h.LoadBalancing, "SelectionPolicyRaw") if err != nil { - return fmt.Errorf("loading load balancing selection module: %s", err) + return fmt.Errorf("loading load balancing selection policy: %s", err) } - h.LoadBalancing.SelectionPolicy = val.(Selector) - h.LoadBalancing.SelectionPolicyRaw = nil // allow GC to deallocate + h.LoadBalancing.SelectionPolicy = mod.(Selector) } if h.CBRaw != nil { - val, err := ctx.LoadModuleInline("type", "http.handlers.reverse_proxy.circuit_breakers", h.CBRaw) + mod, err := ctx.LoadModule(h, "CBRaw") if err != nil { - return fmt.Errorf("loading circuit breaker module: %s", err) + return fmt.Errorf("loading circuit breaker: %s", err) } - h.CB = val.(CircuitBreaker) - h.CBRaw = nil // allow GC to deallocate + h.CB = mod.(CircuitBreaker) } // set up transport @@ -128,12 +123,14 @@ func (h *Handler) Provision(ctx caddy.Context) error { // defaulting to a sane wait period between attempts h.LoadBalancing.TryInterval = caddy.Duration(250 * time.Millisecond) } - lbMatcherSets, err := h.LoadBalancing.RetryMatchRaw.Setup(ctx) + lbMatcherSets, err := ctx.LoadModule(h.LoadBalancing, "RetryMatchRaw") + if err != nil { + return err + } + err = h.LoadBalancing.RetryMatch.FromInterface(lbMatcherSets) if err != nil { return err } - h.LoadBalancing.RetryMatch = lbMatcherSets - h.LoadBalancing.RetryMatchRaw = nil // allow GC to deallocate // if active health checks are enabled, configure them and start a worker if h.HealthChecks != nil && @@ -407,7 +404,7 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia // do the round-trip start := time.Now() res, err := h.Transport.RoundTrip(req) - latency := time.Since(start) + duration := time.Since(start) if err != nil { return err } @@ -415,12 +412,13 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia h.logger.Debug("upstream roundtrip", zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: req}), zap.Object("headers", caddyhttp.LoggableHTTPHeader(res.Header)), + zap.Duration("duration", duration), zap.Int("status", res.StatusCode), ) // update circuit breaker on current conditions if di.Upstream.cb != nil { - di.Upstream.cb.RecordMetric(res.StatusCode, latency) + di.Upstream.cb.RecordMetric(res.StatusCode, duration) } // perform passive health checks (if enabled) @@ -434,7 +432,7 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia // strike if the roundtrip took too long if h.HealthChecks.Passive.UnhealthyLatency > 0 && - latency >= time.Duration(h.HealthChecks.Passive.UnhealthyLatency) { + duration >= time.Duration(h.HealthChecks.Passive.UnhealthyLatency) { h.countFailure(di.Upstream) } } @@ -651,10 +649,10 @@ func removeConnectionHeaders(h http.Header) { // LoadBalancing has parameters related to load balancing. type LoadBalancing struct { - SelectionPolicyRaw json.RawMessage `json:"selection_policy,omitempty"` + SelectionPolicyRaw json.RawMessage `json:"selection_policy,omitempty" caddy:"namespace=http.reverse_proxy.selection_policies inline_key=policy"` TryDuration caddy.Duration `json:"try_duration,omitempty"` TryInterval caddy.Duration `json:"try_interval,omitempty"` - RetryMatchRaw caddyhttp.RawMatcherSets `json:"retry_match,omitempty"` + RetryMatchRaw caddyhttp.RawMatcherSets `json:"retry_match,omitempty" caddy:"namespace=http.matchers"` SelectionPolicy Selector `json:"-"` RetryMatch caddyhttp.MatcherSets `json:"-"` diff --git a/modules/caddyhttp/reverseproxy/selectionpolicies.go b/modules/caddyhttp/reverseproxy/selectionpolicies.go index a21e44c..937ae37 100644 --- a/modules/caddyhttp/reverseproxy/selectionpolicies.go +++ b/modules/caddyhttp/reverseproxy/selectionpolicies.go @@ -48,8 +48,8 @@ type RandomSelection struct{} // CaddyModule returns the Caddy module information. func (RandomSelection) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.selection_policies.random", - New: func() caddy.Module { return new(RandomSelection) }, + ID: "http.reverse_proxy.selection_policies.random", + New: func() caddy.Module { return new(RandomSelection) }, } } @@ -84,8 +84,8 @@ type RandomChoiceSelection struct { // CaddyModule returns the Caddy module information. func (RandomChoiceSelection) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.selection_policies.random_choose", - New: func() caddy.Module { return new(RandomChoiceSelection) }, + ID: "http.reverse_proxy.selection_policies.random_choose", + New: func() caddy.Module { return new(RandomChoiceSelection) }, } } @@ -154,8 +154,8 @@ type LeastConnSelection struct{} // CaddyModule returns the Caddy module information. func (LeastConnSelection) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.selection_policies.least_conn", - New: func() caddy.Module { return new(LeastConnSelection) }, + ID: "http.reverse_proxy.selection_policies.least_conn", + New: func() caddy.Module { return new(LeastConnSelection) }, } } @@ -199,8 +199,8 @@ type RoundRobinSelection struct { // CaddyModule returns the Caddy module information. func (RoundRobinSelection) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.selection_policies.round_robin", - New: func() caddy.Module { return new(RoundRobinSelection) }, + ID: "http.reverse_proxy.selection_policies.round_robin", + New: func() caddy.Module { return new(RoundRobinSelection) }, } } @@ -227,8 +227,8 @@ type FirstSelection struct{} // CaddyModule returns the Caddy module information. func (FirstSelection) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.selection_policies.first", - New: func() caddy.Module { return new(FirstSelection) }, + ID: "http.reverse_proxy.selection_policies.first", + New: func() caddy.Module { return new(FirstSelection) }, } } @@ -249,8 +249,8 @@ type IPHashSelection struct{} // CaddyModule returns the Caddy module information. func (IPHashSelection) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.selection_policies.ip_hash", - New: func() caddy.Module { return new(IPHashSelection) }, + ID: "http.reverse_proxy.selection_policies.ip_hash", + New: func() caddy.Module { return new(IPHashSelection) }, } } @@ -270,8 +270,8 @@ type URIHashSelection struct{} // CaddyModule returns the Caddy module information. func (URIHashSelection) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.selection_policies.uri_hash", - New: func() caddy.Module { return new(URIHashSelection) }, + ID: "http.reverse_proxy.selection_policies.uri_hash", + New: func() caddy.Module { return new(URIHashSelection) }, } } @@ -289,8 +289,8 @@ type HeaderHashSelection struct { // CaddyModule returns the Caddy module information. func (HeaderHashSelection) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.reverse_proxy.selection_policies.header", - New: func() caddy.Module { return new(HeaderHashSelection) }, + ID: "http.reverse_proxy.selection_policies.header", + New: func() caddy.Module { return new(HeaderHashSelection) }, } } diff --git a/modules/caddyhttp/rewrite/rewrite.go b/modules/caddyhttp/rewrite/rewrite.go index 5a84a33..f610658 100644 --- a/modules/caddyhttp/rewrite/rewrite.go +++ b/modules/caddyhttp/rewrite/rewrite.go @@ -47,8 +47,8 @@ type Rewrite struct { // CaddyModule returns the Caddy module information. func (Rewrite) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.rewrite", - New: func() caddy.Module { return new(Rewrite) }, + ID: "http.handlers.rewrite", + New: func() caddy.Module { return new(Rewrite) }, } } diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go index 550b14e..0dce990 100644 --- a/modules/caddyhttp/routes.go +++ b/modules/caddyhttp/routes.go @@ -22,14 +22,73 @@ import ( "github.com/caddyserver/caddy/v2" ) -// Route represents a set of matching rules, -// middlewares, and a responder for handling HTTP -// requests. +// Route consists of a set of rules for matching HTTP requests, +// a list of handlers to execute, and optional flow control +// parameters which customize the handling of HTTP requests +// in a highly flexible and performant manner. type Route struct { - Group string `json:"group,omitempty"` - MatcherSetsRaw RawMatcherSets `json:"match,omitempty"` - HandlersRaw []json.RawMessage `json:"handle,omitempty"` - Terminal bool `json:"terminal,omitempty"` + // Group is an optional name for a group to which this + // route belongs. If a route belongs to a group, only + // the first matching route in the group will be used. + Group string `json:"group,omitempty"` + + // The matcher sets which will be used to qualify this + // route for a request. Essentially the "if" statement + // of this route. Each matcher set is OR'ed, but matchers + // within a set are AND'ed together. + MatcherSetsRaw RawMatcherSets `json:"match,omitempty" caddy:"namespace=http.matchers"` + + // The list of handlers for this route. Upon matching a request, they are chained + // together in a middleware fashion: requests flow from the first handler to the last + // (top of the list to the bottom), with the possibility that any handler could stop + // the chain and/or return an error. Responses flow back through the chain (bottom of + // the list to the top) as they are written out to the client. + // + // Not all handlers call the next handler in the chain. For example, the reverse_proxy + // handler always sends a request upstream or returns an error. Thus, configuring + // handlers after reverse_proxy in the same route is illogical, since they would never + // be executed. You will want to put handlers which originate the response at the very + // end of your route(s). The documentation for a module should state whether it invokes + // the next handler, but sometimes it is common sense. + // + // Some handlers manipulate the response. Remember that requests flow down the list, and + // responses flow up the list. + // + // For example, if you wanted to use both `templates` and `encode` handlers, you would + // need to put `templates` after `encode` in your route, because responses flow up. + // Thus, `templates` will be able to parse and execute the plain-text response as a + // template, and then return it up to the `encode` handler which will then compress it + // into a binary format. + // + // If `templates` came before `encode`, then `encode` would write a compressed, + // binary-encoded response to `templates` which would not be able to parse the response + // properly. + // + // The correct order, then, is this: + // + // [ + // {"handler": "encode"}, + // {"handler": "templates"}, + // {"handler": "file_server"} + // ] + // + // The request flows ⬇️ DOWN (`encode` -> `templates` -> `file_server`). + // + // 1. First, `encode` will choose how to `encode` the response and wrap the response. + // 2. Then, `templates` will wrap the response with a buffer. + // 3. Finally, `file_server` will originate the content from a file. + // + // The response flows ⬆️ UP (`file_server` -> `templates` -> `encode`): + // + // 1. First, `file_server` will write the file to the response. + // 2. That write will be buffered and then executed by `templates`. + // 3. Lastly, the write from `templates` will flow into `encode` which will compress the stream. + // + // If you think of routes in this way, it will be easy and even fun to solve the puzzle of writing correct routes. + HandlersRaw []json.RawMessage `json:"handle,omitempty" caddy:"namespace=http.handlers inline_key=handler"` + + // If true, no more routes will be executed after this one, even if they matched. + Terminal bool `json:"terminal,omitempty"` // decoded values MatcherSets MatcherSets `json:"-"` @@ -54,22 +113,23 @@ type RouteList []Route func (routes RouteList) Provision(ctx caddy.Context) error { for i, route := range routes { // matchers - matcherSets, err := route.MatcherSetsRaw.Setup(ctx) + matchersIface, err := ctx.LoadModule(&route, "MatcherSetsRaw") + if err != nil { + return fmt.Errorf("loadng matchers in route %d: %v", i, err) + } + err = routes[i].MatcherSets.FromInterface(matchersIface) if err != nil { - return err + return fmt.Errorf("route %d: %v", i, err) } - routes[i].MatcherSets = matcherSets - routes[i].MatcherSetsRaw = nil // allow GC to deallocate // handlers - for j, rawMsg := range route.HandlersRaw { - mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg) - if err != nil { - return fmt.Errorf("loading handler module in position %d: %v", j, err) - } - routes[i].Handlers = append(routes[i].Handlers, mh.(MiddlewareHandler)) + handlersIface, err := ctx.LoadModule(&route, "HandlersRaw") + if err != nil { + return fmt.Errorf("loading handler modules in route %d: %v", i, err) + } + for _, handler := range handlersIface.([]interface{}) { + routes[i].Handlers = append(routes[i].Handlers, handler.(MiddlewareHandler)) } - routes[i].HandlersRaw = nil // allow GC to deallocate } return nil } @@ -171,28 +231,7 @@ func (mset MatcherSet) Match(r *http.Request) bool { // RawMatcherSets is a group of matcher sets // in their raw, JSON form. -type RawMatcherSets []map[string]json.RawMessage - -// Setup sets up all matcher sets by loading each matcher module -// and returning the group of provisioned matcher sets. -func (rm RawMatcherSets) Setup(ctx caddy.Context) (MatcherSets, error) { - if rm == nil { - return nil, nil - } - var ms MatcherSets - for _, matcherSet := range rm { - var matchers MatcherSet - for modName, rawMsg := range matcherSet { - val, err := ctx.LoadModule("http.matchers."+modName, rawMsg) - if err != nil { - return nil, fmt.Errorf("loading matcher module '%s': %v", modName, err) - } - matchers = append(matchers, val.(RequestMatcher)) - } - ms = append(ms, matchers) - } - return ms, nil -} +type RawMatcherSets []caddy.ModuleMap // MatcherSets is a group of matcher sets capable // of checking whether a request matches any of @@ -202,11 +241,27 @@ type MatcherSets []MatcherSet // AnyMatch returns true if req matches any of the // matcher sets in mss or if there are no matchers, // in which case the request always matches. -func (mss MatcherSets) AnyMatch(req *http.Request) bool { - for _, ms := range mss { - if ms.Match(req) { +func (ms MatcherSets) AnyMatch(req *http.Request) bool { + for _, m := range ms { + if m.Match(req) { return true } } - return len(mss) == 0 + return len(ms) == 0 +} + +// FromInterface fills ms from an interface{} value obtained from LoadModule. +func (ms *MatcherSets) FromInterface(matcherSets interface{}) error { + for _, matcherSetIfaces := range matcherSets.([]map[string]interface{}) { + var matcherSet MatcherSet + for _, matcher := range matcherSetIfaces { + reqMatcher, ok := matcher.(RequestMatcher) + if !ok { + return fmt.Errorf("decoded module is not a RequestMatcher: %#v", matcher) + } + matcherSet = append(matcherSet, reqMatcher) + } + *ms = append(*ms, matcherSet) + } + return nil } diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index fef887d..c34444e 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -31,23 +31,101 @@ import ( "go.uber.org/zap/zapcore" ) -// Server is an HTTP server. +// Server describes an HTTP server. type Server struct { - Listen []string `json:"listen,omitempty"` - ReadTimeout caddy.Duration `json:"read_timeout,omitempty"` - ReadHeaderTimeout caddy.Duration `json:"read_header_timeout,omitempty"` - WriteTimeout caddy.Duration `json:"write_timeout,omitempty"` - IdleTimeout caddy.Duration `json:"idle_timeout,omitempty"` - MaxHeaderBytes int `json:"max_header_bytes,omitempty"` - Routes RouteList `json:"routes,omitempty"` - Errors *HTTPErrorConfig `json:"errors,omitempty"` - TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies,omitempty"` - AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"` - MaxRehandles *int `json:"max_rehandles,omitempty"` - StrictSNIHost *bool `json:"strict_sni_host,omitempty"` - Logs *ServerLogConfig `json:"logs,omitempty"` - - // This field is not subject to compatibility promises + // Socket interfaces to which to bind listeners. Caddy network + // addresses have the following form: + // + // network/address + // + // The network part is anything that [Go's `net` package](https://golang.org/pkg/net/) + // recognizes, and is optional. The default network is `tcp`. If + // a network is specified, a single forward slash `/` is used to + // separate the network and address portions. + // + // The address part may be any of these forms: + // + // - `host` + // - `host:port` + // - `:port` + // - `/path/to/unix/socket` + // + // The host may be any hostname, resolvable domain name, or IP address. + // The port may be a single value (`:8080`) or a range (`:8080-8085`). + // A port range will be multiplied into singular addresses. Not all + // config parameters accept port ranges, but Listen does. + // + // Valid examples: + // + // :8080 + // 127.0.0.1:8080 + // localhost:8080 + // localhost:8080-8085 + // tcp/localhost:8080 + // tcp/localhost:8080-8085 + // udp/localhost:9005 + // unix//path/to/socket + // + Listen []string `json:"listen,omitempty"` + + // How long to allow a read from a client's upload. Setting this + // to a short, non-zero value can mitigate slowloris attacks, but + // may also affect legitimately slow clients. + ReadTimeout caddy.Duration `json:"read_timeout,omitempty"` + + // ReadHeaderTimeout is like ReadTimeout but for request headers. + ReadHeaderTimeout caddy.Duration `json:"read_header_timeout,omitempty"` + + // WriteTimeout is how long to allow a write to a client. Note + // that setting this to a small value when serving large files + // may negatively affect legitimately slow clients. + WriteTimeout caddy.Duration `json:"write_timeout,omitempty"` + + // IdleTimeout is the maximum time to wait for the next request + // when keep-alives are enabled. If zero, ReadTimeout is used. + // If both are zero, there is no timeout. + IdleTimeout caddy.Duration `json:"idle_timeout,omitempty"` + + // MaxHeaderBytes is the maximum size to parse from a client's + // HTTP request headers. + MaxHeaderBytes int `json:"max_header_bytes,omitempty"` + + // Routes describes how this server will handle requests. + // When a request comes in, each route's matchers will + // be evaluated against the request, and matching routes + // will be compiled into a middleware chain in the order + // in which they appear in the list. + Routes RouteList `json:"routes,omitempty"` + + // Errors is how this server will handle errors returned from + // any of the handlers in the primary routes. + Errors *HTTPErrorConfig `json:"errors,omitempty"` + + // How to handle TLS connections. + TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies,omitempty"` + + // AutoHTTPS configures or disables automatic HTTPS within this server. + // HTTPS is enabled automatically and by default when qualifying names + // are present in a Host matcher. + AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"` + + // MaxRehandles is the maximum number of times to allow a + // request to be rehandled, to prevent accidental infinite + // loops. Default: 1. + MaxRehandles *int `json:"max_rehandles,omitempty"` + + // If true, will require that a request's Host header match + // the value of the ServerName sent by the client's TLS + // ClientHello; often a necessary safeguard when using TLS + // client authentication. + StrictSNIHost *bool `json:"strict_sni_host,omitempty"` + + // Logs customizes how access logs are handled in this server. + Logs *ServerLogConfig `json:"logs,omitempty"` + + // Enable experimental HTTP/3 support. Note that HTTP/3 is not a + // finished standard and has extremely limited client support. + // This field is not subject to compatibility promises. ExperimentalHTTP3 bool `json:"experimental_http3,omitempty"` tlsApp *caddytls.TLS @@ -296,6 +374,8 @@ func (s *Server) hasTLSClientAuth() bool { // AutoHTTPSConfig is used to disable automatic HTTPS // or certain aspects of it for a specific server. +// HTTPS is enabled automatically and by default when +// qualifying hostnames are available from the config. type AutoHTTPSConfig struct { // If true, automatic HTTPS will be entirely disabled. Disabled bool `json:"disable,omitempty"` diff --git a/modules/caddyhttp/starlarkmw/internal/lib/module.go b/modules/caddyhttp/starlarkmw/internal/lib/module.go index a7164cd..a75aedf 100644 --- a/modules/caddyhttp/starlarkmw/internal/lib/module.go +++ b/modules/caddyhttp/starlarkmw/internal/lib/module.go @@ -64,7 +64,7 @@ func (r *LoadMiddleware) Run(thread *starlark.Thread, fn *starlark.Builtin, args name = fmt.Sprintf("http.handlers.%s", name) } - inst, err := r.Ctx.LoadModule(name, js) + inst, err := r.Ctx.LoadModuleByID(name, js) if err != nil { return starlark.None, err } @@ -112,7 +112,7 @@ func (r *LoadResponder) Run(thread *starlark.Thread, fn *starlark.Builtin, args name = fmt.Sprintf("http.handlers.%s", name) } - inst, err := r.Ctx.LoadModule(name, js) + inst, err := r.Ctx.LoadModuleByID(name, js) if err != nil { return starlark.None, err } diff --git a/modules/caddyhttp/starlarkmw/starlarkmw.go b/modules/caddyhttp/starlarkmw/starlarkmw.go index 007ddb4..47e335d 100644 --- a/modules/caddyhttp/starlarkmw/starlarkmw.go +++ b/modules/caddyhttp/starlarkmw/starlarkmw.go @@ -7,8 +7,8 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" - caddyscript "github.com/caddyserver/caddy/v2/pkg/caddyscript/lib" "github.com/caddyserver/caddy/v2/modules/caddyhttp/starlarkmw/internal/lib" + caddyscript "github.com/caddyserver/caddy/v2/pkg/caddyscript/lib" "github.com/starlight-go/starlight/convert" "go.starlark.net/starlark" ) @@ -34,8 +34,8 @@ type StarlarkMW struct { // CaddyModule returns the Caddy module information. func (StarlarkMW) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.starlark", - New: func() caddy.Module { return new(StarlarkMW) }, + ID: "http.handlers.starlark", + New: func() caddy.Module { return new(StarlarkMW) }, } } diff --git a/modules/caddyhttp/staticerror.go b/modules/caddyhttp/staticerror.go index 3a45366..fd1490d 100644 --- a/modules/caddyhttp/staticerror.go +++ b/modules/caddyhttp/staticerror.go @@ -35,8 +35,8 @@ type StaticError struct { // CaddyModule returns the Caddy module information. func (StaticError) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.error", - New: func() caddy.Module { return new(StaticError) }, + ID: "http.handlers.error", + New: func() caddy.Module { return new(StaticError) }, } } diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go index 732a3fb..44b045e 100644 --- a/modules/caddyhttp/staticresp.go +++ b/modules/caddyhttp/staticresp.go @@ -38,8 +38,8 @@ type StaticResponse struct { // CaddyModule returns the Caddy module information. func (StaticResponse) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.static_response", - New: func() caddy.Module { return new(StaticResponse) }, + ID: "http.handlers.static_response", + New: func() caddy.Module { return new(StaticResponse) }, } } diff --git a/modules/caddyhttp/subroute.go b/modules/caddyhttp/subroute.go index 57fb80a..a60eaf7 100644 --- a/modules/caddyhttp/subroute.go +++ b/modules/caddyhttp/subroute.go @@ -44,8 +44,8 @@ type Subroute struct { // CaddyModule returns the Caddy module information. func (Subroute) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.subroute", - New: func() caddy.Module { return new(Subroute) }, + ID: "http.handlers.subroute", + New: func() caddy.Module { return new(Subroute) }, } } diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go index e9c1da8..ac37e9d 100644 --- a/modules/caddyhttp/templates/templates.go +++ b/modules/caddyhttp/templates/templates.go @@ -39,8 +39,8 @@ type Templates struct { // CaddyModule returns the Caddy module information. func (Templates) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.templates", - New: func() caddy.Module { return new(Templates) }, + ID: "http.handlers.templates", + New: func() caddy.Module { return new(Templates) }, } } diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go index 3fb8fa3..791203b 100644 --- a/modules/caddyhttp/vars.go +++ b/modules/caddyhttp/vars.go @@ -32,8 +32,8 @@ type VarsMiddleware map[string]string // CaddyModule returns the Caddy module information. func (VarsMiddleware) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.handlers.vars", - New: func() caddy.Module { return new(VarsMiddleware) }, + ID: "http.handlers.vars", + New: func() caddy.Module { return new(VarsMiddleware) }, } } @@ -55,8 +55,8 @@ type VarsMatcher map[string]string // CaddyModule returns the Caddy module information. func (VarsMatcher) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "http.matchers.vars", - New: func() caddy.Module { return new(VarsMatcher) }, + ID: "http.matchers.vars", + New: func() caddy.Module { return new(VarsMatcher) }, } } diff --git a/modules/caddytls/acmemanager.go b/modules/caddytls/acmemanager.go index 9f31215..31c954f 100644 --- a/modules/caddytls/acmemanager.go +++ b/modules/caddytls/acmemanager.go @@ -40,16 +40,50 @@ func init() { // after you have configured this struct // to your liking. type ACMEManagerMaker struct { - CA string `json:"ca,omitempty"` - Email string `json:"email,omitempty"` - RenewAhead caddy.Duration `json:"renew_ahead,omitempty"` - KeyType string `json:"key_type,omitempty"` - ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"` - MustStaple bool `json:"must_staple,omitempty"` - Challenges *ChallengesConfig `json:"challenges,omitempty"` - OnDemand bool `json:"on_demand,omitempty"` - Storage json.RawMessage `json:"storage,omitempty"` - TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"` + // The URL to the CA's ACME directory endpoint. + CA string `json:"ca,omitempty"` + + // Your email address, so the CA can contact you if necessary. + // Not required, but strongly recommended to provide one so + // you can be reached if there is a problem. Your email is + // not sent to any Caddy mothership or used for any purpose + // other than ACME transactions. + Email string `json:"email,omitempty"` + + // How long before a certificate's expiration to try renewing it. + // Should usually be about 1/3 of certificate lifetime, but long + // enough to give yourself time to troubleshoot problems before + // expiration. Default: 30d + RenewAhead caddy.Duration `json:"renew_ahead,omitempty"` + + // The type of key to generate for the certificate. + // Supported values: `rsa2048`, `rsa4096`, `p256`, `p384`. + KeyType string `json:"key_type,omitempty"` + + // Time to wait before timing out an ACME operation. + ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"` + + // If true, certificates will be requested with MustStaple. Not all + // CAs support this, and there are potentially serious consequences + // of enabling this feature without proper threat modeling. + MustStaple bool `json:"must_staple,omitempty"` + + // Configures the various ACME challenge types. + Challenges *ChallengesConfig `json:"challenges,omitempty"` + + // If true, certificates will be managed "on demand", that is, during + // TLS handshakes or when needed, as opposed to at startup or config + // load. + OnDemand bool `json:"on_demand,omitempty"` + + // Optionally configure a separate storage module associated with this + // manager, instead of using Caddy's global/default-configured storage. + Storage json.RawMessage `json:"storage,omitempty"` + + // An array of files of CA certificates to accept when connecting to the + // ACME CA. Generally, you should only use this if the ACME CA endpoint + // is internal or for development/testing purposes. + TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"` storage certmagic.Storage rootPool *x509.CertPool @@ -58,8 +92,8 @@ type ACMEManagerMaker struct { // CaddyModule returns the Caddy module information. func (ACMEManagerMaker) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.management.acme", - New: func() caddy.Module { return new(ACMEManagerMaker) }, + ID: "tls.management.acme", + New: func() caddy.Module { return new(ACMEManagerMaker) }, } } @@ -73,26 +107,24 @@ func (m ACMEManagerMaker) NewManager(interactive bool) (certmagic.Manager, error func (m *ACMEManagerMaker) Provision(ctx caddy.Context) error { // DNS providers if m.Challenges != nil && m.Challenges.DNSRaw != nil { - val, err := ctx.LoadModuleInline("provider", "tls.dns", m.Challenges.DNSRaw) + val, err := ctx.LoadModule(m.Challenges, "DNSRaw") if err != nil { - return fmt.Errorf("loading DNS provider module: %s", err) + return fmt.Errorf("loading DNS provider module: %v", err) } m.Challenges.DNS = val.(challenge.Provider) - m.Challenges.DNSRaw = nil // allow GC to deallocate } // policy-specific storage implementation if m.Storage != nil { - val, err := ctx.LoadModuleInline("module", "caddy.storage", m.Storage) + val, err := ctx.LoadModule(m, "Storage") if err != nil { - return fmt.Errorf("loading TLS storage module: %s", err) + return fmt.Errorf("loading TLS storage module: %v", err) } cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage() if err != nil { return fmt.Errorf("creating TLS storage configuration: %v", err) } m.storage = cmStorage - m.Storage = nil // allow GC to deallocate } // add any custom CAs to trust store diff --git a/modules/caddytls/certselection.go b/modules/caddytls/certselection.go index b56185a..eb01605 100644 --- a/modules/caddytls/certselection.go +++ b/modules/caddytls/certselection.go @@ -28,8 +28,8 @@ type Policy struct { // CaddyModule returns the Caddy module information. func (Policy) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.certificate_selection.custom", - New: func() caddy.Module { return new(Policy) }, + ID: "tls.certificate_selection.custom", + New: func() caddy.Module { return new(Policy) }, } } diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index c82337d..6ce6b9e 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -39,23 +39,21 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) { // set up each of the connection policies for i, pol := range cp { // matchers - for modName, rawMsg := range pol.Matchers { - val, err := ctx.LoadModule("tls.handshake_match."+modName, rawMsg) - if err != nil { - return nil, fmt.Errorf("loading handshake matcher module '%s': %s", modName, err) - } - cp[i].matchers = append(cp[i].matchers, val.(ConnectionMatcher)) + mods, err := ctx.LoadModule(pol, "MatchersRaw") + if err != nil { + return nil, fmt.Errorf("loading handshake matchers: %v", err) + } + for _, modIface := range mods.(map[string]interface{}) { + cp[i].matchers = append(cp[i].matchers, modIface.(ConnectionMatcher)) } - cp[i].Matchers = nil // allow GC to deallocate // certificate selector if pol.CertSelection != nil { - val, err := ctx.LoadModuleInline("policy", "tls.certificate_selection", pol.CertSelection) + val, err := ctx.LoadModule(pol, "CertSelection") if err != nil { return nil, fmt.Errorf("loading certificate selection module: %s", err) } cp[i].certSelector = val.(certmagic.CertificateSelector) - cp[i].CertSelection = nil // allow GC to deallocate } } @@ -109,14 +107,33 @@ func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) (*tls.Config, error) { // ConnectionPolicy specifies the logic for handling a TLS handshake. type ConnectionPolicy struct { - Matchers map[string]json.RawMessage `json:"match,omitempty"` - CertSelection json.RawMessage `json:"certificate_selection,omitempty"` - - CipherSuites []string `json:"cipher_suites,omitempty"` - Curves []string `json:"curves,omitempty"` - ALPN []string `json:"alpn,omitempty"` - ProtocolMin string `json:"protocol_min,omitempty"` - ProtocolMax string `json:"protocol_max,omitempty"` + // How to match this policy with a TLS ClientHello. If + // this policy is the first to match, it will be used. + MatchersRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=tls.handshake_match"` + + // How to choose a certificate if more than one matched + // the given ServerName (SNI) value. + CertSelection json.RawMessage `json:"certificate_selection,omitempty" caddy:"namespace=tls.certificate_selection inline_key=policy"` + + // The list of cipher suites to support. Caddy's + // defaults are modern and secure. + CipherSuites []string `json:"cipher_suites,omitempty"` + + // The list of elliptic curves to support. Caddy's + // defaults are modern and secure. + Curves []string `json:"curves,omitempty"` + + // Protocols to use for Application-Layer Protocol + // Negotiation (ALPN) during the handshake. + ALPN []string `json:"alpn,omitempty"` + + // Minimum TLS protocol version to allow. Default: `tls1.2` + ProtocolMin string `json:"protocol_min,omitempty"` + + // Maximum TLS protocol version to allow. Default: `tls1.3` + ProtocolMax string `json:"protocol_max,omitempty"` + + // Enables and configures TLS client authentication. ClientAuthentication *ClientAuthentication `json:"client_authentication,omitempty"` matchers []ConnectionMatcher diff --git a/modules/caddytls/distributedstek/distributedstek.go b/modules/caddytls/distributedstek/distributedstek.go index a0c4cd2..cef3733 100644 --- a/modules/caddytls/distributedstek/distributedstek.go +++ b/modules/caddytls/distributedstek/distributedstek.go @@ -39,9 +39,15 @@ func init() { caddy.RegisterModule(Provider{}) } -// Provider implements a distributed STEK provider. +// Provider implements a distributed STEK provider. This +// module will obtain STEKs from a storage module instead +// of generating STEKs internally. This allows STEKs to be +// coordinated, improving TLS session resumption in a cluster. type Provider struct { - Storage json.RawMessage `json:"storage,omitempty"` + // The storage module wherein to store and obtain session + // ticket keys. If unset, Caddy's default/global-configured + // storage module will be used. + Storage json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` storage certmagic.Storage stekConfig *caddytls.SessionTicketService @@ -51,8 +57,8 @@ type Provider struct { // CaddyModule returns the Caddy module information. func (Provider) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.stek.distributed", - New: func() caddy.Module { return new(Provider) }, + ID: "tls.stek.distributed", + New: func() caddy.Module { return new(Provider) }, } } @@ -60,7 +66,7 @@ func (Provider) CaddyModule() caddy.ModuleInfo { func (s *Provider) Provision(ctx caddy.Context) error { // unpack the storage module to use, if different from the default if s.Storage != nil { - val, err := ctx.LoadModuleInline("module", "caddy.storage", s.Storage) + val, err := ctx.LoadModule(s, "Storage") if err != nil { return fmt.Errorf("loading TLS storage module: %s", err) } @@ -69,7 +75,6 @@ func (s *Provider) Provision(ctx caddy.Context) error { return fmt.Errorf("creating TLS storage configuration: %v", err) } s.storage = cmStorage - s.Storage = nil // allow GC to deallocate } // otherwise, use default storage diff --git a/modules/caddytls/fileloader.go b/modules/caddytls/fileloader.go index b2cc132..6d6ff99 100644 --- a/modules/caddytls/fileloader.go +++ b/modules/caddytls/fileloader.go @@ -32,18 +32,27 @@ type FileLoader []CertKeyFilePair // CaddyModule returns the Caddy module information. func (FileLoader) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.certificates.load_files", - New: func() caddy.Module { return new(FileLoader) }, + ID: "tls.certificates.load_files", + New: func() caddy.Module { return new(FileLoader) }, } } // CertKeyFilePair pairs certificate and key file names along with their // encoding format so that they can be loaded from disk. type CertKeyFilePair struct { - Certificate string `json:"certificate"` - Key string `json:"key"` - Format string `json:"format,omitempty"` // "pem" is default - Tags []string `json:"tags,omitempty"` + // Path to the certificate (public key) file. + Certificate string `json:"certificate"` + + // Path to the private key file. + Key string `json:"key"` + + // The format of the cert and key. Can be "pem". Default: "pem" + Format string `json:"format,omitempty"` + + // Arbitrary values to associate with this certificate. + // Can be useful when you want to select a particular + // certificate when there may be multiple valid candidates. + Tags []string `json:"tags,omitempty"` } // LoadCertificates returns the certificates to be loaded by fl. diff --git a/modules/caddytls/folderloader.go b/modules/caddytls/folderloader.go index da1dff0..f1a742d 100644 --- a/modules/caddytls/folderloader.go +++ b/modules/caddytls/folderloader.go @@ -39,8 +39,8 @@ type FolderLoader []string // CaddyModule returns the Caddy module information. func (FolderLoader) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.certificates.load_folders", - New: func() caddy.Module { return new(FolderLoader) }, + ID: "tls.certificates.load_folders", + New: func() caddy.Module { return new(FolderLoader) }, } } diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go index 47fb296..9e2dfc5 100644 --- a/modules/caddytls/matchers.go +++ b/modules/caddytls/matchers.go @@ -30,8 +30,8 @@ type MatchServerName []string // CaddyModule returns the Caddy module information. func (MatchServerName) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.handshake_match.sni", - New: func() caddy.Module { return new(MatchServerName) }, + ID: "tls.handshake_match.sni", + New: func() caddy.Module { return new(MatchServerName) }, } } diff --git a/modules/caddytls/pemloader.go b/modules/caddytls/pemloader.go index 30a491c..46d06a8 100644 --- a/modules/caddytls/pemloader.go +++ b/modules/caddytls/pemloader.go @@ -33,16 +33,23 @@ type PEMLoader []CertKeyPEMPair // CaddyModule returns the Caddy module information. func (PEMLoader) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.certificates.load_pem", - New: func() caddy.Module { return PEMLoader{} }, + ID: "tls.certificates.load_pem", + New: func() caddy.Module { return PEMLoader{} }, } } // CertKeyPEMPair pairs certificate and key PEM blocks. type CertKeyPEMPair struct { - CertificatePEM string `json:"certificate"` - KeyPEM string `json:"key"` - Tags []string `json:"tags,omitempty"` + // The certificate (public key) in PEM format. + CertificatePEM string `json:"certificate"` + + // The private key in PEM format. + KeyPEM string `json:"key"` + + // Arbitrary values to associate with this certificate. + // Can be useful when you want to select a particular + // certificate when there may be multiple valid candidates. + Tags []string `json:"tags,omitempty"` } // LoadCertificates returns the certificates contained in pl. diff --git a/modules/caddytls/sessiontickets.go b/modules/caddytls/sessiontickets.go index 6ca921d..258c135 100644 --- a/modules/caddytls/sessiontickets.go +++ b/modules/caddytls/sessiontickets.go @@ -28,11 +28,22 @@ import ( // SessionTicketService configures and manages TLS session tickets. type SessionTicketService struct { - KeySource json.RawMessage `json:"key_source,omitempty"` - RotationInterval caddy.Duration `json:"rotation_interval,omitempty"` - MaxKeys int `json:"max_keys,omitempty"` - DisableRotation bool `json:"disable_rotation,omitempty"` - Disabled bool `json:"disabled,omitempty"` + // KeySource is the method by which Caddy produces or obtains + // TLS session ticket keys (STEKs). By default, Caddy generates + // them internally using a secure pseudorandom source. + KeySource json.RawMessage `json:"key_source,omitempty" caddy:"namespace=tls.stek inline_key=provider"` + + // How often Caddy rotates STEKs. Default: 12h. + RotationInterval caddy.Duration `json:"rotation_interval,omitempty"` + + // The maximum number of keys to keep in rotation. Default: 4. + MaxKeys int `json:"max_keys,omitempty"` + + // Disables STEK rotation. + DisableRotation bool `json:"disable_rotation,omitempty"` + + // Disables TLS session resumption by tickets. + Disabled bool `json:"disabled,omitempty"` keySource STEKProvider configs map[*tls.Config]struct{} @@ -57,12 +68,11 @@ func (s *SessionTicketService) provision(ctx caddy.Context) error { } // load the STEK module, which will provide keys - val, err := ctx.LoadModuleInline("provider", "tls.stek", s.KeySource) + val, err := ctx.LoadModule(s, "KeySource") if err != nil { return fmt.Errorf("loading TLS session ticket ephemeral keys provider module: %s", err) } s.keySource = val.(STEKProvider) - s.KeySource = nil // allow GC to deallocate // if session tickets or just rotation are // disabled, no need to start service diff --git a/modules/caddytls/standardstek/stek.go b/modules/caddytls/standardstek/stek.go index 6d10c76..eb609ca 100644 --- a/modules/caddytls/standardstek/stek.go +++ b/modules/caddytls/standardstek/stek.go @@ -35,8 +35,8 @@ type standardSTEKProvider struct { // CaddyModule returns the Caddy module information. func (standardSTEKProvider) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls.stek.standard", - New: func() caddy.Module { return new(standardSTEKProvider) }, + ID: "tls.stek.standard", + New: func() caddy.Module { return new(standardSTEKProvider) }, } } diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index 5dfe063..1b155b0 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -30,15 +30,30 @@ import ( func init() { caddy.RegisterModule(TLS{}) + caddy.RegisterModule(AutomateLoader{}) } -// TLS represents a process-wide TLS configuration. +// TLS provides TLS facilities including certificate +// loading and management, client auth, and more. type TLS struct { - Certificates map[string]json.RawMessage `json:"certificates,omitempty"` - Automation *AutomationConfig `json:"automation,omitempty"` - SessionTickets *SessionTicketService `json:"session_tickets,omitempty"` + // Caches certificates in memory for quick use during + // TLS handshakes. Each key is the name of a certificate + // loader module. All loaded certificates get pooled + // into the same cache and may be used to complete TLS + // handshakes for the relevant server names (SNI). + // Certificates loaded manually (anything other than + // "automate") are not automatically managed and will + // have to be refreshed manually before they expire. + CertificatesRaw caddy.ModuleMap `json:"certificates,omitempty" caddy:"namespace=tls.certificates"` + + // Configures the automation of certificate management. + Automation *AutomationConfig `json:"automation,omitempty"` + + // Configures session ticket ephemeral keys (STEKs). + SessionTickets *SessionTicketService `json:"session_tickets,omitempty"` certificateLoaders []CertificateLoader + automateNames []string certCache *certmagic.Cache ctx caddy.Context storageCleanTicker *time.Ticker @@ -49,8 +64,8 @@ type TLS struct { // CaddyModule returns the Caddy module information. func (TLS) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "tls", - New: func() caddy.Module { return new(TLS) }, + ID: "tls", + New: func() caddy.Module { return new(TLS) }, } } @@ -74,25 +89,32 @@ func (t *TLS) Provision(ctx caddy.Context) error { // automation/management policies if t.Automation != nil { for i, ap := range t.Automation.Policies { - val, err := ctx.LoadModuleInline("module", "tls.management", ap.ManagementRaw) + val, err := ctx.LoadModule(&ap, "ManagementRaw") if err != nil { return fmt.Errorf("loading TLS automation management module: %s", err) } t.Automation.Policies[i].Management = val.(ManagerMaker) - t.Automation.Policies[i].ManagementRaw = nil // allow GC to deallocate } } // certificate loaders - for modName, rawMsg := range t.Certificates { - if modName == automateKey { - continue // special case; these will be loaded in later - } - val, err := ctx.LoadModule("tls.certificates."+modName, rawMsg) - if err != nil { - return fmt.Errorf("loading certificate module '%s': %s", modName, err) + val, err := ctx.LoadModule(t, "CertificatesRaw") + if err != nil { + return fmt.Errorf("loading TLS automation management module: %s", err) + } + for modName, modIface := range val.(map[string]interface{}) { + if modName == "automate" { + // special case; these will be loaded in later + // using our automation facilities, which we + // want to avoid during provisioning + var ok bool + t.automateNames, ok = modIface.([]string) + if !ok { + return fmt.Errorf("loading certificates with 'automate' requires []string, got: %#v", modIface) + } + continue } - t.certificateLoaders = append(t.certificateLoaders, val.(CertificateLoader)) + t.certificateLoaders = append(t.certificateLoaders, modIface.(CertificateLoader)) } // session ticket ephemeral keys (STEK) service and provider @@ -115,7 +137,8 @@ func (t *TLS) Provision(ctx caddy.Context) error { // load manual/static (unmanaged) certificates - we do this in // provision so that other apps (such as http) can know which - // certificates have been manually loaded + // certificates have been manually loaded, and also so that + // commands like validate can be a better test magic := certmagic.New(t.certCache, certmagic.Config{ Storage: ctx.Storage(), }) @@ -137,19 +160,12 @@ func (t *TLS) Provision(ctx caddy.Context) error { // Start activates the TLS module. func (t *TLS) Start() error { - // load automated (managed) certificates - if automatedRawMsg, ok := t.Certificates[automateKey]; ok { - var names []string - err := json.Unmarshal(automatedRawMsg, &names) - if err != nil { - return fmt.Errorf("automate: decoding names: %v", err) - } - err = t.Manage(names) - if err != nil { - return fmt.Errorf("automate: managing %v: %v", names, err) - } + // now that we are running, and all manual certificates have + // been loaded, time to load the automated/managed certificates + err := t.Manage(t.automateNames) + if err != nil { + return fmt.Errorf("automate: managing %v: %v", t.automateNames, err) } - t.Certificates = nil // allow GC to deallocate t.keepStorageClean() @@ -311,18 +327,48 @@ type Certificate struct { // AutomationConfig designates configuration for the // construction and use of ACME clients. type AutomationConfig struct { - Policies []AutomationPolicy `json:"policies,omitempty"` - OnDemand *OnDemandConfig `json:"on_demand,omitempty"` - OCSPCheckInterval caddy.Duration `json:"ocsp_interval,omitempty"` - RenewCheckInterval caddy.Duration `json:"renew_interval,omitempty"` + // The list of automation policies. The first matching + // policy will be applied for a given certificate/name. + Policies []AutomationPolicy `json:"policies,omitempty"` + + // On-Demand TLS defers certificate operations to the + // moment they are needed, e.g. during a TLS handshake. + // Useful when you don't know all the hostnames up front. + // Caddy was the first web server to deploy this technology. + OnDemand *OnDemandConfig `json:"on_demand,omitempty"` + + // Caddy staples OCSP (and caches the response) for all + // qualifying certificates by default. This setting + // changes how often it scans responses for freshness, + // and updates them if they are getting stale. + OCSPCheckInterval caddy.Duration `json:"ocsp_interval,omitempty"` + + // Every so often, Caddy will scan all loaded, managed + // certificates for expiration. Certificates which are + // about 2/3 into their valid lifetime are due for + // renewal. This setting changes how frequently the scan + // is performed. If your certificate lifetimes are very + // short (less than ~1 week), you should customize this. + RenewCheckInterval caddy.Duration `json:"renew_interval,omitempty"` } // AutomationPolicy designates the policy for automating the -// management of managed TLS certificates. +// management (obtaining, renewal, and revocation) of managed +// TLS certificates. type AutomationPolicy struct { - Hosts []string `json:"hosts,omitempty"` - ManagementRaw json.RawMessage `json:"management,omitempty"` - ManageSync bool `json:"manage_sync,omitempty"` + // Which hostnames this policy applies to. + Hosts []string `json:"hosts,omitempty"` + + // How to manage certificates. + ManagementRaw json.RawMessage `json:"management,omitempty" caddy:"namespace=tls.management inline_key=module"` + + // If true, certificate management will be conducted + // in the foreground; this will block config reloads + // and return errors if there were problems with + // obtaining or renewing certificates. This is often + // not desirable, especially when serving sites out + // of your control. Default: false + ManageSync bool `json:"manage_sync,omitempty"` Management ManagerMaker `json:"-"` } @@ -345,36 +391,84 @@ func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy.Context) certmagic.Conf // ChallengesConfig configures the ACME challenges. type ChallengesConfig struct { - HTTP *HTTPChallengeConfig `json:"http,omitempty"` + // HTTP configures the ACME HTTP challenge. This + // challenge is enabled and used automatically + // and by default. + HTTP *HTTPChallengeConfig `json:"http,omitempty"` + + // TLSALPN configures the ACME TLS-ALPN challenge. + // This challenge is enabled and used automatically + // and by default. TLSALPN *TLSALPNChallengeConfig `json:"tls-alpn,omitempty"` - DNSRaw json.RawMessage `json:"dns,omitempty"` + + // Configures the ACME DNS challenge. Because this + // challenge typically requires credentials for + // interfacing with a DNS provider, this challenge is + // not enabled by default. This is the only challenge + // type which does not require a direct connection + // to Caddy from an external server. + DNSRaw json.RawMessage `json:"dns,omitempty" caddy:"namespace=tls.dns inline_key=provider"` DNS challenge.Provider `json:"-"` } // HTTPChallengeConfig configures the ACME HTTP challenge. type HTTPChallengeConfig struct { - Disabled bool `json:"disabled,omitempty"` - AlternatePort int `json:"alternate_port,omitempty"` + // If true, the HTTP challenge will be disabled. + Disabled bool `json:"disabled,omitempty"` + + // An alternate port on which to service this + // challenge. Note that the HTTP challenge port is + // hard-coded into the spec and cannot be changed, + // so you would have to forward packets from the + // standard HTTP challenge port to this one. + AlternatePort int `json:"alternate_port,omitempty"` } // TLSALPNChallengeConfig configures the ACME TLS-ALPN challenge. type TLSALPNChallengeConfig struct { - Disabled bool `json:"disabled,omitempty"` - AlternatePort int `json:"alternate_port,omitempty"` + // If true, the TLS-ALPN challenge will be disabled. + Disabled bool `json:"disabled,omitempty"` + + // An alternate port on which to service this + // challenge. Note that the TLS-ALPN challenge port + // is hard-coded into the spec and cannot be changed, + // so you would have to forward packets from the + // standard TLS-ALPN challenge port to this one. + AlternatePort int `json:"alternate_port,omitempty"` } // OnDemandConfig configures on-demand TLS, for obtaining -// needed certificates at handshake-time. +// needed certificates at handshake-time. Because this +// feature can easily be abused, you should set up rate +// limits and/or an internal endpoint that Caddy can +// "ask" if it should be allowed to manage certificates +// for a given hostname. type OnDemandConfig struct { + // An optional rate limit to throttle the + // issuance of certificates from handshakes. RateLimit *RateLimit `json:"rate_limit,omitempty"` - Ask string `json:"ask,omitempty"` + + // If Caddy needs to obtain or renew a certificate + // during a TLS handshake, it will perform a quick + // HTTP request to this URL to check if it should be + // allowed to try to get a certificate for the name + // in the "domain" query string parameter, like so: + // `?domain=example.com`. The endpoint must return a + // 200 OK status if a certificate is allowed; + // anything else will cause it to be denied. + // Redirects are not followed. + Ask string `json:"ask,omitempty"` } // RateLimit specifies an interval with optional burst size. type RateLimit struct { + // A duration value. A certificate may be obtained 'burst' + // times during this interval. Interval caddy.Duration `json:"interval,omitempty"` - Burst int `json:"burst,omitempty"` + + // How many times during an interval a certificate can be obtained. + Burst int `json:"burst,omitempty"` } // ManagerMaker makes a certificate manager. @@ -382,6 +476,21 @@ type ManagerMaker interface { NewManager(interactive bool) (certmagic.Manager, error) } +// AutomateLoader is a no-op certificate loader module +// that is treated as a special case: it uses this app's +// automation features to load certificates for the +// list of hostnames, rather than loading certificates +// manually. +type AutomateLoader []string + +// CaddyModule returns the Caddy module information. +func (AutomateLoader) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "tls.certificates.automate", + New: func() caddy.Module { return new(AutomateLoader) }, + } +} + // These perpetual values are used for on-demand TLS. var ( onDemandRateLimiter = certmagic.NewRateLimiter(0, 0) diff --git a/modules/filestorage/filestorage.go b/modules/filestorage/filestorage.go index 6217a07..55607ba 100644 --- a/modules/filestorage/filestorage.go +++ b/modules/filestorage/filestorage.go @@ -32,8 +32,8 @@ type FileStorage struct { // CaddyModule returns the Caddy module information. func (FileStorage) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "caddy.storage.file_system", - New: func() caddy.Module { return new(FileStorage) }, + ID: "caddy.storage.file_system", + New: func() caddy.Module { return new(FileStorage) }, } } diff --git a/modules/logging/encoders.go b/modules/logging/encoders.go index c3c8aba..28cda55 100644 --- a/modules/logging/encoders.go +++ b/modules/logging/encoders.go @@ -43,8 +43,8 @@ type ConsoleEncoder struct { // CaddyModule returns the Caddy module information. func (ConsoleEncoder) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "caddy.logging.encoders.console", - New: func() caddy.Module { return new(ConsoleEncoder) }, + ID: "caddy.logging.encoders.console", + New: func() caddy.Module { return new(ConsoleEncoder) }, } } @@ -63,8 +63,8 @@ type JSONEncoder struct { // CaddyModule returns the Caddy module information. func (JSONEncoder) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "caddy.logging.encoders.json", - New: func() caddy.Module { return new(JSONEncoder) }, + ID: "caddy.logging.encoders.json", + New: func() caddy.Module { return new(JSONEncoder) }, } } @@ -84,8 +84,8 @@ type LogfmtEncoder struct { // CaddyModule returns the Caddy module information. func (LogfmtEncoder) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "caddy.logging.encoders.logfmt", - New: func() caddy.Module { return new(LogfmtEncoder) }, + ID: "caddy.logging.encoders.logfmt", + New: func() caddy.Module { return new(LogfmtEncoder) }, } } @@ -102,25 +102,24 @@ func (lfe *LogfmtEncoder) Provision(_ caddy.Context) error { type StringEncoder struct { zapcore.Encoder FieldName string `json:"field,omitempty"` - FallbackRaw json.RawMessage `json:"fallback,omitempty"` + FallbackRaw json.RawMessage `json:"fallback,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"` } // CaddyModule returns the Caddy module information. func (StringEncoder) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "caddy.logging.encoders.string", - New: func() caddy.Module { return new(StringEncoder) }, + ID: "caddy.logging.encoders.string", + New: func() caddy.Module { return new(StringEncoder) }, } } // Provision sets up the encoder. func (se *StringEncoder) Provision(ctx caddy.Context) error { if se.FallbackRaw != nil { - val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", se.FallbackRaw) + val, err := ctx.LoadModule(se, "FallbackRaw") if err != nil { return fmt.Errorf("loading fallback encoder module: %v", err) } - se.FallbackRaw = nil // allow GC to deallocate se.Encoder = val.(zapcore.Encoder) } if se.Encoder == nil { diff --git a/modules/logging/filewriter.go b/modules/logging/filewriter.go index 6957b6a..cc60c64 100644 --- a/modules/logging/filewriter.go +++ b/modules/logging/filewriter.go @@ -28,22 +28,42 @@ func init() { caddy.RegisterModule(FileWriter{}) } -// FileWriter can write logs to files. +// FileWriter can write logs to files. By default, log files +// are rotated ("rolled") when they get large, and old log +// files get deleted, to ensure that the process does not +// exhaust disk space. type FileWriter struct { - Filename string `json:"filename,omitempty"` - Roll *bool `json:"roll,omitempty"` - RollSizeMB int `json:"roll_size_mb,omitempty"` - RollCompress *bool `json:"roll_gzip,omitempty"` - RollLocalTime bool `json:"roll_local_time,omitempty"` - RollKeep int `json:"roll_keep,omitempty"` - RollKeepDays int `json:"roll_keep_days,omitempty"` + // Filename is the name of the file to write. + Filename string `json:"filename,omitempty"` + + // Roll toggles log rolling or rotation, which is + // enabled by default. + Roll *bool `json:"roll,omitempty"` + + // When a log file reaches approximately this size, + // it will be rotated. + RollSizeMB int `json:"roll_size_mb,omitempty"` + + // Whether to compress rolled files. Default: true + RollCompress *bool `json:"roll_gzip,omitempty"` + + // Whether to use local timestamps in rolled filenames. + // Default: false + RollLocalTime bool `json:"roll_local_time,omitempty"` + + // The maximum number of rolled log files to keep. + // Default: 10 + RollKeep int `json:"roll_keep,omitempty"` + + // How many days to keep rolled log files. Default: 90 + RollKeepDays int `json:"roll_keep_days,omitempty"` } // CaddyModule returns the Caddy module information. func (FileWriter) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "caddy.logging.writers.file", - New: func() caddy.Module { return new(FileWriter) }, + ID: "caddy.logging.writers.file", + New: func() caddy.Module { return new(FileWriter) }, } } diff --git a/modules/logging/filterencoder.go b/modules/logging/filterencoder.go index eff0279..6680019 100644 --- a/modules/logging/filterencoder.go +++ b/modules/logging/filterencoder.go @@ -29,13 +29,17 @@ func init() { caddy.RegisterModule(FilterEncoder{}) } -// FilterEncoder wraps an underlying encoder. It does -// not do any encoding itself, but it can manipulate -// (filter) fields before they are actually encoded. -// A wrapped encoder is required. +// FilterEncoder can filter (manipulate) fields on +// log entries before they are actually encoded by +// an underlying encoder. type FilterEncoder struct { - WrappedRaw json.RawMessage `json:"wrap,omitempty"` - FieldsRaw map[string]json.RawMessage `json:"fields,omitempty"` + // The underlying encoder that actually + // encodes the log entries. Required. + WrappedRaw json.RawMessage `json:"wrap,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"` + + // A map of field names to their filters. Note that this + // is not a module map; the keys are field names. + FieldsRaw map[string]json.RawMessage `json:"fields,omitempty" caddy:"namespace=caddy.logging.encoders.filter inline_key=filter"` wrapped zapcore.Encoder Fields map[string]LogFieldFilter `json:"-"` @@ -47,8 +51,8 @@ type FilterEncoder struct { // CaddyModule returns the Caddy module information. func (FilterEncoder) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "caddy.logging.encoders.filter", - New: func() caddy.Module { return new(FilterEncoder) }, + ID: "caddy.logging.encoders.filter", + New: func() caddy.Module { return new(FilterEncoder) }, } } @@ -59,28 +63,23 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error { } // set up wrapped encoder (required) - val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", fe.WrappedRaw) + val, err := ctx.LoadModule(fe, "WrappedRaw") if err != nil { return fmt.Errorf("loading fallback encoder module: %v", err) } - fe.WrappedRaw = nil // allow GC to deallocate fe.wrapped = val.(zapcore.Encoder) // set up each field filter if fe.Fields == nil { fe.Fields = make(map[string]LogFieldFilter) } - for field, filterRaw := range fe.FieldsRaw { - if filterRaw == nil { - continue - } - val, err := ctx.LoadModuleInline("filter", "caddy.logging.encoders.filter", filterRaw) - if err != nil { - return fmt.Errorf("loading log filter module: %v", err) - } - fe.Fields[field] = val.(LogFieldFilter) - } - fe.FieldsRaw = nil // allow GC to deallocate + vals, err := ctx.LoadModule(fe, "FieldsRaw") + if err != nil { + return fmt.Errorf("loading log filter modules: %v", err) + } + for fieldName, modIface := range vals.(map[string]interface{}) { + fe.Fields[fieldName] = modIface.(LogFieldFilter) + } return nil } diff --git a/modules/logging/filters.go b/modules/logging/filters.go index b44e084..52db2fe 100644 --- a/modules/logging/filters.go +++ b/modules/logging/filters.go @@ -41,8 +41,8 @@ type DeleteFilter struct{} // CaddyModule returns the Caddy module information. func (DeleteFilter) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "caddy.logging.encoders.filter.delete", - New: func() caddy.Module { return new(DeleteFilter) }, + ID: "caddy.logging.encoders.filter.delete", + New: func() caddy.Module { return new(DeleteFilter) }, } } @@ -55,15 +55,18 @@ func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field { // IPMaskFilter is a Caddy log field filter that // masks IP addresses. type IPMaskFilter struct { + // The IPv4 range in CIDR notation. IPv4CIDR int `json:"ipv4_cidr,omitempty"` + + // The IPv6 range in CIDR notation. IPv6CIDR int `json:"ipv6_cidr,omitempty"` } // CaddyModule returns the Caddy module information. func (IPMaskFilter) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - Name: "caddy.logging.encoders.filter.ip_mask", - New: func() caddy.Module { return new(IPMaskFilter) }, + ID: "caddy.logging.encoders.filter.ip_mask", + New: func() caddy.Module { return new(IPMaskFilter) }, } } |