From dc9f4f13fc1cc102c713c69ded295b3fcf685e96 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Mon, 11 May 2020 17:00:35 -0400 Subject: [PATCH] httpcaddyfile: Make global options pluggable (#3265) * httpcaddyfile: Make global options pluggable * httpcaddyfile: Add a global options adapt test * httpcaddyfile: Wrap err Co-Authored-By: Dave Henderson * httpcaddyfile: Revert wrap err Co-authored-by: Dave Henderson --- caddyconfig/httpcaddyfile/directives.go | 18 +++++ caddyconfig/httpcaddyfile/httptype.go | 39 +++------- caddyconfig/httpcaddyfile/options.go | 40 +++++++--- caddytest/integration/caddyfile_adapt_test.go | 74 +++++++++++++++++++ 4 files changed, 130 insertions(+), 41 deletions(-) diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index ac401e1f..15787724 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -120,6 +120,17 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) { }) } +// RegisterGlobalOption registers a unique global option opt with +// an associated unmarshaling (setup) function. When the global +// option opt is encountered in a Caddyfile, setupFunc will be +// called to unmarshal its tokens. +func RegisterGlobalOption(opt string, setupFunc UnmarshalGlobalFunc) { + if _, ok := registeredGlobalOptions[opt]; ok { + panic("global option " + opt + " already registered") + } + registeredGlobalOptions[opt] = setupFunc +} + // Helper is a type which helps setup a value from // Caddyfile tokens. type Helper struct { @@ -454,6 +465,13 @@ type ( // for you. These are passed to a call to // RegisterHandlerDirective. UnmarshalHandlerFunc func(h Helper) (caddyhttp.MiddlewareHandler, error) + + // UnmarshalGlobalFunc is a function which can unmarshal Caddyfile + // tokens into a global option config value using a Helper type. + // These are passed in a call to RegisterGlobalOption. + UnmarshalGlobalFunc func(d *caddyfile.Dispenser) (interface{}, error) ) var registeredDirectives = make(map[string]UnmarshalFunc) + +var registeredGlobalOptions = make(map[string]UnmarshalGlobalFunc) diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index aacaf913..378289c3 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -284,39 +284,18 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options var val interface{} var err error disp := caddyfile.NewDispenser(segment) - switch dir { - case "debug": - val = true - case "http_port": - val, err = parseOptHTTPPort(disp) - case "https_port": - val, err = parseOptHTTPSPort(disp) - case "default_sni": - val, err = parseOptSingleString(disp) - case "order": - val, err = parseOptOrder(disp) - case "experimental_http3": - val, err = parseOptExperimentalHTTP3(disp) - case "storage": - val, err = parseOptStorage(disp) - case "acme_ca", "acme_dns", "acme_ca_root": - val, err = parseOptSingleString(disp) - case "email": - val, err = parseOptSingleString(disp) - case "admin": - val, err = parseOptAdmin(disp) - case "on_demand_tls": - val, err = parseOptOnDemand(disp) - case "local_certs": - val = true - case "key_type": - val, err = parseOptSingleString(disp) - default: - return nil, fmt.Errorf("unrecognized parameter name: %s", dir) + + dirFunc, ok := registeredGlobalOptions[dir] + if !ok { + tkn := segment[0] + return nil, fmt.Errorf("%s:%d: unrecognized global option: %s", tkn.File, tkn.Line, dir) } + + val, err = dirFunc(disp) if err != nil { - return nil, fmt.Errorf("%s: %v", dir, err) + return nil, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err) } + options[dir] = val } diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index 2ab73b24..de288db9 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -23,7 +23,29 @@ import ( "github.com/caddyserver/caddy/v2/modules/caddytls" ) -func parseOptHTTPPort(d *caddyfile.Dispenser) (int, error) { +func init() { + RegisterGlobalOption("debug", parseOptTrue) + RegisterGlobalOption("http_port", parseOptHTTPPort) + RegisterGlobalOption("https_port", parseOptHTTPSPort) + RegisterGlobalOption("default_sni", parseOptSingleString) + RegisterGlobalOption("order", parseOptOrder) + RegisterGlobalOption("experimental_http3", parseOptTrue) + RegisterGlobalOption("storage", parseOptStorage) + RegisterGlobalOption("acme_ca", parseOptSingleString) + RegisterGlobalOption("acme_dns", parseOptSingleString) + RegisterGlobalOption("acme_ca_root", parseOptSingleString) + RegisterGlobalOption("email", parseOptSingleString) + RegisterGlobalOption("admin", parseOptAdmin) + RegisterGlobalOption("on_demand_tls", parseOptOnDemand) + RegisterGlobalOption("local_certs", parseOptTrue) + RegisterGlobalOption("key_type", parseOptSingleString) +} + +func parseOptTrue(d *caddyfile.Dispenser) (interface{}, error) { + return true, nil +} + +func parseOptHTTPPort(d *caddyfile.Dispenser) (interface{}, error) { var httpPort int for d.Next() { var httpPortStr string @@ -39,7 +61,7 @@ func parseOptHTTPPort(d *caddyfile.Dispenser) (int, error) { return httpPort, nil } -func parseOptHTTPSPort(d *caddyfile.Dispenser) (int, error) { +func parseOptHTTPSPort(d *caddyfile.Dispenser) (interface{}, error) { var httpsPort int for d.Next() { var httpsPortStr string @@ -55,11 +77,7 @@ func parseOptHTTPSPort(d *caddyfile.Dispenser) (int, error) { return httpsPort, nil } -func parseOptExperimentalHTTP3(d *caddyfile.Dispenser) (bool, error) { - return true, nil -} - -func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) { +func parseOptOrder(d *caddyfile.Dispenser) (interface{}, error) { newOrder := directiveOrder for d.Next() { @@ -135,7 +153,7 @@ func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) { return newOrder, nil } -func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) { +func parseOptStorage(d *caddyfile.Dispenser) (interface{}, error) { if !d.Next() { // consume option name return nil, d.ArgErr() } @@ -162,7 +180,7 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) { return storage, nil } -func parseOptSingleString(d *caddyfile.Dispenser) (string, error) { +func parseOptSingleString(d *caddyfile.Dispenser) (interface{}, error) { d.Next() // consume parameter name if !d.Next() { return "", d.ArgErr() @@ -174,7 +192,7 @@ func parseOptSingleString(d *caddyfile.Dispenser) (string, error) { return val, nil } -func parseOptAdmin(d *caddyfile.Dispenser) (string, error) { +func parseOptAdmin(d *caddyfile.Dispenser) (interface{}, error) { if d.Next() { var listenAddress string if !d.AllArgs(&listenAddress) { @@ -188,7 +206,7 @@ func parseOptAdmin(d *caddyfile.Dispenser) (string, error) { return "", nil } -func parseOptOnDemand(d *caddyfile.Dispenser) (*caddytls.OnDemandConfig, error) { +func parseOptOnDemand(d *caddyfile.Dispenser) (interface{}, error) { var ond *caddytls.OnDemandConfig for d.Next() { if d.NextArg() { diff --git a/caddytest/integration/caddyfile_adapt_test.go b/caddytest/integration/caddyfile_adapt_test.go index 514a5064..c2ad892d 100644 --- a/caddytest/integration/caddyfile_adapt_test.go +++ b/caddytest/integration/caddyfile_adapt_test.go @@ -415,3 +415,77 @@ func TestNotBlockMerging(t *testing.T) { } }`) } + +func TestGlobalOptions(t *testing.T) { + caddytest.AssertAdapt(t, ` + { + debug + http_port 8080 + https_port 8443 + default_sni localhost + order root first + storage file_system { + root /data + } + acme_ca https://example.com + acme_ca_root /path/to/ca.crt + email test@example.com + admin off + on_demand_tls { + ask https://example.com + interval 30s + burst 20 + } + local_certs + key_type ed25519 + } + + :80 + `, "caddyfile", `{ + "admin": { + "disabled": true + }, + "logging": { + "logs": { + "default": { + "level": "DEBUG" + } + } + }, + "storage": { + "module": "file_system", + "root": "/data" + }, + "apps": { + "http": { + "http_port": 8080, + "https_port": 8443, + "servers": { + "srv0": { + "listen": [ + ":80" + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "issuer": { + "module": "internal" + } + } + ], + "on_demand": { + "rate_limit": { + "interval": 30000000000, + "burst": 20 + }, + "ask": "https://example.com" + } + } + } + } +}`) +}