process nodes
diff --git a/core/nebula/apis/nebula/v1/register.go b/core/nebula/apis/nebula/v1/register.go
index 3e17a02..5586188 100644
--- a/core/nebula/apis/nebula/v1/register.go
+++ b/core/nebula/apis/nebula/v1/register.go
@@ -33,6 +33,8 @@
 	scheme.AddKnownTypes(SchemeGroupVersion,
 		&NebulaCA{},
 		&NebulaCAList{},
+		&NebulaNode{},
+		&NebulaNodeList{},
 	)
 
 	scheme.AddKnownTypes(SchemeGroupVersion,
diff --git a/core/nebula/apis/nebula/v1/types.go b/core/nebula/apis/nebula/v1/types.go
index 0eddd7b..1a33752 100644
--- a/core/nebula/apis/nebula/v1/types.go
+++ b/core/nebula/apis/nebula/v1/types.go
@@ -4,10 +4,9 @@
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-// genclient:nonNamespaced
-
-// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 // +genclient
+// genclient:nonNamespaced
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 type NebulaCA struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata"`
@@ -40,3 +39,41 @@
 
 	Items []NebulaCA `json:"items"`
 }
+
+// +genclient
+// genclient:nonNamespaced
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+type NebulaNode struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata"`
+
+	Spec   NebulaNodeSpec   `json:"spec"`
+	Status NebulaNodeStatus `json:"status,omitempty"`
+}
+
+type NebulaNodeSpec struct {
+	CAName     string `json:"caName"`
+	NodeName   string `json:"nodeName"`
+	IPCidr     string `json:"ipCidr"`
+	SecretName string `json:"secretName"`
+}
+
+type NebulaNodeStatus struct {
+	State   NebulaNodeState `json:"state,omitempty"`
+	Message string          `json:"message,omitempty"`
+}
+
+type NebulaNodeState string
+
+const (
+	NebulaNodeStateCreating NebulaNodeState = "Creating"
+	NebulaNodeStateReady    NebulaNodeState = "Ready"
+)
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+type NebulaNodeList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata"`
+
+	Items []NebulaNode `json:"items"`
+}
diff --git a/core/nebula/apis/nebula/v1/zz_generated.deepcopy.go b/core/nebula/apis/nebula/v1/zz_generated.deepcopy.go
index 2acd8b3..e47733d 100644
--- a/core/nebula/apis/nebula/v1/zz_generated.deepcopy.go
+++ b/core/nebula/apis/nebula/v1/zz_generated.deepcopy.go
@@ -102,3 +102,96 @@
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NebulaNode) DeepCopyInto(out *NebulaNode) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	out.Spec = in.Spec
+	out.Status = in.Status
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NebulaNode.
+func (in *NebulaNode) DeepCopy() *NebulaNode {
+	if in == nil {
+		return nil
+	}
+	out := new(NebulaNode)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *NebulaNode) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NebulaNodeList) DeepCopyInto(out *NebulaNodeList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]NebulaNode, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NebulaNodeList.
+func (in *NebulaNodeList) DeepCopy() *NebulaNodeList {
+	if in == nil {
+		return nil
+	}
+	out := new(NebulaNodeList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *NebulaNodeList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NebulaNodeSpec) DeepCopyInto(out *NebulaNodeSpec) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NebulaNodeSpec.
+func (in *NebulaNodeSpec) DeepCopy() *NebulaNodeSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(NebulaNodeSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NebulaNodeStatus) DeepCopyInto(out *NebulaNodeStatus) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NebulaNodeStatus.
+func (in *NebulaNodeStatus) DeepCopy() *NebulaNodeStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(NebulaNodeStatus)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/core/nebula/controllers/ca.go b/core/nebula/controllers/ca.go
index e5279da..452b9e5 100644
--- a/core/nebula/controllers/ca.go
+++ b/core/nebula/controllers/ca.go
@@ -15,44 +15,74 @@
 	"k8s.io/apimachinery/pkg/selection"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/apimachinery/pkg/util/wait"
+	corev1informers "k8s.io/client-go/informers/core/v1"
 	"k8s.io/client-go/kubernetes"
+	corev1listers "k8s.io/client-go/listers/core/v1"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 	"k8s.io/klog/v2"
 
 	nebulav1 "github.com/giolekva/pcloud/core/nebula/apis/nebula/v1"
 	clientset "github.com/giolekva/pcloud/core/nebula/generated/clientset/versioned"
-	informers "github.com/giolekva/pcloud/core/nebula/generated/informers/externalversions"
+	informers "github.com/giolekva/pcloud/core/nebula/generated/informers/externalversions/nebula/v1"
 	listers "github.com/giolekva/pcloud/core/nebula/generated/listers/nebula/v1"
 )
 
 var secretImmutable = true
 
+type caRef struct {
+	key string
+}
+
+type nodeRef struct {
+	key string
+}
+
 type CAController struct {
 	kubeClient   kubernetes.Interface
 	nebulaClient clientset.Interface
 	caLister     listers.NebulaCALister
 	caSynced     cache.InformerSynced
+	nodeLister   listers.NebulaNodeLister
+	nodeSynced   cache.InformerSynced
+	secretLister corev1listers.SecretLister
+	secretSynced cache.InformerSynced
 	workqueue    workqueue.RateLimitingInterface
 
 	nebulaCert string
 }
 
