VPN: move certificate signing logit to api service to which controller delegates ops
diff --git a/core/nebula/controller/controllers/ca.go b/core/nebula/controller/controllers/ca.go
index 37b3856..9cc284b 100644
--- a/core/nebula/controller/controllers/ca.go
+++ b/core/nebula/controller/controllers/ca.go
@@ -3,10 +3,6 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
-	"os"
-	"os/exec"
-	"path/filepath"
 	"time"
 
 	corev1 "k8s.io/api/core/v1"
@@ -47,16 +43,13 @@
 	secretLister corev1listers.SecretLister
 	secretSynced cache.InformerSynced
 	workqueue    workqueue.RateLimitingInterface
-
-	nebulaCert string
 }
 
 func NewNebulaController(kubeClient kubernetes.Interface,
 	nebulaClient clientset.Interface,
 	caInformer informers.NebulaCAInformer,
 	nodeInformer informers.NebulaNodeInformer,
-	secretInformer corev1informers.SecretInformer,
-	nebulaCert string) *NebulaController {
+	secretInformer corev1informers.SecretInformer) *NebulaController {
 	c := &NebulaController{
 		kubeClient:   kubeClient,
 		nebulaClient: nebulaClient,
@@ -67,7 +60,6 @@
 		secretLister: secretInformer.Lister(),
 		secretSynced: secretInformer.Informer().HasSynced,
 		workqueue:    workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Nebula"),
-		nebulaCert:   nebulaCert,
 	}
 
 	caInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
@@ -186,17 +178,20 @@
 		fmt.Printf("%s CA is already in Ready state\n", ca.Name)
 		return nil
 	}
-	keyDir, err := generateCAKey(ca.Name, c.nebulaCert)
+	privKey, cert, err := CreateCertificateAuthority(apiAddr(ca.Name), ca.Name)
 	if err != nil {
 		return err
 	}
-	defer os.RemoveAll(keyDir)
-	secret, err := createSecretFromDir(keyDir)
-	if err != nil {
-		return err
+	secret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: ca.Spec.SecretName,
+		},
+		Immutable: &secretImmutable,
+		Data: map[string][]byte{
+			"ca.key": privKey,
+			"ca.crt": cert,
+		},
 	}
-	secret.Immutable = &secretImmutable
-	secret.Name = ca.Spec.SecretName
 	_, err = c.kubeClient.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
 	if err != nil {
 		return err
@@ -239,32 +234,25 @@
 		}
 		return err
 	}
-	dir, err := extractSecret(caSecret)
+	var pubKey []byte
+	if node.Spec.PubKey != "" {
+		pubKey = []byte(node.Spec.PubKey)
+	}
+	privKey, nodeCert, err := SignNebulaNode(apiAddr(ca.Name), caSecret.Data["ca.key"], caSecret.Data["ca.crt"], node.Name, pubKey, node.Spec.IPCidr)
 	if err != nil {
 		return err
 	}
-	if node.Spec.PubKey == "" {
-		if err := generateNodeKey(node.Name, node.Spec.IPCidr, dir, c.nebulaCert); err != nil {
-			return err
-		}
-	} else {
-		if err := generateNodeKeyFromPub(node.Name, node.Spec.IPCidr, node.Spec.PubKey, dir, c.nebulaCert); err != nil {
-			return err
-		}
+	secret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: node.Spec.SecretName,
+		},
+		Immutable: &secretImmutable,
+		Data: map[string][]byte{
+			"ca.crt":   caSecret.Data["ca.crt"],
+			"host.crt": nodeCert,
+			"host.key": privKey,
+		},
 	}
-	defer os.RemoveAll(dir)
-	if err := os.Remove(filepath.Join(dir, "ca.key")); err != nil {
-		return err
-	}
-	if err := os.Remove(filepath.Join(dir, "ca.png")); err != nil {
-		return err
-	}
-	secret, err := createSecretFromDir(dir)
-	if err != nil {
-		return err
-	}
-	secret.Immutable = &secretImmutable
-	secret.Name = node.Spec.SecretName
 	_, err = c.kubeClient.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
 	if err != nil {
 		return err
@@ -292,88 +280,7 @@
 	return err
 }
 
