Basic implementation of client: render qa code and scan from mobile app
diff --git a/core/nebula/api/manager.go b/core/nebula/api/manager.go
new file mode 100644
index 0000000..40f6eeb
--- /dev/null
+++ b/core/nebula/api/manager.go
@@ -0,0 +1,163 @@
+package main
+
+import (
+	"context"
+	"crypto"
+	"crypto/ed25519"
+	"crypto/rand"
+	"errors"
+	"fmt"
+
+	"inet.af/netaddr"
+
+	"github.com/slackhq/nebula/cert"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+
+	nebulav1 "github.com/giolekva/pcloud/core/nebula/controller/apis/nebula/v1"
+	clientset "github.com/giolekva/pcloud/core/nebula/controller/generated/clientset/versioned"
+)
+
+type nebulaCA struct {
+	Name      string
+	Namespace string
+	Nodes     []nebulaNode
+}
+
+type nebulaNode struct {
+	Name      string
+	Namespace string
+	IP        string
+}
+
+type Manager struct {
+	kubeClient   kubernetes.Interface
+	nebulaClient clientset.Interface
+	namespace    string
+	caSecretName string
+}
+
+func (m *Manager) ListAll() ([]*nebulaCA, error) {
+	ret := make([]*nebulaCA, 0)
+	cas, err := m.nebulaClient.LekvaV1().NebulaCAs("").List(context.TODO(), metav1.ListOptions{})
+	if err != nil {
+		return nil, err
+	}
+	for _, ca := range cas.Items {
+		ret = append(ret, &nebulaCA{
+			Name:      ca.Name,
+			Namespace: ca.Namespace,
+			Nodes:     make([]nebulaNode, 0),
+		})
+	}
+	nodes, err := m.nebulaClient.LekvaV1().NebulaNodes("").List(context.TODO(), metav1.ListOptions{})
+	if err != nil {
+		return nil, err
+	}
+	for _, node := range nodes.Items {
+		for _, ca := range ret {
+			if ca.Name == node.Spec.CAName {
+				ca.Nodes = append(ca.Nodes, nebulaNode{
+					Name:      node.Name,
+					Namespace: node.Namespace,
+					IP:        node.Spec.IPCidr,
+				})
+			}
+		}
+	}
+	return ret, nil
+}
+
+func (m *Manager) CreateNode(namespace, name, caNamespace, caName, ipCidr, pubKey string) (string, string, error) {
+	node := &nebulav1.NebulaNode{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+		},
+		Spec: nebulav1.NebulaNodeSpec{
+			CAName:      caName,
+			CANamespace: caNamespace,
+			IPCidr:      ipCidr,
+			PubKey:      pubKey,
+			SecretName:  fmt.Sprintf("%s-cert", name),
+		},
+	}
+	node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Create(context.TODO(), node, metav1.CreateOptions{})
+	if err != nil {
+		return "", "", err
+	}
+	return node.Namespace, node.Name, nil
+}
+
+func (m *Manager) GetNodeCertQR(namespace, name string) ([]byte, error) {
+	secret, err := m.getNodeSecret(namespace, name)
+	if err != nil {
+		return nil, err
+	}
+	return secret.Data["host.png"], nil
+}
+
+func (m *Manager) GetCACertQR(namespace, name string) ([]byte, error) {
+	ca, err := m.nebulaClient.LekvaV1().NebulaCAs(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+	secret, err := m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), ca.Spec.SecretName, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+	return secret.Data["ca.png"], nil
+}
+
+func (m *Manager) getNextIP() (netaddr.IP, error) {
+	nodes, err := m.nebulaClient.LekvaV1().NebulaNodes(m.namespace).List(context.TODO(), metav1.ListOptions{})
+	if err != nil {
+		return netaddr.IP{}, err
+	}
+	var max netaddr.IP
+	for _, node := range nodes.Items {
+		ip := netaddr.MustParseIPPrefix(node.Spec.IPCidr)
+		if max.Less(ip.IP()) {
+			max = ip.IP()
+		}
+	}
+	n := max.Next()
+	if n.IsZero() {
+		return n, errors.New("IP address range exhausted")
+	}
+	return n, nil
+}
+
+func (m *Manager) Sign(message []byte) ([]byte, error) {
+	secret, err := m.kubeClient.CoreV1().Secrets(m.namespace).Get(context.TODO(), m.caSecretName, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+	edPriv, _, err := cert.UnmarshalEd25519PrivateKey(secret.Data["ca.key"])
+	if err != nil {
+		return nil, err
+	}
+	return edPriv.Sign(rand.Reader, message, crypto.Hash(0))
+}
+
+func (m *Manager) VerifySignature(message, signature []byte) (bool, error) {
+	secret, err := m.kubeClient.CoreV1().Secrets(m.namespace).Get(context.TODO(), m.caSecretName, metav1.GetOptions{})
+	if err != nil {
+		return false, err
+	}
+	edPriv, _, err := cert.UnmarshalEd25519PrivateKey(secret.Data["ca.key"])
+	if err != nil {
+		return false, err
+	}
+	return ed25519.Verify(edPriv.Public().(ed25519.PublicKey), message, signature), nil
+}
+
+func (m *Manager) getNodeSecret(namespace, name string) (*corev1.Secret, error) {
+	node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+	return m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), node.Spec.SecretName, metav1.GetOptions{})
+}