-func NewCAController(kubeClient kubernetes.Interface, nebulaClient clientset.Interface, nebulaInformerFactory informers.SharedInformerFactory, nebulaCert string) *CAController {
-	nebulaInformer := nebulaInformerFactory.Lekva().V1().NebulaCAs().Informer()
+func NewCAController(kubeClient kubernetes.Interface,
+	nebulaClient clientset.Interface,
+	caInformer informers.NebulaCAInformer,
+	nodeInformer informers.NebulaNodeInformer,
+	secretInformer corev1informers.SecretInformer,
+	nebulaCert string) *CAController {
 	c := &CAController{
 		kubeClient:   kubeClient,
 		nebulaClient: nebulaClient,
-		caLister:     nebulaInformerFactory.Lekva().V1().NebulaCAs().Lister(),
-		caSynced:     nebulaInformer.HasSynced,
-		workqueue:    workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "NebulaCAs"),
+		caLister:     caInformer.Lister(),
+		caSynced:     caInformer.Informer().HasSynced,
+		nodeLister:   nodeInformer.Lister(),
+		nodeSynced:   nodeInformer.Informer().HasSynced,
+		secretLister: secretInformer.Lister(),
+		secretSynced: secretInformer.Informer().HasSynced,
+		workqueue:    workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Nebula"),
 		nebulaCert:   nebulaCert,
 	}
 
-	nebulaInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: c.enqueue,
+	caInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: c.enqueueCA,
 		UpdateFunc: func(_, o interface{}) {
-			c.enqueue(o)
+			c.enqueueCA(o)
+		},
+		DeleteFunc: func(o interface{}) {
+		},
+	})
+	nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: c.enqueueNode,
+		UpdateFunc: func(_, o interface{}) {
+			c.enqueueNode(o)
 		},
 		DeleteFunc: func(o interface{}) {
 		},
@@ -61,14 +91,24 @@
 	return c
 }
 
