renewal.go (4263B)
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 ck certKey 22 key crypto.Signer 23 24 timerMu sync.Mutex 25 timer *time.Timer 26 timerClose chan struct{} // if non-nil, renew closes this channel (and nils out the timer fields) instead of running 27 } 28 29 // start starts a cert renewal timer at the time 30 // defined by the certificate expiration time exp. 31 // 32 // If the timer is already started, calling start is a noop. 33 func (dr *domainRenewal) start(exp time.Time) { 34 dr.timerMu.Lock() 35 defer dr.timerMu.Unlock() 36 if dr.timer != nil { 37 return 38 } 39 dr.timer = time.AfterFunc(dr.next(exp), dr.renew) 40 } 41 42 // stop stops the cert renewal timer and waits for any in-flight calls to renew 43 // to complete. If the timer is already stopped, calling stop is a noop. 44 func (dr *domainRenewal) stop() { 45 dr.timerMu.Lock() 46 defer dr.timerMu.Unlock() 47 for { 48 if dr.timer == nil { 49 return 50 } 51 if dr.timer.Stop() { 52 dr.timer = nil 53 return 54 } else { 55 // dr.timer fired, and we acquired dr.timerMu before the renew callback did. 56 // (We know this because otherwise the renew callback would have reset dr.timer!) 57 timerClose := make(chan struct{}) 58 dr.timerClose = timerClose 59 dr.timerMu.Unlock() 60 <-timerClose 61 dr.timerMu.Lock() 62 } 63 } 64 } 65 66 // renew is called periodically by a timer. 67 // The first renew call is kicked off by dr.start. 68 func (dr *domainRenewal) renew() { 69 dr.timerMu.Lock() 70 defer dr.timerMu.Unlock() 71 if dr.timerClose != nil { 72 close(dr.timerClose) 73 dr.timer, dr.timerClose = nil, nil 74 return 75 } 76 77 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) 78 defer cancel() 79 // TODO: rotate dr.key at some point? 80 next, err := dr.do(ctx) 81 if err != nil { 82 next = renewJitter / 2 83 next += time.Duration(pseudoRand.int63n(int64(next))) 84 } 85 testDidRenewLoop(next, err) 86 dr.timer = time.AfterFunc(next, dr.renew) 87 } 88 89 // updateState locks and replaces the relevant Manager.state item with the given 90 // state. It additionally updates dr.key with the given state's key. 91 func (dr *domainRenewal) updateState(state *certState) { 92 dr.m.stateMu.Lock() 93 defer dr.m.stateMu.Unlock() 94 dr.key = state.key 95 dr.m.state[dr.ck] = state 96 } 97 98 // do is similar to Manager.createCert but it doesn't lock a Manager.state item. 99 // Instead, it requests a new certificate independently and, upon success, 100 // replaces dr.m.state item with a new one and updates cache for the given domain. 101 // 102 // It may lock and update the Manager.state if the expiration date of the currently 103 // cached cert is far enough in the future. 104 // 105 // The returned value is a time interval after which the renewal should occur again. 106 func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) { 107 // a race is likely unavoidable in a distributed environment 108 // but we try nonetheless 109 if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil { 110 next := dr.next(tlscert.Leaf.NotAfter) 111 if next > dr.m.renewBefore()+renewJitter { 112 signer, ok := tlscert.PrivateKey.(crypto.Signer) 113 if ok { 114 state := &certState{ 115 key: signer, 116 cert: tlscert.Certificate, 117 leaf: tlscert.Leaf, 118 } 119 dr.updateState(state) 120 return next, nil 121 } 122 } 123 } 124 125 der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck) 126 if err != nil { 127 return 0, err 128 } 129 state := &certState{ 130 key: dr.key, 131 cert: der, 132 leaf: leaf, 133 } 134 tlscert, err := state.tlscert() 135 if err != nil { 136 return 0, err 137 } 138 if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil { 139 return 0, err 140 } 141 dr.updateState(state) 142 return dr.next(leaf.NotAfter), nil 143 } 144 145 func (dr *domainRenewal) next(expiry time.Time) time.Duration { 146 d := expiry.Sub(dr.m.now()) - dr.m.renewBefore() 147 // add a bit of randomness to renew deadline 148 n := pseudoRand.int63n(int64(renewJitter)) 149 d -= time.Duration(n) 150 if d < 0 { 151 return 0 152 } 153 return d 154 } 155 156 var testDidRenewLoop = func(next time.Duration, err error) {}