vpn client + api: add feature to approve one device from another
diff --git a/charts/nebula/crds/nebula-node.yaml b/charts/nebula/crds/nebula-node.yaml
index d59963f..ddb79de 100644
--- a/charts/nebula/crds/nebula-node.yaml
+++ b/charts/nebula/crds/nebula-node.yaml
@@ -34,6 +34,8 @@
type: string
pubKey:
type: string
+ encPubKey:
+ type: string
secretName:
type: string
status:
diff --git a/charts/nebula/crds/nebula.crds.yaml b/charts/nebula/crds/nebula.crds.yaml
deleted file mode 100644
index f6b8411..0000000
--- a/charts/nebula/crds/nebula.crds.yaml
+++ /dev/null
@@ -1,37 +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
diff --git "a/core/client/cmd/pcloud/\043app_android.go\043" "b/core/client/cmd/pcloud/\043app_android.go\043"
new file mode 100644
index 0000000..2b28a11
--- /dev/null
+++ "b/core/client/cmd/pcloud/\043app_android.go\043"
@@ -0,0 +1,241 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "time"
+ "unsafe"
+
+ "gioui.org/app"
+ "github.com/giolekva/pcloud/core/client/jni"
+ "github.com/sirupsen/logrus"
+ "github.com/slackhq/nebula"
+ "github.com/slackhq/nebula/cert"
+ nc "github.com/slackhq/nebula/config"
+)
+
+type androidApp struct {
+ jvm *jni.JVM
+ appCtx jni.Object // PCloudApp
+ activity jni.Object // PCloudActivity
+ service jni.Object // PCloudVPNService
+ nebulaConfig []byte
+ ctrl *nebula.Control
+}
+
+func createApp() App {
+ return &androidApp{
+ jvm: (*jni.JVM)(unsafe.Pointer(app.JavaVM())),
+ appCtx: jni.Object(app.AppContext()),
+ }
+}
+
+func (a *androidApp) LaunchBarcodeScanner() error {
+ return jni.Do(a.jvm, func(env *jni.Env) error {
+ cls := jni.GetObjectClass(env, a.activity)
+ m := jni.GetMethodID(env, cls, "launchBarcodeScanner", "()Ljava/lang/String;")
+ _, err := jni.CallObjectMethod(env, a.activity, m)
+ return err
+ })
+}
+
+func (a *androidApp) OnView(e app.ViewEvent) error {
+ a.deleteActivityRef()
+ view := jni.Object(e.View)
+ if view == 0 {
+ return nil
+ }
+ activity, err := a.contextForView(view)
+ if err != nil {
+ return err
+ }
+ a.activity = activity
+ return nil
+}
+
+func (a *androidApp) deleteActivityRef() {
+ if a.activity == 0 {
+ return
+ }
+ jni.Do(a.jvm, func(env *jni.Env) error {
+ jni.DeleteGlobalRef(env, a.activity)
+ return nil
+ })
+ a.activity = 0
+}
+
+func (a *androidApp) contextForView(view jni.Object) (jni.Object, error) {
+ if view == 0 {
+ return 0, errors.New("Should not reach")
+ }
+ var ctx jni.Object
+ err := jni.Do(a.jvm, func(env *jni.Env) error {
+ cls := jni.GetObjectClass(env, view)
+ m := jni.GetMethodID(env, cls, "getContext", "()Landroid/content/Context;")
+ var err error
+ ctx, err = jni.CallObjectMethod(env, view, m)
+ ctx = jni.NewGlobalRef(env, ctx)
+ return err
+ })
+ if err != nil {
+ return 0, err
+ }
+ return ctx, nil
+}
+
+func (a *androidApp) StartVPN(config []byte) error {
+ fmt.Println("2222222")
+ a.nebulaConfig = config
+ fmt.Println(string(a.nebulaConfig))
+ return jni.Do(a.jvm, func(env *jni.Env) error {
+ fmt.Println(123123)
+ cls := jni.GetObjectClass(env, a.activity)
+ m := jni.GetMethodID(env, cls, "startVpn", "(Ljava/lang/String;)Ljava/lang/String;")
+ jConfig := jni.JavaString(env, string(config))
+ _, err := jni.CallObjectMethod(env, a.activity, m, jni.Value(jConfig))
+ fmt.Println(123123123)
+ return err
+
+ })
+}
+
+func (a *androidApp) Connect(serv interface{}) error {
+ s, ok := serv.(jni.Object)
+ if !ok {
+ return fmt.Errorf("Unexpected service type: %T", serv)
+ }
+ jni.Do(a.jvm, func(env *jni.Env) error {
+ if jni.IsSameObject(env, s, a.service) {
+ // We already have a reference.
+ jni.DeleteGlobalRef(env, s)
+ return nil
+ }
+ if a.service != 0 {
+ jni.DeleteGlobalRef(env, a.service)
+ }
+ // netns.SetAndroidProtectFunc(func(fd int) error {
+ // return jni.Do(a.jvm, func(env *jni.Env) error {
+ // // Call https://developer.android.com/reference/android/net/VpnService#protect(int)
+ // // to mark fd as a socket that should bypass the VPN and use the underlying network.
+ // cls := jni.GetObjectClass(env, s)
+ // m := jni.GetMethodID(env, cls, "protect", "(I)Z")
+ // ok, err := jni.CallBooleanMethod(env, s, m, jni.Value(fd))
+ // // TODO(bradfitz): return an error back up to netns if this fails, once
+ // // we've had some experience with this and analyzed the logs over a wide
+ // // range of Android phones. For now we're being paranoid and conservative
+ // // and do the JNI call to protect best effort, only logging if it fails.
+ // // The risk of returning an error is that it breaks users on some Android
+ // // versions even when they're not using exit nodes. I'd rather the
+ // // relatively few number of exit node users file bug reports if Tailscale
+ // // doesn't work and then we can look for this log print.
+ // if err != nil || !ok {
+ // log.Printf("[unexpected] VpnService.protect(%d) = %v, %v", fd, ok, err)
+ // }
+ // return nil // even on error. see big TODO above.
+ // })
+ // })
+ a.service = s
+ return nil
+ })
+ return a.buildVPNConfigurationAndConnect()
+}
+
+func (a *androidApp) buildVPNConfigurationAndConnect() error {
+ if string(a.nebulaConfig) == "" {
+ return nil
+ }
+ fmt.Println(333333333)
+ fmt.Println(string(a.nebulaConfig))
+ // return nil
+ // if err := a.callVoidMethod(a.appCtx, "prepareVPN", "(Landroid/app/Activity;I)V",
+ // jni.Value(act), jni.Value(requestPrepareVPN)); err != nil {
+ // return nil
+ // }
+
+ config := nc.NewC(logrus.StandardLogger())
+ if err := config.LoadString(string(a.nebulaConfig)); err != nil {
+ return err
+ }
+ pki := config.GetMap("pki", nil)
+ hostCert, _, err := cert.UnmarshalNebulaCertificateFromPEM([]byte(pki["cert"].(string)))
+ if err != nil {
+ panic(err)
+ }
+ t := time.Now()
+ fmt.Println("#########3")
+ fmt.Println(t.String())
+ fmt.Println(hostCert.Details.NotBefore.String())
+ fmt.Println(hostCert.Details.NotAfter.String())
+ fmt.Println(t.Before(hostCert.Details.NotBefore))
+ fmt.Println(t.After(hostCert.Details.NotAfter))
+ fmt.Println("#########3")
+ // return nil
+ return jni.Do(a.jvm, func(env *jni.Env) error {
+ fmt.Println("---------")
+ cls := jni.GetObjectClass(env, a.service)
+ m := jni.GetMethodID(env, cls, "newBuilder", "()Landroid/net/VpnService$Builder;")
+ b, err := jni.CallObjectMethod(env, a.service, m)
+ if err != nil {
+ return fmt.Errorf("PCloudVPNService.newBuilder: %v", err)
+ }
+ fmt.Println("---------")
+ bcls := jni.GetObjectClass(env, b)
+ addAddress := jni.GetMethodID(env, bcls, "addAddress", "(Ljava/lang/String;I)Landroid/net/VpnService$Builder;")
+ addRoute := jni.GetMethodID(env, bcls, "addRoute", "(Ljava/lang/String;I)Landroid/net/VpnService$Builder;")
+ for _, ipNet := range hostCert.Details.Ips {
+ ip := ipNet.IP.String()
+ prefix, _ := ipNet.Mask.Size()
+ _, err := jni.CallObjectMethod(
+ env,
+ b,
+ addAddress,
+ jni.Value(jni.JavaString(env, ip)),
+ jni.Value(jni.Value(prefix)))
+ if err != nil {
+ return err
+ }
+ _, err = jni.CallObjectMethod(
+ env,
+ b,
+ addRoute,
+ jni.Value(jni.JavaString(env, ip)),
+ jni.Value(jni.Value(prefix)))
+ }
+ tun := config.GetMap("tun", nil)
+ setMtu := jni.GetMethodID(env, bcls, "setMtu", "(I)Landroid/net/VpnService$Builder;")
+ if _, err := jni.CallObjectMethod(env, b, setMtu, jni.Value(tun["mtu"].(int))); err != nil {
+ return err
+ }
+ establish := jni.GetMethodID(env, bcls, "establish", "()Landroid/os/ParcelFileDescriptor;")
+ parcelFD, err := jni.CallObjectMethod(env, b, establish)
+ if err != nil {
+ return err
+ }
+ parcelCls := jni.GetObjectClass(env, parcelFD)
+ detachFd := jni.GetMethodID(env, parcelCls, "detachFd", "()I")
+ tunFD, err := jni.CallIntMethod(env, parcelFD, detachFd)
+ if err != nil {
+ return fmt.Errorf("detachFd: %v", err)
+ }
+ fd := int(tunFD)
+ protect := jni.GetMethodID(env, cls, "protect", "(I)")
+ ok, err := jni.CallBooleanMethod(env, a.service, protect, jni.Value(fd))
+ if err != nil || !ok {
+ return fmt.Errorf("protect: %v %v", err, ok)
+ }
+ // getFd := jni.GetMethodID(env, parcelCls, "getFd", "()I")
+ // tunFD, err := jni.CallIntMethod(env, parcelFD, getFd)
+ // if err != nil {
+ // return err
+ // }
+
+ fmt.Println("===========")
+ ctrl, err := nebula.Main(config, false, "pcloud", logrus.StandardLogger(), &fd)
+ if err != nil {
+ return err
+ }
+ a.ctrl = ctrl
+ return nil
+ })
+
+}
diff --git a/core/client/cmd/pcloud/app.go b/core/client/cmd/pcloud/app.go
index a0cab6c..5020e68 100644
--- a/core/client/cmd/pcloud/app.go
+++ b/core/client/cmd/pcloud/app.go
@@ -3,6 +3,7 @@
import "gioui.org/app"
type App interface {
+ Capabilities() DeviceCapabilities
LaunchBarcodeScanner() error
OnView(app.ViewEvent) error
UpdateService(service interface{}) error
diff --git a/core/client/cmd/pcloud/app_android.go b/core/client/cmd/pcloud/app_android.go
index 3d18663..8feb2e7 100644
--- a/core/client/cmd/pcloud/app_android.go
+++ b/core/client/cmd/pcloud/app_android.go
@@ -28,6 +28,12 @@
}
}
+func (a *androidApp) Capabilities() DeviceCapabilities {
+ return DeviceCapabilities{
+ HasCamera: true,
+ }
+}
+
func (a *androidApp) CreateStorage() Storage {
return CreateStorage(a.jvm, a.appCtx)
}
@@ -134,11 +140,11 @@
}
func (a *androidApp) Connect(config Config) error {
- if config.Network == nil {
+ if config.Network.Config == nil {
return nil
}
nebulaConfig := nc.NewC(logrus.StandardLogger())
- if err := nebulaConfig.LoadString(string(config.Network)); err != nil {
+ if err := nebulaConfig.LoadString(string(config.Network.Config)); err != nil {
return err
}
pki := nebulaConfig.GetMap("pki", nil)
diff --git a/core/client/cmd/pcloud/app_darwin.go b/core/client/cmd/pcloud/app_darwin.go
deleted file mode 100644
index 3e4bd04..0000000
--- a/core/client/cmd/pcloud/app_darwin.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package main
-
-import (
- "errors"
-
- "gioui.org/app"
-)
-
-type darwinApp struct {
-}
-
-func createApp() App {
- return &darwinApp{}
-}
-
-func (a *darwinApp) LaunchBarcodeScanner() error {
- return errors.New("no camera")
-}
-
-func (a *darwinApp) OnView(e app.ViewEvent) error {
- return nil
-}
-
-func (a *darwinApp) Connect(config Config) error {
- return nil
-}
-
-func (a *darwinApp) UpdateService(serv interface{}) error {
- return nil
-}
-
-func (a *darwinApp) TriggerService() error {
- return nil
-}
-
-func (a *darwinApp) CreateStorage() Storage {
- return nil
-}
-
-func (a *darwinApp) GetHostname() (string, error) {
- return "", nil
-}
diff --git a/core/client/cmd/pcloud/app_macos.go b/core/client/cmd/pcloud/app_macos.go
new file mode 100644
index 0000000..3a231d3
--- /dev/null
+++ b/core/client/cmd/pcloud/app_macos.go
@@ -0,0 +1,70 @@
+//go:build darwin && !ios
+// +build darwin,!ios
+
+package main
+
+import (
+ "errors"
+ "os"
+
+ "gioui.org/app"
+ "github.com/sirupsen/logrus"
+ "github.com/slackhq/nebula"
+ nc "github.com/slackhq/nebula/config"
+)
+
+type macosApp struct {
+ ctrl *nebula.Control
+}
+
+func createApp() App {
+ return &macosApp{}
+}
+
+func (a *macosApp) Capabilities() DeviceCapabilities {
+ return DeviceCapabilities{
+ HasCamera: false,
+ }
+}
+
+func (a *macosApp) LaunchBarcodeScanner() error {
+ return errors.New("no camera")
+}
+
+func (a *macosApp) OnView(e app.ViewEvent) error {
+ return nil
+}
+
+func (a *macosApp) Connect(config Config) error {
+ if config.Network.Config == nil {
+ return nil
+ }
+ nebulaConfig := nc.NewC(logrus.StandardLogger())
+ if err := nebulaConfig.LoadString(string(config.Network.Config)); err != nil {
+ return err
+ }
+ ctrl, err := nebula.Main(nebulaConfig, false, "pcloud", logrus.StandardLogger(), nil)
+ if err != nil {
+ return err
+ }
+ ctrl.Start()
+ a.ctrl = ctrl
+ return nil
+}
+
+func (a *macosApp) UpdateService(serv interface{}) error {
+ return nil
+}
+
+func (a *macosApp) TriggerService() error {
+ p.ConnectRequested(nil)
+ return nil
+}
+
+func (a *macosApp) CreateStorage() Storage {
+ return CreateStorage()
+}
+
+func (a *macosApp) GetHostname() (string, error) {
+ return os.Hostname()
+}
diff --git a/core/client/cmd/pcloud/callbacks_android.go b/core/client/cmd/pcloud/callbacks_android.go
index b7060aa..4664538 100644
--- a/core/client/cmd/pcloud/callbacks_android.go
+++ b/core/client/cmd/pcloud/callbacks_android.go
@@ -15,7 +15,7 @@
func Java_me_lekva_pcloud_PCloudActivity_qrcodeScanned(env *C.JNIEnv, this C.jobject, contents C.jobject) {
jenv := (*jni.Env)(unsafe.Pointer(env))
code := jni.GoString(jenv, jni.String(contents))
- p.InviteQRCodeScanned([]byte(code))
+ p.QRCodeScanned([]byte(code))
}
//export Java_me_lekva_pcloud_PCloudVPNService_connect
diff --git a/core/client/cmd/pcloud/client.go b/core/client/cmd/pcloud/client.go
index 78a31f1..f755281 100644
--- a/core/client/cmd/pcloud/client.go
+++ b/core/client/cmd/pcloud/client.go
@@ -2,10 +2,15 @@
import (
"bytes"
+ "crypto/aes"
+ "crypto/cipher"
"crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
"crypto/tls"
"encoding/base64"
"encoding/json"
+ "fmt"
"io"
"net/http"
@@ -16,7 +21,9 @@
type VPNClient interface {
Sign(apiAddr string, message []byte) ([]byte, error)
- Join(apiAddr, hostname string, message, signature []byte) ([]byte, error)
+ Join(apiAddr, hostname string, publicKey, privateKey []byte, message, signature []byte) ([]byte, error)
+ Approve(apiAddr, hostname, ipCidr string, encPublicKey, netPublicKey []byte) error
+ Get(apiAddr, hostname string, encPrivateKey *rsa.PrivateKey, netPrivateKey []byte) ([]byte, error)
}
type directVPNClient struct {
@@ -69,16 +76,12 @@
cfgYamlB64 string
}
-func (c *directVPNClient) Join(apiAddr, hostname string, message, signature []byte) ([]byte, error) {
- pubKey, privKey, err := x25519Keypair()
- if err != nil {
- return nil, err
- }
+func (c *directVPNClient) Join(apiAddr, hostname string, publicKey, privateKey []byte, message, signature []byte) ([]byte, error) {
req := joinReq{
message,
signature,
hostname,
- cert.MarshalX25519PublicKey(pubKey),
+ cert.MarshalX25519PublicKey(publicKey),
"111.0.0.13/24",
}
var data bytes.Buffer
@@ -111,7 +114,101 @@
if pki, ok = cfgMap["pki"].(map[string]interface{}); !ok {
panic("Must not reach")
}
- pki["key"] = string(cert.MarshalX25519PrivateKey(privKey))
+ pki["key"] = string(cert.MarshalX25519PrivateKey(privateKey))
+ return yaml.Marshal(cfgMap)
+}
+
+type approveReq struct {
+ EncPublicKey []byte `json:"enc_public_key"`
+ Name string `json:"name"`
+ NetPublicKey []byte `json:"net_public_key"`
+ IPCidr string `json:"ip_cidr"`
+}
+
+func (c *directVPNClient) Approve(apiAddr, hostname, ipCidr string, encPublicKey, netPublicKey []byte) error {
+ req := approveReq{
+ encPublicKey,
+ hostname,
+ cert.MarshalX25519PublicKey(netPublicKey),
+ ipCidr,
+ }
+ var data bytes.Buffer
+ if err := json.NewEncoder(&data).Encode(req); err != nil {
+ return 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},
+ },
+ }
+ _, err := client.Post(apiAddr+"/api/approve", "application/json", &data)
+ return err
+}
+
+type getResp struct {
+ Key []byte `json:"key"`
+ Nonce []byte `json:"nonce"`
+ Data []byte `json:"data"`
+}
+
+func (c *directVPNClient) Get(apiAddr, hostname string, encPrivateKey *rsa.PrivateKey, netPrivateKey []byte) ([]byte, error) {
+ 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},
+ },
+ }
+ r, err := client.Get(apiAddr + "/api/get/" + hostname)
+ if err != nil {
+ fmt.Println(err.Error())
+ return nil, err
+ }
+ var resp getResp
+ if err := json.NewDecoder(r.Body).Decode(&resp); err != nil {
+ fmt.Println(err.Error())
+ return nil, err
+ }
+ // TODO(giolekva): encrypt key and nonce together
+ key, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, encPrivateKey, resp.Key, []byte(""))
+ if err != nil {
+ fmt.Println(err.Error())
+ return nil, err
+ }
+ nonce, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, encPrivateKey, resp.Nonce, []byte(""))
+ if err != nil {
+ fmt.Println(1123123)
+ fmt.Println(err.Error())
+ return nil, err
+ }
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ fmt.Println(err.Error())
+ return nil, err
+ }
+ aesgcm, err := cipher.NewGCM(block)
+ if err != nil {
+ fmt.Println(err.Error())
+ return nil, err
+ }
+
+ cfgYaml, err := aesgcm.Open(nil, nonce, resp.Data, nil)
+ if err != nil {
+ fmt.Println(22222)
+ fmt.Println(err.Error())
+ return nil, err
+ }
+ var cfgMap map[string]interface{}
+ if err := yaml.Unmarshal(cfgYaml, &cfgMap); err != nil {
+ fmt.Println(err.Error())
+ return nil, err
+ }
+ var pki map[string]interface{}
+ var ok bool
+ if pki, ok = cfgMap["pki"].(map[string]interface{}); !ok {
+ panic("Must not reach")
+ }
+ pki["key"] = string(cert.MarshalX25519PrivateKey(netPrivateKey))
return yaml.Marshal(cfgMap)
}
diff --git a/core/client/cmd/pcloud/config.go b/core/client/cmd/pcloud/config.go
index 0cf551d..6a12ada 100644
--- a/core/client/cmd/pcloud/config.go
+++ b/core/client/cmd/pcloud/config.go
@@ -1,6 +1,14 @@
package main
type Config struct {
- ApiAddr string `json:"api_addr"`
- Network []byte `json:"network"`
+ ApiAddr string `json:"api_addr,omitempty"`
+ Enc struct {
+ PublicKey []byte `json:"public_key,omitempty"`
+ PrivateKey []byte `json:"private_key,omitempty"`
+ } `json:"encyption,omitempty"`
+ Network struct {
+ PublicKey []byte `json:"public_key,omitempty"`
+ PrivateKey []byte `json:"private_key,omitempty"`
+ Config []byte `json:"network,omitempty"`
+ } `json:"network,omitempty"`
}
diff --git a/core/client/cmd/pcloud/main.go b/core/client/cmd/pcloud/main.go
index 287407b..38c55ff 100644
--- a/core/client/cmd/pcloud/main.go
+++ b/core/client/cmd/pcloud/main.go
@@ -2,7 +2,11 @@
import (
"bytes"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
"encoding/json"
+ "errors"
"flag"
"fmt"
"image"
@@ -24,14 +28,23 @@
var p *processor
+type ScanQRFor int
+
+const (
+ ScanQRForJoin ScanQRFor = 0
+ ScanQRForApprove = 1
+)
+
type processor struct {
vc VPNClient
app App
st Storage
ui *UI
- inviteQrCh chan image.Image
- inviteQrScannedCh chan []byte
+ scanQRFor ScanQRFor
+ inviteQrCh chan image.Image
+ qrScannedCh chan []byte
+ joinQrCh chan image.Image
onConnectCh chan interface{}
onDisconnectCh chan interface{}
@@ -43,20 +56,21 @@
th := material.NewTheme(gofont.Collection())
app := createApp()
return &processor{
- vc: NewDirectVPNClient(*vpnApiAddr),
- app: app,
- st: app.CreateStorage(),
- ui: NewUI(th),
- inviteQrCh: make(chan image.Image, 1),
- inviteQrScannedCh: make(chan []byte, 1),
- onConnectCh: make(chan interface{}, 1),
- onDisconnectCh: make(chan interface{}, 1),
- onConfigCh: make(chan struct{}, 1),
+ vc: NewDirectVPNClient(*vpnApiAddr),
+ app: app,
+ st: app.CreateStorage(),
+ ui: NewUI(th, app.Capabilities()),
+ inviteQrCh: make(chan image.Image, 1),
+ qrScannedCh: make(chan []byte, 1),
+ joinQrCh: make(chan image.Image, 1),
+ onConnectCh: make(chan interface{}, 1),
+ onDisconnectCh: make(chan interface{}, 1),
+ onConfigCh: make(chan struct{}, 1),
}
}
-func (p *processor) InviteQRCodeScanned(code []byte) {
- p.inviteQrScannedCh <- code
+func (p *processor) QRCodeScanned(code []byte) {
+ p.qrScannedCh <- code
}
func (p *processor) ConnectRequested(service interface{}) {
@@ -70,7 +84,31 @@
p.onDisconnectCh <- service
}
+func (p *processor) generatePublicPrivateKey() error {
+ config, err := p.st.Get()
+ if err != nil {
+ return err
+ }
+ if config.Enc.PrivateKey != nil {
+ return nil
+ }
+ privKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ panic(err)
+ }
+ config.Enc.PrivateKey = x509.MarshalPKCS1PrivateKey(privKey)
+ config.Enc.PublicKey = x509.MarshalPKCS1PublicKey(&privKey.PublicKey)
+ config.Network.PublicKey, config.Network.PrivateKey, err = x25519Keypair()
+ if err != nil {
+ return err
+ }
+ return p.st.Store(config)
+}
+
func (p *processor) run() error {
+ if err := p.generatePublicPrivateKey(); err != nil {
+ return err
+ }
w := app.NewWindow(
app.Size(unit.Px(1500), unit.Px(1500)),
app.Title("PCloud"),
@@ -89,7 +127,7 @@
if config, err := p.st.Get(); err != nil {
return err
} else {
- if config.Network != nil {
+ if config.Network.Config != nil {
p.onConfigCh <- struct{}{}
}
}
@@ -111,10 +149,57 @@
case img := <-p.inviteQrCh:
p.ui.InviteQRGenerated(img)
w.Invalidate()
- case code := <-p.inviteQrScannedCh:
- if err := p.JoinAndGetNetworkConfig(code); err != nil {
- return err
+ case code := <-p.qrScannedCh:
+ switch p.scanQRFor {
+ case ScanQRForJoin:
+ if err := p.JoinAndGetNetworkConfig(code); err != nil {
+ return err
+ }
+ case ScanQRForApprove:
+ if err := p.ApproveOther(code); err != nil {
+ return err
+ }
+ default:
+ return errors.New("Must not reach!")
}
+ case img := <-p.joinQrCh:
+ p.ui.JoinQRGenerated(img)
+ w.Invalidate()
+ go func() {
+ cnt := 0
+ for {
+ fmt.Println(cnt)
+ cnt++
+ if cnt > 5 {
+ break
+ }
+ time.Sleep(time.Second)
+ config, err := p.st.Get()
+ if err != nil {
+ continue
+ }
+ privKey, err := x509.ParsePKCS1PrivateKey(config.Enc.PrivateKey)
+ if err != nil {
+ continue
+ }
+ hostname, err := p.app.GetHostname()
+ if err != nil {
+ continue
+ }
+ hostname = sanitizeHostname(hostname)
+ network, err := p.vc.Get("https://vpn.lekva.me", hostname, privKey, config.Network.PrivateKey)
+ if err != nil {
+ continue
+ }
+ config.ApiAddr = "https://vpn.lekva.me"
+ config.Network.Config = network
+ if err := p.st.Store(config); err != nil {
+ continue
+ }
+ p.onConfigCh <- struct{}{}
+ break
+ }
+ }()
case <-p.onConfigCh:
if err := p.app.TriggerService(); err != nil {
return err
@@ -147,8 +232,25 @@
panic(err)
}
}()
+ case EventGetJoinQRCode:
+ go func() {
+ if img, err := p.generateJoinQRCode(); err == nil {
+ p.joinQrCh <- img
+ } else {
+ // TODO(giolekva): do not panic
+ panic(err)
+ }
+ }()
+ case EventApproveOther:
+ p.scanQRFor = ScanQRForApprove
+ if err := p.app.LaunchBarcodeScanner(); err != nil {
+ return err
+ }
case EventScanBarcode:
- return p.app.LaunchBarcodeScanner()
+ p.scanQRFor = ScanQRForJoin
+ if err := p.app.LaunchBarcodeScanner(); err != nil {
+ return err
+ }
default:
return fmt.Errorf("Unhandled event: %#v", e)
}
@@ -156,12 +258,19 @@
return nil
}
-type qrCodeData struct {
+type inviteQrCodeData struct {
VPNApiAddr string `json:"vpn_api_addr"`
Message []byte `json:"message"`
Signature []byte `json:"signature"`
}
+type joinQrCodeData struct {
+ EncPublicKey []byte `json:"enc_public_key"`
+ Name string `json:"name"`
+ NetPublicKey []byte `json:"net_public_key"`
+ IPCidr string `json:"ip_cidr"`
+}
+
func (p *processor) generateInviteQRCode() (image.Image, error) {
config, err := p.st.Get()
if err != nil {
@@ -172,7 +281,7 @@
if err != nil {
return nil, err
}
- c := qrCodeData{
+ c := inviteQrCodeData{
config.ApiAddr,
message,
signature,
@@ -192,8 +301,43 @@
return img, nil
}
+func (p *processor) generateJoinQRCode() (image.Image, error) {
+ config, err := p.st.Get()
+ if err != nil {
+ return nil, err
+ }
+ hostname, err := p.app.GetHostname()
+ if err != nil {
+ return nil, err
+ }
+ hostname = sanitizeHostname(hostname)
+ c := joinQrCodeData{
+ EncPublicKey: config.Enc.PublicKey,
+ Name: hostname,
+ NetPublicKey: config.Network.PublicKey,
+ IPCidr: "111.0.0.14/24",
+ }
+ var data bytes.Buffer
+ if err := json.NewEncoder(&data).Encode(c); err != nil {
+ return nil, err
+ }
+ qr, err := qrcode.Encode(data.String(), qrcode.Medium, 1024)
+ if err != nil {
+ return nil, err
+ }
+ img, err := png.Decode(bytes.NewReader(qr))
+ if err != nil {
+ return nil, err
+ }
+ return img, nil
+}
+
func (p *processor) JoinAndGetNetworkConfig(code []byte) error {
- var invite qrCodeData
+ config, err := p.st.Get()
+ if err != nil {
+ return err
+ }
+ var invite inviteQrCodeData
if err := json.NewDecoder(bytes.NewReader(code)).Decode(&invite); err != nil {
return err
}
@@ -201,19 +345,39 @@
if err != nil {
return err
}
- hostname = strings.ToLower(strings.ReplaceAll(hostname, " ", "-"))
- fmt.Printf("------ %s\n", hostname)
- network, err := p.vc.Join(invite.VPNApiAddr, hostname, invite.Message, invite.Signature)
+ hostname = sanitizeHostname(hostname)
+ network, err := p.vc.Join(invite.VPNApiAddr, hostname, config.Network.PublicKey, config.Network.PrivateKey, invite.Message, invite.Signature)
if err != nil {
return err
}
- if err := p.st.Store(Config{invite.VPNApiAddr, network}); err != nil {
+ config.ApiAddr = invite.VPNApiAddr
+ config.Network.Config = network
+ if err := p.st.Store(config); err != nil {
return err
}
p.onConfigCh <- struct{}{}
return nil
}
+func (p *processor) ApproveOther(code []byte) error {
+ config, err := p.st.Get()
+ if err != nil {
+ return err
+ }
+ var approve joinQrCodeData
+ if err := json.NewDecoder(bytes.NewReader(code)).Decode(&approve); err != nil {
+ return err
+ }
+ return p.vc.Approve(config.ApiAddr, approve.Name, approve.IPCidr, approve.EncPublicKey, approve.NetPublicKey)
+}
+
+func sanitizeHostname(hostname string) string {
+ return strings.ToLower(
+ strings.ReplaceAll(
+ strings.ReplaceAll(hostname, " ", "-"),
+ ".", "-"))
+}
+
func main() {
flag.Parse()
p = newProcessor()
diff --git a/core/client/cmd/pcloud/storage_darwin.go b/core/client/cmd/pcloud/storage_darwin.go
index 8fabc98..d45d34a 100644
--- a/core/client/cmd/pcloud/storage_darwin.go
+++ b/core/client/cmd/pcloud/storage_darwin.go
@@ -1,6 +1,11 @@
package main
-import "errors"
+import (
+ "bytes"
+ "encoding/json"
+
+ "github.com/keybase/go-keychain"
+)
type darwinStorage struct {
}
@@ -10,9 +15,43 @@
}
func (s *darwinStorage) Get() (Config, error) {
- return nil, errors.New("Not implemented")
+ q := configItem()
+ q.SetMatchLimit(keychain.MatchLimitOne)
+ q.SetReturnData(true)
+ results, err := keychain.QueryItem(q)
+ if err != nil {
+ return Config{}, err
+ } else if len(results) != 1 {
+ return Config{}, nil
+ }
+ var config Config
+ err = json.NewDecoder(bytes.NewReader(results[0].Data)).Decode(&config)
+ return config, err
}
func (s *darwinStorage) Store(config Config) error {
- return errors.New("Not implemented")
+ var data bytes.Buffer
+ if err := json.NewEncoder(&data).Encode(config); err != nil {
+ return err
+ }
+ q := configItem()
+ item := configItem()
+ item.SetData(data.Bytes())
+ if err := keychain.UpdateItem(q, item); err == keychain.ErrorItemNotFound {
+ return keychain.AddItem(item)
+ } else {
+ return err
+ }
+}
+
+func configItem() keychain.Item {
+ item := keychain.NewItem()
+ item.SetSecClass(keychain.SecClassGenericPassword)
+ item.SetService("pcloud")
+ item.SetAccount("pcloud")
+ item.SetLabel("pcloud-config")
+ item.SetAccessGroup("me.lekva.pcloud")
+ item.SetSynchronizable(keychain.SynchronizableNo)
+ item.SetAccessible(keychain.AccessibleWhenUnlocked)
+ return item
}
diff --git a/core/client/cmd/pcloud/ui.go b/core/client/cmd/pcloud/ui.go
index 78afa76..8f35a3b 100644
--- a/core/client/cmd/pcloud/ui.go
+++ b/core/client/cmd/pcloud/ui.go
@@ -17,33 +17,51 @@
D = layout.Dimensions
)
+type DeviceCapabilities struct {
+ HasCamera bool
+}
+
type UI struct {
- th *material.Theme
+ th *material.Theme
+ cap DeviceCapabilities
+
invite struct {
open widget.Clickable
show bool
qr image.Image
}
+ approve struct {
+ open widget.Clickable
+ show bool
+ }
+
join struct {
- open widget.Clickable
- show bool
- qrcode string
+ open widget.Clickable
+ show bool
+ qr image.Image
}
}
-func NewUI(th *material.Theme) *UI {
- return &UI{th: th}
+func NewUI(th *material.Theme, cap DeviceCapabilities) *UI {
+ return &UI{th: th, cap: cap}
}
func (ui *UI) InviteQRGenerated(img image.Image) {
ui.invite.qr = img
}
+func (ui *UI) JoinQRGenerated(img image.Image) {
+ ui.join.qr = img
+}
+
func (ui *UI) OnBack() bool {
if ui.invite.show {
ui.invite.show = false
return true
+ } else if ui.approve.show {
+ ui.approve.show = false
+ return true
} else if ui.join.show {
ui.join.show = false
return true
@@ -54,14 +72,22 @@
func (ui *UI) Layout(gtx C) []UIEvent {
var events []UIEvent
if ui.invite.open.Clicked() {
- ui.join.show = false
ui.invite.show = true
+ ui.approve.show = false
+ ui.join.show = false
ui.invite.qr = nil
events = append(events, EventGetInviteQRCode{})
+ } else if ui.approve.open.Clicked() {
+ events = append(events, EventApproveOther{})
} else if ui.join.open.Clicked() {
- // ui.invite.show = false
- // ui.join.show = true
- events = append(events, EventScanBarcode{})
+ if ui.cap.HasCamera {
+ events = append(events, EventScanBarcode{})
+ } else {
+ ui.invite.show = false
+ ui.approve.show = false
+ ui.join.show = true
+ events = append(events, EventGetJoinQRCode{})
+ }
}
if ui.invite.show {
ui.layout(gtx, ui.layoutInvite)
@@ -96,13 +122,18 @@
}
func (ui *UI) layoutActions(gtx C) D {
- return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceAround, WeightSum: 2.2}.Layout(gtx,
+ return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceAround, WeightSum: 3.2}.Layout(gtx,
layout.Flexed(1, func(gtx C) D {
b := material.Button(ui.th, &ui.invite.open, "Invite")
b.CornerRadius = unit.Px(20)
return b.Layout(gtx)
}),
layout.Flexed(1, func(gtx C) D {
+ b := material.Button(ui.th, &ui.approve.open, "Approve")
+ b.CornerRadius = unit.Px(20)
+ return b.Layout(gtx)
+ }),
+ layout.Flexed(1, func(gtx C) D {
b := material.Button(ui.th, &ui.join.open, "Join")
b.CornerRadius = unit.Px(20)
return b.Layout(gtx)
@@ -110,24 +141,26 @@
)
}
-func (ui *UI) layoutInvite(gtx C) D {
- if ui.invite.qr == nil {
+func layoutQR(gtx C, qr image.Image) D {
+ if qr == nil {
return ColorBox(gtx, gtx.Constraints.Max, color.NRGBA{})
}
- d := ui.invite.qr.Bounds().Max.Sub(ui.invite.qr.Bounds().Min)
+ d := qr.Bounds().Max.Sub(qr.Bounds().Min)
return layout.Inset{
Left: unit.Px(0.5 * float32(gtx.Constraints.Max.X-d.X)),
Top: unit.Px(0.5 * float32(gtx.Constraints.Max.Y-d.Y)),
}.Layout(gtx, func(gtx C) D {
- paint.NewImageOp(ui.invite.qr).Add(gtx.Ops)
+ paint.NewImageOp(qr).Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
return D{Size: gtx.Constraints.Max}
})
+
+}
+
+func (ui *UI) layoutInvite(gtx C) D {
+ return layoutQR(gtx, ui.invite.qr)
}
func (ui *UI) layoutJoin(gtx C) D {
- if ui.join.qrcode == "" {
- return ColorBox(gtx, gtx.Constraints.Min, color.NRGBA{R: 255, A: 255})
- }
- return ColorBox(gtx, gtx.Constraints.Min, color.NRGBA{R: 255, G: 255, B: 255, A: 255})
+ return layoutQR(gtx, ui.join.qr)
}
diff --git a/core/client/cmd/pcloud/ui_events.go b/core/client/cmd/pcloud/ui_events.go
index 581c5d4..e59be5b 100644
--- a/core/client/cmd/pcloud/ui_events.go
+++ b/core/client/cmd/pcloud/ui_events.go
@@ -5,3 +5,7 @@
type EventScanBarcode struct{}
type EventGetInviteQRCode struct{}
+
+type EventGetJoinQRCode struct{}
+
+type EventApproveOther struct{}
diff --git a/core/client/go.mod b/core/client/go.mod
index c14150b..f419e35 100644
--- a/core/client/go.mod
+++ b/core/client/go.mod
@@ -5,11 +5,11 @@
require (
gioui.org v0.0.0-20211201162354-9a5298914282
gioui.org/cmd v0.0.0-20211214104907-f5c9d2725c7b
+ github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621
github.com/sirupsen/logrus v1.8.1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/slackhq/nebula v1.5.1
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e
- golang.org/x/exp v0.0.0-20210722180016-6781d3edade3
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098 // indirect
sigs.k8s.io/yaml v1.1.0
)
diff --git a/core/client/go.sum b/core/client/go.sum
index f062a62..2a36dad 100644
--- a/core/client/go.sum
+++ b/core/client/go.sum
@@ -253,6 +253,9 @@
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
+github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621 h1:aMQ7pA4f06yOVXSulygyGvy4xA94fyzjUGs0iqQdMOI=
+github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621/go.mod h1:enrU/ug069Om7vWxuFE6nikLI2BZNwevMiGSo43Kt5w=
+github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -399,6 +402,7 @@
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -448,6 +452,7 @@
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
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