-func (c *CAController) enqueue(o interface{}) {
+func (c *CAController) enqueueCA(o interface{}) {
 	var key string
 	var err error
 	if key, err = cache.MetaNamespaceKeyFunc(o); err != nil {
 		utilruntime.HandleError(err)
 		return
 	}
-	c.workqueue.Add(key)
+	c.workqueue.Add(caRef{key})
+}
+
+func (c *CAController) enqueueNode(o interface{}) {
+	var key string
+	var err error
+	if key, err = cache.MetaNamespaceKeyFunc(o); err != nil {
+		utilruntime.HandleError(err)
+		return
+	}
+	c.workqueue.Add(nodeRef{key})
 }
 
 func (c *CAController) Run(workers int, stopCh <-chan struct{}) error {
@@ -76,7 +116,7 @@
 	defer c.workqueue.ShutDown()
 	klog.Info("Starting NebulaCA controller")
 	klog.Info("Waiting for informer caches to sync")
-	if ok := cache.WaitForCacheSync(stopCh, c.caSynced); !ok {
+	if ok := cache.WaitForCacheSync(stopCh, c.caSynced, c.nodeSynced, c.secretSynced); !ok {
 		return fmt.Errorf("Failed to wait for caches to sync")
 	}
 	fmt.Println("Starting workers")
@@ -101,19 +141,24 @@
 	}
 	err := func(o interface{}) error {
 		defer c.workqueue.Done(o)
-		var key string
-		var ok bool
-		if key, ok = o.(string); !ok {
+		if ref, ok := o.(caRef); ok {
+			if err := c.processCAWithKey(ref.key); err != nil {
+				c.workqueue.AddRateLimited(ref)
+				return fmt.Errorf("Error syncing '%s': %s, requeuing", ref.key, err.Error())
+			}
+			fmt.Printf("Successfully synced CA '%s'\n", ref.key)
+		} else if ref, ok := o.(nodeRef); ok {
+			if err := c.processNodeWithKey(ref.key); err != nil {
+				c.workqueue.AddRateLimited(ref)
+				return fmt.Errorf("Error syncing '%s': %s, requeuing", ref.key, err.Error())
+			}
+			fmt.Printf("Successfully synced Node '%s'\n", ref.key)
+		} else {
 			c.workqueue.Forget(o)
-			utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", o))
+			utilruntime.HandleError(fmt.Errorf("expected reference in workqueue but got %#v", o))
 			return nil
 		}
-		if err := c.processCAWithKey(key); err != nil {
-			c.workqueue.AddRateLimited(key)
-			return fmt.Errorf("Rrror syncing '%s': %s, requeuing", key, err.Error())
-		}
 		c.workqueue.Forget(o)
-		fmt.Printf("Successfully synced '%s'\n", key)
 		return nil
 	}(o)
 	if err != nil {
@@ -151,14 +196,66 @@
 	if err != nil {
 		panic(err)
 	}
-	err = c.updateStatus(ca, nebulav1.NebulaCAStateReady, "Generated credentials")
+	err = c.updateCAStatus(ca, nebulav1.NebulaCAStateReady, "Generated credentials")
 	if err != nil {
 		panic(err)
 	}
 	return nil
 }
 
-func (c *CAController) updateStatus(ca *nebulav1.NebulaCA, state nebulav1.NebulaCAState, msg string) error {
+func (c *CAController) processNodeWithKey(key string) error {
+	namespace, name, err := cache.SplitMetaNamespaceKey(key)
+	if err != nil {
+		return nil
+	}
+	node, err := c.getNode(namespace, name)
+	if err != nil {
+		panic(err)
+	}
+	if node.Status.State == nebulav1.NebulaNodeStateReady {
+		fmt.Printf("%s Node is already in Ready state\n", node.Name)
+		return nil
+	}
+	ca, err := c.getCA(namespace, node.Spec.CAName)
+	if ca.Status.State != nebulav1.NebulaCAStateReady {
+		return fmt.Errorf("Referenced CA %s is not ready yet.", node.Spec.CAName)
+	}
+	caSecret, err := c.getSecret(ca.Namespace, ca.Spec.SecretName)
+	if err != nil {
+		panic(err)
+	}
+	dir, err := extractSecret(caSecret)
+	if err != nil {
+		panic(err)
+	}
+	if err := generateNodeKey(node.Spec.NodeName, node.Spec.IPCidr, dir, c.nebulaCert); err != nil {
+		panic(err)
+	}
+	defer os.RemoveAll(dir)
+	if err := os.Remove(filepath.Join(dir, "ca.key")); err != nil {
+		panic(err)
+	}
+	if err := os.Remove(filepath.Join(dir, "ca.png")); err != nil {
+		panic(err)
+	}
+	secret, err := createSecretFromDir(dir)
+	if err != nil {
+		panic(err)
+	}
+	secret.Immutable = &secretImmutable
+	secret.Name = node.Spec.SecretName
+	_, err = c.kubeClient.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
+	if err != nil {
+		panic(err)
+	}
+	err = c.updateNodeStatus(node, nebulav1.NebulaNodeStateReady, "Generated credentials")
+	if err != nil {
+		panic(err)
+	}
+	return nil
+}
+
+func (c *CAController) updateCAStatus(ca *nebulav1.NebulaCA, state nebulav1.NebulaCAState, msg string) error {
 	cp := ca.DeepCopy()
 	cp.Status.State = state
 	cp.Status.Message = msg
@@ -166,6 +263,14 @@
 	return err
 }
 
+func (c *CAController) updateNodeStatus(node *nebulav1.NebulaNode, state nebulav1.NebulaNodeState, msg string) error {
+	cp := node.DeepCopy()
+	cp.Status.State = state
+	cp.Status.Message = msg
+	_, err := c.nebulaClient.LekvaV1().NebulaNodes(cp.Namespace).UpdateStatus(context.TODO(), cp, metav1.UpdateOptions{})
+	return err
+}
+
 func createSecretFromDir(path string) (*corev1.Secret, error) {
 	all, err := ioutil.ReadDir(path)
 	if err != nil {
@@ -187,12 +292,25 @@
 	return secret, nil
 }
 
+func extractSecret(secret *corev1.Secret) (string, error) {
+	tmp, err := os.MkdirTemp("", secret.Name)
+	if err != nil {
+		return "", err
+	}
+	for name, data := range secret.Data {
+		if err := ioutil.WriteFile(filepath.Join(tmp, name), data, 0644); err != nil {
+			defer os.RemoveAll(tmp)
+			return "", nil
+		}
+	}
+	return tmp, nil
+}
+
 func generateCAKey(name, nebulaCert string) (string, error) {
 	tmp, err := os.MkdirTemp("", name)
 	if err != nil {
 		return "", err
 	}
-	fmt.Println(tmp)
 	cmd := exec.Command(nebulaCert, "ca",
 		"-name", name,
 		"-out-key", filepath.Join(tmp, "ca.key"),
@@ -204,6 +322,21 @@
 	return tmp, nil
 }
 
+func generateNodeKey(name, ip, dir, nebulaCert string) error {
+	cmd := exec.Command(nebulaCert, "sign",
+		"-ca-crt", filepath.Join(dir, "ca.crt"),
+		"-ca-key", filepath.Join(dir, "ca.key"),
+		"-name", name,
+		"-ip", ip,
+		"-out-key", filepath.Join(dir, "host.key"),
+		"-out-crt", filepath.Join(dir, "host.crt"),
+		"-out-qr", filepath.Join(dir, "host.png"))
+	if d, err := cmd.CombinedOutput(); err != nil {
+		return fmt.Errorf(string(d))
+	}
+	return nil
+}
+
 func (c *CAController) getCA(namespace, name string) (*nebulav1.NebulaCA, error) {
 	s := labels.NewSelector()
 	r, err := labels.NewRequirement("metadata.namespace", selection.Equals, []string{namespace})
@@ -224,3 +357,28 @@
 	}
 	return ncas[0], nil
 }
+
+func (c *CAController) getNode(namespace, name string) (*nebulav1.NebulaNode, error) {
+	s := labels.NewSelector()
+	r, err := labels.NewRequirement("metadata.namespace", selection.Equals, []string{namespace})
+	if err != nil {
+		panic(err)
+	}
+	r1, err := labels.NewRequirement("metadata.name", selection.Equals, []string{name})
+	if err != nil {
+		panic(err)
+	}
+	s.Add(*r, *r1)
+	nodes, err := c.nodeLister.List(s)
+	if err != nil {
+		panic(err)
+	}
+	if len(nodes) != 1 {
+		panic("err")
+	}
+	return nodes[0], nil
+}
+
+func (c *CAController) getSecret(namespace, name string) (*corev1.Secret, error) {
+	return c.secretLister.Secrets(namespace).Get(name)
+}
diff --git a/core/nebula/crds/nebula.crds.yaml b/core/nebula/crds/nebula.crds.yaml
index 984b2c9..d75cac0 100644
--- a/core/nebula/crds/nebula.crds.yaml
+++ b/core/nebula/crds/nebula.crds.yaml
@@ -37,3 +37,47 @@
                   type: string
                 message:
                   type: string
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: nebulanodes.lekva.me
+spec:
+  group: lekva.me
+  scope: Namespaced
+  names:
+    kind: NebulaNode
+    listKind: NebulaNodeList
+    plural: nebulanodes
+    singular: nebulanode
+    shortNames:
+      - nnode
+      - nnodes
+  versions:
+    - name: v1
+      served: true
+      storage: true
+      subresources:
+        status: {}
+      schema:
+        openAPIV3Schema:
+          type: object
+          properties:
+            spec:
+              type: object
+              properties:
+                caName:
+                  type: string
+                nodeName:
+                  type: string
+                ipCidr:
+                  type: string
+                secretName:
+                  type: string
+            status:
+              type: object
+              properties:
+                state:
+                  type: string
+                message:
+                  type: string
diff --git a/core/nebula/crds/test-node.yaml b/core/nebula/crds/test-node.yaml
new file mode 100644
index 0000000..bcabecf
--- /dev/null
+++ b/core/nebula/crds/test-node.yaml
@@ -0,0 +1,10 @@
+apiVersion: lekva.me/v1
+kind: NebulaNode
+metadata:
+  name: test-host
+  namespace: test-nebula
+spec:
+  caName: lekva-pcloud
+  nodeName: lighthouse
+  ipCidr: "111.0.0.1/24"
+  secretName: node-lighthouse
diff --git a/core/nebula/generated/clientset/versioned/typed/nebula/v1/fake/fake_nebula_client.go b/core/nebula/generated/clientset/versioned/typed/nebula/v1/fake/fake_nebula_client.go
index 727021a..e5f530e 100644
--- a/core/nebula/generated/clientset/versioned/typed/nebula/v1/fake/fake_nebula_client.go
+++ b/core/nebula/generated/clientset/versioned/typed/nebula/v1/fake/fake_nebula_client.go
@@ -18,6 +18,10 @@
 	return &FakeNebulaCAs{c, namespace}
 }
 
+func (c *FakeLekvaV1) NebulaNodes(namespace string) v1.NebulaNodeInterface {
+	return &FakeNebulaNodes{c, namespace}
+}
+
 // RESTClient returns a RESTClient that is used to communicate
 // with API server by this client implementation.
 func (c *FakeLekvaV1) RESTClient() rest.Interface {
diff --git a/core/nebula/generated/clientset/versioned/typed/nebula/v1/fake/fake_nebulanode.go b/core/nebula/generated/clientset/versioned/typed/nebula/v1/fake/fake_nebulanode.go
new file mode 100644
index 0000000..cbd3957
--- /dev/null
+++ b/core/nebula/generated/clientset/versioned/typed/nebula/v1/fake/fake_nebulanode.go
@@ -0,0 +1,128 @@
+// gen
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+	"context"
+
+	nebulav1 "github.com/giolekva/pcloud/core/nebula/apis/nebula/v1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	labels "k8s.io/apimachinery/pkg/labels"
+	schema "k8s.io/apimachinery/pkg/runtime/schema"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	testing "k8s.io/client-go/testing"
+)
+
+// FakeNebulaNodes implements NebulaNodeInterface
+type FakeNebulaNodes struct {
+	Fake *FakeLekvaV1
+	ns   string
+}
+
+var nebulanodesResource = schema.GroupVersionResource{Group: "lekva.me", Version: "v1", Resource: "nebulanodes"}
+
+var nebulanodesKind = schema.GroupVersionKind{Group: "lekva.me", Version: "v1", Kind: "NebulaNode"}
+
+// Get takes name of the nebulaNode, and returns the corresponding nebulaNode object, and an error if there is any.
+func (c *FakeNebulaNodes) Get(ctx context.Context, name string, options v1.GetOptions) (result *nebulav1.NebulaNode, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewGetAction(nebulanodesResource, c.ns, name), &nebulav1.NebulaNode{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*nebulav1.NebulaNode), err
+}
+
+// List takes label and field selectors, and returns the list of NebulaNodes that match those selectors.
+func (c *FakeNebulaNodes) List(ctx context.Context, opts v1.ListOptions) (result *nebulav1.NebulaNodeList, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewListAction(nebulanodesResource, nebulanodesKind, c.ns, opts), &nebulav1.NebulaNodeList{})
+
+	if obj == nil {
+		return nil, err
+	}
+
+	label, _, _ := testing.ExtractFromListOptions(opts)
+	if label == nil {
+		label = labels.Everything()
+	}
+	list := &nebulav1.NebulaNodeList{ListMeta: obj.(*nebulav1.NebulaNodeList).ListMeta}
+	for _, item := range obj.(*nebulav1.NebulaNodeList).Items {
+		if label.Matches(labels.Set(item.Labels)) {
+			list.Items = append(list.Items, item)
+		}
+	}
+	return list, err
+}
+
+// Watch returns a watch.Interface that watches the requested nebulaNodes.
+func (c *FakeNebulaNodes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+	return c.Fake.
+		InvokesWatch(testing.NewWatchAction(nebulanodesResource, c.ns, opts))
+
+}
+
+// Create takes the representation of a nebulaNode and creates it.  Returns the server's representation of the nebulaNode, and an error, if there is any.
+func (c *FakeNebulaNodes) Create(ctx context.Context, nebulaNode *nebulav1.NebulaNode, opts v1.CreateOptions) (result *nebulav1.NebulaNode, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewCreateAction(nebulanodesResource, c.ns, nebulaNode), &nebulav1.NebulaNode{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*nebulav1.NebulaNode), err
+}
+
+// Update takes the representation of a nebulaNode and updates it. Returns the server's representation of the nebulaNode, and an error, if there is any.
+func (c *FakeNebulaNodes) Update(ctx context.Context, nebulaNode *nebulav1.NebulaNode, opts v1.UpdateOptions) (result *nebulav1.NebulaNode, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewUpdateAction(nebulanodesResource, c.ns, nebulaNode), &nebulav1.NebulaNode{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*nebulav1.NebulaNode), err
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+func (c *FakeNebulaNodes) UpdateStatus(ctx context.Context, nebulaNode *nebulav1.NebulaNode, opts v1.UpdateOptions) (*nebulav1.NebulaNode, error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewUpdateSubresourceAction(nebulanodesResource, "status", c.ns, nebulaNode), &nebulav1.NebulaNode{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*nebulav1.NebulaNode), err
+}
+
+// Delete takes name of the nebulaNode and deletes it. Returns an error if one occurs.
+func (c *FakeNebulaNodes) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+	_, err := c.Fake.
+		Invokes(testing.NewDeleteAction(nebulanodesResource, c.ns, name), &nebulav1.NebulaNode{})
+
+	return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeNebulaNodes) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+	action := testing.NewDeleteCollectionAction(nebulanodesResource, c.ns, listOpts)
+
+	_, err := c.Fake.Invokes(action, &nebulav1.NebulaNodeList{})
+	return err
+}
+
+// Patch applies the patch and returns the patched nebulaNode.
+func (c *FakeNebulaNodes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *nebulav1.NebulaNode, err error) {
+	obj, err := c.Fake.
+		Invokes(testing.NewPatchSubresourceAction(nebulanodesResource, c.ns, name, pt, data, subresources...), &nebulav1.NebulaNode{})
+
+	if obj == nil {
+		return nil, err
+	}
+	return obj.(*nebulav1.NebulaNode), err
+}
diff --git a/core/nebula/generated/clientset/versioned/typed/nebula/v1/generated_expansion.go b/core/nebula/generated/clientset/versioned/typed/nebula/v1/generated_expansion.go
index 00ebd78..db9c80a 100644
--- a/core/nebula/generated/clientset/versioned/typed/nebula/v1/generated_expansion.go
+++ b/core/nebula/generated/clientset/versioned/typed/nebula/v1/generated_expansion.go
@@ -5,3 +5,5 @@
 package v1
 
 type NebulaCAExpansion interface{}
+
+type NebulaNodeExpansion interface{}
diff --git a/core/nebula/generated/clientset/versioned/typed/nebula/v1/nebula_client.go b/core/nebula/generated/clientset/versioned/typed/nebula/v1/nebula_client.go
index bf63c93..261bb36 100644
--- a/core/nebula/generated/clientset/versioned/typed/nebula/v1/nebula_client.go
+++ b/core/nebula/generated/clientset/versioned/typed/nebula/v1/nebula_client.go
@@ -13,6 +13,7 @@
 type LekvaV1Interface interface {
 	RESTClient() rest.Interface
 	NebulaCAsGetter
+	NebulaNodesGetter
 }
 
 // LekvaV1Client is used to interact with features provided by the lekva.me group.
@@ -24,6 +25,10 @@
 	return newNebulaCAs(c, namespace)
 }
 
+func (c *LekvaV1Client) NebulaNodes(namespace string) NebulaNodeInterface {
+	return newNebulaNodes(c, namespace)
+}
+
 // NewForConfig creates a new LekvaV1Client for the given config.
 func NewForConfig(c *rest.Config) (*LekvaV1Client, error) {
 	config := *c
diff --git a/core/nebula/generated/clientset/versioned/typed/nebula/v1/nebulanode.go b/core/nebula/generated/clientset/versioned/typed/nebula/v1/nebulanode.go
new file mode 100644
index 0000000..4c38986
--- /dev/null
+++ b/core/nebula/generated/clientset/versioned/typed/nebula/v1/nebulanode.go
@@ -0,0 +1,181 @@
+// gen
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1
+
+import (
+	"context"
+	"time"
+
+	v1 "github.com/giolekva/pcloud/core/nebula/apis/nebula/v1"
+	scheme "github.com/giolekva/pcloud/core/nebula/generated/clientset/versioned/scheme"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	rest "k8s.io/client-go/rest"
+)
+
+// NebulaNodesGetter has a method to return a NebulaNodeInterface.
+// A group's client should implement this interface.
+type NebulaNodesGetter interface {
+	NebulaNodes(namespace string) NebulaNodeInterface
+}
+
+// NebulaNodeInterface has methods to work with NebulaNode resources.
+type NebulaNodeInterface interface {
+	Create(ctx context.Context, nebulaNode *v1.NebulaNode, opts metav1.CreateOptions) (*v1.NebulaNode, error)
+	Update(ctx context.Context, nebulaNode *v1.NebulaNode, opts metav1.UpdateOptions) (*v1.NebulaNode, error)
+	UpdateStatus(ctx context.Context, nebulaNode *v1.NebulaNode, opts metav1.UpdateOptions) (*v1.NebulaNode, error)
+	Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
+	DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
+	Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.NebulaNode, error)
+	List(ctx context.Context, opts metav1.ListOptions) (*v1.NebulaNodeList, error)
+	Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
+	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.NebulaNode, err error)
+	NebulaNodeExpansion
+}
+
+// nebulaNodes implements NebulaNodeInterface
+type nebulaNodes struct {
+	client rest.Interface
+	ns     string
+}
+
+// newNebulaNodes returns a NebulaNodes
+func newNebulaNodes(c *LekvaV1Client, namespace string) *nebulaNodes {
+	return &nebulaNodes{
+		client: c.RESTClient(),
+		ns:     namespace,
+	}
+}
+
+// Get takes name of the nebulaNode, and returns the corresponding nebulaNode object, and an error if there is any.
+func (c *nebulaNodes) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.NebulaNode, err error) {
+	result = &v1.NebulaNode{}
+	err = c.client.Get().
+		Namespace(c.ns).
+		Resource("nebulanodes").
+		Name(name).
+		VersionedParams(&options, scheme.ParameterCodec).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// List takes label and field selectors, and returns the list of NebulaNodes that match those selectors.
+func (c *nebulaNodes) List(ctx context.Context, opts metav1.ListOptions) (result *v1.NebulaNodeList, err error) {
+	var timeout time.Duration
+	if opts.TimeoutSeconds != nil {
+		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+	}
+	result = &v1.NebulaNodeList{}
+	err = c.client.Get().
+		Namespace(c.ns).
+		Resource("nebulanodes").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// Watch returns a watch.Interface that watches the requested nebulaNodes.
+func (c *nebulaNodes) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
+	var timeout time.Duration
+	if opts.TimeoutSeconds != nil {
+		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+	}
+	opts.Watch = true
+	return c.client.Get().
+		Namespace(c.ns).
+		Resource("nebulanodes").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Watch(ctx)
+}
+
+// Create takes the representation of a nebulaNode and creates it.  Returns the server's representation of the nebulaNode, and an error, if there is any.
+func (c *nebulaNodes) Create(ctx context.Context, nebulaNode *v1.NebulaNode, opts metav1.CreateOptions) (result *v1.NebulaNode, err error) {
+	result = &v1.NebulaNode{}
+	err = c.client.Post().
+		Namespace(c.ns).
+		Resource("nebulanodes").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(nebulaNode).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// Update takes the representation of a nebulaNode and updates it. Returns the server's representation of the nebulaNode, and an error, if there is any.
+func (c *nebulaNodes) Update(ctx context.Context, nebulaNode *v1.NebulaNode, opts metav1.UpdateOptions) (result *v1.NebulaNode, err error) {
+	result = &v1.NebulaNode{}
+	err = c.client.Put().
+		Namespace(c.ns).
+		Resource("nebulanodes").
+		Name(nebulaNode.Name).
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(nebulaNode).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+func (c *nebulaNodes) UpdateStatus(ctx context.Context, nebulaNode *v1.NebulaNode, opts metav1.UpdateOptions) (result *v1.NebulaNode, err error) {
+	result = &v1.NebulaNode{}
+	err = c.client.Put().
+		Namespace(c.ns).
+		Resource("nebulanodes").
+		Name(nebulaNode.Name).
+		SubResource("status").
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(nebulaNode).
+		Do(ctx).
+		Into(result)
+	return
+}
+
+// Delete takes name of the nebulaNode and deletes it. Returns an error if one occurs.
+func (c *nebulaNodes) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
+	return c.client.Delete().
+		Namespace(c.ns).
+		Resource("nebulanodes").
+		Name(name).
+		Body(&opts).
+		Do(ctx).
+		Error()
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *nebulaNodes) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
+	var timeout time.Duration
+	if listOpts.TimeoutSeconds != nil {
+		timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
+	}
+	return c.client.Delete().
+		Namespace(c.ns).
+		Resource("nebulanodes").
+		VersionedParams(&listOpts, scheme.ParameterCodec).
+		Timeout(timeout).
+		Body(&opts).
+		Do(ctx).
+		Error()
+}
+
+// Patch applies the patch and returns the patched nebulaNode.
+func (c *nebulaNodes) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.NebulaNode, err error) {
+	result = &v1.NebulaNode{}
+	err = c.client.Patch(pt).
+		Namespace(c.ns).
+		Resource("nebulanodes").
+		Name(name).
+		SubResource(subresources...).
+		VersionedParams(&opts, scheme.ParameterCodec).
+		Body(data).
+		Do(ctx).
+		Into(result)
+	return
+}
diff --git a/core/nebula/generated/informers/externalversions/generic.go b/core/nebula/generated/informers/externalversions/generic.go
index fbefa76..9df56e4 100644
--- a/core/nebula/generated/informers/externalversions/generic.go
+++ b/core/nebula/generated/informers/externalversions/generic.go
@@ -41,6 +41,8 @@
 	// Group=lekva.me, Version=v1
 	case v1.SchemeGroupVersion.WithResource("nebulacas"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Lekva().V1().NebulaCAs().Informer()}, nil
