blob: d31b6cde913e3e7bdb3bf9e3e455a398fb14b586 [file] [log] [blame]
giolekva96755fa2021-10-06 21:00:00 +04001package controllers
2
3import (
4 "context"
5 "fmt"
6 "io/ioutil"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "time"
11
12 corev1 "k8s.io/api/core/v1"
13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
giolekva96755fa2021-10-06 21:00:00 +040014 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
15 "k8s.io/apimachinery/pkg/util/wait"
giolekva5ebab802021-10-07 21:50:34 +040016 corev1informers "k8s.io/client-go/informers/core/v1"
giolekva96755fa2021-10-06 21:00:00 +040017 "k8s.io/client-go/kubernetes"
giolekva5ebab802021-10-07 21:50:34 +040018 corev1listers "k8s.io/client-go/listers/core/v1"
giolekva96755fa2021-10-06 21:00:00 +040019 "k8s.io/client-go/tools/cache"
20 "k8s.io/client-go/util/workqueue"
21 "k8s.io/klog/v2"
22
23 nebulav1 "github.com/giolekva/pcloud/core/nebula/apis/nebula/v1"
24 clientset "github.com/giolekva/pcloud/core/nebula/generated/clientset/versioned"
giolekva5ebab802021-10-07 21:50:34 +040025 informers "github.com/giolekva/pcloud/core/nebula/generated/informers/externalversions/nebula/v1"
giolekva96755fa2021-10-06 21:00:00 +040026 listers "github.com/giolekva/pcloud/core/nebula/generated/listers/nebula/v1"
27)
28
29var secretImmutable = true
30
giolekva5ebab802021-10-07 21:50:34 +040031type caRef struct {
32 key string
33}
34
35type nodeRef struct {
36 key string
37}
38
giolekva695960b2021-10-07 22:00:29 +040039type NebulaController struct {
giolekva96755fa2021-10-06 21:00:00 +040040 kubeClient kubernetes.Interface
41 nebulaClient clientset.Interface
42 caLister listers.NebulaCALister
43 caSynced cache.InformerSynced
giolekva5ebab802021-10-07 21:50:34 +040044 nodeLister listers.NebulaNodeLister
45 nodeSynced cache.InformerSynced
46 secretLister corev1listers.SecretLister
47 secretSynced cache.InformerSynced
giolekva96755fa2021-10-06 21:00:00 +040048 workqueue workqueue.RateLimitingInterface
49
50 nebulaCert string
51}
52
giolekva695960b2021-10-07 22:00:29 +040053func NewNebulaController(kubeClient kubernetes.Interface,
giolekva5ebab802021-10-07 21:50:34 +040054 nebulaClient clientset.Interface,
55 caInformer informers.NebulaCAInformer,
56 nodeInformer informers.NebulaNodeInformer,
57 secretInformer corev1informers.SecretInformer,
giolekva695960b2021-10-07 22:00:29 +040058 nebulaCert string) *NebulaController {
59 c := &NebulaController{
giolekva96755fa2021-10-06 21:00:00 +040060 kubeClient: kubeClient,
61 nebulaClient: nebulaClient,
giolekva5ebab802021-10-07 21:50:34 +040062 caLister: caInformer.Lister(),
63 caSynced: caInformer.Informer().HasSynced,
64 nodeLister: nodeInformer.Lister(),
65 nodeSynced: nodeInformer.Informer().HasSynced,
66 secretLister: secretInformer.Lister(),
67 secretSynced: secretInformer.Informer().HasSynced,
68 workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Nebula"),
giolekva96755fa2021-10-06 21:00:00 +040069 nebulaCert: nebulaCert,
70 }
71
giolekva5ebab802021-10-07 21:50:34 +040072 caInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
73 AddFunc: c.enqueueCA,
giolekva96755fa2021-10-06 21:00:00 +040074 UpdateFunc: func(_, o interface{}) {
giolekva5ebab802021-10-07 21:50:34 +040075 c.enqueueCA(o)
76 },
77 DeleteFunc: func(o interface{}) {
78 },
79 })
80 nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
81 AddFunc: c.enqueueNode,
82 UpdateFunc: func(_, o interface{}) {
83 c.enqueueNode(o)
giolekva96755fa2021-10-06 21:00:00 +040084 },
85 DeleteFunc: func(o interface{}) {
86 },
87 })
88
89 return c
90}
91
giolekva695960b2021-10-07 22:00:29 +040092func (c *NebulaController) enqueueCA(o interface{}) {
giolekva96755fa2021-10-06 21:00:00 +040093 var key string
94 var err error
95 if key, err = cache.MetaNamespaceKeyFunc(o); err != nil {
96 utilruntime.HandleError(err)
97 return
98 }
giolekva5ebab802021-10-07 21:50:34 +040099 c.workqueue.Add(caRef{key})
100}
101
giolekva695960b2021-10-07 22:00:29 +0400102func (c *NebulaController) enqueueNode(o interface{}) {
giolekva5ebab802021-10-07 21:50:34 +0400103 var key string
104 var err error
105 if key, err = cache.MetaNamespaceKeyFunc(o); err != nil {
106 utilruntime.HandleError(err)
107 return
108 }
109 c.workqueue.Add(nodeRef{key})
giolekva96755fa2021-10-06 21:00:00 +0400110}
111
giolekva695960b2021-10-07 22:00:29 +0400112func (c *NebulaController) Run(workers int, stopCh <-chan struct{}) error {
giolekva96755fa2021-10-06 21:00:00 +0400113 defer utilruntime.HandleCrash()
114 defer c.workqueue.ShutDown()
115 klog.Info("Starting NebulaCA controller")
116 klog.Info("Waiting for informer caches to sync")
giolekva5ebab802021-10-07 21:50:34 +0400117 if ok := cache.WaitForCacheSync(stopCh, c.caSynced, c.nodeSynced, c.secretSynced); !ok {
giolekva96755fa2021-10-06 21:00:00 +0400118 return fmt.Errorf("Failed to wait for caches to sync")
119 }
120 fmt.Println("Starting workers")
121 for i := 0; i < workers; i++ {
122 go wait.Until(c.runWorker, time.Second, stopCh)
123 }
124 fmt.Println("Started workers")
125 <-stopCh
126 fmt.Println("Shutting down workers")
127 return nil
128}
129
giolekva695960b2021-10-07 22:00:29 +0400130func (c *NebulaController) runWorker() {
giolekva96755fa2021-10-06 21:00:00 +0400131 for c.processNextWorkItem() {
132 }
133}
134
giolekva695960b2021-10-07 22:00:29 +0400135func (c *NebulaController) processNextWorkItem() bool {
giolekva96755fa2021-10-06 21:00:00 +0400136 o, shutdown := c.workqueue.Get()
137 if shutdown {
138 return false
139 }
140 err := func(o interface{}) error {
141 defer c.workqueue.Done(o)
giolekva5ebab802021-10-07 21:50:34 +0400142 if ref, ok := o.(caRef); ok {
143 if err := c.processCAWithKey(ref.key); err != nil {
144 c.workqueue.AddRateLimited(ref)
145 return fmt.Errorf("Error syncing '%s': %s, requeuing", ref.key, err.Error())
146 }
147 fmt.Printf("Successfully synced CA '%s'\n", ref.key)
148 } else if ref, ok := o.(nodeRef); ok {
149 if err := c.processNodeWithKey(ref.key); err != nil {
150 c.workqueue.AddRateLimited(ref)
151 return fmt.Errorf("Error syncing '%s': %s, requeuing", ref.key, err.Error())
152 }
153 fmt.Printf("Successfully synced Node '%s'\n", ref.key)
154 } else {
giolekva96755fa2021-10-06 21:00:00 +0400155 c.workqueue.Forget(o)
giolekva5ebab802021-10-07 21:50:34 +0400156 utilruntime.HandleError(fmt.Errorf("expected reference in workqueue but got %#v", o))
giolekva96755fa2021-10-06 21:00:00 +0400157 return nil
158 }
giolekva96755fa2021-10-06 21:00:00 +0400159 c.workqueue.Forget(o)
giolekva96755fa2021-10-06 21:00:00 +0400160 return nil
161 }(o)
162 if err != nil {
163 utilruntime.HandleError(err)
164 return true
165 }
166 return true
167}
168
giolekva695960b2021-10-07 22:00:29 +0400169func (c *NebulaController) processCAWithKey(key string) error {
giolekva96755fa2021-10-06 21:00:00 +0400170 namespace, name, err := cache.SplitMetaNamespaceKey(key)
171 if err != nil {
172 return nil
173 }
174 ca, err := c.getCA(namespace, name)
175 if err != nil {
176 panic(err)
177 }
178 if ca.Status.State == nebulav1.NebulaCAStateReady {
179 fmt.Printf("%s CA is already in Ready state\n", ca.Name)
180 return nil
181 }
giolekva4b2934b2021-10-08 19:37:12 +0400182 keyDir, err := generateCAKey(ca.Name, c.nebulaCert)
giolekva96755fa2021-10-06 21:00:00 +0400183 if err != nil {
184 panic(err)
185 }
186 defer os.RemoveAll(keyDir)
187 secret, err := createSecretFromDir(keyDir)
188 if err != nil {
189 panic(err)
190 }
191 secret.Immutable = &secretImmutable
192 secret.Name = ca.Spec.SecretName
193 _, err = c.kubeClient.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
194 if err != nil {
195 panic(err)
196 }
giolekva5ebab802021-10-07 21:50:34 +0400197 err = c.updateCAStatus(ca, nebulav1.NebulaCAStateReady, "Generated credentials")
giolekva96755fa2021-10-06 21:00:00 +0400198 if err != nil {
199 panic(err)
200 }
201 return nil
202}
203
giolekva695960b2021-10-07 22:00:29 +0400204func (c *NebulaController) processNodeWithKey(key string) error {
giolekva5ebab802021-10-07 21:50:34 +0400205 namespace, name, err := cache.SplitMetaNamespaceKey(key)
206 if err != nil {
207 return nil
208 }
209 node, err := c.getNode(namespace, name)
210 if err != nil {
211 panic(err)
212 }
213 if node.Status.State == nebulav1.NebulaNodeStateReady {
214 fmt.Printf("%s Node is already in Ready state\n", node.Name)
215 return nil
216 }
giolekva4b2934b2021-10-08 19:37:12 +0400217 ca, err := c.getCA(node.Spec.CANamespace, node.Spec.CAName)
giolekva5ebab802021-10-07 21:50:34 +0400218 if ca.Status.State != nebulav1.NebulaCAStateReady {
219 return fmt.Errorf("Referenced CA %s is not ready yet.", node.Spec.CAName)
220 }
221 caSecret, err := c.getSecret(ca.Namespace, ca.Spec.SecretName)
222 if err != nil {
223 panic(err)
224 }
225 dir, err := extractSecret(caSecret)
226 if err != nil {
227 panic(err)
228 }
giolekva4b2934b2021-10-08 19:37:12 +0400229 if node.Spec.PubKey == "" {
230 if err := generateNodeKey(node.Name, node.Spec.IPCidr, dir, c.nebulaCert); err != nil {
231 panic(err)
232 }
233 } else {
234 if err := generateNodeKeyFromPub(node.Name, node.Spec.IPCidr, node.Spec.PubKey, dir, c.nebulaCert); err != nil {
235 panic(err)
236 }
giolekva5ebab802021-10-07 21:50:34 +0400237 }
238 defer os.RemoveAll(dir)
239 if err := os.Remove(filepath.Join(dir, "ca.key")); err != nil {
240 panic(err)
241 }
242 if err := os.Remove(filepath.Join(dir, "ca.png")); err != nil {
243 panic(err)
244 }
245 secret, err := createSecretFromDir(dir)
246 if err != nil {
247 panic(err)
248 }
249 secret.Immutable = &secretImmutable
250 secret.Name = node.Spec.SecretName
251 _, err = c.kubeClient.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
252 if err != nil {
253 panic(err)
254 }
255 err = c.updateNodeStatus(node, nebulav1.NebulaNodeStateReady, "Generated credentials")
256 if err != nil {
257 panic(err)
258 }
259 return nil
260}
261
giolekva695960b2021-10-07 22:00:29 +0400262func (c *NebulaController) updateCAStatus(ca *nebulav1.NebulaCA, state nebulav1.NebulaCAState, msg string) error {
giolekva96755fa2021-10-06 21:00:00 +0400263 cp := ca.DeepCopy()
264 cp.Status.State = state
265 cp.Status.Message = msg
266 _, err := c.nebulaClient.LekvaV1().NebulaCAs(cp.Namespace).UpdateStatus(context.TODO(), cp, metav1.UpdateOptions{})
267 return err
268}
269
giolekva695960b2021-10-07 22:00:29 +0400270func (c *NebulaController) updateNodeStatus(node *nebulav1.NebulaNode, state nebulav1.NebulaNodeState, msg string) error {
giolekva5ebab802021-10-07 21:50:34 +0400271 cp := node.DeepCopy()
272 cp.Status.State = state
273 cp.Status.Message = msg
274 _, err := c.nebulaClient.LekvaV1().NebulaNodes(cp.Namespace).UpdateStatus(context.TODO(), cp, metav1.UpdateOptions{})
275 return err
276}
277
giolekva96755fa2021-10-06 21:00:00 +0400278func createSecretFromDir(path string) (*corev1.Secret, error) {
279 all, err := ioutil.ReadDir(path)
280 if err != nil {
281 return nil, err
282 }
283 secret := &corev1.Secret{
284 Data: make(map[string][]byte),
285 }
286 for _, f := range all {
287 if f.IsDir() {
288 continue
289 }
290 d, err := ioutil.ReadFile(filepath.Join(path, f.Name()))
291 if err != nil {
292 return nil, err
293 }
294 secret.Data[f.Name()] = d
295 }
296 return secret, nil
297}
298
giolekva5ebab802021-10-07 21:50:34 +0400299func extractSecret(secret *corev1.Secret) (string, error) {
300 tmp, err := os.MkdirTemp("", secret.Name)
301 if err != nil {
302 return "", err
303 }
304 for name, data := range secret.Data {
305 if err := ioutil.WriteFile(filepath.Join(tmp, name), data, 0644); err != nil {
306 defer os.RemoveAll(tmp)
307 return "", nil
308 }
309 }
310 return tmp, nil
311}
312
giolekva96755fa2021-10-06 21:00:00 +0400313func generateCAKey(name, nebulaCert string) (string, error) {
314 tmp, err := os.MkdirTemp("", name)
315 if err != nil {
316 return "", err
317 }
giolekva96755fa2021-10-06 21:00:00 +0400318 cmd := exec.Command(nebulaCert, "ca",
319 "-name", name,
320 "-out-key", filepath.Join(tmp, "ca.key"),
321 "-out-crt", filepath.Join(tmp, "ca.crt"),
322 "-out-qr", filepath.Join(tmp, "ca.png"))
giolekva4b2934b2021-10-08 19:37:12 +0400323 if d, err := cmd.CombinedOutput(); err != nil {
324 return "", fmt.Errorf(string(d))
giolekva96755fa2021-10-06 21:00:00 +0400325 }
326 return tmp, nil
327}
328
giolekva4b2934b2021-10-08 19:37:12 +0400329func generateNodeKeyFromPub(name, ip, pubKey, dir, nebulaCert string) error {
330 hostPub := filepath.Join(dir, "host.pub")
331 if err := ioutil.WriteFile(hostPub, []byte(pubKey), 0644); err != nil {
332 return err
333 }
334 defer os.Remove(hostPub)
335 cmd := exec.Command(nebulaCert, "sign",
336 "-ca-crt", filepath.Join(dir, "ca.crt"),
337 "-ca-key", filepath.Join(dir, "ca.key"),
338 "-name", name,
339 "-ip", ip,
340 "-in-pub", hostPub,
341 "-out-crt", filepath.Join(dir, "host.crt"),
342 "-out-qr", filepath.Join(dir, "host.png"))
343 if d, err := cmd.CombinedOutput(); err != nil {
344 return fmt.Errorf(string(d))
345 }
346 return nil
347}
348
giolekva5ebab802021-10-07 21:50:34 +0400349func generateNodeKey(name, ip, dir, nebulaCert string) error {
350 cmd := exec.Command(nebulaCert, "sign",
351 "-ca-crt", filepath.Join(dir, "ca.crt"),
352 "-ca-key", filepath.Join(dir, "ca.key"),
353 "-name", name,
354 "-ip", ip,
355 "-out-key", filepath.Join(dir, "host.key"),
356 "-out-crt", filepath.Join(dir, "host.crt"),
357 "-out-qr", filepath.Join(dir, "host.png"))
358 if d, err := cmd.CombinedOutput(); err != nil {
359 return fmt.Errorf(string(d))
360 }
361 return nil
362}
363
giolekva695960b2021-10-07 22:00:29 +0400364func (c *NebulaController) getCA(namespace, name string) (*nebulav1.NebulaCA, error) {
giolekva4b2934b2021-10-08 19:37:12 +0400365 return c.caLister.NebulaCAs(namespace).Get(name)
366 // s := labels.NewSelector()
367 // r, err := labels.NewRequirement("metadata.namespace", selection.Equals, []string{namespace})
368 // if err != nil {
369 // panic(err)
370 // }
371 // r1, err := labels.NewRequirement("metadata.name", selection.Equals, []string{name})
372 // if err != nil {
373 // panic(err)
374 // }
375 // s.Add(*r, *r1)
376 // ncas, err := c.caLister.List(s)
377 // if err != nil {
378 // panic(err)
379 // }
380 // if len(ncas) != 1 {
381 // panic("err")
382 // }
383 // return ncas[0], nil
giolekva96755fa2021-10-06 21:00:00 +0400384}
giolekva5ebab802021-10-07 21:50:34 +0400385
giolekva695960b2021-10-07 22:00:29 +0400386func (c *NebulaController) getNode(namespace, name string) (*nebulav1.NebulaNode, error) {
giolekva4b2934b2021-10-08 19:37:12 +0400387 return c.nodeLister.NebulaNodes(namespace).Get(name)
388 // s := labels.NewSelector()
389 // r, err := labels.NewRequirement("metadata.namespace", selection.Equals, []string{namespace})
390 // if err != nil {
391 // panic(err)
392 // }
393 // r1, err := labels.NewRequirement("metadata.name", selection.Equals, []string{name})
394 // if err != nil {
395 // panic(err)
396 // }
397 // s.Add(*r, *r1)
398 // nodes, err := c.nodeLister.List(s)
399 // if err != nil {
400 // panic(err)
401 // }
402 // if len(nodes) != 1 {
403 // panic("err")
404 // }
405 // return nodes[0], nil
giolekva5ebab802021-10-07 21:50:34 +0400406}
407
giolekva695960b2021-10-07 22:00:29 +0400408func (c *NebulaController) getSecret(namespace, name string) (*corev1.Secret, error) {
giolekva5ebab802021-10-07 21:50:34 +0400409 return c.secretLister.Secrets(namespace).Get(name)
410}