diff --git a/README.md b/README.md index 87e3072..e3739ef 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,8 @@ # vppn: Virtual Pretty Private Network -## Roadmap +## TODO -* Use probe and relayed-probe packets vs ping/pong. -* Rename Mediator -> Relay -* Use default port 456 -* Remove signing key from hub -* Peer: UDP hole-punching -* Peer: local peer discovery - part of RoutingProcessor -* Peer: update hub w/ latest port on startup - -## Learnings - -* Encryption / decryption is 20x faster than signing/opening. -* Allowing out-of order packets is massively important for throughput with TCP - -## Principles - -* Creates an IPv4/24 network with a maximum of 254 peers. (1-254) -* Simple setup: via setup link from the hub. -* Each peer has full network state replicated from the hub. - -## Routing - -* Routing is different for public vs non-public peers - * Public: routes are initialized via incoming ping requests - * NonPub: routes are initialized via incoming ping responses - -A non-public peer needs to maintain connections with every public peer. - -* Sending: - * Public: send to address - * Non-public: send to a mediator - -* Pings: - * Servers don't need to ping - * Clients need to ping all public and local peers to keep connections open +* Add `-force-init` argument to `node` main? ## Hub Server Configuration @@ -106,7 +73,7 @@ AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN Type=simple User=user WorkingDirectory=/home/user/ -ExecStart=/home/user/vppn -name vppn +ExecStart=/home/user/vppn -name vppn -hub-address https://my.hub -api-key 1234567890 Restart=always RestartSec=8 TimeoutStopSec=24 diff --git a/hub/api/api.go b/hub/api/api.go index ec8d77b..801f689 100644 --- a/hub/api/api.go +++ b/hub/api/api.go @@ -1,7 +1,6 @@ package api import ( - "crypto/rand" "database/sql" "embed" "errors" @@ -14,17 +13,14 @@ import ( "git.crumpington.com/lib/go/idgen" "git.crumpington.com/lib/go/sqliteutil" "golang.org/x/crypto/bcrypt" - "golang.org/x/crypto/nacl/box" - "golang.org/x/crypto/nacl/sign" ) //go:embed migrations var migrations embed.FS type API struct { - db *sql.DB - lock sync.Mutex - initIntents map[string]byte // Map from intent key to peer IP + db *sql.DB + lock sync.Mutex } func New(dbPath string) (*API, error) { @@ -38,8 +34,7 @@ func New(dbPath string) (*API, error) { } a := &API{ - db: sqlDB, - initIntents: map[string]byte{}, + db: sqlDB, } return a, a.ensurePassword() @@ -151,55 +146,13 @@ func (a *API) Peer_CreateNew(p *Peer) error { return db.Peer_Insert(a.db, p) } -// Create the intention to initialize a peer. The returned code is used to -// complete the peer initialization. The code is valid for 5 minutes. -func (a *API) Peer_CreateInitIntent(peerIP byte) string { +func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) (*m.PeerConfig, error) { a.lock.Lock() defer a.lock.Unlock() - code := idgen.NewToken() - a.initIntents[code] = peerIP - - go func() { - time.Sleep(5 * time.Minute) - a.lock.Lock() - defer a.lock.Unlock() - delete(a.initIntents, code) - }() - - return code -} - -func (a *API) Peer_Init(initCode string) (*m.PeerConfig, error) { - a.lock.Lock() - defer a.lock.Unlock() - - ip, ok := a.initIntents[initCode] - if !ok { - return nil, ErrNotAuthorized - } - - peer, err := a.Peer_Get(ip) - if err != nil { - return nil, err - } - - delete(a.initIntents, initCode) - - encPubKey, encPrivKey, err := box.GenerateKey(rand.Reader) - if err != nil { - return nil, err - } - - signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader) - if err != nil { - return nil, err - } - peer.Version = idgen.NextID(0) - peer.APIKey = idgen.NewToken() - peer.PubKey = encPubKey[:] - peer.PubSignKey = signPubKey[:] + peer.PubKey = args.EncPubKey + peer.PubSignKey = args.PubSignKey if err := db.Peer_UpdateFull(a.db, peer); err != nil { return nil, err @@ -208,17 +161,11 @@ func (a *API) Peer_Init(initCode string) (*m.PeerConfig, error) { conf := a.Config_Get() return &m.PeerConfig{ - PeerIP: peer.PeerIP, - HubAddress: conf.HubAddress, - APIKey: peer.APIKey, - Network: conf.VPNNetwork, - PublicIP: peer.PublicIP, - Port: peer.Port, - Relay: peer.Relay, - PubKey: encPubKey[:], - PrivKey: encPrivKey[:], - PubSignKey: signPubKey[:], - PrivSignKey: signPrivKey[:], + PeerIP: peer.PeerIP, + Network: conf.VPNNetwork, + PublicIP: peer.PublicIP, + Port: peer.Port, + Relay: peer.Relay, }, nil } diff --git a/hub/handler.go b/hub/handler.go index ffaf6fc..f3602b1 100644 --- a/hub/handler.go +++ b/hub/handler.go @@ -65,13 +65,26 @@ func (app *App) handleSignedIn(pattern string, fn handlerFunc) { }) } -type peerHandlerFunc func(w http.ResponseWriter, r *http.Request) error +type peerHandlerFunc func(p *api.Peer, w http.ResponseWriter, r *http.Request) error func (app *App) handlePeer(pattern string, fn peerHandlerFunc) { wrapped := func(w http.ResponseWriter, r *http.Request) { + _, apiKey, ok := r.BasicAuth() + if !ok { + http.Error(w, "Not authorized", http.StatusUnauthorized) + return + } + + peer, err := app.api.Peer_GetByAPIKey(apiKey) + if err != nil { + http.Error(w, "Not authorized", http.StatusUnauthorized) + return + } + r.ParseForm() - if err := fn(w, r); err != nil { + if err := fn(peer, w, r); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } } diff --git a/hub/handlers.go b/hub/handlers.go index dcd2688..c81c9ad 100644 --- a/hub/handlers.go +++ b/hub/handlers.go @@ -1,6 +1,7 @@ package hub import ( + "encoding/json" "errors" "log" "net/http" @@ -201,22 +202,6 @@ func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *h return a.redirect(w, r, "/admin/peer/view/?PeerIP=%d", p.PeerIP) } -func (a *App) _adminPeerInit(s *api.Session, w http.ResponseWriter, r *http.Request) error { - var peerIP byte - err := webutil.NewFormScanner(r.Form).Scan("PeerIP", &peerIP).Error() - if err != nil { - return err - } - code := a.api.Peer_CreateInitIntent(peerIP) - log.Printf("Got code: %v / %v", peerIP, code) - - return a.render("/admin-peer-init.html", w, struct { - Session *api.Session - HubAddress string - Code string - }{s, a.api.Config_Get().HubAddress, code}) -} - func (a *App) _adminPeerView(s *api.Session, w http.ResponseWriter, r *http.Request) error { var peerIP byte err := webutil.NewFormScanner(r.Form).Scan("PeerIP", &peerIP).Error() @@ -321,9 +306,13 @@ func (a *App) _adminPeerDeleteSubmit(s *api.Session, w http.ResponseWriter, r *h return a.redirect(w, r, "/admin/peer/list/") } -func (a *App) _peerInit(w http.ResponseWriter, r *http.Request) error { - code := r.FormValue("Code") - conf, err := a.api.Peer_Init(code) +func (a *App) _peerInit(peer *api.Peer, w http.ResponseWriter, r *http.Request) error { + args := m.PeerInitArgs{} + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { + return err + } + + conf, err := a.api.Peer_Init(peer, args) if err != nil { return err } @@ -331,31 +320,13 @@ func (a *App) _peerInit(w http.ResponseWriter, r *http.Request) error { return a.sendJSON(w, conf) } -func (a *App) _peerFetchState(w http.ResponseWriter, r *http.Request) error { - _, apiKey, ok := r.BasicAuth() - if !ok { - return api.ErrNotAuthorized - } - - peer, err := a.api.Peer_GetByAPIKey(apiKey) - if err != nil { - return err - } - +func (a *App) _peerFetchState(peer *api.Peer, w http.ResponseWriter, r *http.Request) error { peers, err := a.api.Peer_List() if err != nil { return err } - conf := a.api.Config_Get() - - state := m.NetworkState{ - HubAddress: conf.HubAddress, - Network: conf.VPNNetwork, - PeerIP: peer.PeerIP, - PublicIP: peer.PublicIP, - Port: peer.Port, - } + state := m.NetworkState{} for _, p := range peers { if len(p.PubKey) != 0 { diff --git a/hub/routes.go b/hub/routes.go index adf9b58..7d505c5 100644 --- a/hub/routes.go +++ b/hub/routes.go @@ -19,13 +19,12 @@ func (a *App) registerRoutes() { a.handleSignedIn("GET /admin/peer/hosts/", a._adminHosts) a.handleSignedIn("GET /admin/peer/create/", a._adminPeerCreate) a.handleSignedIn("POST /admin/peer/create/", a._adminPeerCreateSubmit) - a.handleSignedIn("GET /admin/peer/init/", a._adminPeerInit) a.handleSignedIn("GET /admin/peer/view/", a._adminPeerView) a.handleSignedIn("GET /admin/peer/edit/", a._adminPeerEdit) a.handleSignedIn("POST /admin/peer/edit/", a._adminPeerEditSubmit) a.handleSignedIn("GET /admin/peer/delete/", a._adminPeerDelete) a.handleSignedIn("POST /admin/peer/delete/", a._adminPeerDeleteSubmit) - a.handlePeer("GET /peer/init/", a._peerInit) + a.handlePeer("POST /peer/init/", a._peerInit) a.handlePeer("GET /peer/fetch-state/", a._peerFetchState) } diff --git a/hub/templates/base.html b/hub/templates/base.html index f984646..5179441 100644 --- a/hub/templates/base.html +++ b/hub/templates/base.html @@ -10,6 +10,7 @@