From 1e92258dd670dc62a55b100d1e68e7f482da14a1 Mon Sep 17 00:00:00 2001 From: Klooven Date: Tue, 8 Jun 2021 23:10:37 +0300 Subject: httpcaddyfile: Add `preferred_chains` global option and issuer subdirective (#4192) * Added preferred_chains option to Caddyfile * Caddyfile adapt tests for preferred_chains --- caddyconfig/httpcaddyfile/options.go | 6 +++ caddyconfig/httpcaddyfile/tlsapp.go | 7 ++- .../global_options_preferred_chains.txt | 56 +++++++++++++++++++ .../caddyfile_adapt/tls_acme_preferred_chains.txt | 57 ++++++++++++++++++++ modules/caddytls/acmeissuer.go | 62 ++++++++++++++++++++++ 5 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 caddytest/integration/caddyfile_adapt/global_options_preferred_chains.txt create mode 100644 caddytest/integration/caddyfile_adapt/tls_acme_preferred_chains.txt diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index fe8e319..f693110 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -49,6 +49,7 @@ func init() { RegisterGlobalOption("servers", parseServerOptions) RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions) RegisterGlobalOption("log", parseLogOptions) + RegisterGlobalOption("preferred_chains", parseOptPreferredChains) } func parseOptTrue(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) { return true, nil } @@ -452,3 +453,8 @@ func parseLogOptions(d *caddyfile.Dispenser, existingVal interface{}) (interface return configValues, nil } + +func parseOptPreferredChains(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) { + d.Next() + return caddytls.ParseCaddyfilePreferredChainsOptions(d) +} diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go index 2510a9b..b7a8f02 100644 --- a/caddyconfig/httpcaddyfile/tlsapp.go +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -321,7 +321,8 @@ func (st ServerType) buildTLSApp( globalACMECARoot := options["acme_ca_root"] globalACMEDNS := options["acme_dns"] globalACMEEAB := options["acme_eab"] - hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil + globalPreferredChains := options["preferred_chains"] + hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil if hasGlobalACMEDefaults { for _, ap := range tlsApp.Automation.Policies { if len(ap.Issuers) == 0 { @@ -405,6 +406,7 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]interf globalACMECARoot := options["acme_ca_root"] globalACMEDNS := options["acme_dns"] globalACMEEAB := options["acme_eab"] + globalPreferredChains := options["preferred_chains"] if globalEmail != nil && acmeIssuer.Email == "" { acmeIssuer.Email = globalEmail.(string) @@ -425,6 +427,9 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]interf if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil { acmeIssuer.ExternalAccount = globalACMEEAB.(*acme.EAB) } + if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil { + acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference) + } return nil } diff --git a/caddytest/integration/caddyfile_adapt/global_options_preferred_chains.txt b/caddytest/integration/caddyfile_adapt/global_options_preferred_chains.txt new file mode 100644 index 0000000..893b34b --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/global_options_preferred_chains.txt @@ -0,0 +1,56 @@ +{ + preferred_chains smallest +} + +localhost +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "localhost" + ], + "issuers": [ + { + "module": "acme", + "preferred_chains": { + "smallest": true + } + }, + { + "module": "zerossl", + "preferred_chains": { + "smallest": true + } + } + ] + } + ] + } + } + } +} diff --git a/caddytest/integration/caddyfile_adapt/tls_acme_preferred_chains.txt b/caddytest/integration/caddyfile_adapt/tls_acme_preferred_chains.txt new file mode 100644 index 0000000..d6242d7 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/tls_acme_preferred_chains.txt @@ -0,0 +1,57 @@ +localhost + +tls { + issuer acme { + preferred_chains { + any_common_name "Generic CA 1" "Generic CA 2" + } + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "localhost" + ], + "issuers": [ + { + "module": "acme", + "preferred_chains": { + "any_common_name": [ + "Generic CA 1", + "Generic CA 2" + ] + } + } + ] + } + ] + } + } + } +} diff --git a/modules/caddytls/acmeissuer.go b/modules/caddytls/acmeissuer.go index 6085044..b60e560 100644 --- a/modules/caddytls/acmeissuer.go +++ b/modules/caddytls/acmeissuer.go @@ -265,6 +265,10 @@ func (iss *ACMEIssuer) GetACMEIssuer() *ACMEIssuer { return iss } // trusted_roots // dns [] // resolvers +// preferred_chains [smallest] { +// root_common_name +// any_common_name +// } // } // func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { @@ -416,6 +420,13 @@ func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return d.ArgErr() } + case "preferred_chains": + chainPref, err := ParseCaddyfilePreferredChainsOptions(d) + if err != nil { + return err + } + iss.PreferredChains = chainPref + default: return d.Errf("unrecognized ACME issuer property: %s", d.Val()) } @@ -452,6 +463,57 @@ func onDemandAskRequest(ask string, name string) error { return nil } +func ParseCaddyfilePreferredChainsOptions(d *caddyfile.Dispenser) (*ChainPreference, error) { + chainPref := new(ChainPreference) + if d.NextArg() { + smallestOpt := d.Val() + if smallestOpt == "smallest" { + trueBool := true + chainPref.Smallest = &trueBool + if d.NextArg() { // Only one argument allowed + return nil, d.ArgErr() + } + if d.NextBlock(d.Nesting()) { // Don't allow other options when smallest == true + return nil, d.Err("No more options are accepted when using the 'smallest' option") + } + } else { // Smallest option should always be 'smallest' or unset + return nil, d.Errf("Invalid argument '%s'", smallestOpt) + } + } + for nesting := d.Nesting(); d.NextBlock(nesting); { + switch d.Val() { + case "root_common_name": + rootCommonNameOpt := d.RemainingArgs() + chainPref.RootCommonName = rootCommonNameOpt + if rootCommonNameOpt == nil { + return nil, d.ArgErr() + } + if chainPref.AnyCommonName != nil { + return nil, d.Err("Can't set root_common_name when any_common_name is already set") + } + + case "any_common_name": + anyCommonNameOpt := d.RemainingArgs() + chainPref.AnyCommonName = anyCommonNameOpt + if anyCommonNameOpt == nil { + return nil, d.ArgErr() + } + if chainPref.RootCommonName != nil { + return nil, d.Err("Can't set any_common_name when root_common_name is already set") + } + + default: + return nil, d.Errf("Received unrecognized parameter '%s'", d.Val()) + } + } + + if chainPref.Smallest == nil && chainPref.RootCommonName == nil && chainPref.AnyCommonName == nil { + return nil, d.Err("No options for preferred_chains received") + } + + return chainPref, nil +} + // ChainPreference describes the client's preferred certificate chain, // useful if the CA offers alternate chains. The first matching chain // will be selected. -- cgit v1.2.3