This commit is contained in:
jdl
2025-01-02 07:42:00 +01:00
parent 5d97cccb98
commit f0076939d5
12 changed files with 131 additions and 197 deletions

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -10,6 +10,7 @@
<h1>VPPN</h1>
<nav>
{{if .Session.SignedIn -}}
<a href="/admin/config/">Home</a> /
<a href="/admin/sign-out/">Sign out</a>
{{- end}}
</nav>