Skip to content

Commit 8728b18

Browse files
authored
Refactor Managers into on-demand config (#231)
1 parent 53140d5 commit 8728b18

File tree

4 files changed

+39
-47
lines changed

4 files changed

+39
-47
lines changed

certificates.go

+3
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ func (cert Certificate) HasTag(tag string) bool {
113113
// resolution of ASN.1 UTCTime/GeneralizedTime by including the extra fraction
114114
// of a second of certificate validity beyond the NotAfter value.
115115
func expiresAt(cert *x509.Certificate) time.Time {
116+
if cert == nil {
117+
return time.Time{}
118+
}
116119
return cert.NotAfter.Truncate(time.Second).Add(1 * time.Second)
117120
}
118121

certmagic.go

+9
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,15 @@ type OnDemandConfig struct {
270270
// request will be denied.
271271
DecisionFunc func(name string) error
272272

273+
// Sources for getting new, unmanaged certificates.
274+
// They will be invoked only during TLS handshakes
275+
// before on-demand certificate management occurs,
276+
// for certificates that are not already loaded into
277+
// the in-memory cache.
278+
//
279+
// TODO: EXPERIMENTAL: subject to change and/or removal.
280+
Managers []Manager
281+
273282
// List of allowed hostnames (SNI values) for
274283
// deferred (on-demand) obtaining of certificates.
275284
// Used only by higher-level functions in this

config.go

-12
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,6 @@ type Config struct {
9595
// turn until one succeeds.
9696
Issuers []Issuer
9797

98-
// Sources for getting new, unmanaged certificates.
99-
// They will be invoked only during TLS handshakes
100-
// before on-demand certificate management occurs,
101-
// for certificates that are not already loaded into
102-
// the in-memory cache.
103-
//
104-
// TODO: EXPERIMENTAL: subject to change and/or removal.
105-
Managers []Manager
106-
10798
// The source of new private keys for certificates;
10899
// the default KeySource is StandardKeyGenerator.
109100
KeySource KeyGenerator
@@ -234,9 +225,6 @@ func newWithCache(certCache *Cache, cfg Config) *Config {
234225
cfg.Issuers = []Issuer{NewACMEIssuer(&cfg, DefaultACME)}
235226
}
236227
}
237-
if cfg.Managers == nil {
238-
cfg.Managers = Default.Managers
239-
}
240228
if cfg.RenewalWindowRatio == 0 {
241229
cfg.RenewalWindowRatio = Default.RenewalWindowRatio
242230
}

handshake.go

+27-35
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (cfg *Config) GetCertificateWithContext(ctx context.Context, clientHello *t
8181
}
8282

8383
// get the certificate and serve it up
84-
cert, err := cfg.getCertDuringHandshake(ctx, clientHello, true, true)
84+
cert, err := cfg.getCertDuringHandshake(ctx, clientHello, true)
8585

8686
return &cert.Certificate, err
8787
}
@@ -253,19 +253,19 @@ func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificat
253253
// An error will be returned if and only if no certificate is available.
254254
//
255255
// This function is safe for concurrent use.
256-
func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.ClientHelloInfo, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) {
257-
log := logWithRemote(cfg.Logger.Named("handshake"), hello)
256+
func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.ClientHelloInfo, loadOrObtainIfNecessary bool) (Certificate, error) {
257+
logger := logWithRemote(cfg.Logger.Named("handshake"), hello)
258258
name := cfg.getNameFromClientHello(hello)
259259

260260
// First check our in-memory cache to see if we've already loaded it
261261
cert, matched, defaulted := cfg.getCertificateFromCache(hello)
262262
if matched {
263-
log.Debug("matched certificate in cache",
263+
logger.Debug("matched certificate in cache",
264264
zap.Strings("subjects", cert.Names),
265265
zap.Bool("managed", cert.managed),
266266
zap.Time("expiration", expiresAt(cert.Leaf)),
267267
zap.String("hash", cert.hash))
268-
if cert.managed && cfg.OnDemand != nil && obtainIfNecessary {
268+
if cert.managed && cfg.OnDemand != nil && loadOrObtainIfNecessary {
269269
// On-demand certificates are maintained in the background, but
270270
// maintenance is triggered by handshakes instead of by a timer
271271
// as in maintain.go.
@@ -294,7 +294,7 @@ func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.Client
294294
timeout.Stop()
295295
}
296296

297-
return cfg.getCertDuringHandshake(ctx, hello, false, false)
297+
return cfg.getCertDuringHandshake(ctx, hello, false)
298298
} else {
299299
// no other goroutine is currently trying to load this cert
300300
wait = make(chan struct{})
@@ -319,7 +319,7 @@ func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.Client
319319

320320
// If an external Manager is configured, try to get it from them.
321321
// Only continue to use our own logic if it returns empty+nil.
322-
externalCert, err := cfg.getCertFromAnyCertManager(ctx, hello, log)
322+
externalCert, err := cfg.getCertFromAnyCertManager(ctx, hello, logger)
323323
if err != nil {
324324
return Certificate{}, err
325325
}
@@ -345,45 +345,45 @@ func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.Client
345345
cacheAlmostFull := cacheCapacity > 0 && float64(cacheSize) >= cacheCapacity*.9
346346
loadDynamically := cfg.OnDemand != nil || cacheAlmostFull
347347

348-
if loadDynamically && loadIfNecessary {
348+
if loadDynamically && loadOrObtainIfNecessary {
349349
// Check to see if we have one on disk
350-
loadedCert, err := cfg.loadCertFromStorage(ctx, log, hello)
350+
loadedCert, err := cfg.loadCertFromStorage(ctx, logger, hello)
351351
if err == nil {
352352
return loadedCert, nil
353353
}
354-
log.Debug("did not load cert from storage",
354+
logger.Debug("did not load cert from storage",
355355
zap.String("server_name", hello.ServerName),
356356
zap.Error(err))
357357
if cfg.OnDemand != nil {
358358
// By this point, we need to ask the CA for a certificate
359359
return cfg.obtainOnDemandCertificate(ctx, hello)
360360
}
361+
return loadedCert, nil
361362
}
362363

363364
// Fall back to another certificate if there is one (either DefaultServerName or FallbackServerName)
364365
if defaulted {
365-
log.Debug("fell back to other certificate",
366+
logger.Debug("fell back to default certificate",
366367
zap.Strings("subjects", cert.Names),
367368
zap.Bool("managed", cert.managed),
368369
zap.Time("expiration", expiresAt(cert.Leaf)),
369370
zap.String("hash", cert.hash))
370371
return cert, nil
371372
}
372373

373-
log.Debug("no certificate matching TLS ClientHello",
374+
logger.Debug("no certificate matching TLS ClientHello",
374375
zap.String("server_name", hello.ServerName),
375376
zap.String("remote", hello.Conn.RemoteAddr().String()),
376377
zap.String("identifier", name),
377378
zap.Uint16s("cipher_suites", hello.CipherSuites),
378379
zap.Float64("cert_cache_fill", float64(cacheSize)/cacheCapacity), // may be approximate! because we are not within the lock
379-
zap.Bool("load_if_necessary", loadIfNecessary),
380-
zap.Bool("obtain_if_necessary", obtainIfNecessary),
380+
zap.Bool("load_or_obtain_if_necessary", loadOrObtainIfNecessary),
381381
zap.Bool("on_demand", cfg.OnDemand != nil))
382382

383383
return Certificate{}, fmt.Errorf("no certificate available for '%s'", name)
384384
}
385385

386-
func (cfg *Config) loadCertFromStorage(ctx context.Context, log *zap.Logger, hello *tls.ClientHelloInfo) (Certificate, error) {
386+
func (cfg *Config) loadCertFromStorage(ctx context.Context, logger *zap.Logger, hello *tls.ClientHelloInfo) (Certificate, error) {
387387
name := normalizedName(hello.ServerName)
388388
loadedCert, err := cfg.CacheManagedCertificate(ctx, name)
389389
if errors.Is(err, fs.ErrNotExist) {
@@ -395,14 +395,14 @@ func (cfg *Config) loadCertFromStorage(ctx context.Context, log *zap.Logger, hel
395395
if err != nil {
396396
return Certificate{}, fmt.Errorf("no matching certificate to load for %s: %w", name, err)
397397
}
398-
log.Debug("loaded certificate from storage",
398+
logger.Debug("loaded certificate from storage",
399399
zap.Strings("subjects", loadedCert.Names),
400400
zap.Bool("managed", loadedCert.managed),
401401
zap.Time("expiration", expiresAt(loadedCert.Leaf)),
402402
zap.String("hash", loadedCert.hash))
403403
loadedCert, err = cfg.handshakeMaintenance(ctx, hello, loadedCert)
404404
if err != nil {
405-
log.Error("maintaining newly-loaded certificate",
405+
logger.Error("maintaining newly-loaded certificate",
406406
zap.String("server_name", name),
407407
zap.Error(err))
408408
}
@@ -465,10 +465,6 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
465465

466466
name := cfg.getNameFromClientHello(hello)
467467

468-
getCertWithoutReobtaining := func() (Certificate, error) {
469-
return cfg.loadCertFromStorage(ctx, log, hello)
470-
}
471-
472468
// We must protect this process from happening concurrently, so synchronize.
473469
obtainCertWaitChansMu.Lock()
474470
wait, ok := obtainCertWaitChans[name]
@@ -486,7 +482,7 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
486482
timeout.Stop()
487483
}
488484

489-
return getCertWithoutReobtaining()
485+
return cfg.loadCertFromStorage(ctx, log, hello)
490486
}
491487

492488
// looks like it's up to us to do all the work and obtain the cert.
@@ -525,7 +521,7 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
525521

526522
// success; certificate was just placed on disk, so
527523
// we need only restart serving the certificate
528-
return getCertWithoutReobtaining()
524+
return cfg.loadCertFromStorage(ctx, log, hello)
529525
}
530526

531527
// handshakeMaintenance performs a check on cert for expiration and OCSP validity.
@@ -613,10 +609,6 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
613609
timeLeft := time.Until(expiresAt(currentCert.Leaf))
614610
revoked := currentCert.ocsp != nil && currentCert.ocsp.Status == ocsp.Revoked
615611

616-
getCertWithoutReobtaining := func() (Certificate, error) {
617-
return cfg.loadCertFromStorage(ctx, log, hello)
618-
}
619-
620612
// see if another goroutine is already working on this certificate
621613
obtainCertWaitChansMu.Lock()
622614
wait, ok := obtainCertWaitChans[name]
@@ -651,7 +643,7 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
651643
timeout.Stop()
652644
}
653645

654-
return getCertWithoutReobtaining()
646+
return cfg.loadCertFromStorage(ctx, log, hello)
655647
}
656648

657649
// looks like it's up to us to do all the work and renew the cert
@@ -726,7 +718,7 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
726718
return newCert, err
727719
}
728720

729-
return getCertWithoutReobtaining()
721+
return cfg.loadCertFromStorage(ctx, log, hello)
730722
}
731723

732724
// if the certificate hasn't expired, we can serve what we have and renew in the background
@@ -744,20 +736,20 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
744736
// getCertFromAnyCertManager gets a certificate from cfg's Managers. If there are no Managers defined, this is
745737
// a no-op that returns empty values. Otherwise, it gets a certificate for hello from the first Manager that
746738
// returns a certificate and no error.
747-
func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.ClientHelloInfo, log *zap.Logger) (Certificate, error) {
739+
func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.ClientHelloInfo, logger *zap.Logger) (Certificate, error) {
748740
// fast path if nothing to do
749-
if len(cfg.Managers) == 0 {
741+
if cfg.OnDemand == nil || len(cfg.OnDemand.Managers) == 0 {
750742
return Certificate{}, nil
751743
}
752744

753745
var upstreamCert *tls.Certificate
754746

755747
// try all the GetCertificate methods on external managers; use first one that returns a certificate
756-
for i, certManager := range cfg.Managers {
748+
for i, certManager := range cfg.OnDemand.Managers {
757749
var err error
758750
upstreamCert, err = certManager.GetCertificate(ctx, hello)
759751
if err != nil {
760-
log.Error("getting certificate from external certificate manager",
752+
logger.Error("getting certificate from external certificate manager",
761753
zap.String("sni", hello.ServerName),
762754
zap.Int("cert_manager", i),
763755
zap.Error(err))
@@ -768,7 +760,7 @@ func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.Cli
768760
}
769761
}
770762
if upstreamCert == nil {
771-
log.Debug("all external certificate managers yielded no certificates and no errors", zap.String("sni", hello.ServerName))
763+
logger.Debug("all external certificate managers yielded no certificates and no errors", zap.String("sni", hello.ServerName))
772764
return Certificate{}, nil
773765
}
774766

@@ -778,7 +770,7 @@ func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.Cli
778770
return Certificate{}, fmt.Errorf("external certificate manager: %s: filling cert from leaf: %v", hello.ServerName, err)
779771
}
780772

781-
log.Debug("using externally-managed certificate",
773+
logger.Debug("using externally-managed certificate",
782774
zap.String("sni", hello.ServerName),
783775
zap.Strings("names", cert.Names),
784776
zap.Time("expiration", expiresAt(cert.Leaf)))

0 commit comments

Comments
 (0)