WIP - cleanup / local discovery

This commit is contained in:
jdl
2024-12-30 09:26:48 +01:00
parent f47a8245b4
commit 8407fd5b48
27 changed files with 523 additions and 151 deletions

View File

@@ -15,6 +15,7 @@ import (
"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
@@ -24,6 +25,7 @@ type API struct {
db *sql.DB
lock sync.Mutex
peerIntents map[string]PeerCreateArgs
initIntents map[string]byte // Map from intent key to peer IP
}
func New(dbPath string) (*API, error) {
@@ -39,6 +41,7 @@ func New(dbPath string) (*API, error) {
a := &API{
db: sqlDB,
peerIntents: map[string]PeerCreateArgs{},
initIntents: map[string]byte{},
}
return a, a.ensurePassword()
@@ -141,6 +144,16 @@ func (a *API) Session_SignIn(s *Session, pwd string) error {
return db.Session_SetSignedIn(a.db, s.SessionID)
}
func (a *API) Peer_CreateNew(p *Peer) error {
p.Version = idgen.NextID(0)
p.PubKey = []byte{}
p.PubSignKey = []byte{}
p.APIKey = idgen.NewToken()
return db.Peer_Insert(a.db, p)
}
// TODO: Remove
type PeerCreateArgs struct {
Name string
PublicIP []byte
@@ -148,6 +161,7 @@ type PeerCreateArgs struct {
Relay bool
}
// TODO: Remove
// Create the intention to add a peer. The returned code is used to complete
// the peer creation. The code is valid for 5 minutes.
func (a *API) Peer_CreateIntent(args PeerCreateArgs) string {
@@ -167,6 +181,78 @@ func (a *API) Peer_CreateIntent(args PeerCreateArgs) string {
return code
}
// 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 {
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[:]
if err := db.Peer_UpdateFull(a.db, peer); err != nil {
return nil, err
}
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[:],
}, nil
}
// TODO: Remove
func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
a.lock.Lock()
defer a.lock.Unlock()
@@ -183,6 +269,11 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
return nil, err
}
signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
// Get peer IP.
peerIP := byte(0)
@@ -202,14 +293,15 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
}
peer := &Peer{
PeerIP: peerIP,
Version: idgen.NextID(0),
APIKey: idgen.NewToken(),
Name: args.Name,
PublicIP: args.PublicIP,
Port: args.Port,
Relay: args.Relay,
PubKey: encPubKey[:],
PeerIP: peerIP,
Version: idgen.NextID(0),
APIKey: idgen.NewToken(),
Name: args.Name,
PublicIP: args.PublicIP,
Port: args.Port,
Relay: args.Relay,
PubKey: encPubKey[:],
PubSignKey: signPubKey[:],
}
if err := db.Peer_Insert(a.db, peer); err != nil {
@@ -219,15 +311,17 @@ func (a *API) Peer_Create(creationCode 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[:],
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[:],
}, nil
}

View File

@@ -307,17 +307,18 @@ func Session_List(
// ----------------------------------------------------------------------------
type Peer struct {
PeerIP byte
Version int64
APIKey string
Name string
PublicIP []byte
Port uint16
Relay bool
PubKey []byte
PeerIP byte
Version int64
APIKey string
Name string
PublicIP []byte
Port uint16
Relay bool
PubKey []byte
PubSignKey []byte
}
const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers"
const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers"
func Peer_Insert(
tx TX,
@@ -328,7 +329,7 @@ func Peer_Insert(
return err
}
_, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey) VALUES(?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey)
_, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey) VALUES(?,?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey)
return err
}
@@ -369,7 +370,7 @@ func Peer_UpdateFull(
return err
}
result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PeerIP)
result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=?,PubSignKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey, row.PeerIP)
if err != nil {
return err
}
@@ -419,8 +420,8 @@ func Peer_Get(
err error,
) {
row = &Peer{}
r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers WHERE PeerIP=?", PeerIP)
err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey)
r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers WHERE PeerIP=?", PeerIP)
err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey)
return
}
@@ -434,7 +435,7 @@ func Peer_GetWhere(
) {
row = &Peer{}
r := tx.QueryRow(query, args...)
err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey)
err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey)
return
}
@@ -454,7 +455,7 @@ func Peer_Iterate(
defer rows.Close()
for rows.Next() {
row := &Peer{}
err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey)
err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey)
if !yield(row, err) {
return
}

