@marcel91200 / caddy Mirror · read-only / commits / 96919acc
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 13, 01:45 UTC

caddyhttp: Refactor cert Managers (fix #5415) (#5533)

Matt Holt committed May 15, 2023 at 10:47 UTC 96919acc9d583ef11ea1f9c72a9991fb3f8aab9f
6 files changed +84 -77
caddyconfig/httpcaddyfile/tlsapp.go
+4
@@ -218,6 +218,10 @@ func (st ServerType) buildTLSApp(
218 if len(ap.Issuers) == 0 {
219 var internal, external []string
220 for _, s := range ap.SubjectsRaw {
221 + // do not create Issuers for Tailscale domains; they will be given a Manager instead
222 + if strings.HasSuffix(strings.ToLower(s), ".ts.net") {
223 + continue
224 + }
225 if !certmagic.SubjectQualifiesForCert(s) {
226 return nil, warnings, fmt.Errorf("subject does not qualify for certificate: '%s'", s)
227 }
go.mod
+1 -1
@@ -7,7 +7,7 @@ require (
7 github.com/Masterminds/sprig/v3 v3.2.3
8 github.com/alecthomas/chroma/v2 v2.7.0
9 github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
10 - github.com/caddyserver/certmagic v0.17.3-0.20230510193943-53140d52202c
10 + github.com/caddyserver/certmagic v0.17.3-0.20230511183644-8728b186fa68
11 github.com/dustin/go-humanize v1.0.1
12 github.com/go-chi/chi v4.1.2+incompatible
13 github.com/google/cel-go v0.14.0
go.sum
+2 -2
@@ -97,8 +97,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
97 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
98 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
99 github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
100 -github.com/caddyserver/certmagic v0.17.3-0.20230510193943-53140d52202c h1:pEMS0l8kE/5xxrncv+Qq81fzr29R+zk++E7KAYiyBe4=
101 -github.com/caddyserver/certmagic v0.17.3-0.20230510193943-53140d52202c/go.mod h1:e0YLTnXIopZ05bBWCLzpIf1Yvk27Q90FGUmGowFRDY8=
100 +github.com/caddyserver/certmagic v0.17.3-0.20230511183644-8728b186fa68 h1:B3ZszrtQ3ksok0Cby1mGT/4T513vxXpOUCf5862c/NQ=
101 +github.com/caddyserver/certmagic v0.17.3-0.20230511183644-8728b186fa68/go.mod h1:e0YLTnXIopZ05bBWCLzpIf1Yvk27Q90FGUmGowFRDY8=
102 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
103 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
104 github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
modules/caddyhttp/autohttps.go
+63 -52
@@ -83,6 +83,8 @@ func (ahc AutoHTTPSConfig) Skipped(name string, skipSlice []string) bool {
83 // even servers to the app, which still need to be set up with the
84 // rest of them during provisioning.
85 func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) error {
86 + logger := app.logger.Named("auto_https")
87 +
88 // this map acts as a set to store the domain names
89 // for which we will manage certificates automatically
90 uniqueDomainsForCerts := make(map[string]struct{})
@@ -114,13 +116,13 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
116 srv.AutoHTTPS = new(AutoHTTPSConfig)
117 }
118 if srv.AutoHTTPS.Disabled {
117 - app.logger.Warn("automatic HTTPS is completely disabled for server", zap.String("server_name", srvName))
119 + logger.Warn("automatic HTTPS is completely disabled for server", zap.String("server_name", srvName))
120 continue
121 }
122
123 // skip if all listeners use the HTTP port
124 if !srv.listenersUseAnyPortOtherThan(app.httpPort()) {
123 - app.logger.Warn("server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server",
125 + logger.Warn("server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server",
126 zap.String("server_name", srvName),
127 zap.Int("http_port", app.httpPort()),
128 )
@@ -134,7 +136,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
136 // needing to specify one empty policy to enable it
137 if srv.TLSConnPolicies == nil &&
138 !srv.listenersUseAnyPortOtherThan(app.httpsPort()) {
137 - app.logger.Info("server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS",
139 + logger.Info("server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS",
140 zap.String("server_name", srvName),
141 zap.Int("https_port", app.httpsPort()),
142 )
@@ -186,14 +188,9 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
188 // a deduplicated list of names for which to obtain certs
189 // (only if cert management not disabled for this server)
190 if srv.AutoHTTPS.DisableCerts {
189 - app.logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
191 + logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
192 } else {
193 for d := range serverDomainSet {
192 - // the implicit Tailscale manager module will get its own certs at run-time
193 - if isTailscaleDomain(d) {
194 - continue
195 - }
196 -
194 if certmagic.SubjectQualifiesForCert(d) &&
195 !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
196 // if a certificate for this name is already loaded,
@@ -201,7 +198,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
198 // supposed to ignore loaded certificates
199 if !srv.AutoHTTPS.IgnoreLoadedCerts &&
200 len(app.tlsApp.AllMatchingCertificates(d)) > 0 {
204 - app.logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
201 + logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
202 zap.String("domain", d),
203 zap.String("server_name", srvName),
204 )
@@ -212,7 +209,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
209 // can handle that, but as a courtesy, warn the user
210 if strings.Contains(d, "*") &&
211 strings.Count(strings.Trim(d, "."), ".") == 1 {
215 - app.logger.Warn("most clients do not trust second-level wildcard certificates (*.tld)",
212 + logger.Warn("most clients do not trust second-level wildcard certificates (*.tld)",
213 zap.String("domain", d))
214 }
215
@@ -228,11 +225,11 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
225
226 // nothing left to do if auto redirects are disabled
227 if srv.AutoHTTPS.DisableRedir {
231 - app.logger.Warn("automatic HTTP->HTTPS redirects are disabled", zap.String("server_name", srvName))
228 + logger.Warn("automatic HTTP->HTTPS redirects are disabled", zap.String("server_name", srvName))
229 continue
230 }
231
235 - app.logger.Info("enabling automatic HTTP->HTTPS redirects", zap.String("server_name", srvName))
232 + logger.Info("enabling automatic HTTP->HTTPS redirects", zap.String("server_name", srvName))
233
234 // create HTTP->HTTPS redirects
235 for _, listenAddr := range srv.Listen {
@@ -272,12 +269,15 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
269 // we now have a list of all the unique names for which we need certs;
270 // turn the set into a slice so that phase 2 can use it
271 app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts))
275 - var internal []string
272 + var internal, tailscale []string
273 uniqueDomainsLoop:
274 for d := range uniqueDomainsForCerts {
278 - // whether or not there is already an automation policy for this
279 - // name, we should add it to the list to manage a cert for it
280 - app.allCertDomains = append(app.allCertDomains, d)
275 + if !isTailscaleDomain(d) {
276 + // whether or not there is already an automation policy for this
277 + // name, we should add it to the list to manage a cert for it,
278 + // unless it's a Tailscale domain, because we don't manage those
279 + app.allCertDomains = append(app.allCertDomains, d)
280 + }
281
282 // some names we've found might already have automation policies
283 // explicitly specified for them; we should exclude those from
@@ -295,13 +295,15 @@ uniqueDomainsLoop:
295
296 // if no automation policy exists for the name yet, we
297 // will associate it with an implicit one
298 - if !certmagic.SubjectQualifiesForPublicCert(d) {
298 + if isTailscaleDomain(d) {
299 + tailscale = append(tailscale, d)
300 + } else if !certmagic.SubjectQualifiesForPublicCert(d) {
301 internal = append(internal, d)
302 }
303 }
304
305 // ensure there is an automation policy to handle these certs
304 - err := app.createAutomationPolicies(ctx, internal)
306 + err := app.createAutomationPolicies(ctx, internal, tailscale)
307 if err != nil {
308 return err
309 }
@@ -424,6 +426,10 @@ redirServersLoop:
426 }
427 }
428
429 + logger.Debug("adjusted config",
430 + zap.Reflect("tls", app.tlsApp),
431 + zap.Reflect("http", app))
432 +
433 return nil
434 }
435
@@ -466,7 +472,7 @@ func (app *App) makeRedirRoute(redirToPort uint, matcherSet MatcherSet) Route {
472 // automation policy exists, it will be shallow-copied and used as the
473 // base for the new ones (this is important for preserving behavior the
474 // user intends to be "defaults").
469 -func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []string) error {
475 +func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames, tailscaleNames []string) error {
476 // before we begin, loop through the existing automation policies
477 // and, for any ACMEIssuers we find, make sure they're filled in
478 // with default values that might be specified in our HTTP app; also
@@ -480,6 +486,22 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
486 app.tlsApp.Automation = new(caddytls.AutomationConfig)
487 }
488 for _, ap := range app.tlsApp.Automation.Policies {
489 + // on-demand policies can have the tailscale manager added implicitly
490 + // if there's no explicit manager configured -- for convenience
491 + if ap.OnDemand && len(ap.Managers) == 0 {
492 + var ts caddytls.Tailscale
493 + if err := ts.Provision(ctx); err != nil {
494 + return err
495 + }
496 + ap.Managers = []certmagic.Manager{ts}
497 +
498 + // must reprovision the automation policy so that the underlying
499 + // CertMagic config knows about the updated Managers
500 + if err := ap.Provision(app.tlsApp); err != nil {
501 + return fmt.Errorf("re-provisioning automation policy: %v", err)
502 + }
503 + }
504 +
505 // set up default issuer -- honestly, this is only
506 // really necessary because the HTTP app is opinionated
507 // and has settings which could be inferred as new
@@ -501,22 +523,6 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
523 }
524 }
525
504 - // if no external managers were configured, enable
505 - // implicit Tailscale support for convenience
506 - if ap.Managers == nil {
507 - ts, err := implicitTailscale(ctx)
508 - if err != nil {
509 - return err
510 - }
511 - ap.Managers = []certmagic.Manager{ts}
512 -
513 - // must reprovision the automation policy so that the underlying
514 - // CertMagic config knows about the updated Managers
515 - if err := ap.Provision(app.tlsApp); err != nil {
516 - return fmt.Errorf("re-provisioning automation policy: %v", err)
517 - }
518 - }
519 -
526 // while we're here, is this the catch-all/base policy?
527 if !foundBasePolicy && len(ap.SubjectsRaw) == 0 {
528 basePolicy = ap
@@ -529,15 +535,6 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
535 basePolicy = new(caddytls.AutomationPolicy)
536 }
537
532 - if basePolicy.Managers == nil {
533 - // add implicit Tailscale integration, for harmless convenience
534 - ts, err := implicitTailscale(ctx)
535 - if err != nil {
536 - return err
537 - }
538 - basePolicy.Managers = []certmagic.Manager{ts}
539 - }
540 -
538 // if the basePolicy has an existing ACMEIssuer (particularly to
539 // include any type that embeds/wraps an ACMEIssuer), let's use it
540 // (I guess we just use the first one?), otherwise we'll make one
@@ -642,6 +639,27 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
639 }
640 }
641
642 + // tailscale names go in their own automation policies because
643 + // they require on-demand TLS to be enabled, which we obviously
644 + // can't enable for everything
645 + if len(tailscaleNames) > 0 {
646 + policyCopy := *basePolicy
647 + newPolicy := &policyCopy
648 +
649 + var ts caddytls.Tailscale
650 + if err := ts.Provision(ctx); err != nil {
651 + return err
652 + }
653 +
654 + newPolicy.SubjectsRaw = tailscaleNames
655 + newPolicy.Issuers = nil
656 + newPolicy.Managers = append(newPolicy.Managers, ts)
657 + err := app.tlsApp.AddAutomationPolicy(newPolicy)
658 + if err != nil {
659 + return err
660 + }
661 + }
662 +
663 // we just changed a lot of stuff, so double-check that it's all good
664 err := app.tlsApp.Validate()
665 if err != nil {
@@ -720,13 +738,6 @@ func (app *App) automaticHTTPSPhase2() error {
738 return nil
739 }
740
723 -// implicitTailscale returns a new and provisioned Tailscale module configured to be optional.
724 -func implicitTailscale(ctx caddy.Context) (caddytls.Tailscale, error) {
725 - ts := caddytls.Tailscale{Optional: true}
726 - err := ts.Provision(ctx)
727 - return ts, err
728 -}
729 -
741 func isTailscaleDomain(name string) bool {
742 return strings.HasSuffix(strings.ToLower(name), ".ts.net")
743 }
modules/caddytls/automation.go
+13 -8
@@ -95,9 +95,11 @@ type AutomationPolicy struct {
95 // Modules that can get a custom certificate to use for any
96 // given TLS handshake at handshake-time. Custom certificates
97 // can be useful if another entity is managing certificates
98 - // and Caddy need only get it and serve it.
98 + // and Caddy need only get it and serve it. Specifying a Manager
99 + // enables on-demand TLS, i.e. it has the side-effect of setting
100 + // the on_demand parameter to `true`.
101 //
100 - // TODO: This is an EXPERIMENTAL feature. It is subject to change or removal.
102 + // TODO: This is an EXPERIMENTAL feature. Subject to change or removal.
103 ManagersRaw []json.RawMessage `json:"get_certificate,omitempty" caddy:"namespace=tls.get_certificate inline_key=via"`
104
105 // If true, certificates will be requested with MustStaple. Not all
@@ -233,15 +235,18 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
235
236 // on-demand TLS
237 var ond *certmagic.OnDemandConfig
236 - if ap.OnDemand {
238 + if ap.OnDemand || len(ap.Managers) > 0 {
239 // ask endpoint is now required after a number of negligence cases causing abuse;
240 // but is still allowed for explicit subjects (non-wildcard, non-unbounded),
239 - // and for the internal issuer since it doesn't cause ACME issuer pressure
241 + // for the internal issuer since it doesn't cause ACME issuer pressure
242 if ap.isWildcardOrDefault() && !ap.onlyInternalIssuer() && (tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil || tlsApp.Automation.OnDemand.Ask == "") {
243 return fmt.Errorf("on-demand TLS cannot be enabled without an 'ask' endpoint to prevent abuse; please refer to documentation for details")
244 }
245 ond = &certmagic.OnDemandConfig{
246 DecisionFunc: func(name string) error {
247 + if tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil {
248 + return nil
249 + }
250 if err := onDemandAskRequest(tlsApp.logger, tlsApp.Automation.OnDemand.Ask, name); err != nil {
251 // distinguish true errors from denials, because it's important to elevate actual errors
252 if errors.Is(err, errAskDenied) {
@@ -264,6 +269,7 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
269 }
270 return nil
271 },
272 + Managers: ap.Managers,
273 }
274 }
275
@@ -277,10 +283,9 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
283 DisableStapling: ap.DisableOCSPStapling,
284 ResponderOverrides: ap.OCSPOverrides,
285 },
280 - Storage: storage,
281 - Issuers: issuers,
282 - Managers: ap.Managers,
283 - Logger: tlsApp.logger,
286 + Storage: storage,
287 + Issuers: issuers,
288 + Logger: tlsApp.logger,
289 }
290 ap.magic = certmagic.New(tlsApp.certCache, template)
291
modules/caddytls/certmanagers.go
+1 -14
@@ -23,14 +23,6 @@ func init() {
23
24 // Tailscale is a module that can get certificates from the local Tailscale process.
25 type Tailscale struct {
26 - // If true, this module will operate in "best-effort" mode and
27 - // ignore "soft" errors; i.e. try Tailscale, and if it doesn't connect
28 - // or return a certificate, oh well. Failure to connect to Tailscale
29 - // results in a no-op instead of an error. Intended for the use case
30 - // where this module is added implicitly for convenience, even if
31 - // Tailscale isn't necessarily running.
32 - Optional bool `json:"optional,omitempty"`
33 -
26 logger *zap.Logger
27 }
28
@@ -60,16 +52,11 @@ func (ts Tailscale) GetCertificate(ctx context.Context, hello *tls.ClientHelloIn
52
53 // canHazCertificate returns true if Tailscale reports it can get a certificate for the given ClientHello.
54 func (ts Tailscale) canHazCertificate(ctx context.Context, hello *tls.ClientHelloInfo) (bool, error) {
63 - if ts.Optional && !strings.HasSuffix(strings.ToLower(hello.ServerName), tailscaleDomainAliasEnding) {
55 + if !strings.HasSuffix(strings.ToLower(hello.ServerName), tailscaleDomainAliasEnding) {
56 return false, nil
57 }
58 status, err := tscert.GetStatus(ctx)
59 if err != nil {
68 - if ts.Optional {
69 - // ignore error if we don't expect/require it to work anyway, but log it for debugging
70 - ts.logger.Debug("error getting tailscale status", zap.Error(err), zap.String("server_name", hello.ServerName))
71 - return false, nil
72 - }
60 return false, err
61 }
62 for _, domain := range status.CertDomains {