summaryrefslogtreecommitdiff
path: root/caddyconfig/httpcaddyfile
diff options
context:
space:
mode:
Diffstat (limited to 'caddyconfig/httpcaddyfile')
-rw-r--r--caddyconfig/httpcaddyfile/addresses.go20
-rw-r--r--caddyconfig/httpcaddyfile/builtins.go113
-rw-r--r--caddyconfig/httpcaddyfile/builtins_test.go142
-rw-r--r--caddyconfig/httpcaddyfile/directives.go38
-rw-r--r--caddyconfig/httpcaddyfile/httptype.go270
-rw-r--r--caddyconfig/httpcaddyfile/options.go6
-rw-r--r--caddyconfig/httpcaddyfile/pkiapp.go1
-rw-r--r--caddyconfig/httpcaddyfile/serveroptions.go25
-rw-r--r--caddyconfig/httpcaddyfile/shorthands.go92
-rw-r--r--caddyconfig/httpcaddyfile/testdata/import_variadic.txt9
-rw-r--r--caddyconfig/httpcaddyfile/testdata/import_variadic_snippet.txt9
-rw-r--r--caddyconfig/httpcaddyfile/testdata/import_variadic_with_import.txt15
-rw-r--r--caddyconfig/httpcaddyfile/tlsapp.go48
-rw-r--r--caddyconfig/httpcaddyfile/tlsapp_test.go4
14 files changed, 632 insertions, 160 deletions
diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go
index 93bad27..658da48 100644
--- a/caddyconfig/httpcaddyfile/addresses.go
+++ b/caddyconfig/httpcaddyfile/addresses.go
@@ -24,10 +24,11 @@ import (
"strings"
"unicode"
+ "github.com/caddyserver/certmagic"
+
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
- "github.com/caddyserver/certmagic"
)
// mapAddressToServerBlocks returns a map of listener address to list of server
@@ -77,7 +78,8 @@ import (
// multiple addresses to the same lists of server blocks (a many:many mapping).
// (Doing this is essentially a map-reduce technique.)
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
- options map[string]any) (map[string][]serverBlock, error) {
+ options map[string]any,
+) (map[string][]serverBlock, error) {
sbmap := make(map[string][]serverBlock)
for i, sblock := range originalServerBlocks {
@@ -187,13 +189,25 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]se
// listenerAddrsForServerBlockKey essentially converts the Caddyfile
// site addresses to Caddy listener addresses for each server block.
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
- options map[string]any) ([]string, error) {
+ options map[string]any,
+) ([]string, error) {
addr, err := ParseAddress(key)
if err != nil {
return nil, fmt.Errorf("parsing key: %v", err)
}
addr = addr.Normalize()
+ switch addr.Scheme {
+ case "wss":
+ return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
+ case "ws":
+ return nil, fmt.Errorf("the scheme ws:// is only supported in browsers; use http:// instead")
+ case "https", "http", "":
+ // Do nothing or handle the valid schemes
+ default:
+ return nil, fmt.Errorf("unsupported URL scheme %s://", addr.Scheme)
+ }
+
// figure out the HTTP and HTTPS ports; either
// use defaults, or override with user config
httpPort, httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPPort), strconv.Itoa(caddyhttp.DefaultHTTPSPort)
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 45da4a8..94ca007 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -26,14 +26,15 @@ import (
"strings"
"time"
+ "github.com/caddyserver/certmagic"
+ "github.com/mholt/acmez/acme"
+ "go.uber.org/zap/zapcore"
+
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls"
- "github.com/caddyserver/certmagic"
- "github.com/mholt/acmez/acme"
- "go.uber.org/zap/zapcore"
)
func init() {
@@ -48,6 +49,7 @@ func init() {
RegisterHandlerDirective("route", parseRoute)
RegisterHandlerDirective("handle", parseHandle)
RegisterDirective("handle_errors", parseHandleErrors)
+ RegisterHandlerDirective("invoke", parseInvoke)
RegisterDirective("log", parseLog)
RegisterHandlerDirective("skip_log", parseSkipLog)
}
@@ -179,17 +181,17 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
case "protocols":
args := h.RemainingArgs()
if len(args) == 0 {
- return nil, h.SyntaxErr("one or two protocols")
+ return nil, h.Errf("protocols requires one or two arguments")
}
if len(args) > 0 {
if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
- return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
+ return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[0])
}
cp.ProtocolMin = args[0]
}
if len(args) > 1 {
if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
- return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
+ return nil, h.Errf("wrong protocol name or protocol not supported: '%s'", args[1])
}
cp.ProtocolMax = args[1]
}
@@ -197,7 +199,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
case "ciphers":
for h.NextArg() {
if !caddytls.CipherSuiteNameSupported(h.Val()) {
- return nil, h.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
+ return nil, h.Errf("wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
}
cp.CipherSuites = append(cp.CipherSuites, h.Val())
}
@@ -764,9 +766,31 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
}, nil
}
+// parseInvoke parses the invoke directive.
+func parseInvoke(h Helper) (caddyhttp.MiddlewareHandler, error) {
+ h.Next() // consume directive
+ if !h.NextArg() {
+ return nil, h.ArgErr()
+ }
+ for h.Next() || h.NextBlock(0) {
+ return nil, h.ArgErr()
+ }
+
+ // remember that we're invoking this name
+ // to populate the server with these named routes
+ if h.State[namedRouteKey] == nil {
+ h.State[namedRouteKey] = map[string]struct{}{}
+ }
+ h.State[namedRouteKey].(map[string]struct{})[h.Val()] = struct{}{}
+
+ // return the handler
+ return &caddyhttp.Invoke{Name: h.Val()}, nil
+}
+
// parseLog parses the log directive. Syntax:
//
-// log {
+// log <logger_name> {
+// hostnames <hostnames...>
// output <writer_module> ...
// format <encoder_module> ...
// level <level>
@@ -787,11 +811,13 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
var configValues []ConfigValue
for h.Next() {
// Logic below expects that a name is always present when a
- // global option is being parsed.
- var globalLogName string
+ // global option is being parsed; or an optional override
+ // is supported for access logs.
+ var logName string
+
if parseAsGlobalOption {
if h.NextArg() {
- globalLogName = h.Val()
+ logName = h.Val()
// Only a single argument is supported.
if h.NextArg() {
@@ -802,26 +828,47 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
// reference the default logger. See the
// setupNewDefault function in the logging
// package for where this is configured.
- globalLogName = caddy.DefaultLoggerName
+ logName = caddy.DefaultLoggerName
}
// Verify this name is unused.
- _, used := globalLogNames[globalLogName]
+ _, used := globalLogNames[logName]
if used {
- return nil, h.Err("duplicate global log option for: " + globalLogName)
+ return nil, h.Err("duplicate global log option for: " + logName)
}
- globalLogNames[globalLogName] = struct{}{}
+ globalLogNames[logName] = struct{}{}
} else {
- // No arguments are supported for the server block log directive
+ // An optional override of the logger name can be provided;
+ // otherwise a default will be used, like "log0", "log1", etc.
if h.NextArg() {
- return nil, h.ArgErr()
+ logName = h.Val()
+
+ // Only a single argument is supported.
+ if h.NextArg() {
+ return nil, h.ArgErr()
+ }
}
}
cl := new(caddy.CustomLog)
+ // allow overriding the current site block's hostnames for this logger;
+ // this is useful for setting up loggers per subdomain in a site block
+ // with a wildcard domain
+ customHostnames := []string{}
+
for h.NextBlock(0) {
switch h.Val() {
+ case "hostnames":
+ if parseAsGlobalOption {
+ return nil, h.Err("hostnames is not allowed in the log global options")
+ }
+ args := h.RemainingArgs()
+ if len(args) == 0 {
+ return nil, h.ArgErr()
+ }
+ customHostnames = append(customHostnames, args...)
+
case "output":
if !h.NextArg() {
return nil, h.ArgErr()
@@ -880,18 +927,16 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
}
case "include":
- // This configuration is only allowed in the global options
if !parseAsGlobalOption {
- return nil, h.ArgErr()
+ return nil, h.Err("include is not allowed in the log directive")
}
for h.NextArg() {
cl.Include = append(cl.Include, h.Val())
}
case "exclude":
- // This configuration is only allowed in the global options
if !parseAsGlobalOption {
- return nil, h.ArgErr()
+ return nil, h.Err("exclude is not allowed in the log directive")
}
for h.NextArg() {
cl.Exclude = append(cl.Exclude, h.Val())
@@ -903,24 +948,34 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
}
var val namedCustomLog
+ val.hostnames = customHostnames
+
+ isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog))
+
// Skip handling of empty logging configs
- if !reflect.DeepEqual(cl, new(caddy.CustomLog)) {
- if parseAsGlobalOption {
- // Use indicated name for global log options
- val.name = globalLogName
- val.log = cl
- } else {
+
+ if parseAsGlobalOption {
+ // Use indicated name for global log options
+ val.name = logName
+ } else {
+ if logName != "" {
+ val.name = logName
+ } else if !isEmptyConfig {
// Construct a log name for server log streams
logCounter, ok := h.State["logCounter"].(int)
if !ok {
logCounter = 0
}
val.name = fmt.Sprintf("log%d", logCounter)
- cl.Include = []string{"http.log.access." + val.name}
- val.log = cl
logCounter++
h.State["logCounter"] = logCounter
}
+ if val.name != "" {
+ cl.Include = []string{"http.log.access." + val.name}
+ }
+ }
+ if !isEmptyConfig {
+ val.log = cl
}
configValues = append(configValues, ConfigValue{
Class: "custom_log",
diff --git a/caddyconfig/httpcaddyfile/builtins_test.go b/caddyconfig/httpcaddyfile/builtins_test.go
index bd5e116..70f347d 100644
--- a/caddyconfig/httpcaddyfile/builtins_test.go
+++ b/caddyconfig/httpcaddyfile/builtins_test.go
@@ -1,6 +1,7 @@
package httpcaddyfile
import (
+ "strings"
"testing"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
@@ -51,12 +52,13 @@ func TestLogDirectiveSyntax(t *testing.T) {
},
{
input: `:8080 {
- log invalid {
+ log name-override {
output file foo.log
}
}
`,
- expectError: true,
+ output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`,
+ expectError: false,
},
} {
@@ -213,3 +215,139 @@ func TestRedirDirectiveSyntax(t *testing.T) {
}
}
}
+
+func TestImportErrorLine(t *testing.T) {
+ for i, tc := range []struct {
+ input string
+ errorFunc func(err error) bool
+ }{
+ {
+ input: `(t1) {
+ abort {args[:]}
+ }
+ :8080 {
+ import t1
+ import t1 true
+ }`,
+ errorFunc: func(err error) bool {
+ return err != nil && strings.Contains(err.Error(), "Caddyfile:6 (import t1)")
+ },
+ },
+ {
+ input: `(t1) {
+ abort {args[:]}
+ }
+ :8080 {
+ import t1 true
+ }`,
+ errorFunc: func(err error) bool {
+ return err != nil && strings.Contains(err.Error(), "Caddyfile:5 (import t1)")
+ },
+ },
+ {
+ input: `
+ import testdata/import_variadic_snippet.txt
+ :8080 {
+ import t1 true
+ }`,
+ errorFunc: func(err error) bool {
+ return err == nil
+ },
+ },
+ {
+ input: `
+ import testdata/import_variadic_with_import.txt
+ :8080 {
+ import t1 true
+ import t2 true
+ }`,
+ errorFunc: func(err error) bool {
+ return err == nil
+ },
+ },
+ } {
+ adapter := caddyfile.Adapter{
+ ServerType: ServerType{},
+ }
+
+ _, _, err := adapter.Adapt([]byte(tc.input), nil)
+
+ if !tc.errorFunc(err) {
+ t.Errorf("Test %d error expectation failed, got %s", i, err)
+ continue
+ }
+ }
+}
+
+func TestNestedImport(t *testing.T) {
+ for i, tc := range []struct {
+ input string
+ errorFunc func(err error) bool
+ }{
+ {
+ input: `(t1) {
+ respond {args[0]} {args[1]}
+ }
+
+ (t2) {
+ import t1 {args[0]} 202
+ }
+
+ :8080 {
+ handle {
+ import t2 "foobar"
+ }
+ }`,
+ errorFunc: func(err error) bool {
+ return err == nil
+ },
+ },
+ {
+ input: `(t1) {
+ respond {args[:]}
+ }
+
+ (t2) {
+ import t1 {args[0]} {args[1]}
+ }
+
+ :8080 {
+ handle {
+ import t2 "foobar" 202
+ }
+ }`,
+ errorFunc: func(err error) bool {
+ return err == nil
+ },
+ },
+ {
+ input: `(t1) {
+ respond {args[0]} {args[1]}
+ }
+
+ (t2) {
+ import t1 {args[:]}
+ }
+
+ :8080 {
+ handle {
+ import t2 "foobar" 202
+ }
+ }`,
+ errorFunc: func(err error) bool {
+ return err == nil
+ },
+ },
+ } {
+ adapter := caddyfile.Adapter{
+ ServerType: ServerType{},
+ }
+
+ _, _, err := adapter.Adapt([]byte(tc.input), nil)
+
+ if !tc.errorFunc(err) {
+ t.Errorf("Test %d error expectation failed, got %s", i, err)
+ continue
+ }
+ }
+}
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index a772dba..13229ed 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -65,6 +65,7 @@ var directiveOrder = []string{
"templates",
// special routing & dispatching directives
+ "invoke",
"handle",
"handle_path",
"route",
@@ -172,6 +173,7 @@ func (h Helper) Caddyfiles() []string {
for file := range files {
filesSlice = append(filesSlice, file)
}
+ sort.Strings(filesSlice)
return filesSlice
}
@@ -215,7 +217,8 @@ func (h Helper) ExtractMatcherSet() (caddy.ModuleMap, error) {
// NewRoute returns config values relevant to creating a new HTTP route.
func (h Helper) NewRoute(matcherSet caddy.ModuleMap,
- handler caddyhttp.MiddlewareHandler) []ConfigValue {
+ handler caddyhttp.MiddlewareHandler,
+) []ConfigValue {
mod, err := caddy.GetModule(caddy.GetModuleID(handler))
if err != nil {
*h.warnings = append(*h.warnings, caddyconfig.Warning{
@@ -427,26 +430,16 @@ func sortRoutes(routes []ConfigValue) {
jPathLen = len(jPM[0])
}
- // some directives involve setting values which can overwrite
- // each other, so it makes most sense to reverse the order so
- // that the lease specific matcher is first; everything else
- // has most-specific matcher first
- if iDir == "vars" {
+ sortByPath := func() bool {
// we can only confidently compare path lengths if both
// directives have a single path to match (issue #5037)
if iPathLen > 0 && jPathLen > 0 {
- // sort least-specific (shortest) path first
- return iPathLen < jPathLen
- }
+ // if both paths are the same except for a trailing wildcard,
+ // sort by the shorter path first (which is more specific)
+ if strings.TrimSuffix(iPM[0], "*") == strings.TrimSuffix(jPM[0], "*") {
+ return iPathLen < jPathLen
+ }
- // if both directives don't have a single path to compare,
- // sort whichever one has no matcher first; if both have
- // no matcher, sort equally (stable sort preserves order)
- return len(iRoute.MatcherSetsRaw) == 0 && len(jRoute.MatcherSetsRaw) > 0
- } else {
- // we can only confidently compare path lengths if both
- // directives have a single path to match (issue #5037)
- if iPathLen > 0 && jPathLen > 0 {
// sort most-specific (longest) path first
return iPathLen > jPathLen
}
@@ -455,7 +448,18 @@ func sortRoutes(routes []ConfigValue) {
// sort whichever one has a matcher first; if both have
// a matcher, sort equally (stable sort preserves order)
return len(iRoute.MatcherSetsRaw) > 0 && len(jRoute.MatcherSetsRaw) == 0
+ }()
+
+ // some directives involve setting values which can overwrite
+ // each other, so it makes most sense to reverse the order so
+ // that the least-specific matcher is first, allowing the last
+ // matching one to win
+ if iDir == "vars" {
+ return !sortByPath
}
+
+ // everything else is most-specific matcher first
+ return sortByPath
})
}
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index 50e98ac..3e8fdca 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -18,18 +18,19 @@ import (
"encoding/json"
"fmt"
"reflect"
- "regexp"
"sort"
"strconv"
"strings"
+ "go.uber.org/zap"
+ "golang.org/x/exp/slices"
+
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddypki"
"github.com/caddyserver/caddy/v2/modules/caddytls"
- "go.uber.org/zap"
)
func init() {
@@ -48,12 +49,13 @@ type App struct {
}
// ServerType can set up a config from an HTTP Caddyfile.
-type ServerType struct {
-}
+type ServerType struct{}
// Setup makes a config from the tokens.
-func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
- options map[string]any) (*caddy.Config, []caddyconfig.Warning, error) {
+func (st ServerType) Setup(
+ inputServerBlocks []caddyfile.ServerBlock,
+ options map[string]any,
+) (*caddy.Config, []caddyconfig.Warning, error) {
var warnings []caddyconfig.Warning
gc := counter{new(int)}
state := make(map[string]any)
@@ -79,41 +81,18 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
return nil, warnings, err
}
- // replace shorthand placeholders (which are convenient
- // when writing a Caddyfile) with their actual placeholder
- // identifiers or variable names
- replacer := strings.NewReplacer(placeholderShorthands()...)
-
- // these are placeholders that allow a user-defined final
- // parameters, but we still want to provide a shorthand
- // for those, so we use a regexp to replace
- regexpReplacements := []struct {
- search *regexp.Regexp
- replace string
- }{
- {regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"},
- {regexp.MustCompile(`{cookie\.([\w-]*)}`), "{http.request.cookie.$1}"},
- {regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"},
- {regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"},
- {regexp.MustCompile(`{file\.([\w-]*)}`), "{http.request.uri.path.file.$1}"},
- {regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"},
- {regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"},
- {regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
- {regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
- {regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
- {regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
+ // this will replace both static and user-defined placeholder shorthands
+ // with actual identifiers used by Caddy
+ replacer := NewShorthandReplacer()
+
+ originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings, replacer)
+ if err != nil {
+ return nil, warnings, err
}
for _, sb := range originalServerBlocks {
- for _, segment := range sb.block.Segments {
- for i := 0; i < len(segment); i++ {
- // simple string replacements
- segment[i].Text = replacer.Replace(segment[i].Text)
- // complex regexp replacements
- for _, r := range regexpReplacements {
- segment[i].Text = r.search.ReplaceAllString(segment[i].Text, r.replace)
- }
- }
+ for i := range sb.block.Segments {
+ replacer.ApplyToSegment(&sb.block.Segments[i])
}
if len(sb.block.Keys) == 0 {
@@ -172,6 +151,18 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
result.directive = dir
sb.pile[result.Class] = append(sb.pile[result.Class], result)
}
+
+ // specially handle named routes that were pulled out from
+ // the invoke directive, which could be nested anywhere within
+ // some subroutes in this directive; we add them to the pile
+ // for this server block
+ if state[namedRouteKey] != nil {
+ for name := range state[namedRouteKey].(map[string]struct{}) {
+ result := ConfigValue{Class: namedRouteKey, Value: name}
+ sb.pile[result.Class] = append(sb.pile[result.Class], result)
+ }
+ state[namedRouteKey] = nil
+ }
}
}
@@ -222,7 +213,7 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
if ncl.name == caddy.DefaultLoggerName {
hasDefaultLog = true
}
- if _, ok := options["debug"]; ok && ncl.log.Level == "" {
+ if _, ok := options["debug"]; ok && ncl.log != nil && ncl.log.Level == "" {
ncl.log.Level = zap.DebugLevel.CapitalString()
}
customLogs = append(customLogs, ncl)
@@ -241,7 +232,9 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
if _, ok := options["debug"]; ok {
customLogs = append(customLogs, namedCustomLog{
name: caddy.DefaultLoggerName,
- log: &caddy.CustomLog{Level: zap.DebugLevel.CapitalString()},
+ log: &caddy.CustomLog{
+ BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()},
+ },
})
}
}
@@ -303,7 +296,21 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
Logs: make(map[string]*caddy.CustomLog),
}
}
+
+ // Add the default log first if defined, so that it doesn't
+ // accidentally get re-created below due to the Exclude logic
for _, ncl := range customLogs {
+ if ncl.name == caddy.DefaultLoggerName && ncl.log != nil {
+ cfg.Logging.Logs[caddy.DefaultLoggerName] = ncl.log
+ break
+ }
+ }
+
+ // Add the rest of the custom logs
+ for _, ncl := range customLogs {
+ if ncl.log == nil || ncl.name == caddy.DefaultLoggerName {
+ continue
+ }
if ncl.name != "" {
cfg.Logging.Logs[ncl.name] = ncl.log
}
@@ -317,8 +324,16 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
cfg.Logging.Logs[caddy.DefaultLoggerName] = defaultLog
}
defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...)
+
+ // avoid duplicates by sorting + compacting
+ sort.Strings(defaultLog.Exclude)
+ defaultLog.Exclude = slices.Compact[[]string, string](defaultLog.Exclude)
}
}
+ // we may have not actually added anything, so remove if empty
+ if len(cfg.Logging.Logs) == 0 {
+ cfg.Logging = nil
+ }
}
return cfg, warnings, nil
@@ -401,6 +416,81 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
return serverBlocks[1:], nil
}
+// extractNamedRoutes pulls out any named route server blocks
+// so they don't get parsed as sites, and stores them in options
+// for later.
+func (ServerType) extractNamedRoutes(
+ serverBlocks []serverBlock,
+ options map[string]any,
+ warnings *[]caddyconfig.Warning,
+ replacer ShorthandReplacer,
+) ([]serverBlock, error) {
+ namedRoutes := map[string]*caddyhttp.Route{}
+
+ gc := counter{new(int)}
+ state := make(map[string]any)
+
+ // copy the server blocks so we can
+ // splice out the named route ones
+ filtered := append([]serverBlock{}, serverBlocks...)
+ index := -1
+
+ for _, sb := range serverBlocks {
+ index++
+ if !sb.block.IsNamedRoute {
+ continue
+ }
+
+ // splice out this block, because we know it's not a real server
+ filtered = append(filtered[:index], filtered[index+1:]...)
+ index--
+
+ if len(sb.block.Segments) == 0 {
+ continue
+ }
+
+ wholeSegment := caddyfile.Segment{}
+ for i := range sb.block.Segments {
+ // replace user-defined placeholder shorthands in extracted named routes
+ replacer.ApplyToSegment(&sb.block.Segments[i])
+
+ // zip up all the segments since ParseSegmentAsSubroute
+ // was designed to take a directive+
+ wholeSegment = append(wholeSegment, sb.block.Segments[i]...)
+ }
+
+ h := Helper{
+ Dispenser: caddyfile.NewDispenser(wholeSegment),
+ options: options,
+ warnings: warnings,
+ matcherDefs: nil,
+ parentBlock: sb.block,
+ groupCounter: gc,
+ State: state,
+ }
+
+ handler, err := ParseSegmentAsSubroute(h)
+ if err != nil {
+ return nil, err
+ }
+ subroute := handler.(*caddyhttp.Subroute)
+ route := caddyhttp.Route{}
+
+ if len(subroute.Routes) == 1 && len(subroute.Routes[0].MatcherSetsRaw) == 0 {
+ // if there's only one route with no matcher, then we can simplify
+ route.HandlersRaw = append(route.HandlersRaw, subroute.Routes[0].HandlersRaw[0])
+ } else {
+ // otherwise we need the whole subroute
+ route.HandlersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", subroute.CaddyModule().ID.Name(), h.warnings)}
+ }
+
+ namedRoutes[sb.block.Keys[0]] = &route
+ }
+ options["named_routes"] = namedRoutes
+
+ return filtered, nil
+}
+
// serversFromPairings creates the servers for each pairing of addresses
// to server blocks. Each pairing is essentially a server definition.
func (st *ServerType) serversFromPairings(
@@ -411,6 +501,7 @@ func (st *ServerType) serversFromPairings(
) (map[string]*caddyhttp.Server, error) {
servers := make(map[string]*caddyhttp.Server)
defaultSNI := tryString(options["default_sni"], warnings)
+ fallbackSNI := tryString(options["fallback_sni"], warnings)
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
if hp, ok := options["http_port"].(int); ok {
@@ -539,6 +630,24 @@ func (st *ServerType) serversFromPairings(
}
}
+ // add named routes to the server if 'invoke' was used inside of it
+ configuredNamedRoutes := options["named_routes"].(map[string]*caddyhttp.Route)
+ for _, sblock := range p.serverBlocks {
+ if len(sblock.pile[namedRouteKey]) == 0 {
+ continue
+ }
+ for _, value := range sblock.pile[namedRouteKey] {
+ if srv.NamedRoutes == nil {
+ srv.NamedRoutes = map[string]*caddyhttp.Route{}
+ }
+ name := value.Value.(string)
+ if configuredNamedRoutes[name] == nil {
+ return nil, fmt.Errorf("cannot invoke named route '%s', which was not defined", name)
+ }
+ srv.NamedRoutes[name] = configuredNamedRoutes[name]
+ }
+ }
+
// create a subroute for each site in the server block
for _, sblock := range p.serverBlocks {
matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
@@ -568,14 +677,21 @@ func (st *ServerType) serversFromPairings(
cp.DefaultSNI = defaultSNI
break
}
+ if h == fallbackSNI {
+ hosts = append(hosts, "")
+ cp.FallbackSNI = fallbackSNI
+ break
+ }
}
if len(hosts) > 0 {
+ slices.Sort(hosts) // for deterministic JSON output
cp.MatchersRaw = caddy.ModuleMap{
"sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
}
} else {
cp.DefaultSNI = defaultSNI
+ cp.FallbackSNI = fallbackSNI
}
// only append this policy if it actually changes something
@@ -601,10 +717,20 @@ func (st *ServerType) serversFromPairings(
}
}
+ // If TLS is specified as directive, it will also result in 1 or more connection policy being created
+ // Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without
+ // specifying prefix "https://"
+ // Second part of the condition is to allow creating TLS conn policy even though `auto_https` has been disabled
+ // ensuring compatibility with behavior described in below link
+ // https://caddy.community/t/making-sense-of-auto-https-and-why-disabling-it-still-serves-https-instead-of-http/9761
+ createdTLSConnPolicies, ok := sblock.pile["tls.connection_policy"]
+ hasTLSEnabled := (ok && len(createdTLSConnPolicies) > 0) ||
+ (addr.Host != "" && srv.AutoHTTPS != nil && !sliceContains(srv.AutoHTTPS.Skip, addr.Host))
+
// we'll need to remember if the address qualifies for auto-HTTPS, so we
// can add a TLS conn policy if necessary
if addr.Scheme == "https" ||
- (addr.Scheme != "http" && addr.Host != "" && addr.Port != httpPort) {
+ (addr.Scheme != "http" && addr.Port != httpPort && hasTLSEnabled) {
addressQualifiesForTLS = true
}
// predict whether auto-HTTPS will add the conn policy for us; if so, we
@@ -653,12 +779,20 @@ func (st *ServerType) serversFromPairings(
sblockLogHosts := sblock.hostsFromKeys(true)
for _, cval := range sblock.pile["custom_log"] {
ncl := cval.Value.(namedCustomLog)
- if sblock.hasHostCatchAllKey() {
+ if sblock.hasHostCatchAllKey() && len(ncl.hostnames) == 0 {
// all requests for hosts not able to be listed should use
// this log because it's a catch-all-hosts server block
srv.Logs.DefaultLoggerName = ncl.name
+ } else if len(ncl.hostnames) > 0 {
+ // if the logger overrides the hostnames, map that to the logger name
+ for _, h := range ncl.hostnames {
+ if srv.Logs.LoggerNames == nil {
+ srv.Logs.LoggerNames = make(map[string]string)
+ }
+ srv.Logs.LoggerNames[h] = ncl.name
+ }
} else {
- // map each host to the user's desired logger name
+ // otherwise, map each host to the logger name
for _, h := range sblockLogHosts {
if srv.Logs.LoggerNames == nil {
srv.Logs.LoggerNames = make(map[string]string)
@@ -701,8 +835,8 @@ func (st *ServerType) serversFromPairings(
// policy missing for any HTTPS-enabled hosts, if so, add it... maybe?
if addressQualifiesForTLS &&
!hasCatchAllTLSConnPolicy &&
- (len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "") {
- srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI})
+ (len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "" || fallbackSNI != "") {
+ srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI, FallbackSNI: fallbackSNI})
}
// tidy things up a bit
@@ -911,8 +1045,8 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
subroute *caddyhttp.Subroute,
matcherSetsEnc []caddy.ModuleMap,
p sbAddrAssociation,
- warnings *[]caddyconfig.Warning) caddyhttp.RouteList {
-
+ warnings *[]caddyconfig.Warning,
+) caddyhttp.RouteList {
// nothing to do if... there's nothing to do
if len(matcherSetsEnc) == 0 && len(subroute.Routes) == 0 && subroute.Errors == nil {
return routeList
@@ -974,7 +1108,7 @@ func buildSubroute(routes []ConfigValue, groupCounter counter, needsSorting bool
if needsSorting {
for _, val := range routes {
if !directiveIsOrdered(val.directive) {
- return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here", val.directive)
+ return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option", val.directive)
}
}
@@ -1301,36 +1435,6 @@ func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.Modul
return msEncoded, nil
}
-// placeholderShorthands returns a slice of old-new string pairs,
-// where the left of the pair is a placeholder shorthand that may
-// be used in the Caddyfile, and the right is the replacement.
-func placeholderShorthands() []string {
- return []string{
- "{dir}", "{http.request.uri.path.dir}",
- "{file}", "{http.request.uri.path.file}",
- "{host}", "{http.request.host}",
- "{hostport}", "{http.request.hostport}",
- "{port}", "{http.request.port}",
- "{method}", "{http.request.method}",
- "{path}", "{http.request.uri.path}",
- "{query}", "{http.request.uri.query}",
- "{remote}", "{http.request.remote}",
- "{remote_host}", "{http.request.remote.host}",
- "{remote_port}", "{http.request.remote.port}",
- "{scheme}", "{http.request.scheme}",
- "{uri}", "{http.request.uri}",
- "{tls_cipher}", "{http.request.tls.cipher_suite}",
- "{tls_version}", "{http.request.tls.version}",
- "{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
- "{tls_client_issuer}", "{http.request.tls.client.issuer}",
- "{tls_client_serial}", "{http.request.tls.client.serial}",
- "{tls_client_subject}", "{http.request.tls.client.subject}",
- "{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
- "{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
- "{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
- }
-}
-
// WasReplacedPlaceholderShorthand checks if a token string was
// likely a replaced shorthand of the known Caddyfile placeholder
// replacement outputs. Useful to prevent some user-defined map
@@ -1446,8 +1550,9 @@ func (c counter) nextGroup() string {
}
type namedCustomLog struct {
- name string
- log *caddy.CustomLog
+ name string
+ hostnames []string
+ log *caddy.CustomLog
}
// sbAddrAssociation is a mapping from a list of
@@ -1458,7 +1563,10 @@ type sbAddrAssociation struct {
serverBlocks []serverBlock
}
-const matcherPrefix = "@"
+const (
+ matcherPrefix = "@"
+ namedRouteKey = "named_route"
+)
// Interface guard
var _ caddyfile.ServerType = (*ServerType)(nil)
diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go
index 4e5212b..ba1896b 100644
--- a/caddyconfig/httpcaddyfile/options.go
+++ b/caddyconfig/httpcaddyfile/options.go
@@ -17,12 +17,13 @@ package httpcaddyfile
import (
"strconv"
+ "github.com/caddyserver/certmagic"
+ "github.com/mholt/acmez/acme"
+
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddytls"
- "github.com/caddyserver/certmagic"
- "github.com/mholt/acmez/acme"
)
func init() {
@@ -33,6 +34,7 @@ func init() {
RegisterGlobalOption("grace_period", parseOptDuration)
RegisterGlobalOption("shutdown_delay", parseOptDuration)
RegisterGlobalOption("default_sni", parseOptSingleString)
+ RegisterGlobalOption("fallback_sni", parseOptSingleString)
RegisterGlobalOption("order", parseOptOrder)
RegisterGlobalOption("storage", parseOptStorage)
RegisterGlobalOption("storage_clean_interval", parseOptDuration)
diff --git a/caddyconfig/httpcaddyfile/pkiapp.go b/caddyconfig/httpcaddyfile/pkiapp.go
index 3414636..b5c6821 100644
--- a/caddyconfig/httpcaddyfile/pkiapp.go
+++ b/caddyconfig/httpcaddyfile/pkiapp.go
@@ -174,7 +174,6 @@ func (st ServerType) buildPKIApp(
options map[string]any,
warnings []caddyconfig.Warning,
) (*caddypki.PKI, []caddyconfig.Warning, error) {
-
skipInstallTrust := false
if _, ok := options["skip_install_trust"]; ok {
skipInstallTrust = true
diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go
index eb57c58..6d7c678 100644
--- a/caddyconfig/httpcaddyfile/serveroptions.go
+++ b/caddyconfig/httpcaddyfile/serveroptions.go
@@ -18,11 +18,12 @@ import (
"encoding/json"
"fmt"
+ "github.com/dustin/go-humanize"
+
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
- "github.com/dustin/go-humanize"
)
// serverOptions collects server config overrides parsed from Caddyfile global options
@@ -41,9 +42,11 @@ type serverOptions struct {
IdleTimeout caddy.Duration
KeepAliveInterval caddy.Duration
MaxHeaderBytes int
+ EnableFullDuplex bool
Protocols []string
StrictSNIHost *bool
TrustedProxiesRaw json.RawMessage
+ ClientIPHeaders []string
ShouldLogCredentials bool
Metrics *caddyhttp.Metrics
}
@@ -156,6 +159,12 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
}
serverOpts.MaxHeaderBytes = int(size)
+ case "enable_full_duplex":
+ if d.NextArg() {
+ return nil, d.ArgErr()
+ }
+ serverOpts.EnableFullDuplex = true
+
case "log_credentials":
if d.NextArg() {
return nil, d.ArgErr()
@@ -208,6 +217,18 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
)
serverOpts.TrustedProxiesRaw = jsonSource
+ case "client_ip_headers":
+ headers := d.RemainingArgs()
+ for _, header := range headers {
+ if sliceContains(serverOpts.ClientIPHeaders, header) {
+ return nil, d.Errf("client IP header %s specified more than once", header)
+ }
+ serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header)
+ }
+ if nesting := d.Nesting(); d.NextBlock(nesting) {
+ return nil, d.ArgErr()
+ }
+
case "metrics":
if d.NextArg() {
return nil, d.ArgErr()
@@ -314,9 +335,11 @@ func applyServerOptions(
server.IdleTimeout = opts.IdleTimeout
server.KeepAliveInterval = opts.KeepAliveInterval
server.MaxHeaderBytes = opts.MaxHeaderBytes
+ server.EnableFullDuplex = opts.EnableFullDuplex
server.Protocols = opts.Protocols
server.StrictSNIHost = opts.StrictSNIHost
server.TrustedProxiesRaw = opts.TrustedProxiesRaw
+ server.ClientIPHeaders = opts.ClientIPHeaders
server.Metrics = opts.Metrics
if opts.ShouldLogCredentials {
if server.Logs == nil {
diff --git a/caddyconfig/httpcaddyfile/shorthands.go b/caddyconfig/httpcaddyfile/shorthands.go
new file mode 100644
index 0000000..102bc36
--- /dev/null
+++ b/caddyconfig/httpcaddyfile/shorthands.go
@@ -0,0 +1,92 @@
+package httpcaddyfile
+
+import (
+ "regexp"
+ "strings"
+
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+)
+
+type ComplexShorthandReplacer struct {
+ search *regexp.Regexp
+ replace string
+}
+
+type ShorthandReplacer struct {
+ complex []ComplexShorthandReplacer
+ simple *strings.Replacer
+}
+
+func NewShorthandReplacer() ShorthandReplacer {
+ // replace shorthand placeholders (which are convenient
+ // when writing a Caddyfile) with their actual placeholder
+ // identifiers or variable names
+ replacer := strings.NewReplacer(placeholderShorthands()...)
+
+ // these are placeholders that allow a user-defined final
+ // parameters, but we still want to provide a shorthand
+ // for those, so we use a regexp to replace
+ regexpReplacements := []ComplexShorthandReplacer{
+ {regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"},
+ {regexp.MustCompile(`{cookie\.([\w-]*)}`), "{http.request.cookie.$1}"},
+ {regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"},
+ {regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"},
+ {regexp.MustCompile(`{file\.([\w-]*)}`), "{http.request.uri.path.file.$1}"},
+ {regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"},
+ {regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"},
+ {regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
+ {regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
+ {regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
+ {regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
+ }
+
+ return ShorthandReplacer{
+ complex: regexpReplacements,
+ simple: replacer,
+ }
+}
+
+// placeholderShorthands returns a slice of old-new string pairs,
+// where the left of the pair is a placeholder shorthand that may
+// be used in the Caddyfile, and the right is the replacement.
+func placeholderShorthands() []string {
+ return []string{
+ "{dir}", "{http.request.uri.path.dir}",
+ "{file}", "{http.request.uri.path.file}",
+ "{host}", "{http.request.host}",
+ "{hostport}", "{http.request.hostport}",
+ "{port}", "{http.request.port}",
+ "{method}", "{http.request.method}",
+ "{path}", "{http.request.uri.path}",
+ "{query}", "{http.request.uri.query}",
+ "{remote}", "{http.request.remote}",
+ "{remote_host}", "{http.request.remote.host}",
+ "{remote_port}", "{http.request.remote.port}",
+ "{scheme}", "{http.request.scheme}",
+ "{uri}", "{http.request.uri}",
+ "{tls_cipher}", "{http.request.tls.cipher_suite}",
+ "{tls_version}", "{http.request.tls.version}",
+ "{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
+ "{tls_client_issuer}", "{http.request.tls.client.issuer}",
+ "{tls_client_serial}", "{http.request.tls.client.serial}",
+ "{tls_client_subject}", "{http.request.tls.client.subject}",
+ "{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
+ "{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
+ "{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
+ "{client_ip}", "{http.vars.client_ip}",
+ }
+}
+
+// ApplyToSegment replaces shorthand placeholder to its full placeholder, understandable by Caddy.
+func (s ShorthandReplacer) ApplyToSegment(segment *caddyfile.Segment) {
+ if segment != nil {
+ for i := 0; i < len(*segment); i++ {
+ // simple string replacements
+ (*segment)[i].Text = s.simple.Replace((*segment)[i].Text)
+ // complex regexp replacements
+ for _, r := range s.complex {
+ (*segment)[i].Text = r.search.ReplaceAllString((*segment)[i].Text, r.replace)
+ }
+ }
+ }
+}
diff --git a/caddyconfig/httpcaddyfile/testdata/import_variadic.txt b/caddyconfig/httpcaddyfile/testdata/import_variadic.txt
new file mode 100644
index 0000000..f1e50e0
--- /dev/null
+++ b/caddyconfig/httpcaddyfile/testdata/import_variadic.txt
@@ -0,0 +1,9 @@
+(t2) {
+ respond 200 {
+ body {args[:]}
+ }
+}
+
+:8082 {
+ import t2 false
+} \ No newline at end of file
diff --git a/caddyconfig/httpcaddyfile/testdata/import_variadic_snippet.txt b/caddyconfig/httpcaddyfile/testdata/import_variadic_snippet.txt
new file mode 100644
index 0000000..a02fcf9
--- /dev/null
+++ b/caddyconfig/httpcaddyfile/testdata/import_variadic_snippet.txt
@@ -0,0 +1,9 @@
+(t1) {
+ respond 200 {
+ body {args[:]}
+ }
+}
+
+:8081 {
+ import t1 false
+} \ No newline at end of file
diff --git a/caddyconfig/httpcaddyfile/testdata/import_variadic_with_import.txt b/caddyconfig/httpcaddyfile/testdata/import_variadic_with_import.txt
new file mode 100644
index 0000000..ab1b32d
--- /dev/null
+++ b/caddyconfig/httpcaddyfile/testdata/import_variadic_with_import.txt
@@ -0,0 +1,15 @@
+(t1) {
+ respond 200 {
+ body {args[:]}
+ }
+}
+
+:8081 {
+ import t1 false
+}
+
+import import_variadic.txt
+
+:8083 {
+ import t2 true
+} \ No newline at end of file
diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go
index 3d32b4f..927f225 100644
--- a/caddyconfig/httpcaddyfile/tlsapp.go
+++ b/caddyconfig/httpcaddyfile/tlsapp.go
@@ -23,12 +23,13 @@ import (
"strconv"
"strings"
+ "github.com/caddyserver/certmagic"
+ "github.com/mholt/acmez/acme"
+
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls"
- "github.com/caddyserver/certmagic"
- "github.com/mholt/acmez/acme"
)
func (st ServerType) buildTLSApp(
@@ -36,7 +37,6 @@ func (st ServerType) buildTLSApp(
options map[string]any,
warnings []caddyconfig.Warning,
) (*caddytls.TLS, []caddyconfig.Warning, error) {
-
tlsApp := &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
var certLoaders []caddytls.CertificateLoader
@@ -206,8 +206,8 @@ func (st ServerType) buildTLSApp(
}
// associate our new automation policy with this server block's hosts
- ap.Subjects = sblock.hostsFromKeysNotHTTP(httpPort)
- sort.Strings(ap.Subjects) // solely for deterministic test results
+ ap.SubjectsRaw = sblock.hostsFromKeysNotHTTP(httpPort)
+ sort.Strings(ap.SubjectsRaw) // solely for deterministic test results
// if a combination of public and internal names were given
// for this same server block and no issuer was specified, we
@@ -217,7 +217,11 @@ func (st ServerType) buildTLSApp(
var ap2 *caddytls.AutomationPolicy
if len(ap.Issuers) == 0 {
var internal, external []string
- for _, s := range ap.Subjects {
+ for _, s := range ap.SubjectsRaw {
+ // do not create Issuers for Tailscale domains; they will be given a Manager instead
+ if strings.HasSuffix(strings.ToLower(s), ".ts.net") {
+ continue
+ }
if !certmagic.SubjectQualifiesForCert(s) {
return nil, warnings, fmt.Errorf("subject does not qualify for certificate: '%s'", s)
}
@@ -235,10 +239,10 @@ func (st ServerType) buildTLSApp(
}
}
if len(external) > 0 && len(internal) > 0 {
- ap.Subjects = external
+ ap.SubjectsRaw = external
apCopy := *ap
ap2 = &apCopy
- ap2.Subjects = internal
+ ap2.SubjectsRaw = internal
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
}
}
@@ -339,14 +343,14 @@ func (st ServerType) buildTLSApp(
for h := range httpsHostsSharedWithHostlessKey {
al = append(al, h)
if !certmagic.SubjectQualifiesForPublicCert(h) {
- internalAP.Subjects = append(internalAP.Subjects, h)
+ internalAP.SubjectsRaw = append(internalAP.SubjectsRaw, h)
}
}
}
if len(al) > 0 {
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
}
- if len(internalAP.Subjects) > 0 {
+ if len(internalAP.SubjectsRaw) > 0 {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
@@ -412,7 +416,7 @@ func (st ServerType) buildTLSApp(
// for convenience)
automationHostSet := make(map[string]struct{})
for _, ap := range tlsApp.Automation.Policies {
- for _, s := range ap.Subjects {
+ for _, s := range ap.SubjectsRaw {
if _, ok := automationHostSet[s]; ok {
return nil, warnings, fmt.Errorf("hostname appears in more than one automation policy, making certificate management ambiguous: %s", s)
}
@@ -533,7 +537,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
if automationPolicyIsSubset(aps[j], aps[i]) {
return false
}
- return len(aps[i].Subjects) > len(aps[j].Subjects)
+ return len(aps[i].SubjectsRaw) > len(aps[j].SubjectsRaw)
})
emptyAPCount := 0
@@ -541,7 +545,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
// compute the number of empty policies (disregarding subjects) - see #4128
emptyAP := new(caddytls.AutomationPolicy)
for i := 0; i < len(aps); i++ {
- emptyAP.Subjects = aps[i].Subjects
+ emptyAP.SubjectsRaw = aps[i].SubjectsRaw
if reflect.DeepEqual(aps[i], emptyAP) {
emptyAPCount++
if !automationPolicyHasAllPublicNames(aps[i]) {
@@ -583,7 +587,7 @@ outer:
aps[i].KeyType == aps[j].KeyType &&
aps[i].OnDemand == aps[j].OnDemand &&
aps[i].RenewalWindowRatio == aps[j].RenewalWindowRatio {
- if len(aps[i].Subjects) > 0 && len(aps[j].Subjects) == 0 {
+ if len(aps[i].SubjectsRaw) > 0 && len(aps[j].SubjectsRaw) == 0 {
// later policy (at j) has no subjects ("catch-all"), so we can
// remove the identical-but-more-specific policy that comes first
// AS LONG AS it is not shadowed by another policy before it; e.g.
@@ -598,9 +602,9 @@ outer:
}
} else {
// avoid repeated subjects
- for _, subj := range aps[j].Subjects {
- if !sliceContains(aps[i].Subjects, subj) {
- aps[i].Subjects = append(aps[i].Subjects, subj)
+ for _, subj := range aps[j].SubjectsRaw {
+ if !sliceContains(aps[i].SubjectsRaw, subj) {
+ aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
}
}
aps = append(aps[:j], aps[j+1:]...)
@@ -616,15 +620,15 @@ outer:
// automationPolicyIsSubset returns true if a's subjects are a subset
// of b's subjects.
func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
- if len(b.Subjects) == 0 {
+ if len(b.SubjectsRaw) == 0 {
return true
}
- if len(a.Subjects) == 0 {
+ if len(a.SubjectsRaw) == 0 {
return false
}
- for _, aSubj := range a.Subjects {
+ for _, aSubj := range a.SubjectsRaw {
var inSuperset bool
- for _, bSubj := range b.Subjects {
+ for _, bSubj := range b.SubjectsRaw {
if certmagic.MatchWildcard(aSubj, bSubj) {
inSuperset = true
break
@@ -662,7 +666,7 @@ func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) b
}
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
- for _, subj := range ap.Subjects {
+ for _, subj := range ap.SubjectsRaw {
if !subjectQualifiesForPublicCert(ap, subj) {
return false
}
diff --git a/caddyconfig/httpcaddyfile/tlsapp_test.go b/caddyconfig/httpcaddyfile/tlsapp_test.go
index 1925e02..d8edbdf 100644
--- a/caddyconfig/httpcaddyfile/tlsapp_test.go
+++ b/caddyconfig/httpcaddyfile/tlsapp_test.go
@@ -47,8 +47,8 @@ func TestAutomationPolicyIsSubset(t *testing.T) {
expect: false,
},
} {
- apA := &caddytls.AutomationPolicy{Subjects: test.a}
- apB := &caddytls.AutomationPolicy{Subjects: test.b}
+ apA := &caddytls.AutomationPolicy{SubjectsRaw: test.a}
+ apB := &caddytls.AutomationPolicy{SubjectsRaw: test.b}
if actual := automationPolicyIsSubset(apA, apB); actual != test.expect {
t.Errorf("Test %d: Expected %t but got %t (A: %v B: %v)", i, test.expect, actual, test.a, test.b)
}