View File

@@ -21,5 +21,6 @@ TABLE peers OF Peer (
PublicIP []byte,
Port uint16,
Relay bool,
PubKey []byte NoUpdate
PubKey []byte NoUpdate,
PubSignKey []byte NoUpdate
);

View File

@@ -0,0 +1 @@
ALTER TABLE peers ADD COLUMN PubSignKey BLOB NOT NULL DEFAULT '';

View File

@@ -54,11 +54,18 @@ func (a *App) _adminSignOutSubmit(s *api.Session, w http.ResponseWriter, r *http
}
func (a *App) _adminConfig(s *api.Session, w http.ResponseWriter, r *http.Request) error {
peers, err := a.api.Peer_List()
if err != nil {
return err
}
return a.render("/admin-config.html", w, struct {
Session *api.Session
Peers []*api.Peer
Config *api.Config
}{
s,
peers,
a.api.Config_Get(),
})
}
@@ -142,21 +149,6 @@ func (a *App) _adminPasswordSubmit(s *api.Session, w http.ResponseWriter, r *htt
return a.redirect(w, r, "/admin/config/")
}
func (a *App) _adminPeerList(s *api.Session, w http.ResponseWriter, r *http.Request) error {
peers, err := a.api.Peer_List()
if err != nil {
return err
}
return a.render("/admin-peer-list.html", w, struct {
Session *api.Session
Peers []*api.Peer
}{
s,
peers,
})
}
func (a *App) _adminHosts(s *api.Session, w http.ResponseWriter, r *http.Request) error {
conf := a.api.Config_Get()
@@ -187,25 +179,45 @@ func (a *App) _adminPeerCreate(s *api.Session, w http.ResponseWriter, r *http.Re
func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error {
var ipStr string
args := api.PeerCreateArgs{}
p := &api.Peer{}
err := webutil.NewFormScanner(r.Form).
Scan("Name", &args.Name).
Scan("IP", &p.PeerIP).
Scan("Name", &p.Name).
Scan("PublicIP", &ipStr).
Scan("Port", &args.Port).
Scan("Relay", &args.Relay).
Scan("Port", &p.Port).
Scan("Relay", &p.Relay).
Error()
if err != nil {
return err
}
if args.PublicIP, err = stringToIP(ipStr); err != nil {
if p.PublicIP, err = stringToIP(ipStr); err != nil {
return err
}
code := a.api.Peer_CreateIntent(args)
return a.redirect(w, r, "/admin/peer/intent-created/?Code=%s", code)
if err := a.api.Peer_CreateNew(p); err != nil {
return err
}
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, cod)
return a.render("/admin-peer-init.html", w, struct {
Session *api.Session
HubAddress string
Code string
}{s, a.api.Config_Get().HubAddress, code})
}
// TODO: Remove
func (a *App) _adminPeerIntentCreated(s *api.Session, w http.ResponseWriter, r *http.Request) error {
code := r.FormValue("Code")
if code == "" {
@@ -323,6 +335,17 @@ 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)
if err != nil {
return err
}
return a.sendJSON(w, conf)
}
// TODO: Remove
func (a *App) _peerCreate(w http.ResponseWriter, r *http.Request) error {
code := r.FormValue("Code")
conf, err := a.api.Peer_Create(code)
@@ -360,14 +383,16 @@ func (a *App) _peerFetchState(w http.ResponseWriter, r *http.Request) error {
}
for _, p := range peers {
state.Peers[p.PeerIP] = &m.Peer{
PeerIP: p.PeerIP,
Version: p.Version,
Name: p.Name,
PublicIP: p.PublicIP,
Port: p.Port,
Relay: p.Relay,
PubKey: p.PubKey,
if len(p.PubKey) != 0 {
state.Peers[p.PeerIP] = &m.Peer{
PeerIP: p.PeerIP,
Version: p.Version,
Name: p.Name,
PublicIP: p.PublicIP,
Port: p.Port,
Relay: p.Relay,
PubKey: p.PubKey,
}
}
}

View File

@@ -16,10 +16,11 @@ func (a *App) registerRoutes() {
a.handleSignedIn("POST /admin/sign-out/", a._adminSignOutSubmit)
a.handleSignedIn("GET /admin/password/edit/", a._adminPasswordEdit)
a.handleSignedIn("POST /admin/password/edit/", a._adminPasswordSubmit)
a.handleSignedIn("GET /admin/peer/list/", a._adminPeerList)
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)
// TODO: Remove
a.handleSignedIn("GET /admin/peer/intent-created/", a._adminPeerIntentCreated)
a.handleSignedIn("GET /admin/peer/view/", a._adminPeerView)
a.handleSignedIn("GET /admin/peer/edit/", a._adminPeerEdit)
@@ -27,6 +28,7 @@ func (a *App) registerRoutes() {
a.handleSignedIn("GET /admin/peer/delete/", a._adminPeerDelete)
a.handleSignedIn("POST /admin/peer/delete/", a._adminPeerDeleteSubmit)
a.handlePeer("GET /peer/create/", a._peerCreate)
a.handlePeer("GET /peer/create/", a._peerCreate) // TODO: Remove
a.handlePeer("GET /peer/init/", a._peerInit)
a.handlePeer("GET /peer/fetch-state/", a._peerFetchState)
}

View File

@@ -16,4 +16,42 @@
<td>{{ipToString .Config.VPNNetwork}}</td>
</tr>
</table>
<h2>Peers</h2>
<p>
<a href="/admin/peer/create/">Add Peer</a> /
<a href="/admin/peer/hosts/">Hosts</a>
</p>
{{if .Peers -}}
<table>
<thead>
<tr>
<th>PeerIP</th>
<th>Name</th>
<th>Public IP</th>
<th>Port</th>
<th>Relay</th>
</tr>
</thead>
<tbody>
{{range .Peers -}}
<tr>
<td>
<a href="/admin/peer/view/?PeerIP={{.PeerIP}}">
{{.PeerIP}}
</a>
</td>
<td>{{.Name}}</td>
<td>{{ipToString .PublicIP}}</td>
<td>{{.Port}}</td>
<td>{{if .Relay}}T{{else}}F{{end}}</td>
</tr>
</tbody>
{{- end}}
</table>
{{- else}}
<p>No peers.</p>
{{- end}}
{{- end}}

View File

@@ -3,6 +3,10 @@
<form method="POST">
<input type="hidden" name="CSRF" value="{{.Session.CSRF}}">
<p>
<label>IP</label><br>
<input type="number" name="IP" min="1" max="255" value="0">
</p>
<p>
<label>Name</label><br>
<input type="text" name="Name">

View File

@@ -0,0 +1,13 @@
{{define "body" -}}
<h2>Initialize Peer</h2>
<p>
Configure the peer with the following URL:
</p>
<pre>
{{.HubAddress}}/peer/init/?Code={{.Code}}
</pre>
<p>
<a href="/admin/config/">Done</a>
</p>
{{- end}}

View File

@@ -8,6 +8,6 @@
{{.HubAddress}}/peer/create/?Code={{.Code}}
</pre>
<p>
<a href="/admin/peer/list/">Done</a>
<a href="/admin/config/">Done</a>
</p>
{{- end}}

View File

@@ -1,40 +0,0 @@
{{define "body" -}}
<h2>Peers</h2>
<p>
<a href="/admin/peer/create/">Add Peer</a> /
<a href="/admin/peer/hosts/">Hosts</a>
</p>
{{if .Peers -}}
<table>
<thead>
<tr>
<th>PeerIP</th>
<th>Name</th>
<th>Public IP</th>
<th>Port</th>
<th>Relay</th>
</tr>
</thead>
<tbody>
{{range .Peers -}}
<tr>
<td>
<a href="/admin/peer/view/?PeerIP={{.PeerIP}}">
{{.PeerIP}}
</a>
</td>
<td>{{.Name}}</td>
<td>{{ipToString .PublicIP}}</td>
<td>{{.Port}}</td>
<td>{{if .Relay}}T{{else}}F{{end}}</td>
</tr>
</tbody>
{{- end}}
</table>
{{- else}}
<p>No peers.</p>
{{- end}}
{{- end}}

View File

@@ -3,6 +3,7 @@
<p>
<a href="/admin/peer/edit/?PeerIP={{.Peer.PeerIP}}">Edit</a> /
<a href="/admin/peer/init/?PeerIP={{.Peer.PeerIP}}">Initialize</a> /
<a href="/admin/peer/delete/?PeerIP={{.Peer.PeerIP}}">Delete</a>
</p>

View File

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