-
Notifications
You must be signed in to change notification settings - Fork 2k
/
dynamic_source.go
226 lines (196 loc) · 6.47 KB
/
dynamic_source.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/*
Copyright 2019 The Jetstack cert-manager contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tls
import (
"crypto"
"crypto/tls"
"crypto/x509"
"fmt"
"sync"
"time"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/util/wait"
crlog "sigs.k8s.io/controller-runtime/pkg/log"
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
"github.com/jetstack/cert-manager/pkg/util/pki"
"github.com/jetstack/cert-manager/pkg/webhook/authority"
)
// DynamicSource provides certificate data for a golang HTTP server by
// automatically generating certificates using an authority.SignFunc.
type DynamicSource struct {
// DNSNames that will be set on certificates this source produces.
DNSNames []string
// The authority used to sign certificate templates.
Authority *authority.DynamicAuthority
// Log is an optional logger to write informational and error messages to.
// If not specified, no messages will be logged.
Log logr.Logger
cachedCertificate *tls.Certificate
requiresRotation bool
nextRenew time.Time
lock sync.Mutex
}
var _ CertificateSource = &DynamicSource{}
func (f *DynamicSource) Run(stopCh <-chan struct{}) error {
if f.Log == nil {
f.Log = crlog.NullLogger{}
}
// Run the authority in a separate goroutine
authorityErrChan := make(chan error)
go func() {
defer close(authorityErrChan)
authorityErrChan <- f.Authority.Run(stopCh)
}()
// initially fetch a certificate from the signing CA
interval := time.Second
if err := wait.PollUntil(interval, func() (done bool, err error) {
// check for errors from the authority here too, to prevent retrying
// if the authority has failed to start
select {
case err, ok := <-authorityErrChan:
if err != nil {
return true, fmt.Errorf("failed to run certificate authority: %w", err)
}
if !ok {
return true, fmt.Errorf("certificate authority stopped")
}
default:
// this case avoids blocking if the authority is still running
}
if err := f.regenerateCertificate(); err != nil {
f.Log.Error(err, "Failed to generate initial serving certificate, retrying...", "interval", interval)
return false, nil
}
return true, nil
}, stopCh); err != nil {
return err
}
// watch for changes to the root CA
rotationChan := f.Authority.WatchRotation(stopCh)
renewalChan := func() <-chan struct{} {
ch := make(chan struct{})
go func() {
defer close(ch)
for {
// exit if stopCh closes
select {
case <-stopCh:
return
default:
}
// regenerate the certificate if we have gone past the 'nextRenew' time
if time.Now().After(f.nextRenew) {
ch <- struct{}{}
}
time.Sleep(time.Second * 5)
}
}()
return ch
}()
// check the current certificate every 10s in case it needs updating
return wait.PollImmediateUntil(time.Second*10, func() (done bool, err error) {
// regenerate the serving certificate if the root CA has been rotated
select {
// if the authority has stopped for whatever reason, exit and return the error
case err, ok := <-authorityErrChan:
if err != nil {
return true, fmt.Errorf("failed to run certificate authority: %w", err)
}
if !ok {
return true, fmt.Errorf("certificate authority stopped")
}
// trigger regeneration if the root CA has been rotated
case _, ok := <-rotationChan:
if !ok {
return true, fmt.Errorf("channel closed")
}
f.Log.Info("Detected root CA rotation - regenerating serving certificates")
if err := f.regenerateCertificate(); err != nil {
f.Log.Error(err, "Failed to regenerate serving certificate")
// Return an error here and stop the source running - this case should never
// occur, and if it does, indicates some form of internal error.
return false, err
}
// trigger regeneration if a renewal is required
case <-renewalChan:
f.Log.Info("Serving certificate requires renewal, regenerating")
if err := f.regenerateCertificate(); err != nil {
f.Log.Error(err, "Failed to regenerate serving certificate")
// Return an error here and stop the source running - this case should never
// occur, and if it does, indicates some form of internal error.
return false, err
}
}
return false, nil
}, stopCh)
}
func (f *DynamicSource) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.cachedCertificate == nil {
return nil, ErrNotAvailable
}
return f.cachedCertificate, nil
}
func (f *DynamicSource) Healthy() bool {
return f.cachedCertificate != nil
}
// regenerateCertificate will trigger the cached certificate and private key to
// be regenerated by requesting a new certificate from the authority.
func (f *DynamicSource) regenerateCertificate() error {
f.Log.Info("Generating new ECDSA private key")
pk, err := pki.GenerateECPrivateKey(384)
if err != nil {
return err
}
// create the certificate template to be signed
template := &x509.Certificate{
PublicKeyAlgorithm: x509.ECDSA,
PublicKey: pk.Public(),
DNSNames: f.DNSNames,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
f.Log.Info("Signing new serving certificate")
cert, err := f.Authority.Sign(template)
if err != nil {
return err
}
f.Log.Info("Signed new serving certificate")
if err := f.updateCertificate(pk, cert); err != nil {
return err
}
f.Log.Info("Updated serving TLS certificate")
return nil
}
func (f *DynamicSource) updateCertificate(pk crypto.Signer, cert *x509.Certificate) error {
f.lock.Lock()
defer f.lock.Unlock()
pkData, err := pki.EncodePrivateKey(pk, cmapi.PKCS8)
if err != nil {
return err
}
certData, err := pki.EncodeX509(cert)
if err != nil {
return err
}
bundle, err := tls.X509KeyPair(certData, pkData)
if err != nil {
return err
}
f.cachedCertificate = &bundle
certDuration := cert.NotAfter.Sub(cert.NotBefore)
// renew the certificate 1/3 of the time before its expiry
f.nextRenew = cert.NotAfter.Add(certDuration / -3)
return nil
}