@@ -17,12 +17,18 @@ limitations under the License.
17
17
package main
18
18
19
19
import (
20
+ "context"
20
21
"crypto/tls"
22
+ "encoding/base64"
23
+ "fmt"
21
24
"os"
22
- "path"
23
25
"sync"
24
26
27
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
+ "k8s.io/apimachinery/pkg/types"
29
+
25
30
"github.com/fsnotify/fsnotify"
31
+ admissionregistrationv1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1"
26
32
"k8s.io/klog/v2"
27
33
)
28
34
@@ -42,10 +48,12 @@ func readFile(filePath string) []byte {
42
48
}
43
49
44
50
type certReloader struct {
45
- tlsCertPath string
46
- tlsKeyPath string
47
- cert * tls.Certificate
48
- mu sync.RWMutex
51
+ tlsCertPath string
52
+ tlsKeyPath string
53
+ clientCaPath string
54
+ cert * tls.Certificate
55
+ mu sync.RWMutex
56
+ mutatingWebhookClient admissionregistrationv1.MutatingWebhookConfigurationInterface
49
57
}
50
58
51
59
func (cr * certReloader ) start (stop <- chan struct {}) error {
@@ -54,22 +62,43 @@ func (cr *certReloader) start(stop <-chan struct{}) error {
54
62
return err
55
63
}
56
64
57
- if err = watcher .Add (path .Dir (cr .tlsCertPath )); err != nil {
65
+ if err = watcher .Add (cr .tlsCertPath ); err != nil {
66
+ return err
67
+ }
68
+ if err = watcher .Add (cr .tlsKeyPath ); err != nil {
58
69
return err
59
70
}
60
- if err = watcher .Add (path . Dir ( cr .tlsKeyPath ) ); err != nil {
71
+ if err = watcher .Add (cr .clientCaPath ); err != nil {
61
72
return err
62
73
}
74
+
63
75
go func () {
64
76
defer watcher .Close ()
65
77
for {
66
78
select {
67
79
case event := <- watcher .Events :
68
- if event .Has (fsnotify .Create ) || event .Has (fsnotify .Write ) {
80
+ // we need to watch "Remove" events because Kubernetes uses symbolic links to point to ConfigMaps/Secrets volumes
81
+ if ! event .Has (fsnotify .Remove ) && ! event .Has (fsnotify .Create ) && ! event .Has (fsnotify .Write ) {
82
+ continue
83
+ }
84
+ switch event .Name {
85
+ case cr .tlsCertPath , cr .tlsKeyPath :
69
86
klog .V (2 ).InfoS ("New certificate found, reloading" )
70
87
if err := cr .load (); err != nil {
71
88
klog .ErrorS (err , "Failed to reload certificate" )
72
89
}
90
+ case cr .clientCaPath :
91
+ if err := cr .reloadWebhookCA (); err != nil {
92
+ klog .ErrorS (err , "Failed to reload client CA" )
93
+ }
94
+ default :
95
+ continue
96
+ }
97
+ // watches get removed along with the symlinks, so we need to add them back
98
+ if event .Has (fsnotify .Remove ) {
99
+ if err := watcher .Add (event .Name ); err != nil {
100
+ klog .ErrorS (err , "Failed to add watcher for file" , "filename" , event .Name )
101
+ }
73
102
}
74
103
case err := <- watcher .Errors :
75
104
klog .Warningf ("Error watching certificate files: %s" , err )
@@ -92,6 +121,36 @@ func (cr *certReloader) load() error {
92
121
return nil
93
122
}
94
123
124
+ func (cr * certReloader ) reloadWebhookCA () error {
125
+ client := cr .mutatingWebhookClient
126
+ webhook , err := client .Get (context .TODO (), webhookConfigName , metav1.GetOptions {})
127
+ if err != nil {
128
+ return err
129
+ }
130
+ if webhook == nil {
131
+ return fmt .Errorf ("webhook not found" )
132
+ }
133
+ if len (webhook .Webhooks ) == 0 {
134
+ return fmt .Errorf ("webhook configuration has no webhooks" )
135
+ }
136
+ currentBundle := webhook .Webhooks [0 ].ClientConfig .CABundle [:]
137
+ base64CurrentBundle := base64 .StdEncoding .EncodeToString (currentBundle )
138
+ newBundle := readFile (cr .clientCaPath )
139
+ base64NewBundle := base64 .StdEncoding .EncodeToString (newBundle )
140
+ // make sure clientCA actually changed
141
+ if base64CurrentBundle == base64NewBundle {
142
+ klog .V (2 ).InfoS ("Client CA did not change, skipping patch" )
143
+ return nil
144
+ }
145
+ klog .V (2 ).InfoS ("New client CA found, reloading and patching webhook" )
146
+ patch := []byte (fmt .Sprintf (`{"webhooks":[{"name":"%s","clientConfig":{"caBundle":"%s"}}]}` , webhookName , base64NewBundle ))
147
+ _ , err = client .Patch (context .TODO (), webhookConfigName , types .StrategicMergePatchType , patch , metav1.PatchOptions {})
148
+ if err == nil {
149
+ klog .V (2 ).InfoS ("Successfully patched webhook with new client CA" )
150
+ }
151
+ return err
152
+ }
153
+
95
154
func (cr * certReloader ) getCertificate (_ * tls.ClientHelloInfo ) (* tls.Certificate , error ) {
96
155
cr .mu .RLock ()
97
156
defer cr .mu .RUnlock ()
0 commit comments