+	case v1.SchemeGroupVersion.WithResource("nebulanodes"):
+		return &genericInformer{resource: resource.GroupResource(), informer: f.Lekva().V1().NebulaNodes().Informer()}, nil
 
 	}
 
diff --git a/core/nebula/generated/informers/externalversions/nebula/v1/interface.go b/core/nebula/generated/informers/externalversions/nebula/v1/interface.go
index c9ca5e8..eb7fc27 100644
--- a/core/nebula/generated/informers/externalversions/nebula/v1/interface.go
+++ b/core/nebula/generated/informers/externalversions/nebula/v1/interface.go
@@ -12,6 +12,8 @@
 type Interface interface {
 	// NebulaCAs returns a NebulaCAInformer.
 	NebulaCAs() NebulaCAInformer
+	// NebulaNodes returns a NebulaNodeInformer.
+	NebulaNodes() NebulaNodeInformer
 }
 
 type version struct {
@@ -29,3 +31,8 @@
 func (v *version) NebulaCAs() NebulaCAInformer {
 	return &nebulaCAInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
 }
+
+// NebulaNodes returns a NebulaNodeInformer.
+func (v *version) NebulaNodes() NebulaNodeInformer {
+	return &nebulaNodeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+}
diff --git a/core/nebula/generated/informers/externalversions/nebula/v1/nebulanode.go b/core/nebula/generated/informers/externalversions/nebula/v1/nebulanode.go
new file mode 100644
index 0000000..b3d3c65
--- /dev/null
+++ b/core/nebula/generated/informers/externalversions/nebula/v1/nebulanode.go
@@ -0,0 +1,76 @@
+// gen
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1
+
+import (
+	"context"
+	time "time"
+
+	nebulav1 "github.com/giolekva/pcloud/core/nebula/apis/nebula/v1"
+	versioned "github.com/giolekva/pcloud/core/nebula/generated/clientset/versioned"
+	internalinterfaces "github.com/giolekva/pcloud/core/nebula/generated/informers/externalversions/internalinterfaces"
+	v1 "github.com/giolekva/pcloud/core/nebula/generated/listers/nebula/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+	watch "k8s.io/apimachinery/pkg/watch"
+	cache "k8s.io/client-go/tools/cache"
+)
+
+// NebulaNodeInformer provides access to a shared informer and lister for
+// NebulaNodes.
+type NebulaNodeInformer interface {
+	Informer() cache.SharedIndexInformer
+	Lister() v1.NebulaNodeLister
+}
+
+type nebulaNodeInformer struct {
+	factory          internalinterfaces.SharedInformerFactory
+	tweakListOptions internalinterfaces.TweakListOptionsFunc
+	namespace        string
+}
+
+// NewNebulaNodeInformer constructs a new informer for NebulaNode type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewNebulaNodeInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+	return NewFilteredNebulaNodeInformer(client, namespace, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredNebulaNodeInformer constructs a new informer for NebulaNode type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewFilteredNebulaNodeInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+	return cache.NewSharedIndexInformer(
+		&cache.ListWatch{
+			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.LekvaV1().NebulaNodes(namespace).List(context.TODO(), options)
+			},
+			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.LekvaV1().NebulaNodes(namespace).Watch(context.TODO(), options)
+			},
+		},
+		&nebulav1.NebulaNode{},
+		resyncPeriod,
+		indexers,
+	)
+}
+
+func (f *nebulaNodeInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+	return NewFilteredNebulaNodeInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *nebulaNodeInformer) Informer() cache.SharedIndexInformer {
+	return f.factory.InformerFor(&nebulav1.NebulaNode{}, f.defaultInformer)
+}
+
+func (f *nebulaNodeInformer) Lister() v1.NebulaNodeLister {
+	return v1.NewNebulaNodeLister(f.Informer().GetIndexer())
+}
diff --git a/core/nebula/generated/listers/nebula/v1/expansion_generated.go b/core/nebula/generated/listers/nebula/v1/expansion_generated.go
index a6436ea..8b4d0e8 100644
--- a/core/nebula/generated/listers/nebula/v1/expansion_generated.go
+++ b/core/nebula/generated/listers/nebula/v1/expansion_generated.go
@@ -11,3 +11,11 @@
 // NebulaCANamespaceListerExpansion allows custom methods to be added to
 // NebulaCANamespaceLister.
 type NebulaCANamespaceListerExpansion interface{}
