mirror of
https://github.com/sigcn/pg.git
synced 2026-04-23 00:37:30 +08:00
peermap: add admin api
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/sigcn/pg/peermap"
|
||||
"github.com/sigcn/pg/peermap/admin"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -100,6 +101,7 @@ func run(commandConfig peermap.Config, configPath string) error {
|
||||
cfg, _ := peermap.ReadConfig(configPath)
|
||||
cfg.Overwrite(commandConfig)
|
||||
|
||||
admin.Version = Version
|
||||
srv, err := peermap.New(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package disco
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/url"
|
||||
@@ -58,23 +55,6 @@ const (
|
||||
CONTROL_SERVER_CONNECTED ControlCode = 50
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func (e Error) Wrap(err error) Error {
|
||||
return Error{Code: e.Code, Msg: fmt.Sprintf("%s: %s", e.Msg, err)}
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("ENO%d: %s", e.Code, e.Msg)
|
||||
}
|
||||
|
||||
func (e Error) MarshalTo(w io.Writer) {
|
||||
json.NewEncoder(w).Encode(e)
|
||||
}
|
||||
|
||||
type NATType string
|
||||
|
||||
func (t NATType) AccurateThan(t1 NATType) bool {
|
||||
|
||||
+4
-3
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sigcn/pg/disco"
|
||||
"github.com/sigcn/pg/langs"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
@@ -206,7 +207,7 @@ func (c *WSConn) dial(ctx context.Context, server string) error {
|
||||
handshake := http.Header{}
|
||||
handshake.Set("X-Network", networkSecret.Secret)
|
||||
handshake.Set("X-PeerID", c.peerID.String())
|
||||
handshake.Set("X-Nonce", disco.NewNonce())
|
||||
handshake.Set("X-Nonce", langs.NewNonce())
|
||||
handshake.Set("X-Metadata", c.metadata.Encode())
|
||||
if server == "" {
|
||||
server = c.server.URL
|
||||
@@ -229,7 +230,7 @@ func (c *WSConn) dial(ctx context.Context, server string) error {
|
||||
return fmt.Errorf("dial server %s: 404 not found", server)
|
||||
}
|
||||
if httpResp != nil && httpResp.StatusCode == http.StatusForbidden {
|
||||
var err disco.Error
|
||||
var err langs.Error
|
||||
json.NewDecoder(httpResp.Body).Decode(&err)
|
||||
defer httpResp.Body.Close()
|
||||
return err
|
||||
@@ -253,7 +254,7 @@ func (c *WSConn) dial(ctx context.Context, server string) error {
|
||||
}
|
||||
|
||||
c.rawConn.Store(conn)
|
||||
c.nonce = disco.MustParseNonce(httpResp.Header.Get("X-Nonce"))
|
||||
c.nonce = langs.MustParseNonce(httpResp.Header.Get("X-Nonce"))
|
||||
c.connectedServer = server
|
||||
c.activeTime.Store(time.Now().Unix())
|
||||
conn.SetPingHandler(func(appData string) error {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package langs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func (e Error) Wrap(err error) Error {
|
||||
return Error{Code: e.Code, Msg: fmt.Sprintf("%s: %s", e.Msg, err)}
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("ENO%d: %s", e.Code, e.Msg)
|
||||
}
|
||||
|
||||
func (e Error) MarshalTo(w io.Writer) {
|
||||
json.NewEncoder(w).Encode(e)
|
||||
}
|
||||
|
||||
func Err(err error) Error {
|
||||
if knownErr, ok := err.(Error); ok {
|
||||
return knownErr
|
||||
}
|
||||
return Error{Code: 5000, Msg: err.Error()}
|
||||
}
|
||||
|
||||
type Data[T any] struct {
|
||||
Error
|
||||
Data T `json:"data"`
|
||||
}
|
||||
|
||||
func (d Data[T]) MarshalTo(w io.Writer) {
|
||||
json.NewEncoder(w).Encode(d)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package langs
|
||||
|
||||
func Must[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package disco
|
||||
package langs
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@@ -0,0 +1,106 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
|
||||
"github.com/sigcn/pg/langs"
|
||||
"github.com/sigcn/pg/peermap/admin/types"
|
||||
"github.com/sigcn/pg/peermap/auth"
|
||||
)
|
||||
|
||||
var (
|
||||
Version string = "dev"
|
||||
ErrForbidden = langs.Error{Code: 10000, Msg: "forbidden"}
|
||||
)
|
||||
|
||||
type AdministratorV1 struct {
|
||||
Auth *auth.Authenticator
|
||||
PeerStore types.PeerStore
|
||||
|
||||
mux http.ServeMux
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
func (a *AdministratorV1) init() {
|
||||
a.initOnce.Do(func() {
|
||||
a.mux.HandleFunc("GET /pg/apis/v1/admin/peers", a.handleQueryPeers)
|
||||
a.mux.HandleFunc("GET /pg/apis/v1/admin/server_info", a.handleQueryServerInfo)
|
||||
})
|
||||
}
|
||||
|
||||
func (a *AdministratorV1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
a.init()
|
||||
a.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (a *AdministratorV1) handleQueryPeers(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("X-Token")
|
||||
secret, err := a.Auth.ParseSecret(token)
|
||||
if err != nil {
|
||||
langs.Err(err).MarshalTo(w)
|
||||
return
|
||||
}
|
||||
if !secret.Admin {
|
||||
ErrForbidden.MarshalTo(w)
|
||||
return
|
||||
}
|
||||
peers, err := a.PeerStore.Peers(secret.Network)
|
||||
if err != nil {
|
||||
langs.Err(err).MarshalTo(w)
|
||||
return
|
||||
}
|
||||
langs.Data[[]url.Values]{Data: peers}.MarshalTo(w)
|
||||
}
|
||||
|
||||
func (a *AdministratorV1) handleQueryServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("X-Token")
|
||||
secret, err := a.Auth.ParseSecret(token)
|
||||
if err != nil {
|
||||
langs.Err(err).MarshalTo(w)
|
||||
return
|
||||
}
|
||||
if !secret.Admin {
|
||||
ErrForbidden.MarshalTo(w)
|
||||
return
|
||||
}
|
||||
|
||||
info, err := readBuildInfo()
|
||||
if err != nil {
|
||||
langs.Err(err).MarshalTo(w)
|
||||
}
|
||||
langs.Data[any]{Data: serverInfo{Version: Version, buildInfo: info}}.MarshalTo(w)
|
||||
}
|
||||
|
||||
type buildInfo struct {
|
||||
GoVersion string `json:"go_version"`
|
||||
VCSRevision string `json:"vcs_revision"`
|
||||
VCSTime string `json:"vcs_time"`
|
||||
}
|
||||
|
||||
func readBuildInfo() (buildInfo buildInfo, err error) {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
err = errors.ErrUnsupported
|
||||
return
|
||||
}
|
||||
buildInfo.GoVersion = info.GoVersion
|
||||
for _, s := range info.Settings {
|
||||
if s.Key == "vcs.revision" {
|
||||
buildInfo.VCSRevision = s.Value
|
||||
continue
|
||||
}
|
||||
if s.Key == "vcs.time" {
|
||||
buildInfo.VCSTime = s.Value
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type serverInfo struct {
|
||||
Version string `json:"version"`
|
||||
buildInfo
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package types
|
||||
|
||||
import "net/url"
|
||||
|
||||
type PeerStore interface {
|
||||
Peers(network string) ([]url.Values, error)
|
||||
}
|
||||
@@ -4,15 +4,15 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/sigcn/pg/langs"
|
||||
"github.com/sigcn/pg/secure/aescbc"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidToken = errors.New("invalid token")
|
||||
ErrTokenExpired = errors.New("token expired")
|
||||
ErrInvalidToken = langs.Error{Code: 9000, Msg: "invalid token"}
|
||||
ErrTokenExpired = langs.Error{Code: 9001, Msg: "token expired"}
|
||||
)
|
||||
|
||||
type JSONSecret struct {
|
||||
|
||||
+21
-4
@@ -18,6 +18,8 @@ import (
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sigcn/pg/disco"
|
||||
"github.com/sigcn/pg/langs"
|
||||
"github.com/sigcn/pg/peermap/admin"
|
||||
"github.com/sigcn/pg/peermap/auth"
|
||||
"github.com/sigcn/pg/peermap/exporter"
|
||||
exporterauth "github.com/sigcn/pg/peermap/exporter/auth"
|
||||
@@ -26,8 +28,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAddressAlreadyInuse = disco.Error{Code: 4000, Msg: "the network address is already in use"}
|
||||
ErrNetworkSecretExpired = disco.Error{Code: 4030, Msg: "network secret is expired"}
|
||||
ErrAddressAlreadyInuse = langs.Error{Code: 4000, Msg: "the network address is already in use"}
|
||||
ErrNetworkSecretExpired = langs.Error{Code: 4030, Msg: "network secret is expired"}
|
||||
ErrNetworkNotFound = langs.Error{Code: 60000, Msg: "network not found"}
|
||||
|
||||
_ io.ReadWriter = (*peerConn)(nil)
|
||||
)
|
||||
@@ -611,7 +614,7 @@ func (pm *PeerMap) HandlePeerPacketConnect(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
peerID := r.Header.Get("X-PeerID")
|
||||
nonce := disco.MustParseNonce(r.Header.Get("X-Nonce"))
|
||||
nonce := langs.MustParseNonce(r.Header.Get("X-Nonce"))
|
||||
|
||||
pm.networkMapMutex.RLock()
|
||||
networkCtx, ok := pm.networkMap[jsonSecret.Network]
|
||||
@@ -715,6 +718,19 @@ func (pm *PeerMap) Grant(network, state string) (disco.NetworkSecret, error) {
|
||||
return pm.generateSecret(n, strings.HasPrefix(state, "PG_ADM"))
|
||||
}
|
||||
|
||||
func (pm *PeerMap) Peers(network string) (peers []url.Values, err error) {
|
||||
netctx, ok := pm.getNetwork(network)
|
||||
if !ok {
|
||||
return nil, ErrNetworkNotFound
|
||||
}
|
||||
netctx.peersMutex.RLock()
|
||||
for _, v := range netctx.peers {
|
||||
peers = append(peers, v.metadata)
|
||||
}
|
||||
netctx.peersMutex.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (pm *PeerMap) newNetworkContext(state NetState) *networkContext {
|
||||
return &networkContext{
|
||||
id: state.ID,
|
||||
@@ -768,15 +784,16 @@ func New(cfg Config) (*PeerMap, error) {
|
||||
|
||||
mux := http.NewServeMux()
|
||||
pm.httpServer = &http.Server{Handler: mux, Addr: cfg.Listen}
|
||||
mux.Handle("/pg/apis/v1/admin/", &admin.AdministratorV1{Auth: pm.authenticator, PeerStore: &pm})
|
||||
mux.HandleFunc("GET /pg", pm.HandlePeerPacketConnect)
|
||||
mux.HandleFunc("GET /pg/networks", pm.HandleQueryNetworks)
|
||||
mux.HandleFunc("GET /pg/peers", pm.HandleQueryNetworkPeers)
|
||||
mux.HandleFunc("GET /pg/networks/{network}/meta", pm.HandleGetNetworkMeta)
|
||||
mux.HandleFunc("PUT /pg/networks/{network}/meta", pm.HandlePutNetworkMeta)
|
||||
|
||||
mux.Handle("GET /oidc/authorize/{provider}", &oidc.Authority{Grant: pm.Grant})
|
||||
mux.HandleFunc("GET /oidc", oidc.OIDCSelector)
|
||||
mux.HandleFunc("GET /oidc/secret", oidc.OIDCSecret)
|
||||
mux.HandleFunc("GET /oidc/{provider}", oidc.OIDCAuthURL)
|
||||
mux.Handle("GET /oidc/authorize/{provider}", &oidc.Authority{Grant: pm.Grant})
|
||||
return &pm, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user