Skip to content

Commit eaa9bc5

Browse files
authored
Merge pull request #11 from smallstep/ca-force-renew
Force the renew of the CA server.
2 parents d63f4f0 + 7e2f80a commit eaa9bc5

File tree

2 files changed

+46
-6
lines changed

2 files changed

+46
-6
lines changed

ca/ca.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ func (ca *CA) Run() error {
121121

122122
// Stop stops the CA calling to the server Shutdown method.
123123
func (ca *CA) Stop() error {
124+
ca.renewer.Stop()
124125
return ca.srv.Shutdown()
125126
}
126127

@@ -185,7 +186,7 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) {
185186
// empty we are implicitly forcing GetCertificate to be the only mechanism
186187
// by which the server can find it's own leaf Certificate.
187188
tlsConfig.Certificates = []tls.Certificate{}
188-
tlsConfig.GetCertificate = ca.renewer.GetCertificate
189+
tlsConfig.GetCertificate = ca.renewer.GetCertificateForCA
189190

190191
// Add support for mutual tls to renew certificates
191192
tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven

ca/renew.go

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ import (
1414
// certificate.
1515
type RenewFunc func() (*tls.Certificate, error)
1616

17-
// TLSRenewer renews automatically a tls certificate with a given function.
17+
// TLSRenewer automatically renews a tls certificate using a RenewFunc.
1818
type TLSRenewer struct {
1919
sync.RWMutex
2020
RenewCertificate RenewFunc
2121
cert *tls.Certificate
2222
timer *time.Timer
2323
renewBefore time.Duration
2424
renewJitter time.Duration
25+
certNotAfter time.Time
2526
}
2627

2728
type tlsRenewerOptions func(r *TLSRenewer) error
@@ -43,7 +44,7 @@ func WithRenewJitter(j time.Duration) func(r *TLSRenewer) error {
4344
}
4445

4546
// NewTLSRenewer creates a TLSRenewer for the given cert. It will use the given
46-
// function to get a new certificate when required.
47+
// RenewFunc to get a new certificate when required.
4748
func NewTLSRenewer(cert *tls.Certificate, fn RenewFunc, opts ...tlsRenewerOptions) (*TLSRenewer, error) {
4849
r := &TLSRenewer{
4950
RenewCertificate: fn,
@@ -91,7 +92,10 @@ func (r *TLSRenewer) RunContext(ctx context.Context) {
9192

9293
// Stop prevents the renew timer from firing.
9394
func (r *TLSRenewer) Stop() bool {
94-
return r.timer.Stop()
95+
if r.timer != nil {
96+
return r.timer.Stop()
97+
}
98+
return true
9599
}
96100

97101
// GetCertificate returns the current server certificate.
@@ -101,6 +105,15 @@ func (r *TLSRenewer) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Cert
101105
return r.getCertificate(), nil
102106
}
103107

108+
// GetCertificateForCA returns the current server certificate. It can only be
109+
// used if the renew function creates the new certificate and do not uses a TLS
110+
// request. It's intended to be use by the certificate authority server.
111+
//
112+
// This method is set in the tls.Config GetCertificate property.
113+
func (r *TLSRenewer) GetCertificateForCA(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
114+
return r.getCertificateForCA(), nil
115+
}
116+
104117
// GetClientCertificate returns the current client certificate.
105118
//
106119
// This method is set in the tls.Config GetClientCertificate property.
@@ -109,17 +122,41 @@ func (r *TLSRenewer) GetClientCertificate(*tls.CertificateRequestInfo) (*tls.Cer
109122
}
110123

111124
// getCertificate returns the certificate using a read-only lock.
125+
//
126+
// Known issue: It cannot renew an expired certificate because the /renew
127+
// endpoint requires a valid client certificate. The certificate can expire
128+
// if the timer does not fire e.g. when the CA is run from a laptop that
129+
// enters sleep mode.
112130
func (r *TLSRenewer) getCertificate() *tls.Certificate {
113131
r.RLock()
114132
cert := r.cert
115133
r.RUnlock()
116134
return cert
117135
}
118136

119-
// setCertificate updates the certificate using a read-write lock.
137+
// getCertificateForCA returns the certificate using a read-only lock. It will
138+
// automatically renew the certificate if it has expired.
139+
func (r *TLSRenewer) getCertificateForCA() *tls.Certificate {
140+
r.RLock()
141+
// Force certificate renewal if the timer didn't run.
142+
// This is an special case that can happen after a computer sleep.
143+
if time.Now().After(r.certNotAfter) {
144+
r.RUnlock()
145+
r.renewCertificate()
146+
r.RLock()
147+
}
148+
cert := r.cert
149+
r.RUnlock()
150+
return cert
151+
}
152+
153+
// setCertificate updates the certificate using a read-write lock. It also
154+
// updates certNotAfter with 1m of delta; this will force the renewal of the
155+
// certificate if it is about to expire.
120156
func (r *TLSRenewer) setCertificate(cert *tls.Certificate) {
121157
r.Lock()
122158
r.cert = cert
159+
r.certNotAfter = cert.Leaf.NotAfter.Add(-1 * time.Minute)
123160
r.Unlock()
124161
}
125162

@@ -133,7 +170,9 @@ func (r *TLSRenewer) renewCertificate() {
133170
r.setCertificate(cert)
134171
next = r.nextRenewDuration(cert.Leaf.NotAfter)
135172
}
136-
r.timer = time.AfterFunc(next, r.renewCertificate)
173+
r.Lock()
174+
r.timer.Reset(next)
175+
r.Unlock()
137176
}
138177

139178
func (r *TLSRenewer) nextRenewDuration(notAfter time.Time) time.Duration {

0 commit comments

Comments
 (0)