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) },  	}  }  | 
