vpn client + api: add feature to approve one device from another
diff --git a/core/nebula/api/go.mod b/core/nebula/api/go.mod
index fa5d230..719cac7 100644
--- a/core/nebula/api/go.mod
+++ b/core/nebula/api/go.mod
@@ -2,12 +2,13 @@
go 1.17
+replace github.com/giolekva/pcloud/core/nebula/controller => ../controller
+
require (
github.com/giolekva/pcloud/core/nebula/controller v0.0.0-20211209144208-c054df13a2a1
github.com/gorilla/mux v1.8.0
github.com/jinzhu/copier v0.3.4
github.com/slackhq/nebula v1.5.0
- inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
k8s.io/api v0.23.0
k8s.io/apimachinery v0.23.0
k8s.io/client-go v0.23.0
@@ -27,8 +28,6 @@
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
- go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
- go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
diff --git a/core/nebula/api/go.sum b/core/nebula/api/go.sum
index 950363a..797fc8f 100644
--- a/core/nebula/api/go.sum
+++ b/core/nebula/api/go.sum
@@ -75,7 +75,6 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
-github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
@@ -95,8 +94,6 @@
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/giolekva/pcloud/core/nebula/controller v0.0.0-20211209144208-c054df13a2a1 h1:GyB1dLK2oWARCPXBpnXDI1sr2I9MhFuetrmUexUvUCs=
-github.com/giolekva/pcloud/core/nebula/controller v0.0.0-20211209144208-c054df13a2a1/go.mod h1:IasYiN/e2eUZKb6bPsG3NIOZ6Jpz8y7LuieA15TWqNs=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -329,10 +326,6 @@
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
-go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
-go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
-go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA=
-go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -728,8 +721,6 @@
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw=
-inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8=
k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8=
k8s.io/api v0.23.0 h1:WrL1gb73VSC8obi8cuYETJGXEoFNEh3LU0Pt+Sokgro=
k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg=
diff --git a/core/nebula/api/main.go b/core/nebula/api/main.go
index 1b74d8b..906a674 100644
--- a/core/nebula/api/main.go
+++ b/core/nebula/api/main.go
@@ -1,12 +1,18 @@
package main
import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
"embed"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"html/template"
+ "io"
"io/ioutil"
"log"
"net/http"
@@ -83,35 +89,6 @@
w.Write(qr)
}
-func (h *Handler) handleSignNode(w http.ResponseWriter, r *http.Request) {
- if err := r.ParseForm(); err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
- _, _, err := h.mgr.CreateNode(
- r.FormValue("node-namespace"),
- r.FormValue("node-name"),
- r.FormValue("ca-namespace"),
- r.FormValue("ca-name"),
- r.FormValue("ip-cidr"),
- r.FormValue("pub-key"),
- )
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- http.Redirect(w, r, "/", http.StatusSeeOther)
-}
-
-func (h *Handler) getNextIP(w http.ResponseWriter, r *http.Request) {
- ip, err := h.mgr.getNextIP()
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- fmt.Fprint(w, ip)
-}
-
type signReq struct {
Message []byte `json:"message"`
}
@@ -149,9 +126,6 @@
IPCidr string `json:"ip_cidr"`
}
-type joinResp struct {
-}
-
func (h *Handler) join(w http.ResponseWriter, r *http.Request) {
var req joinReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -174,6 +148,7 @@
*caName,
req.IPCidr,
string(req.PublicKey),
+ nil,
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -200,6 +175,111 @@
}
}
+type getResp struct {
+ Key []byte `json:"key"`
+ Nonce []byte `json:"nonce"`
+ Data []byte `json:"data"`
+}
+
+func (h *Handler) get(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("##### GET")
+ vars := mux.Vars(r)
+ pubKey, err := h.mgr.GetNodeEncryptionPublicKey(*namespace, vars["name"])
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ fmt.Println("Got key")
+ key := make([]byte, 32)
+ if _, err := io.ReadFull(rand.Reader, key); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ aesgcm, err := cipher.NewGCM(block)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ nonce := make([]byte, 12)
+ if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
+ panic(err.Error())
+ }
+ for {
+ time.Sleep(1 * time.Second)
+ cfg, err := h.mgr.GetNodeConfig(*namespace, vars["name"])
+ if err != nil {
+ fmt.Println(err.Error())
+ continue
+ }
+ cfgBytes, err := yaml.Marshal(cfg)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ cfgEnc := aesgcm.Seal(nil, nonce, cfgBytes, nil)
+ keyEnc, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, key, []byte(""))
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ nonceEnc, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, nonce, []byte(""))
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ resp := getResp{
+ Key: keyEnc,
+ Nonce: nonceEnc,
+ Data: cfgEnc,
+ }
+ if err := json.NewEncoder(w).Encode(&resp); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ break
+ }
+}
+
+type approveReq struct {
+ EncPublicKey []byte `json:"enc_public_key"`
+ Name string `json:"name"`
+ NetPublicKey []byte `json:"net_public_key"`
+ IPCidr string `json:"ip_cidr"`
+}
+
+type approveResp struct {
+}
+
+func (h *Handler) approve(w http.ResponseWriter, r *http.Request) {
+ var req approveReq
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ fmt.Println("---- APPROVE")
+ fmt.Printf("%#v\n", req)
+ _, _, err := h.mgr.CreateNode(
+ *namespace,
+ req.Name,
+ *namespace,
+ *caName,
+ req.IPCidr,
+ string(req.NetPublicKey),
+ req.EncPublicKey,
+ )
+ if err != nil {
+ fmt.Println(err.Error())
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
func loadConfigTemplate(path string) (map[string]interface{}, error) {
tmpl, err := ioutil.ReadFile(path)
if err != nil {
@@ -243,12 +323,12 @@
tmpls: t,
}
r := mux.NewRouter()
- r.HandleFunc("/api/ip", handler.getNextIP)
r.HandleFunc("/api/sign", handler.sign)
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("/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("/sign-node", handler.handleSignNode)
r.HandleFunc("/", handler.handleIndex)
http.Handle("/", r)
fmt.Printf("Starting HTTP server on port: %d\n", *port)
diff --git a/core/nebula/api/manager.go b/core/nebula/api/manager.go
index 790a01e..962b3b7 100644
--- a/core/nebula/api/manager.go
+++ b/core/nebula/api/manager.go
@@ -5,11 +5,11 @@
"crypto"
"crypto/ed25519"
"crypto/rand"
- "errors"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/base64"
"fmt"
- "inet.af/netaddr"
-
"github.com/jinzhu/copier"
"github.com/slackhq/nebula/cert"
@@ -72,7 +72,7 @@
return ret, nil
}
-func (m *Manager) CreateNode(namespace, name, caNamespace, caName, ipCidr, pubKey string) (string, string, error) {
+func (m *Manager) CreateNode(namespace, name, caNamespace, caName, ipCidr, pubKey string, encPubKey []byte) (string, string, error) {
node := &nebulav1.NebulaNode{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@@ -86,6 +86,9 @@
SecretName: fmt.Sprintf("%s-cert", name),
},
}
+ if encPubKey != nil {
+ node.Spec.EncPubKey = base64.StdEncoding.EncodeToString(encPubKey)
+ }
node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Create(context.TODO(), node, metav1.CreateOptions{})
if err != nil {
return "", "", err
@@ -128,25 +131,6 @@
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.getCASecret(m.namespace, m.caName)
if err != nil {
@@ -186,3 +170,15 @@
}
return m.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), node.Spec.SecretName, metav1.GetOptions{})
}
+
+func (m *Manager) GetNodeEncryptionPublicKey(namespace, name string) (*rsa.PublicKey, error) {
+ node, err := m.nebulaClient.LekvaV1().NebulaNodes(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+ if err != nil {
+ return nil, err
+ }
+ k, err := base64.StdEncoding.DecodeString(node.Spec.EncPubKey)
+ if err != nil {
+ return nil, err
+ }
+ return x509.ParsePKCS1PublicKey(k)
+}
diff --git a/core/nebula/controller/apis/nebula/v1/types.go b/core/nebula/controller/apis/nebula/v1/types.go
index c4e03f2..60545c0 100644
--- a/core/nebula/controller/apis/nebula/v1/types.go
+++ b/core/nebula/controller/apis/nebula/v1/types.go
@@ -55,6 +55,7 @@
CANamespace string `json:"caNamespace"`
IPCidr string `json:"ipCidr"`
PubKey string `json:"pubKey"`
+ EncPubKey string `json:"encPubKey"`
SecretName string `json:"secretName"`
}
diff --git a/core/nebula/controller/crds/nebula.crds.yaml b/core/nebula/controller/crds/nebula.crds.yaml
deleted file mode 100644
index c8de194..0000000
--- a/core/nebula/controller/crds/nebula.crds.yaml
+++ /dev/null
@@ -1,83 +0,0 @@
-apiVersion: apiextensions.k8s.io/v1
-kind: CustomResourceDefinition
-metadata:
- name: nebulacas.lekva.me
-spec:
- group: lekva.me
- scope: Namespaced
- names:
- kind: NebulaCA
- listKind: NebulaCAList
- plural: nebulacas
- singular: nebulaca
- shortNames:
- - nca
- - ncas
- versions:
- - name: v1
- served: true
- storage: true
- subresources:
- status: {}
- schema:
- openAPIV3Schema:
- type: object
- properties:
- spec:
- type: object
- properties:
- secretName:
- type: string
- status:
- type: object
- properties:
- state:
- 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
- caNamespace:
- type: string
- ipCidr:
- type: string
- pubKey:
- type: string
- secretName:
- type: string
- status:
- type: object
- properties:
- state:
- type: string
- message:
- type: string