+
+// NebulaNodeListerExpansion allows custom methods to be added to
+// NebulaNodeLister.
+type NebulaNodeListerExpansion interface{}
+
+// NebulaNodeNamespaceListerExpansion allows custom methods to be added to
+// NebulaNodeNamespaceLister.
+type NebulaNodeNamespaceListerExpansion interface{}
diff --git a/core/nebula/generated/listers/nebula/v1/nebulanode.go b/core/nebula/generated/listers/nebula/v1/nebulanode.go
new file mode 100644
index 0000000..f861e18
--- /dev/null
+++ b/core/nebula/generated/listers/nebula/v1/nebulanode.go
@@ -0,0 +1,85 @@
+// gen
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1
+
+import (
+	v1 "github.com/giolekva/pcloud/core/nebula/apis/nebula/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/client-go/tools/cache"
+)
+
+// NebulaNodeLister helps list NebulaNodes.
+// All objects returned here must be treated as read-only.
+type NebulaNodeLister interface {
+	// List lists all NebulaNodes in the indexer.
+	// Objects returned here must be treated as read-only.
+	List(selector labels.Selector) (ret []*v1.NebulaNode, err error)
+	// NebulaNodes returns an object that can list and get NebulaNodes.
+	NebulaNodes(namespace string) NebulaNodeNamespaceLister
+	NebulaNodeListerExpansion
+}
+
+// nebulaNodeLister implements the NebulaNodeLister interface.
+type nebulaNodeLister struct {
+	indexer cache.Indexer
+}
+
+// NewNebulaNodeLister returns a new NebulaNodeLister.
+func NewNebulaNodeLister(indexer cache.Indexer) NebulaNodeLister {
+	return &nebulaNodeLister{indexer: indexer}
+}
+
+// List lists all NebulaNodes in the indexer.
+func (s *nebulaNodeLister) List(selector labels.Selector) (ret []*v1.NebulaNode, err error) {
+	err = cache.ListAll(s.indexer, selector, func(m interface{}) {
+		ret = append(ret, m.(*v1.NebulaNode))
+	})
+	return ret, err
+}
+
+// NebulaNodes returns an object that can list and get NebulaNodes.
+func (s *nebulaNodeLister) NebulaNodes(namespace string) NebulaNodeNamespaceLister {
+	return nebulaNodeNamespaceLister{indexer: s.indexer, namespace: namespace}
+}
+
+// NebulaNodeNamespaceLister helps list and get NebulaNodes.
+// All objects returned here must be treated as read-only.
+type NebulaNodeNamespaceLister interface {
+	// List lists all NebulaNodes in the indexer for a given namespace.
+	// Objects returned here must be treated as read-only.
+	List(selector labels.Selector) (ret []*v1.NebulaNode, err error)
+	// Get retrieves the NebulaNode from the indexer for a given namespace and name.
+	// Objects returned here must be treated as read-only.
+	Get(name string) (*v1.NebulaNode, error)
+	NebulaNodeNamespaceListerExpansion
+}
+
+// nebulaNodeNamespaceLister implements the NebulaNodeNamespaceLister
+// interface.
+type nebulaNodeNamespaceLister struct {
+	indexer   cache.Indexer
+	namespace string
+}
+
+// List lists all NebulaNodes in the indexer for a given namespace.
+func (s nebulaNodeNamespaceLister) List(selector labels.Selector) (ret []*v1.NebulaNode, err error) {
+	err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
+		ret = append(ret, m.(*v1.NebulaNode))
+	})
+	return ret, err
+}
+
+// Get retrieves the NebulaNode from the indexer for a given namespace and name.
+func (s nebulaNodeNamespaceLister) Get(name string) (*v1.NebulaNode, error) {
+	obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
+	if err != nil {
+		return nil, err
+	}
+	if !exists {
+		return nil, errors.NewNotFound(v1.Resource("nebulanode"), name)
+	}
+	return obj.(*v1.NebulaNode), nil
+}
diff --git a/core/nebula/main.go b/core/nebula/main.go
index dd62db0..af7b0b5 100644
--- a/core/nebula/main.go
+++ b/core/nebula/main.go
@@ -5,6 +5,7 @@
 	"time"
 
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+	kubeinformers "k8s.io/client-go/informers"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/tools/clientcmd"
 