-func createSecretFromDir(path string) (*corev1.Secret, error) {
-	all, err := ioutil.ReadDir(path)
-	if err != nil {
-		return nil, err
-	}
-	secret := &corev1.Secret{
-		Data: make(map[string][]byte),
-	}
-	for _, f := range all {
-		if f.IsDir() {
-			continue
-		}
-		d, err := ioutil.ReadFile(filepath.Join(path, f.Name()))
-		if err != nil {
-			return nil, err
-		}
-		secret.Data[f.Name()] = d
-	}
-	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 "", err
-		}
-	}
-	return tmp, nil
-}
-
-func generateCAKey(name, nebulaCert string) (string, error) {
-	tmp, err := os.MkdirTemp("", name)
-	if err != nil {
-		return "", err
-	}
-	cmd := exec.Command(nebulaCert, "ca",
-		"-name", name,
-		"-out-key", filepath.Join(tmp, "ca.key"),
-		"-out-crt", filepath.Join(tmp, "ca.crt"),
-		"-out-qr", filepath.Join(tmp, "ca.png"))
-	if d, err := cmd.CombinedOutput(); err != nil {
-		return "", fmt.Errorf(string(d))
-	}
-	return tmp, nil
-}
-
-func generateNodeKeyFromPub(name, ip, pubKey, dir, nebulaCert string) error {
-	hostPub := filepath.Join(dir, "host.pub")
-	if err := ioutil.WriteFile(hostPub, []byte(pubKey), 0644); err != nil {
-		return err
-	}
-	defer os.Remove(hostPub)
-	cmd := exec.Command(nebulaCert, "sign",
-		"-ca-crt", filepath.Join(dir, "ca.crt"),
-		"-ca-key", filepath.Join(dir, "ca.key"),
-		"-name", name,
-		"-ip", ip,
-		"-in-pub", hostPub,
-		"-out-crt", filepath.Join(dir, "host.crt"),
-		"-out-qr", filepath.Join(dir, "host.png"))
-	if _, err := cmd.CombinedOutput(); err != nil {
-		return err
-	}
-	return 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 _, err := cmd.CombinedOutput(); err != nil {
-		return err
-	}
-	return nil
+// TODO(giolekva): maybe pass by flag?
+func apiAddr(ca string) string {
+	return fmt.Sprintf("http://nebula-api.%s-ingress-private.svc.cluster.local", ca)
 }
diff --git a/core/nebula/controller/controllers/client.go b/core/nebula/controller/controllers/client.go
new file mode 100644
index 0000000..da5b818
--- /dev/null
+++ b/core/nebula/controller/controllers/client.go
@@ -0,0 +1,81 @@
+package controllers
+
+import (
+	"bytes"
+	"crypto/tls"
+	"encoding/json"
+	"net/http"
+)
+
+type createCAReq struct {
+	Name string `json:"name"`
+}
+
+type createCAResp struct {
+	PrivateKey  []byte `json:"private_key"`
+	Certificate []byte `json:"certificate"`
+}
+
+func CreateCertificateAuthority(apiAddr, name string) ([]byte, []byte, error) {
+	var data bytes.Buffer
+	if err := json.NewEncoder(&data).Encode(createCAReq{name}); err != nil {
+		return nil, nil, err
+	}
+	client := &http.Client{
+		// TODO(giolekva): remove, for some reason valid certificates are not accepted on gioui android.
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+		},
+	}
+	resp, err := client.Post(apiAddr+"/api/process/ca", "application/json", &data)
+	if err != nil {
+		return nil, nil, err
+	}
+	var ret createCAResp
+	if err := json.NewDecoder(resp.Body).Decode(&ret); err != nil {
+		return nil, nil, err
+	}
+	return ret.PrivateKey, ret.Certificate, nil
+}
+
+type signNodeReq struct {
+	CAPrivateKey  []byte `json:"ca_private_key"`
+	CACert        []byte `json:"ca_certificate"`
+	NodeName      string `json:"node_name"`
+	NodePublicKey []byte `json:"node_public_key,omitempty"`
+	NodeIPCidr    string `json:"node_ip_cidr"`
+}
+
+type signNodeResp struct {
+	PrivateKey  []byte `json:"private_key,omitempty"`
+	Certificate []byte `json:"certificate"`
+}
+
+func SignNebulaNode(apiAddr string, caPrivateKey, caCert []byte, nodeName string, nodePublicKey []byte, nodeIp string) ([]byte, []byte, error) {
+	req := signNodeReq{
+		caPrivateKey,
+		caCert,
+		nodeName,
+		nodePublicKey,
+		nodeIp,
+	}
+	var data bytes.Buffer
+	if err := json.NewEncoder(&data).Encode(req); err != nil {
+		return nil, nil, err
+	}
+	client := &http.Client{
+		// TODO(giolekva): remove, for some reason valid certificates are not accepted on gioui android.
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+		},
+	}
+	resp, err := client.Post(apiAddr+"/api/process/node", "application/json", &data)
+	if err != nil {
+		return nil, nil, err
+	}
+	var ret signNodeResp
+	if err := json.NewDecoder(resp.Body).Decode(&ret); err != nil {
+		return nil, nil, err
+	}
+	return ret.PrivateKey, ret.Certificate, nil
+}