@marcel91200 / caddy Mirror · read-only / commits / 13781e67
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

caddytls: Support multiple issuers (#3862)

* caddytls: Support multiple issuers Defaults are Let's Encrypt and ZeroSSL. There are probably bugs. * Commit updated integration tests, d'oh * Update go.mod

Matt Holt committed Nov 16, 2020 at 11:05 UTC 13781e67ab1b2553598d0dd1a7153ce3cdbd4879
13 files changed +307 -234
caddyconfig/httpcaddyfile/builtins.go
+15 -28
@@ -88,7 +88,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
88 var certSelector caddytls.CustomCertSelectionPolicy
89 var acmeIssuer *caddytls.ACMEIssuer
90 var internalIssuer *caddytls.InternalIssuer
91 - var issuer certmagic.Issuer
91 + var issuers []certmagic.Issuer
92 var onDemand bool
93
94 for h.Next() {
@@ -297,10 +297,11 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
297 if err != nil {
298 return nil, err
299 }
300 - issuer, ok = unm.(certmagic.Issuer)
300 + issuer, ok := unm.(certmagic.Issuer)
301 if !ok {
302 return nil, h.Errf("module %s is not a certmagic.Issuer", mod.ID)
303 }
304 + issuers = append(issuers, issuer)
305
306 case "dns":
307 if !h.NextArg() {
@@ -371,42 +372,28 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
372 })
373 }
374
374 - // issuer
375 - if acmeIssuer != nil && internalIssuer != nil {
376 - // the logic to support this would be complex
377 - return nil, h.Err("cannot use both ACME and internal issuers in same server block")
375 + if len(issuers) > 0 && (acmeIssuer != nil || internalIssuer != nil) {
376 + // some tls subdirectives are shortcuts that implicitly configure issuers, and the
377 + // user can also configure issuers explicitly using the issuer subdirective; the
378 + // logic to support both would likely be complex, or at least unintuitive
379 + return nil, h.Err("cannot mix issuer subdirective (explicit issuers) with other issuer-specific subdirectives (implicit issuers)")
380 }
379 - if issuer != nil && (acmeIssuer != nil || internalIssuer != nil) {
380 - // similarly, the logic to support this would be complex
381 - return nil, h.Err("when defining an issuer, all its config must be in its block, rather than from separate tls subdirectives")
382 - }
383 - switch {
384 - case issuer != nil:
381 + for _, issuer := range issuers {
382 configVals = append(configVals, ConfigValue{
383 Class: "tls.cert_issuer",
384 Value: issuer,
385 })
389 -
390 - case internalIssuer != nil:
386 + }
387 + if acmeIssuer != nil {
388 configVals = append(configVals, ConfigValue{
389 Class: "tls.cert_issuer",
393 - Value: internalIssuer,
390 + Value: disambiguateACMEIssuer(acmeIssuer),
391 })
395 -
396 - case acmeIssuer != nil:
397 - // fill in global defaults, if configured
398 - if email := h.Option("email"); email != nil && acmeIssuer.Email == "" {
399 - acmeIssuer.Email = email.(string)
400 - }
401 - if acmeCA := h.Option("acme_ca"); acmeCA != nil && acmeIssuer.CA == "" {
402 - acmeIssuer.CA = acmeCA.(string)
403 - }
404 - if caPemFile := h.Option("acme_ca_root"); caPemFile != nil {
405 - acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, caPemFile.(string))
406 - }
392 + }
393 + if internalIssuer != nil {
394 configVals = append(configVals, ConfigValue{
395 Class: "tls.cert_issuer",
409 - Value: disambiguateACMEIssuer(acmeIssuer),
396 + Value: internalIssuer,
397 })
398 }
399
caddyconfig/httpcaddyfile/tlsapp.go
+125 -87
@@ -110,47 +110,43 @@ func (st ServerType) buildTLSApp(
110
111 // certificate issuers
112 if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
113 + var issuers []certmagic.Issuer
114 for _, issuerVal := range issuerVals {
114 - issuer := issuerVal.Value.(certmagic.Issuer)
115 - if ap == catchAllAP && !reflect.DeepEqual(ap.Issuer, issuer) {
116 - return nil, warnings, fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v", ap.Issuer, issuer)
117 - }
118 - ap.Issuer = issuer
115 + ap.Issuers = append(ap.Issuers, issuerVal.Value.(certmagic.Issuer))
116 + }
117 + if ap == catchAllAP && !reflect.DeepEqual(ap.Issuers, issuers) {
118 + return nil, warnings, fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v", ap.Issuers, issuers)
119 }
120 }
121
122 // custom bind host
123 for _, cfgVal := range sblock.pile["bind"] {
124 - // if an issuer was already configured and it is NOT an ACME
125 - // issuer, skip, since we intend to adjust only ACME issuers
126 - var acmeIssuer *caddytls.ACMEIssuer
127 - if ap.Issuer != nil {
128 - // ensure we include any issuer that embeds/wraps an underlying ACME issuer
129 - type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
130 - if acmeWrapper, ok := ap.Issuer.(acmeCapable); ok {
124 + for _, iss := range ap.Issuers {
125 + // if an issuer was already configured and it is NOT an ACME issuer,
126 + // skip, since we intend to adjust only ACME issuers; ensure we
127 + // include any issuer that embeds/wraps an underlying ACME issuer
128 + var acmeIssuer *caddytls.ACMEIssuer
129 + if acmeWrapper, ok := iss.(acmeCapable); ok {
130 acmeIssuer = acmeWrapper.GetACMEIssuer()
132 - } else {
133 - break
131 }
135 - }
132 + if acmeIssuer == nil {
133 + continue
134 + }
135
137 - // proceed to configure the ACME issuer's bind host, without
138 - // overwriting any existing settings
139 - if acmeIssuer == nil {
140 - acmeIssuer = new(caddytls.ACMEIssuer)
141 - }
142 - if acmeIssuer.Challenges == nil {
143 - acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
144 - }
145 - if acmeIssuer.Challenges.BindHost == "" {
146 - // only binding to one host is supported
147 - var bindHost string
148 - if bindHosts, ok := cfgVal.Value.([]string); ok && len(bindHosts) > 0 {
149 - bindHost = bindHosts[0]
136 + // proceed to configure the ACME issuer's bind host, without
137 + // overwriting any existing settings
138 + if acmeIssuer.Challenges == nil {
139 + acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
140 + }
141 + if acmeIssuer.Challenges.BindHost == "" {
142 + // only binding to one host is supported
143 + var bindHost string
144 + if bindHosts, ok := cfgVal.Value.([]string); ok && len(bindHosts) > 0 {
145 + bindHost = bindHosts[0]
146 + }
147 + acmeIssuer.Challenges.BindHost = bindHost
148 }
151 - acmeIssuer.Challenges.BindHost = bindHost
149 }
153 - ap.Issuer = acmeIssuer // we'll encode it later
150 }
151
152 // first make sure this block is allowed to create an automation policy;
@@ -188,7 +184,7 @@ func (st ServerType) buildTLSApp(
184 // that the internal names can use the internal issuer and
185 // the other names can use the default/public/ACME issuer
186 var ap2 *caddytls.AutomationPolicy
191 - if ap.Issuer == nil {
187 + if len(ap.Issuers) == 0 {
188 var internal, external []string
189 for _, s := range ap.Subjects {
190 if !certmagic.SubjectQualifiesForCert(s) {
@@ -212,7 +208,7 @@ func (st ServerType) buildTLSApp(
208 apCopy := *ap
209 ap2 = &apCopy
210 ap2.Subjects = internal
215 - ap2.IssuerRaw = caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)
211 + ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
212 }
213 }
214 if tlsApp.Automation == nil {
@@ -277,7 +273,7 @@ func (st ServerType) buildTLSApp(
273 // get internal certificates by default rather than ACME
274 var al caddytls.AutomateLoader
275 internalAP := &caddytls.AutomationPolicy{
280 - IssuerRaw: json.RawMessage(`{"module":"internal"}`),
276 + IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
277 }
278 for h := range hostsSharedWithHostlessKey {
279 al = append(al, h)
@@ -295,14 +291,48 @@ func (st ServerType) buildTLSApp(
291 tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, internalAP)
292 }
293
294 + // if there are any global options set for issuers (ACME ones in particular), make sure they
295 + // take effect in every automation policy that does not have any issuers
296 + if tlsApp.Automation != nil {
297 + globalEmail := options["email"]
298 + globalACMECA := options["acme_ca"]
299 + globalACMECARoot := options["acme_ca_root"]
300 + globalACMEDNS := options["acme_dns"]
301 + globalACMEEAB := options["acme_eab"]
302 + hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil
303 + if hasGlobalACMEDefaults {
304 + for _, ap := range tlsApp.Automation.Policies {
305 + if len(ap.Issuers) == 0 {
306 + acme, zerosslACME := new(caddytls.ACMEIssuer), new(caddytls.ACMEIssuer)
307 + zerossl := &caddytls.ZeroSSLIssuer{ACMEIssuer: zerosslACME}
308 + ap.Issuers = []certmagic.Issuer{acme, zerossl} // TODO: keep this in sync with Caddy's other issuer defaults elsewhere, like in caddytls/automation.go (DefaultIssuers).
309 +
310 + // if a non-ZeroSSL endpoint is specified, we assume we can't use the ZeroSSL issuer successfully
311 + if globalACMECA != nil && !strings.Contains(globalACMECA.(string), "zerossl") {
312 + ap.Issuers = []certmagic.Issuer{acme}
313 + }
314 + }
315 + }
316 + }
317 + }
318 +
319 // finalize and verify policies; do cleanup
320 if tlsApp.Automation != nil {
300 - // encode any issuer values we created, so they will be rendered in the output
301 - for _, ap := range tlsApp.Automation.Policies {
302 - if ap.Issuer != nil && ap.IssuerRaw == nil {
303 - // encode issuer now that it's all set up
304 - issuerName := ap.Issuer.(caddy.Module).CaddyModule().ID.Name()
305 - ap.IssuerRaw = caddyconfig.JSONModuleObject(ap.Issuer, "module", issuerName, &warnings)
321 + for i, ap := range tlsApp.Automation.Policies {
322 + // ensure all issuers have global defaults filled in
323 + for j, issuer := range ap.Issuers {
324 + err := fillInGlobalACMEDefaults(issuer, options)
325 + if err != nil {
326 + return nil, warnings, fmt.Errorf("filling in global issuer defaults for AP %d, issuer %d: %v", i, j, err)
327 + }
328 + }
329 +
330 + // encode all issuer values we created, so they will be rendered in the output
331 + if len(ap.Issuers) > 0 && ap.IssuersRaw == nil {
332 + for _, iss := range ap.Issuers {
333 + issuerName := iss.(caddy.Module).CaddyModule().ID.Name()
334 + ap.IssuersRaw = append(ap.IssuersRaw, caddyconfig.JSONModuleObject(iss, "module", issuerName, &warnings))
335 + }
336 }
337 }
338
@@ -334,6 +364,51 @@ func (st ServerType) buildTLSApp(
364 return tlsApp, warnings, nil
365 }
366
367 +type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
368 +
369 +func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]interface{}) error {
370 + acmeWrapper, ok := issuer.(acmeCapable)
371 + if !ok {
372 + return nil
373 + }
374 + acmeIssuer := acmeWrapper.GetACMEIssuer()
375 + if acmeIssuer == nil {
376 + return nil
377 + }
378 +
379 + globalEmail := options["email"]
380 + globalACMECA := options["acme_ca"]
381 + globalACMECARoot := options["acme_ca_root"]
382 + globalACMEDNS := options["acme_dns"]
383 + globalACMEEAB := options["acme_eab"]
384 +
385 + if globalEmail != nil && acmeIssuer.Email == "" {
386 + acmeIssuer.Email = globalEmail.(string)
387 + }
388 + if globalACMECA != nil && acmeIssuer.CA == "" {
389 + acmeIssuer.CA = globalACMECA.(string)
390 + }
391 + if globalACMECARoot != nil && !sliceContains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
392 + acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
393 + }
394 + if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
395 + provName := globalACMEDNS.(string)
396 + dnsProvModule, err := caddy.GetModule("dns.providers." + provName)
397 + if err != nil {
398 + return fmt.Errorf("getting DNS provider module named '%s': %v", provName, err)
399 + }
400 + acmeIssuer.Challenges = &caddytls.ChallengesConfig{
401 + DNS: &caddytls.DNSChallengeConfig{
402 + ProviderRaw: caddyconfig.JSONModuleObject(dnsProvModule.New(), "name", provName, nil),
403 + },
404 + }
405 + }
406 + if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
407 + acmeIssuer.ExternalAccount = globalACMEEAB.(*acme.EAB)
408 + }
409 + return nil
410 +}
411 +
412 // newBaseAutomationPolicy returns a new TLS automation policy that gets
413 // its values from the global options map. It should be used as the base
414 // for any other automation policies. A nil policy (and no error) will be
@@ -341,17 +416,10 @@ func (st ServerType) buildTLSApp(
416 // true, a non-nil value will always be returned (unless there is an error).
417 func newBaseAutomationPolicy(options map[string]interface{}, warnings []caddyconfig.Warning, always bool) (*caddytls.AutomationPolicy, error) {
418 issuer, hasIssuer := options["cert_issuer"]
344 -
345 - acmeCA, hasACMECA := options["acme_ca"]
346 - acmeCARoot, hasACMECARoot := options["acme_ca_root"]
347 - acmeDNS, hasACMEDNS := options["acme_dns"]
348 - acmeEAB, hasACMEEAB := options["acme_eab"]
349 -
350 - email, hasEmail := options["email"]
351 - localCerts, hasLocalCerts := options["local_certs"]
419 + _, hasLocalCerts := options["local_certs"]
420 keyType, hasKeyType := options["key_type"]
421
354 - hasGlobalAutomationOpts := hasIssuer || hasACMECA || hasACMECARoot || hasACMEDNS || hasACMEEAB || hasEmail || hasLocalCerts || hasKeyType
422 + hasGlobalAutomationOpts := hasIssuer || hasLocalCerts || hasKeyType
423
424 // if there are no global options related to automation policies
425 // set, then we can just return right away
@@ -363,48 +431,18 @@ func newBaseAutomationPolicy(options map[string]interface{}, warnings []caddycon
431 }
432
433 ap := new(caddytls.AutomationPolicy)
366 - if keyType != nil {
434 + if hasKeyType {
435 ap.KeyType = keyType.(string)
436 }
437
438 + if hasIssuer && hasLocalCerts {
439 + return nil, fmt.Errorf("global options are ambiguous: local_certs is confusing when combined with cert_issuer, because local_certs is also a specific kind of issuer")
440 + }
441 +
442 if hasIssuer {
371 - if hasACMECA || hasACMEDNS || hasACMEEAB || hasEmail || hasLocalCerts {
372 - return nil, fmt.Errorf("global options are ambiguous: cert_issuer is confusing when combined with acme_*, email, or local_certs options")
373 - }
374 - ap.Issuer = issuer.(certmagic.Issuer)
375 - } else if localCerts != nil {
376 - // internal issuer enabled trumps any ACME configurations; useful in testing
377 - ap.Issuer = new(caddytls.InternalIssuer) // we'll encode it later
378 - } else {
379 - if acmeCA == nil {
380 - acmeCA = ""
381 - }
382 - if email == nil {
383 - email = ""
384 - }
385 - mgr := &caddytls.ACMEIssuer{
386 - CA: acmeCA.(string),
387 - Email: email.(string),
388 - }
389 - if acmeDNS != nil {
390 - provName := acmeDNS.(string)
391 - dnsProvModule, err := caddy.GetModule("dns.providers." + provName)
392 - if err != nil {
393 - return nil, fmt.Errorf("getting DNS provider module named '%s': %v", provName, err)
394 - }
395 - mgr.Challenges = &caddytls.ChallengesConfig{
396 - DNS: &caddytls.DNSChallengeConfig{
397 - ProviderRaw: caddyconfig.JSONModuleObject(dnsProvModule.New(), "name", provName, &warnings),
398 - },
399 - }
400 - }
401 - if acmeCARoot != nil {
402 - mgr.TrustedRootsPEMFiles = []string{acmeCARoot.(string)}
403 - }
404 - if acmeEAB != nil {
405 - mgr.ExternalAccount = acmeEAB.(*acme.EAB)
406 - }
407 - ap.Issuer = disambiguateACMEIssuer(mgr) // we'll encode it later
443 + ap.Issuers = []certmagic.Issuer{issuer.(certmagic.Issuer)}
444 + } else if hasLocalCerts {
445 + ap.Issuers = []certmagic.Issuer{new(caddytls.InternalIssuer)}
446 }
447
448 return ap, nil
@@ -463,7 +501,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
501 // otherwise the one without any subjects (a catch-all) would be
502 // eaten up by the one with subjects; and if both have subjects, we
503 // need to combine their lists
466 - if bytes.Equal(aps[i].IssuerRaw, aps[j].IssuerRaw) &&
504 + if reflect.DeepEqual(aps[i].IssuersRaw, aps[j].IssuersRaw) &&
505 bytes.Equal(aps[i].StorageRaw, aps[j].StorageRaw) &&
506 aps[i].MustStaple == aps[j].MustStaple &&
507 aps[i].KeyType == aps[j].KeyType &&
caddytest/integration/caddyfile_adapt/global_options.txt
+5 -3
@@ -54,9 +54,11 @@
54 "automation": {
55 "policies": [
56 {
57 - "issuer": {
58 - "module": "internal"
59 - },
57 + "issuers": [
58 + {
59 + "module": "internal"
60 + }
61 + ],
62 "key_type": "ed25519"
63 }
64 ],
caddytest/integration/caddyfile_adapt/global_options_acme.txt
+14 -12
@@ -57,18 +57,20 @@
57 "automation": {
58 "policies": [
59 {
60 - "issuer": {
61 - "ca": "https://example.com",
62 - "email": "test@example.com",
63 - "external_account": {
64 - "key_id": "4K2scIVbBpNd-78scadB2g",
65 - "mac_key": "abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
66 - },
67 - "module": "acme",
68 - "trusted_roots_pem_files": [
69 - "/path/to/ca.crt"
70 - ]
71 - },
60 + "issuers": [
61 + {
62 + "ca": "https://example.com",
63 + "email": "test@example.com",
64 + "external_account": {
65 + "key_id": "4K2scIVbBpNd-78scadB2g",
66 + "mac_key": "abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
67 + },
68 + "module": "acme",
69 + "trusted_roots_pem_files": [
70 + "/path/to/ca.crt"
71 + ]
72 + }
73 + ],
74 "key_type": "ed25519"
75 }
76 ],
caddytest/integration/caddyfile_adapt/global_options_admin.txt
+5 -3
@@ -62,9 +62,11 @@
62 "automation": {
63 "policies": [
64 {
65 - "issuer": {
66 - "module": "internal"
67 - },
65 + "issuers": [
66 + {
67 + "module": "internal"
68 + }
69 + ],
70 "key_type": "ed25519"
71 }
72 ],
caddytest/integration/caddyfile_adapt/tls_automation_policies.txt
+15 -9
@@ -54,24 +54,30 @@ foo.tld, www.foo.tld {
54 "foo.tld",
55 "www.foo.tld"
56 ],
57 - "issuer": {
58 - "module": "internal"
59 - }
57 + "issuers": [
58 + {
59 + "module": "internal"
60 + }
61 + ]
62 },
63 {
64 "subjects": [
65 "*.*.tld",
66 "*.tld"
67 ],
66 - "issuer": {
67 - "module": "internal"
68 - },
68 + "issuers": [
69 + {
70 + "module": "internal"
71 + }
72 + ],
73 "on_demand": true
74 },
75 {
72 - "issuer": {
73 - "module": "internal"
74 - }
76 + "issuers": [
77 + {
78 + "module": "internal"
79 + }
80 + ]
81 }
82 ]
83 }
go.mod
+1 -1
@@ -6,7 +6,7 @@ require (
6 github.com/Masterminds/sprig/v3 v3.1.0
7 github.com/alecthomas/chroma v0.8.0
8 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a
9 - github.com/caddyserver/certmagic v0.12.1-0.20201112220015-12df2be5a021
9 + github.com/caddyserver/certmagic v0.12.1-0.20201116175341-0f8a9f688760
10 github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
11 github.com/go-chi/chi v4.1.2+incompatible
12 github.com/google/cel-go v0.5.1
go.sum
+2 -2
@@ -85,8 +85,8 @@ github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTK
85 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
86 github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
87 github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
88 -github.com/caddyserver/certmagic v0.12.1-0.20201112220015-12df2be5a021 h1:VY4JahIicW/GT84KoIm+TSKwMHj08NyPJhsGypTWJn4=
89 -github.com/caddyserver/certmagic v0.12.1-0.20201112220015-12df2be5a021/go.mod h1:tr26xh+9fY5dN0J6IPAlMj07qpog22PJKa7Nw7j835U=
88 +github.com/caddyserver/certmagic v0.12.1-0.20201116175341-0f8a9f688760 h1:h7KGtOb9TAfZp2/KwPd9iyqiLVZMWbpx5Mu0Her2iRw=
89 +github.com/caddyserver/certmagic v0.12.1-0.20201116175341-0f8a9f688760/go.mod h1:tr26xh+9fY5dN0J6IPAlMj07qpog22PJKa7Nw7j835U=
90 github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
91 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
92 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
modules/caddyhttp/autohttps.go
+40 -21
@@ -241,7 +241,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
241 // we now have a list of all the unique names for which we need certs;
242 // turn the set into a slice so that phase 2 can use it
243 app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts))
244 - var internal, external []string
244 + var internal []string
245 uniqueDomainsLoop:
246 for d := range uniqueDomainsForCerts {
247 // whether or not there is already an automation policy for this
@@ -264,15 +264,13 @@ uniqueDomainsLoop:
264
265 // if no automation policy exists for the name yet, we
266 // will associate it with an implicit one
267 - if certmagic.SubjectQualifiesForPublicCert(d) {
268 - external = append(external, d)
269 - } else {
267 + if !certmagic.SubjectQualifiesForPublicCert(d) {
268 internal = append(internal, d)
269 }
270 }
271
272 // ensure there is an automation policy to handle these certs
275 - err := app.createAutomationPolicies(ctx, external, internal)
273 + err := app.createAutomationPolicies(ctx, internal)
274 if err != nil {
275 return err
276 }
@@ -430,7 +428,7 @@ redirServersLoop:
428 // automation policy exists, it will be shallow-copied and used as the
429 // base for the new ones (this is important for preserving behavior the
430 // user intends to be "defaults").
433 -func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, internalNames []string) error {
431 +func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []string) error {
432 // before we begin, loop through the existing automation policies
433 // and, for any ACMEIssuers we find, make sure they're filled in
434 // with default values that might be specified in our HTTP app; also
@@ -447,16 +445,23 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
445 // set up default issuer -- honestly, this is only
446 // really necessary because the HTTP app is opinionated
447 // and has settings which could be inferred as new
450 - // defaults for the ACMEIssuer in the TLS app
451 - if ap.Issuer == nil {
452 - ap.Issuer = new(caddytls.ACMEIssuer)
453 - }
454 - if acmeIssuer, ok := ap.Issuer.(acmeCapable); ok {
455 - err := app.fillInACMEIssuer(acmeIssuer.GetACMEIssuer())
448 + // defaults for the ACMEIssuer in the TLS app (such as
449 + // what the HTTP and HTTPS ports are)
450 + if ap.Issuers == nil {
451 + var err error
452 + ap.Issuers, err = caddytls.DefaultIssuers(ctx)
453 if err != nil {
454 return err
455 }
456 }
457 + for _, iss := range ap.Issuers {
458 + if acmeIssuer, ok := iss.(acmeCapable); ok {
459 + err := app.fillInACMEIssuer(acmeIssuer.GetACMEIssuer())
460 + if err != nil {
461 + return err
462 + }
463 + }
464 + }
465
466 // while we're here, is this the catch-all/base policy?
467 if !foundBasePolicy && len(ap.Subjects) == 0 {
@@ -471,11 +476,14 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
476 }
477
478 // if the basePolicy has an existing ACMEIssuer (particularly to
474 - // include any type that embeds/wraps an ACMEIssuer), let's use it,
475 - // otherwise we'll make one
479 + // include any type that embeds/wraps an ACMEIssuer), let's use it
480 + // (I guess we just use the first one?), otherwise we'll make one
481 var baseACMEIssuer *caddytls.ACMEIssuer
477 - if acmeWrapper, ok := basePolicy.Issuer.(acmeCapable); ok {
478 - baseACMEIssuer = acmeWrapper.GetACMEIssuer()
482 + for _, iss := range basePolicy.Issuers {
483 + if acmeWrapper, ok := iss.(acmeCapable); ok {
484 + baseACMEIssuer = acmeWrapper.GetACMEIssuer()
485 + break
486 + }
487 }
488 if baseACMEIssuer == nil {
489 // note that this happens if basePolicy.Issuer is nil
@@ -485,7 +493,7 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
493
494 // if there was a base policy to begin with, we already
495 // filled in its issuer's defaults; if there wasn't, we
488 - // stil need to do that
496 + // still need to do that
497 if !foundBasePolicy {
498 err := app.fillInACMEIssuer(baseACMEIssuer)
499 if err != nil {
@@ -494,8 +502,20 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
502 }
503
504 // never overwrite any other issuer that might already be configured
497 - if basePolicy.Issuer == nil {
498 - basePolicy.Issuer = baseACMEIssuer
505 + if basePolicy.Issuers == nil {
506 + var err error
507 + basePolicy.Issuers, err = caddytls.DefaultIssuers(ctx)
508 + if err != nil {
509 + return err
510 + }
511 + for _, iss := range basePolicy.Issuers {
512 + if acmeIssuer, ok := iss.(acmeCapable); ok {
513 + err := app.fillInACMEIssuer(acmeIssuer.GetACMEIssuer())
514 + if err != nil {
515 + return err
516 + }
517 + }
518 + }
519 }
520
521 if !foundBasePolicy {
@@ -549,8 +569,7 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
569 // of names that would normally use the production API;
570 // anyway, that gets into the weeds a bit...
571 newPolicy.Subjects = internalNames
552 - newPolicy.Issuer = internalIssuer
553 -
572 + newPolicy.Issuers = []certmagic.Issuer{internalIssuer}
573 err := app.tlsApp.AddAutomationPolicy(newPolicy)
574 if err != nil {
575 return err
modules/caddytls/acmeissuer.go
+9
@@ -97,6 +97,15 @@ func (ACMEIssuer) CaddyModule() caddy.ModuleInfo {
97 func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
98 iss.logger = ctx.Logger(iss)
99
100 + // expand email address, if non-empty
101 + if iss.Email != "" {
102 + email, err := caddy.NewReplacer().ReplaceOrErr(iss.Email, true, true)
103 + if err != nil {
104 + return fmt.Errorf("expanding email address '%s': %v", iss.Email, err)
105 + }
106 + iss.Email = email
107 + }
108 +
109 // DNS providers
110 if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.ProviderRaw != nil {
111 val, err := ctx.LoadModule(iss.Challenges.DNS, "ProviderRaw")
modules/caddytls/automation.go
+50 -34
@@ -23,7 +23,6 @@ import (
23 "github.com/caddyserver/caddy/v2"
24 "github.com/caddyserver/certmagic"
25 "github.com/mholt/acmez"
26 - "go.uber.org/zap"
26 )
27
28 // AutomationConfig governs the automated management of TLS certificates.
@@ -72,8 +71,13 @@ type AutomationPolicy struct {
71 // Which subjects (hostnames or IP addresses) this policy applies to.
72 Subjects []string `json:"subjects,omitempty"`
73
75 - // The module that will issue certificates. Default: internal if all
76 - // subjects do not qualify for public certificates; othewise acme.
74 + // The modules that may issue certificates. Default: internal if all
75 + // subjects do not qualify for public certificates; othewise acme and
76 + // zerossl.
77 + IssuersRaw []json.RawMessage `json:"issuers,omitempty" caddy:"namespace=tls.issuance inline_key=module"`
78 +
79 + // DEPRECATED: Use `issuers` instead (November 2020). This field will
80 + // be removed in the future.
81 IssuerRaw json.RawMessage `json:"issuer,omitempty" caddy:"namespace=tls.issuance inline_key=module"`
82
83 // If true, certificates will be requested with MustStaple. Not all
@@ -103,10 +107,10 @@ type AutomationPolicy struct {
107 // load.
108 OnDemand bool `json:"on_demand,omitempty"`
109
106 - // Issuer stores the decoded issuer parameters. This is only
107 - // used to populate an underlying certmagic.Config's Issuer
110 + // Issuers stores the decoded issuer parameters. This is only
111 + // used to populate an underlying certmagic.Config's Issuers
112 // field; it is not referenced thereafter.
109 - Issuer certmagic.Issuer `json:"-"`
113 + Issuers []certmagic.Issuer `json:"-"`
114
115 magic *certmagic.Config
116 storage certmagic.Storage
@@ -150,34 +154,30 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
154 }
155 }
156
153 - // if this automation policy has no Issuer defined, and
154 - // none of the subjects qualify for a public certificate,
155 - // set the issuer to internal so that these names can all
156 - // get certificates; critically, we can only do this if an
157 - // issuer is not explicitly configured (IssuerRaw, vs. just
158 - // Issuer) AND if the list of subjects is non-empty
159 - if ap.IssuerRaw == nil && len(ap.Subjects) > 0 {
160 - var anyPublic bool
161 - for _, s := range ap.Subjects {
162 - if certmagic.SubjectQualifiesForPublicCert(s) {
163 - anyPublic = true
164 - break
165 - }
157 + // TODO: IssuerRaw field deprecated as of November 2020 - remove this shim after deprecation is complete
158 + if ap.IssuerRaw != nil {
159 + tlsApp.logger.Warn("the 'issuer' field is deprecated and will be removed in the future; use 'issuers' instead; your issuer has been appended automatically for now")
160 + ap.IssuersRaw = append(ap.IssuersRaw, ap.IssuerRaw)
161 + }
162 +
163 + // load and provision any explicitly-configured issuer modules
164 + if ap.IssuersRaw != nil {
165 + val, err := tlsApp.ctx.LoadModule(ap, "IssuersRaw")
166 + if err != nil {
167 + return fmt.Errorf("loading TLS automation management module: %s", err)
168 }
167 - if !anyPublic {
168 - tlsApp.logger.Info("setting internal issuer for automation policy that has only internal subjects but no issuer configured",
169 - zap.Strings("subjects", ap.Subjects))
170 - ap.IssuerRaw = json.RawMessage(`{"module":"internal"}`)
169 + for _, issVal := range val.([]interface{}) {
170 + ap.Issuers = append(ap.Issuers, issVal.(certmagic.Issuer))
171 }
172 }
173
174 - // load and provision any explicitly-configured issuer module
175 - if ap.IssuerRaw != nil {
176 - val, err := tlsApp.ctx.LoadModule(ap, "IssuerRaw")
174 + issuers := ap.Issuers
175 + if len(issuers) == 0 {
176 + var err error
177 + issuers, err = DefaultIssuers(tlsApp.ctx)
178 if err != nil {
178 - return fmt.Errorf("loading TLS automation management module: %s", err)
179 + return err
180 }
180 - ap.Issuer = val.(certmagic.Issuer)
181 }
182
183 keyType := ap.KeyType
@@ -206,12 +206,9 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
206 KeySource: keySource,
207 OnDemand: ond,
208 Storage: storage,
209 - Issuer: ap.Issuer, // if nil, certmagic.New() will create one
209 + Issuers: issuers,
210 Logger: tlsApp.logger,
211 }
212 - if rev, ok := ap.Issuer.(certmagic.Revoker); ok {
213 - template.Revoker = rev
214 - }
212 ap.magic = certmagic.New(tlsApp.certCache, template)
213
214 // sometimes issuers may need the parent certmagic.Config in
@@ -219,13 +216,32 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
216 // access to the correct storage and cache so it can solve
217 // ACME challenges -- it's an annoying, inelegant circular
218 // dependency that I don't know how to resolve nicely!)
222 - if annoying, ok := ap.Issuer.(ConfigSetter); ok {
223 - annoying.SetConfig(ap.magic)
219 + for _, issuer := range ap.magic.Issuers {
220 + if annoying, ok := issuer.(ConfigSetter); ok {
221 + annoying.SetConfig(ap.magic)
222 + }
223 }
224
225 return nil
226 }
227
228 +// DefaultIssuers returns empty but provisioned default Issuers.
229 +// This function is experimental and has no compatibility promises.
230 +func DefaultIssuers(ctx caddy.Context) ([]certmagic.Issuer, error) {
231 + acme := new(ACMEIssuer)
232 + err := acme.Provision(ctx)
233 + if err != nil {
234 + return nil, err
235 + }
236 + zerossl := new(ZeroSSLIssuer)
237 + err = zerossl.Provision(ctx)
238 + if err != nil {
239 + return nil, err
240 + }
241 + // TODO: eventually, insert ZeroSSL into first position in the slice -- see also httpcaddyfile/tlsapp.go for where similar defaults are configured
242 + return []certmagic.Issuer{acme, zerossl}, nil
243 +}
244 +
245 // ChallengesConfig configures the ACME challenges.
246 type ChallengesConfig struct {
247 // HTTP configures the ACME HTTP challenge. This
modules/caddytls/tls.go
+11 -9
@@ -137,7 +137,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
137 continue
138 }
139 t.Automation.defaultInternalAutomationPolicy = &AutomationPolicy{
140 - IssuerRaw: json.RawMessage(`{"module":"internal"}`),
140 + IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
141 }
142 err = t.Automation.defaultInternalAutomationPolicy.Provision(t)
143 if err != nil {
@@ -303,20 +303,22 @@ func (t *TLS) Manage(names []string) error {
303
304 // HandleHTTPChallenge ensures that the HTTP challenge is handled for the
305 // certificate named by r.Host, if it is an HTTP challenge request. It
306 -// requires that the automation policy for r.Host has an issue of type
307 -// *certmagic.ACMEManager.
306 +// requires that the automation policy for r.Host has an issuer of type
307 +// *certmagic.ACMEManager, or one that is ACME-enabled (GetACMEIssuer()).
308 func (t *TLS) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
309 if !certmagic.LooksLikeHTTPChallenge(r) {
310 return false
311 }
312 + // try all the issuers until we find the one that initiated the challenge
313 ap := t.getAutomationPolicyForName(r.Host)
313 - if ap.magic.Issuer == nil {
314 - return false
315 - }
314 type acmeCapable interface{ GetACMEIssuer() *ACMEIssuer }
317 - if am, ok := ap.magic.Issuer.(acmeCapable); ok {
318 - iss := am.GetACMEIssuer()
319 - return certmagic.NewACMEManager(iss.magic, iss.template).HandleHTTPChallenge(w, r)
315 + for _, iss := range ap.magic.Issuers {
316 + if am, ok := iss.(acmeCapable); ok {
317 + iss := am.GetACMEIssuer()
318 + if certmagic.NewACMEManager(iss.magic, iss.template).HandleHTTPChallenge(w, r) {
319 + return true
320 + }
321 + }
322 }
323 return false
324 }
modules/caddytls/zerosslissuer.go
+15 -25
@@ -59,16 +59,13 @@ func (*ZeroSSLIssuer) CaddyModule() caddy.ModuleInfo {
59 // Provision sets up iss.
60 func (iss *ZeroSSLIssuer) Provision(ctx caddy.Context) error {
61 iss.logger = ctx.Logger(iss)
62 -
62 if iss.ACMEIssuer == nil {
63 iss.ACMEIssuer = new(ACMEIssuer)
64 }
66 - err := iss.ACMEIssuer.Provision(ctx)
67 - if err != nil {
68 - return err
65 + if iss.ACMEIssuer.CA == "" {
66 + iss.ACMEIssuer.CA = certmagic.ZeroSSLProductionCA
67 }
70 -
71 - return nil
68 + return iss.ACMEIssuer.Provision(ctx)
69 }
70
71 func (iss *ZeroSSLIssuer) newAccountCallback(ctx context.Context, am *certmagic.ACMEManager, _ acme.Account) error {
@@ -86,26 +83,22 @@ func (iss *ZeroSSLIssuer) generateEABCredentials(ctx context.Context) (*acme.EAB
83
84 // there are two ways to generate EAB credentials: authenticated with
85 // their API key, or unauthenticated with their email address
89 - switch {
90 - case iss.APIKey != "":
86 + if iss.APIKey != "" {
87 apiKey := caddy.NewReplacer().ReplaceAll(iss.APIKey, "")
88 if apiKey == "" {
89 return nil, fmt.Errorf("missing API key: '%v'", iss.APIKey)
90 }
91 qs := url.Values{"access_key": []string{apiKey}}
92 endpoint = fmt.Sprintf("%s/eab-credentials?%s", zerosslAPIBase, qs.Encode())
97 -
98 - case iss.Email != "":
99 - email := caddy.NewReplacer().ReplaceAll(iss.Email, "")
93 + } else {
94 + email := iss.Email
95 if email == "" {
101 - return nil, fmt.Errorf("missing email: '%v'", iss.Email)
96 + iss.logger.Warn("missing email address for ZeroSSL; it is strongly recommended to set one for next time")
97 + email = "caddy@zerossl.com" // special email address that preserves backwards-compat, but which black-holes dashboard features, oh well
98 }
99 endpoint = zerosslAPIBase + "/eab-credentials-email"
100 form := url.Values{"email": []string{email}}
101 body = strings.NewReader(form.Encode())
106 -
107 - default:
108 - return nil, fmt.Errorf("must configure either an API key or email address to use ZeroSSL without explicit EAB")
102 }
103
104 req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
@@ -161,9 +154,6 @@ func (iss *ZeroSSLIssuer) generateEABCredentials(ctx context.Context) (*acme.EAB
154 func (iss *ZeroSSLIssuer) initialize() {
155 iss.mu.Lock()
156 defer iss.mu.Unlock()
164 - if iss.template.CA == "" {
165 - iss.template.CA = zerosslACMEDirectory
166 - }
157 if iss.template.NewAccountFunc == nil {
158 iss.template.NewAccountFunc = iss.newAccountCallback
159 }
@@ -195,15 +185,18 @@ func (iss *ZeroSSLIssuer) Revoke(ctx context.Context, cert certmagic.Certificate
185
186 // UnmarshalCaddyfile deserializes Caddyfile tokens into iss.
187 //
198 -// ... zerossl <api_key> {
188 +// ... zerossl [<api_key>] {
189 // ...
190 // }
191 //
192 // Any of the subdirectives for the ACME issuer can be used in the block.
193 func (iss *ZeroSSLIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
194 for d.Next() {
205 - if !d.AllArgs(&iss.APIKey) {
206 - return d.ArgErr()
195 + if d.NextArg() {
196 + iss.APIKey = d.Val()
197 + if d.NextArg() {
198 + return d.ArgErr()
199 + }
200 }
201
202 if iss.ACMEIssuer == nil {
@@ -217,10 +210,7 @@ func (iss *ZeroSSLIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
210 return nil
211 }
212
220 -const (
221 - zerosslACMEDirectory = "https://acme.zerossl.com/v2/DV90"
222 - zerosslAPIBase = "https://api.zerossl.com/acme"
223 -)
213 +const zerosslAPIBase = "https://api.zerossl.com/acme"
214
215 // Interface guards
216 var (