github.com/devops-filetransfer/sshego@v7.0.4+incompatible/_vendor/golang.org/x/crypto/acme/autocert/renewal.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package autocert
     6  
     7  import (
     8  	"context"
     9  	"crypto"
    10  	"sync"
    11  	"time"
    12  )
    13  
    14  // renewJitter is the maximum deviation from Manager.RenewBefore.
    15  const renewJitter = time.Hour
    16  
    17  // domainRenewal tracks the state used by the periodic timers
    18  // renewing a single domain's cert.
    19  type domainRenewal struct {
    20  	m      *Manager
    21  	domain string
    22  	key    crypto.Signer
    23  
    24  	timerMu sync.Mutex
    25  	timer   *time.Timer
    26  }
    27  
    28  // start starts a cert renewal timer at the time
    29  // defined by the certificate expiration time exp.
    30  //
    31  // If the timer is already started, calling start is a noop.
    32  func (dr *domainRenewal) start(exp time.Time) {
    33  	dr.timerMu.Lock()
    34  	defer dr.timerMu.Unlock()
    35  	if dr.timer != nil {
    36  		return
    37  	}
    38  	dr.timer = time.AfterFunc(dr.next(exp), dr.renew)
    39  }
    40  
    41  // stop stops the cert renewal timer.
    42  // If the timer is already stopped, calling stop is a noop.
    43  func (dr *domainRenewal) stop() {
    44  	dr.timerMu.Lock()
    45  	defer dr.timerMu.Unlock()
    46  	if dr.timer == nil {
    47  		return
    48  	}
    49  	dr.timer.Stop()
    50  	dr.timer = nil
    51  }
    52  
    53  // renew is called periodically by a timer.
    54  // The first renew call is kicked off by dr.start.
    55  func (dr *domainRenewal) renew() {
    56  	dr.timerMu.Lock()
    57  	defer dr.timerMu.Unlock()
    58  	if dr.timer == nil {
    59  		return
    60  	}
    61  
    62  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
    63  	defer cancel()
    64  	// TODO: rotate dr.key at some point?
    65  	next, err := dr.do(ctx)
    66  	if err != nil {
    67  		next = renewJitter / 2
    68  		next += time.Duration(pseudoRand.int63n(int64(next)))
    69  	}
    70  	dr.timer = time.AfterFunc(next, dr.renew)
    71  	testDidRenewLoop(next, err)
    72  }
    73  
    74  // do is similar to Manager.createCert but it doesn't lock a Manager.state item.
    75  // Instead, it requests a new certificate independently and, upon success,
    76  // replaces dr.m.state item with a new one and updates cache for the given domain.
    77  //
    78  // It may return immediately if the expiration date of the currently cached cert
    79  // is far enough in the future.
    80  //
    81  // The returned value is a time interval after which the renewal should occur again.
    82  func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
    83  	// a race is likely unavoidable in a distributed environment
    84  	// but we try nonetheless
    85  	if tlscert, err := dr.m.cacheGet(ctx, dr.domain); err == nil {
    86  		next := dr.next(tlscert.Leaf.NotAfter)
    87  		if next > dr.m.renewBefore()+renewJitter {
    88  			return next, nil
    89  		}
    90  	}
    91  
    92  	der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.domain)
    93  	if err != nil {
    94  		return 0, err
    95  	}
    96  	state := &certState{
    97  		key:  dr.key,
    98  		cert: der,
    99  		leaf: leaf,
   100  	}
   101  	tlscert, err := state.tlscert()
   102  	if err != nil {
   103  		return 0, err
   104  	}
   105  	dr.m.cachePut(ctx, dr.domain, tlscert)
   106  	dr.m.stateMu.Lock()
   107  	defer dr.m.stateMu.Unlock()
   108  	// m.state is guaranteed to be non-nil at this point
   109  	dr.m.state[dr.domain] = state
   110  	return dr.next(leaf.NotAfter), nil
   111  }
   112  
   113  func (dr *domainRenewal) next(expiry time.Time) time.Duration {
   114  	d := expiry.Sub(timeNow()) - dr.m.renewBefore()
   115  	// add a bit of randomness to renew deadline
   116  	n := pseudoRand.int63n(int64(renewJitter))
   117  	d -= time.Duration(n)
   118  	if d < 0 {
   119  		return 0
   120  	}
   121  	return d
   122  }
   123  
   124  var testDidRenewLoop = func(next time.Duration, err error) {}