1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package certclient
16
17 import (
18 "context"
19 "fmt"
20 "os"
21 "time"
22
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/cert/provisioner"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/cert/writer"
25
26 admissionregistration "k8s.io/api/admissionregistration/v1"
27 corev1 "k8s.io/api/core/v1"
28 apierrors "k8s.io/apimachinery/pkg/api/errors"
29 "k8s.io/apimachinery/pkg/util/wait"
30 "k8s.io/klog/v2"
31 "sigs.k8s.io/controller-runtime/pkg/client"
32 )
33
34 const (
35 maxJitterFactor = 0.1
36 )
37
38
39 var defaultCertRefreshInterval = 3 * 30 * 24 * time.Hour
40
41
42 type CertClient struct {
43 webhookManifests []client.Object
44 svc *corev1.Service
45 kubeClient client.Client
46 provisioner *provisioner.Provisioner
47 }
48
49 type Options struct {
50 WebhookManifests []client.Object
51 Service *corev1.Service
52 KubeClient client.Client
53 CertDir string
54 CertWriter writer.CertWriter
55 }
56
57 func New(opts Options) (*CertClient, error) {
58 certWriter, err := certWriterFromOptsOrNew(opts)
59 if err != nil {
60 return nil, err
61 }
62 return &CertClient{
63 webhookManifests: opts.WebhookManifests,
64 svc: opts.Service,
65 provisioner: &provisioner.Provisioner{
66 CertWriter: certWriter,
67 },
68 kubeClient: opts.KubeClient,
69 }, nil
70 }
71
72 func certWriterFromOptsOrNew(opts Options) (writer.CertWriter, error) {
73 if opts.CertWriter != nil {
74 return opts.CertWriter, nil
75 }
76 certWriter, err := writer.NewFSCertWriter(
77 writer.FSCertWriterOptions{
78 Path: opts.CertDir,
79 })
80 if err != nil {
81 return nil, fmt.Errorf("error creating FS cert writer: %w", err)
82 }
83 return certWriter, nil
84 }
85
86 func (c *CertClient) RefreshCertsAndInstall() error {
87 _, err := c.provisioner.Provision(provisioner.Options{
88 ClientConfig: &admissionregistration.WebhookClientConfig{
89 CABundle: []byte{},
90 Service: &admissionregistration.ServiceReference{
91 Name: c.svc.GetName(),
92 Namespace: c.svc.GetNamespace(),
93 },
94 },
95 Objects: c.webhookManifests,
96 })
97 if err != nil {
98 return fmt.Errorf("error provisioning certs: %w", err)
99 }
100 objects := append([]client.Object{c.svc}, c.webhookManifests...)
101 return batchCreateOrReplace(c.kubeClient, objects...)
102 }
103
104 func (c *CertClient) Start(ctx context.Context) error {
105 timer := time.Tick(wait.Jitter(defaultCertRefreshInterval, maxJitterFactor))
106 for {
107 select {
108 case <-timer:
109 if err := c.RefreshCertsAndInstall(); err != nil {
110 return fmt.Errorf("error refreshing certs: %w", err)
111 }
112
113
114
115
116
117
118
119
120 klog.Warningf("forcing process exit after ~%v to reload webhook certificates", defaultCertRefreshInterval)
121 os.Exit(1)
122
123 case <-ctx.Done():
124 return nil
125 }
126 }
127 }
128
129 type mutateFn func(current, desired *client.Object) error
130
131 var serviceFn = func(current, desired *client.Object) error {
132 typedC := (*current).(*corev1.Service)
133 typedD := (*desired).(*corev1.Service)
134 typedC.Spec.Selector = typedD.Spec.Selector
135 typedC.Spec.Ports = typedD.Spec.Ports
136 return nil
137 }
138
139 var mutatingWebhookConfigFn = func(current, desired *client.Object) error {
140 typedC := (*current).(*admissionregistration.MutatingWebhookConfiguration)
141 typedD := (*desired).(*admissionregistration.MutatingWebhookConfiguration)
142 typedC.Webhooks = typedD.Webhooks
143 return nil
144 }
145
146 var validatingWebhookConfigFn = func(current, desired *client.Object) error {
147 typedC := (*current).(*admissionregistration.ValidatingWebhookConfiguration)
148 typedD := (*desired).(*admissionregistration.ValidatingWebhookConfiguration)
149 typedC.Webhooks = typedD.Webhooks
150 return nil
151 }
152
153 var genericFn = func(current, desired *client.Object) error {
154 *current = *desired
155 return nil
156 }
157
158
159
160
161 func createOrReplaceHelper(c client.Client, obj client.Object, fn mutateFn) error {
162 if obj == nil {
163 return nil
164 }
165 err := c.Create(context.Background(), obj)
166 if apierrors.IsAlreadyExists(err) {
167
168
169 existing, ok := obj.DeepCopyObject().(client.Object)
170 if !ok {
171 return fmt.Errorf("unable to cast deep copy to client.Object")
172 }
173 objectKey := client.ObjectKeyFromObject(obj)
174 err = c.Get(context.Background(), objectKey, existing)
175 if err != nil {
176 return err
177 }
178 err = fn(&existing, &obj)
179 if err != nil {
180 return err
181 }
182 return c.Update(context.Background(), existing)
183 }
184 return err
185 }
186
187
188
189
190
191
192 func createOrReplace(c client.Client, obj client.Object) error {
193 if obj == nil {
194 return nil
195 }
196 switch obj.(type) {
197 case *admissionregistration.MutatingWebhookConfiguration:
198 return createOrReplaceHelper(c, obj, mutatingWebhookConfigFn)
199 case *admissionregistration.ValidatingWebhookConfiguration:
200 return createOrReplaceHelper(c, obj, validatingWebhookConfigFn)
201 case *corev1.Service:
202 return createOrReplaceHelper(c, obj, serviceFn)
203 default:
204 return createOrReplaceHelper(c, obj, genericFn)
205 }
206 }
207
208 func batchCreateOrReplace(c client.Client, objs ...client.Object) error {
209 for i := range objs {
210 err := createOrReplace(c, objs[i])
211 if err != nil {
212 return err
213 }
214 }
215 return nil
216 }
217
View as plain text