From 1e662262179d326586d2beb849f842b82b7324c4 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 30 Sep 2019 09:11:30 -0600 Subject: httpcaddyfile: Add acme_ca and email global options Also add ability to access options from individual unmarshalers through the Helper values --- caddyconfig/httpcaddyfile/builtins.go | 12 ++++- caddyconfig/httpcaddyfile/directives.go | 6 +++ caddyconfig/httpcaddyfile/handlers.go | 56 ---------------------- caddyconfig/httpcaddyfile/httptype.go | 84 ++++++++++++++++++++++++++++----- caddyconfig/httpcaddyfile/options.go | 24 ++++++++++ 5 files changed, 112 insertions(+), 70 deletions(-) delete mode 100644 caddyconfig/httpcaddyfile/handlers.go (limited to 'caddyconfig/httpcaddyfile') diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go index ff93b11..ec3740c 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -86,6 +86,14 @@ func parseTLS(h Helper) ([]ConfigValue, error) { var mgr caddytls.ACMEManagerMaker var off bool + // fill in global defaults, if configured + if email := h.Option("email"); email != nil { + mgr.Email = email.(string) + } + if acmeCA := h.Option("acme_ca"); acmeCA != nil { + mgr.CA = acmeCA.(string) + } + for h.Next() { // file certificate loader firstLine := h.RemainingArgs() @@ -112,7 +120,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) { hasBlock = true switch h.Val() { - // connection policy case "protocols": args := h.RemainingArgs() @@ -164,7 +171,8 @@ func parseTLS(h Helper) ([]ConfigValue, error) { } mgr.CA = arg[0] - // TODO: other properties for automation manager + default: + return nil, h.Errf("unknown subdirective: %s", h.Val()) } } diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index 2f89f6d..0d7e0e4 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -80,11 +80,17 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) { // Caddyfile tokens. type Helper struct { *caddyfile.Dispenser + options map[string]interface{} warnings *[]caddyconfig.Warning matcherDefs map[string]map[string]json.RawMessage parentBlock caddyfile.ServerBlock } +// Option gets the option keyed by name. +func (h Helper) Option(name string) interface{} { + return h.options[name] +} + // Caddyfiles returns the list of config files from // which tokens in the current server block were loaded. func (h Helper) Caddyfiles() []string { diff --git a/caddyconfig/httpcaddyfile/handlers.go b/caddyconfig/httpcaddyfile/handlers.go deleted file mode 100644 index e133028..0000000 --- a/caddyconfig/httpcaddyfile/handlers.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2015 Matthew Holt and The Caddy Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package httpcaddyfile - -import ( - "encoding/json" - "fmt" - - "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" -) - -func (st *ServerType) parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]map[string]json.RawMessage, error) { - matchers := make(map[string]map[string]json.RawMessage) - for d.Next() { - definitionName := d.Val() - for nesting := d.Nesting(); d.NextBlock(nesting); { - matcherName := d.Val() - mod, err := caddy.GetModule("http.matchers." + matcherName) - if err != nil { - return nil, fmt.Errorf("getting matcher module '%s': %v", matcherName, err) - } - unm, ok := mod.New().(caddyfile.Unmarshaler) - if !ok { - return nil, fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName) - } - err = unm.UnmarshalCaddyfile(d.NewFromNextTokens()) - if err != nil { - return nil, err - } - rm, ok := unm.(caddyhttp.RequestMatcher) - if !ok { - return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) - } - if _, ok := matchers[definitionName]; !ok { - matchers[definitionName] = make(map[string]json.RawMessage) - } - matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil) - } - } - return matchers, nil -} diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 265cf73..794919c 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -58,6 +58,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, var val interface{} var err error disp := caddyfile.NewDispenser(segment) + // TODO: make this switch into a map switch dir { case "http_port": val, err = parseOptHTTPPort(disp) @@ -69,6 +70,10 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, val, err = parseOptExperimentalHTTP3(disp) case "storage": val, err = parseOptStorage(disp) + case "acme_ca": + val, err = parseOptACMECA(disp) + case "email": + val, err = parseOptEmail(disp) default: return nil, warnings, fmt.Errorf("unrecognized parameter name: %s", dir) } @@ -108,7 +113,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, // extract matcher definitions d := sb.block.DispenseDirective("matcher") - matcherDefs, err := st.parseMatcherDefinitions(d) + matcherDefs, err := parseMatcherDefinitions(d) if err != nil { return nil, warnings, err } @@ -122,6 +127,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, if dirFunc, ok := registeredDirectives[dir]; ok { results, err := dirFunc(Helper{ Dispenser: caddyfile.NewDispenser(segment), + options: options, warnings: &warnings, matcherDefs: matcherDefs, parentBlock: sb.block, @@ -166,7 +172,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, // now for the TLS app! (TODO: refactor into own func) tlsApp := caddytls.TLS{Certificates: make(map[string]json.RawMessage)} for _, p := range pairings { - for _, sblock := range p.serverBlocks { + for i, sblock := range p.serverBlocks { // tls automation policies if mmVals, ok := sblock.pile["tls.automation_manager"]; ok { for _, mmVal := range mmVals { @@ -175,10 +181,16 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, if err != nil { return nil, warnings, err } - tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{ - Hosts: sblockHosts, - ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID(), &warnings), - }) + if len(sblockHosts) > 0 { + tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{ + Hosts: sblockHosts, + ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID(), &warnings), + }) + } else { + warnings = append(warnings, caddyconfig.Warning{ + Message: fmt.Sprintf("Server block %d %v has no names that qualify for automatic HTTPS, so no TLS automation policy will be added.", i, sblock.block.Keys), + }) + } } } @@ -192,8 +204,25 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, } } } - // consolidate automation policies that are the exact same - tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies) + // if global ACME CA or email were set, append a catch-all automation + // policy that ensures they will be used if no tls directive was used + acmeCA, hasACMECA := options["acme_ca"] + email, hasEmail := options["email"] + if hasACMECA || hasEmail { + if tlsApp.Automation == nil { + tlsApp.Automation = new(caddytls.AutomationConfig) + } + tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{ + ManagementRaw: caddyconfig.JSONModuleObject(caddytls.ACMEManagerMaker{ + CA: acmeCA.(string), + Email: email.(string), + }, "module", "acme", &warnings), + }) + } + if tlsApp.Automation != nil { + // consolidate automation policies that are the exact same + tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies) + } // if experimental HTTP/3 is enabled, enable it on each server if enableH3, ok := options["experimental_http3"].(bool); ok && enableH3 { @@ -207,7 +236,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, if !reflect.DeepEqual(httpApp, caddyhttp.App{}) { cfg.AppsRaw["http"] = caddyconfig.JSON(httpApp, &warnings) } - if !reflect.DeepEqual(tlsApp, caddytls.TLS{}) { + if !reflect.DeepEqual(tlsApp, caddytls.TLS{Certificates: make(map[string]json.RawMessage)}) { cfg.AppsRaw["tls"] = caddyconfig.JSON(tlsApp, &warnings) } if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok { @@ -415,10 +444,10 @@ func consolidateAutomationPolicies(aps []caddytls.AutomationPolicy) []caddytls.A } if reflect.DeepEqual(aps[i].ManagementRaw, aps[j].ManagementRaw) { aps[i].Hosts = append(aps[i].Hosts, aps[j].Hosts...) + aps = append(aps[:j], aps[j+1:]...) + i-- + break } - aps = append(aps[:j], aps[j+1:]...) - i-- - break } } return aps @@ -531,6 +560,37 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([ return matcherSetsEnc, nil } +func parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]map[string]json.RawMessage, error) { + matchers := make(map[string]map[string]json.RawMessage) + for d.Next() { + definitionName := d.Val() + for nesting := d.Nesting(); d.NextBlock(nesting); { + matcherName := d.Val() + mod, err := caddy.GetModule("http.matchers." + matcherName) + if err != nil { + return nil, fmt.Errorf("getting matcher module '%s': %v", matcherName, err) + } + unm, ok := mod.New().(caddyfile.Unmarshaler) + if !ok { + return nil, fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName) + } + err = unm.UnmarshalCaddyfile(d.NewFromNextTokens()) + if err != nil { + return nil, err + } + rm, ok := unm.(caddyhttp.RequestMatcher) + if !ok { + return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) + } + if _, ok := matchers[definitionName]; !ok { + matchers[definitionName] = make(map[string]json.RawMessage) + } + matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil) + } + } + return matchers, nil +} + func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (map[string]json.RawMessage, error) { msEncoded := make(map[string]json.RawMessage) for matcherName, val := range matchers { diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index dadde28..a60d060 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -108,3 +108,27 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) { } return storage, nil } + +func parseOptACMECA(d *caddyfile.Dispenser) (string, error) { + d.Next() // consume parameter name + if !d.Next() { + return "", d.ArgErr() + } + val := d.Val() + if d.Next() { + return "", d.ArgErr() + } + return val, nil +} + +func parseOptEmail(d *caddyfile.Dispenser) (string, error) { + d.Next() // consume parameter name + if !d.Next() { + return "", d.ArgErr() + } + val := d.Val() + if d.Next() { + return "", d.ArgErr() + } + return val, nil +} -- cgit v1.2.3