@@ -12,6 +13,7 @@
 	clientset "github.com/giolekva/pcloud/core/nebula/generated/clientset/versioned"
 	"github.com/giolekva/pcloud/core/nebula/generated/clientset/versioned/scheme"
 	informers "github.com/giolekva/pcloud/core/nebula/generated/informers/externalversions"
+
 	nebulascheme "k8s.io/sample-controller/pkg/generated/clientset/versioned/scheme"
 )
 
@@ -25,15 +27,23 @@
 	if err != nil {
 		panic(err)
 	}
-	kc, err := kubernetes.NewForConfig(cfg)
+	kubeClient, err := kubernetes.NewForConfig(cfg)
 	if err != nil {
 		panic(err)
 	}
 	nebulaClient := clientset.NewForConfigOrDie(cfg)
 	utilruntime.Must(nebulascheme.AddToScheme(scheme.Scheme))
+	kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, time.Second*30)
 	nebulaInformerFactory := informers.NewSharedInformerFactory(nebulaClient, 5*time.Second)
-	c := controllers.NewCAController(kc, nebulaClient, nebulaInformerFactory, *nebulaCert)
+	c := controllers.NewCAController(
+		kubeClient,
+		nebulaClient,
+		nebulaInformerFactory.Lekva().V1().NebulaCAs(),
+		nebulaInformerFactory.Lekva().V1().NebulaNodes(),
+		kubeInformerFactory.Core().V1().Secrets(),
+		*nebulaCert)
 	stopCh := make(chan struct{})
+	kubeInformerFactory.Start(stopCh)
 	nebulaInformerFactory.Start(stopCh)
 	if err := c.Run(1, stopCh); err != nil {
 		panic(err)