VPN: move certificate signing logit to api service to which controller delegates ops
diff --git a/core/nebula/api/main.go b/core/nebula/api/main.go
index 906a674..9127630 100644
--- a/core/nebula/api/main.go
+++ b/core/nebula/api/main.go
@@ -15,6 +15,7 @@
"io"
"io/ioutil"
"log"
+ "net"
"net/http"
"time"
@@ -262,8 +263,6 @@
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
- fmt.Println("---- APPROVE")
- fmt.Printf("%#v\n", req)
_, _, err := h.mgr.CreateNode(
*namespace,
req.Name,
@@ -280,6 +279,59 @@
}
}
+type processCAReq struct {
+ Name string `json:"name"`
+}
+
+func (h *Handler) processCA(w http.ResponseWriter, r *http.Request) {
+ var req processCAReq
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ ca, err := CreateCertificateAuthority(req.Name)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(ca); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+type processNodeReq 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"`
+}
+
+func (h *Handler) processNode(w http.ResponseWriter, r *http.Request) {
+ var req processNodeReq
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ _, ipNet, err := net.ParseCIDR(req.NodeIPCidr)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ node, err := SignNebulaNode(req.CAPrivateKey, req.CACert, req.NodeName, req.NodePublicKey, ipNet)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(node); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
func loadConfigTemplate(path string) (map[string]interface{}, error) {
tmpl, err := ioutil.ReadFile(path)
if err != nil {
@@ -327,6 +379,8 @@
r.HandleFunc("/api/join", handler.join)
r.HandleFunc("/api/approve", handler.approve)
r.HandleFunc("/api/get/{name:[a-zA-z0-9-]+}", handler.get)
+ r.HandleFunc("/api/process/authority", handler.processCA)
+ r.HandleFunc("/api/process/node", handler.processNode)
r.HandleFunc("/node/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleNode)
r.HandleFunc("/ca/{namespace:[a-zA-z0-9-]+}/{name:[a-zA-z0-9-]+}", handler.handleCA)
r.HandleFunc("/", handler.handleIndex)
diff --git a/core/nebula/api/nebula.go b/core/nebula/api/nebula.go
new file mode 100644
index 0000000..b4649f1
--- /dev/null
+++ b/core/nebula/api/nebula.go
@@ -0,0 +1,125 @@
+package main
+
+import (
+ "crypto/ed25519"
+ "crypto/rand"
+ "errors"
+ "io"
+ "net"
+ "time"
+
+ "github.com/slackhq/nebula/cert"
+ "golang.org/x/crypto/curve25519"
+)
+
+type CertificateAuthority struct {
+ PrivateKey []byte `json:"private_key"`
+ Certificate []byte `json:"certificate"`
+}
+
+func CreateCertificateAuthority(name string) (*CertificateAuthority, error) {
+ t := time.Now().Add(time.Duration(-1 * time.Second))
+ rawPub, rawPriv, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ return nil, err
+ }
+ nc := cert.NebulaCertificate{
+ Details: cert.NebulaCertificateDetails{
+ Name: name,
+ NotBefore: t,
+ NotAfter: t.Add(time.Duration(8760 * time.Hour)),
+ PublicKey: rawPub,
+ IsCA: true,
+ },
+ }
+ if err := nc.Sign(rawPriv); err != nil {
+ return nil, err
+ }
+ certSerialized, err := nc.MarshalToPEM()
+ if err != nil {
+ return nil, err
+ }
+ privKeySerialzied := cert.MarshalEd25519PrivateKey(rawPriv)
+ return &CertificateAuthority{
+ privKeySerialzied,
+ certSerialized,
+ }, nil
+}
+
+type NebulaNode struct {
+ PrivateKey []byte `json:"private_key,omitempty"`
+ Certificate []byte `json:"certificate"`
+}
+
+func SignNebulaNode(rawCAPrivateKey []byte, rawCACert []byte, nodeName string, nodePublicKey []byte, ip *net.IPNet) (*NebulaNode, error) {
+ caKey, _, err := cert.UnmarshalEd25519PrivateKey(rawCAPrivateKey)
+ if err != nil {
+ return nil, err
+ }
+ caCert, _, err := cert.UnmarshalNebulaCertificateFromPEM(rawCACert)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := caCert.VerifyPrivateKey(caKey); err != nil {
+ return nil, err
+ }
+ issuer, err := caCert.Sha256Sum()
+ if err != nil {
+ return nil, err
+ }
+ if caCert.Expired(time.Now()) {
+ return nil, errors.New("ca certificate is expired")
+ }
+ var pub, priv []byte
+ if nodePublicKey != nil {
+ var err error
+ pub, _, err = cert.UnmarshalX25519PublicKey(nodePublicKey)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ var rawPriv []byte
+ var err error
+ pub, rawPriv, err = x25519Keypair()
+ if err != nil {
+ return nil, err
+ }
+ priv = cert.MarshalX25519PrivateKey(rawPriv)
+ }
+ t := time.Now().Add(time.Duration(-1 * time.Second))
+ nc := cert.NebulaCertificate{
+ Details: cert.NebulaCertificateDetails{
+ Name: nodeName,
+ Ips: []*net.IPNet{ip},
+ NotBefore: t,
+ NotAfter: caCert.Details.NotAfter.Add(time.Duration(-1 * time.Second)),
+ PublicKey: pub,
+ IsCA: false,
+ Issuer: issuer,
+ },
+ }
+ if err := nc.CheckRootConstrains(caCert); err != nil {
+ return nil, err
+ }
+ if err := nc.Sign(caKey); err != nil {
+ return nil, err
+ }
+ certSerialized, err := nc.MarshalToPEM()
+ if err != nil {
+ return nil, err
+ }
+ return &NebulaNode{
+ PrivateKey: priv,
+ Certificate: certSerialized,
+ }, nil
+}
+
+func x25519Keypair() ([]byte, []byte, error) {
+ var pubkey, privkey [32]byte
+ if _, err := io.ReadFull(rand.Reader, privkey[:]); err != nil {
+ return nil, nil, err
+ }
+ curve25519.ScalarBaseMult(&pubkey, &privkey)
+ return pubkey[:], privkey[:], nil
+}