@@ -17,58 +17,55 @@ limitations under the License.
17
17
package certwatcher
18
18
19
19
import (
20
+ "bytes"
20
21
"context"
21
22
"crypto/tls"
22
- "fmt "
23
+ "os "
23
24
"sync"
24
25
"time"
25
26
26
- "github.com/fsnotify/fsnotify"
27
- kerrors "k8s.io/apimachinery/pkg/util/errors"
28
- "k8s.io/apimachinery/pkg/util/sets"
29
- "k8s.io/apimachinery/pkg/util/wait"
30
27
"sigs.k8s.io/controller-runtime/pkg/certwatcher/metrics"
31
28
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
32
29
)
33
30
34
31
var log = logf .RuntimeLog .WithName ("certwatcher" )
35
32
36
- // CertWatcher watches certificate and key files for changes. When either file
37
- // changes, it reads and parses both and calls an optional callback with the new
38
- // certificate.
33
+ const defaultWatchInterval = 10 * time .Second
34
+
35
+ // CertWatcher watches certificate and key files for changes.
36
+ // It always returns the cached version,
37
+ // but periodically reads and parses certificate and key for changes
38
+ // and calls an optional callback with the new certificate.
39
39
type CertWatcher struct {
40
40
sync.RWMutex
41
41
42
42
currentCert * tls.Certificate
43
- watcher * fsnotify. Watcher
43
+ interval time. Duration
44
44
45
45
certPath string
46
46
keyPath string
47
47
48
+ cachedKeyPEMBlock []byte
49
+
48
50
// callback is a function to be invoked when the certificate changes.
49
51
callback func (tls.Certificate )
50
52
}
51
53
52
54
// New returns a new CertWatcher watching the given certificate and key.
53
55
func New (certPath , keyPath string ) (* CertWatcher , error ) {
54
- var err error
55
-
56
56
cw := & CertWatcher {
57
57
certPath : certPath ,
58
58
keyPath : keyPath ,
59
+ interval : defaultWatchInterval ,
59
60
}
60
61
61
- // Initial read of certificate and key.
62
- if err := cw .ReadCertificate (); err != nil {
63
- return nil , err
64
- }
65
-
66
- cw .watcher , err = fsnotify .NewWatcher ()
67
- if err != nil {
68
- return nil , err
69
- }
62
+ return cw , cw .ReadCertificate ()
63
+ }
70
64
71
- return cw , nil
65
+ // WithWatchInterval sets the watch interval and returns the CertWatcher pointer
66
+ func (cw * CertWatcher ) WithWatchInterval (interval time.Duration ) * CertWatcher {
67
+ cw .interval = interval
68
+ return cw
72
69
}
73
70
74
71
// RegisterCallback registers a callback to be invoked when the certificate changes.
@@ -91,72 +88,64 @@ func (cw *CertWatcher) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate,
91
88
92
89
// Start starts the watch on the certificate and key files.
93
90
func (cw * CertWatcher ) Start (ctx context.Context ) error {
94
- files := sets .New (cw .certPath , cw .keyPath )
95
-
96
- {
97
- var watchErr error
98
- if err := wait .PollUntilContextTimeout (ctx , 1 * time .Second , 10 * time .Second , true , func (ctx context.Context ) (done bool , err error ) {
99
- for _ , f := range files .UnsortedList () {
100
- if err := cw .watcher .Add (f ); err != nil {
101
- watchErr = err
102
- return false , nil //nolint:nilerr // We want to keep trying.
103
- }
104
- // We've added the watch, remove it from the set.
105
- files .Delete (f )
106
- }
107
- return true , nil
108
- }); err != nil {
109
- return fmt .Errorf ("failed to add watches: %w" , kerrors .NewAggregate ([]error {err , watchErr }))
110
- }
111
- }
112
-
113
- go cw .Watch ()
91
+ ticker := time .NewTicker (cw .interval )
92
+ defer ticker .Stop ()
114
93
115
94
log .Info ("Starting certificate watcher" )
116
-
117
- // Block until the context is done.
118
- <- ctx .Done ()
119
-
120
- return cw .watcher .Close ()
121
- }
122
-
123
- // Watch reads events from the watcher's channel and reacts to changes.
124
- func (cw * CertWatcher ) Watch () {
125
95
for {
126
96
select {
127
- case event , ok := <- cw .watcher .Events :
128
- // Channel is closed.
129
- if ! ok {
130
- return
97
+ case <- ctx .Done ():
98
+ return nil
99
+ case <- ticker .C :
100
+ if err := cw .ReadCertificate (); err != nil {
101
+ log .Error (err , "failed read certificate" )
131
102
}
103
+ }
104
+ }
105
+ }
132
106
133
- cw .handleEvent (event )
134
-
135
- case err , ok := <- cw .watcher .Errors :
136
- // Channel is closed.
137
- if ! ok {
138
- return
139
- }
107
+ // updateCachedCertificate checks if the new certificate differs from the cache,
108
+ // updates it and returns the result if it was updated or not
109
+ func (cw * CertWatcher ) updateCachedCertificate (cert * tls.Certificate , keyPEMBlock []byte ) bool {
110
+ cw .Lock ()
111
+ defer cw .Unlock ()
140
112
141
- log .Error (err , "certificate watch error" )
142
- }
113
+ if cw .currentCert != nil &&
114
+ bytes .Equal (cw .currentCert .Certificate [0 ], cert .Certificate [0 ]) &&
115
+ bytes .Equal (cw .cachedKeyPEMBlock , keyPEMBlock ) {
116
+ log .V (7 ).Info ("certificate already cached" )
117
+ return false
143
118
}
119
+ cw .currentCert = cert
120
+ cw .cachedKeyPEMBlock = keyPEMBlock
121
+ return true
144
122
}
145
123
146
124
// ReadCertificate reads the certificate and key files from disk, parses them,
147
- // and updates the current certificate on the watcher. If a callback is set, it
125
+ // and updates the current certificate on the watcher if updated. If a callback is set, it
148
126
// is invoked with the new certificate.
149
127
func (cw * CertWatcher ) ReadCertificate () error {
150
128
metrics .ReadCertificateTotal .Inc ()
151
- cert , err := tls .LoadX509KeyPair (cw .certPath , cw .keyPath )
129
+ certPEMBlock , err := os .ReadFile (cw .certPath )
130
+ if err != nil {
131
+ metrics .ReadCertificateErrors .Inc ()
132
+ return err
133
+ }
134
+ keyPEMBlock , err := os .ReadFile (cw .keyPath )
152
135
if err != nil {
153
136
metrics .ReadCertificateErrors .Inc ()
154
137
return err
155
138
}
156
139
157
- cw .Lock ()
158
- cw .currentCert = & cert
159
- cw .Unlock ()
140
+ cert , err := tls .X509KeyPair (certPEMBlock , keyPEMBlock )
141
+ if err != nil {
142
+ metrics .ReadCertificateErrors .Inc ()
143
+ return err
144
+ }
145
+
146
+ if ! cw .updateCachedCertificate (& cert , keyPEMBlock ) {
147
+ return nil
148
+ }
160
149
161
150
log .Info ("Updated current TLS certificate" )
162
151
@@ -170,39 +159,3 @@ func (cw *CertWatcher) ReadCertificate() error {
170
159
}
171
160
return nil
172
161
}
173
-
174
- func (cw * CertWatcher ) handleEvent (event fsnotify.Event ) {
175
- // Only care about events which may modify the contents of the file.
176
- if ! (isWrite (event ) || isRemove (event ) || isCreate (event ) || isChmod (event )) {
177
- return
178
- }
179
-
180
- log .V (1 ).Info ("certificate event" , "event" , event )
181
-
182
- // If the file was removed or renamed, re-add the watch to the previous name
183
- if isRemove (event ) || isChmod (event ) {
184
- if err := cw .watcher .Add (event .Name ); err != nil {
185
- log .Error (err , "error re-watching file" )
186
- }
187
- }
188
-
189
- if err := cw .ReadCertificate (); err != nil {
190
- log .Error (err , "error re-reading certificate" )
191
- }
192
- }
193
-
194
- func isWrite (event fsnotify.Event ) bool {
195
- return event .Op .Has (fsnotify .Write )
196
- }
197
-
198
- func isCreate (event fsnotify.Event ) bool {
199
- return event .Op .Has (fsnotify .Create )
200
- }
201
-
202
- func isRemove (event fsnotify.Event ) bool {
203
- return event .Op .Has (fsnotify .Remove )
204
- }
205
-
206
- func isChmod (event fsnotify.Event ) bool {
207
- return event .Op .Has (fsnotify .Chmod )
208
- }
0 commit comments