mirror of
https://github.com/mudler/edgevpn.git
synced 2025-10-10 11:10:19 +08:00

Peerguard and peergater are two components that work together to gate peers and add them to a trusted zone. This allows to isolate nodes from the p2p network and avoid to rotate network tokens in case of leaks. For the moment an ECDSA auth provider is implemented as sample purpose, documentation will follow up on how to use them and how to write them up.
353 lines
9.3 KiB
Go
353 lines
9.3 KiB
Go
// Copyright © 2021-2022 Ettore Di Giacinto <mudler@mocaccino.org>
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package api
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"fmt"
|
|
"io/fs"
|
|
"net"
|
|
"net/http"
|
|
_ "net/http/pprof"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/libp2p/go-libp2p-core/metrics"
|
|
"github.com/libp2p/go-libp2p-core/network"
|
|
"github.com/libp2p/go-libp2p-core/peer"
|
|
p2pprotocol "github.com/libp2p/go-libp2p-core/protocol"
|
|
|
|
"github.com/miekg/dns"
|
|
apiTypes "github.com/mudler/edgevpn/api/types"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/mudler/edgevpn/pkg/node"
|
|
"github.com/mudler/edgevpn/pkg/protocol"
|
|
"github.com/mudler/edgevpn/pkg/services"
|
|
"github.com/mudler/edgevpn/pkg/types"
|
|
)
|
|
|
|
//go:embed public
|
|
var embededFiles embed.FS
|
|
|
|
func getFileSystem() http.FileSystem {
|
|
fsys, err := fs.Sub(embededFiles, "public")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return http.FS(fsys)
|
|
}
|
|
|
|
const (
|
|
MachineURL = "/api/machines"
|
|
UsersURL = "/api/users"
|
|
ServiceURL = "/api/services"
|
|
BlockchainURL = "/api/blockchain"
|
|
LedgerURL = "/api/ledger"
|
|
SummaryURL = "/api/summary"
|
|
FileURL = "/api/files"
|
|
NodesURL = "/api/nodes"
|
|
DNSURL = "/api/dns"
|
|
MetricsURL = "/api/metrics"
|
|
PeerstoreURL = "/api/peerstore"
|
|
PeerGateURL = "/api/peergate"
|
|
)
|
|
|
|
func API(ctx context.Context, l string, defaultInterval, timeout time.Duration, e *node.Node, bwc metrics.Reporter, debugMode bool) error {
|
|
|
|
ledger, _ := e.Ledger()
|
|
|
|
ec := echo.New()
|
|
|
|
if strings.HasPrefix(l, "unix://") {
|
|
unixListener, err := net.Listen("unix", strings.ReplaceAll(l, "unix://", ""))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ec.Listener = unixListener
|
|
}
|
|
|
|
assetHandler := http.FileServer(getFileSystem())
|
|
if debugMode {
|
|
ec.GET("/debug/pprof/*", echo.WrapHandler(http.DefaultServeMux))
|
|
}
|
|
|
|
if bwc != nil {
|
|
ec.GET(MetricsURL, func(c echo.Context) error {
|
|
return c.JSON(http.StatusOK, bwc.GetBandwidthTotals())
|
|
})
|
|
ec.GET(filepath.Join(MetricsURL, "protocol"), func(c echo.Context) error {
|
|
return c.JSON(http.StatusOK, bwc.GetBandwidthByProtocol())
|
|
})
|
|
ec.GET(filepath.Join(MetricsURL, "peer"), func(c echo.Context) error {
|
|
return c.JSON(http.StatusOK, bwc.GetBandwidthByPeer())
|
|
})
|
|
ec.GET(filepath.Join(MetricsURL, "peer", ":peer"), func(c echo.Context) error {
|
|
return c.JSON(http.StatusOK, bwc.GetBandwidthForPeer(peer.ID(c.Param("peer"))))
|
|
})
|
|
ec.GET(filepath.Join(MetricsURL, "protocol", ":protocol"), func(c echo.Context) error {
|
|
return c.JSON(http.StatusOK, bwc.GetBandwidthForProtocol(p2pprotocol.ID(c.Param("protocol"))))
|
|
})
|
|
}
|
|
// Get data from ledger
|
|
ec.GET(FileURL, func(c echo.Context) error {
|
|
list := []*types.File{}
|
|
for _, v := range ledger.CurrentData()[protocol.FilesLedgerKey] {
|
|
machine := &types.File{}
|
|
v.Unmarshal(machine)
|
|
list = append(list, machine)
|
|
}
|
|
return c.JSON(http.StatusOK, list)
|
|
})
|
|
|
|
if e.PeerGater() != nil {
|
|
ec.PUT(fmt.Sprintf("%s/:state", PeerGateURL), func(c echo.Context) error {
|
|
state := c.Param("state")
|
|
|
|
switch state {
|
|
case "enable":
|
|
e.PeerGater().Enable()
|
|
case "disable":
|
|
e.PeerGater().Disable()
|
|
}
|
|
return c.JSON(http.StatusOK, e.PeerGater().Enabled())
|
|
})
|
|
|
|
ec.GET(PeerGateURL, func(c echo.Context) error {
|
|
return c.JSON(http.StatusOK, e.PeerGater().Enabled())
|
|
})
|
|
}
|
|
|
|
ec.GET(SummaryURL, func(c echo.Context) error {
|
|
files := len(ledger.CurrentData()[protocol.FilesLedgerKey])
|
|
machines := len(ledger.CurrentData()[protocol.MachinesLedgerKey])
|
|
users := len(ledger.CurrentData()[protocol.UsersLedgerKey])
|
|
services := len(ledger.CurrentData()[protocol.ServicesLedgerKey])
|
|
peers, err := e.MessageHub.ListPeers()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
onChainNodes := len(peers)
|
|
p2pPeers := len(e.Host().Network().Peerstore().Peers())
|
|
nodeID := e.Host().ID().String()
|
|
|
|
blockchain := ledger.Index()
|
|
|
|
return c.JSON(http.StatusOK, types.Summary{
|
|
Files: files,
|
|
Machines: machines,
|
|
Users: users,
|
|
Services: services,
|
|
BlockChain: blockchain,
|
|
OnChainNodes: onChainNodes,
|
|
Peers: p2pPeers,
|
|
NodeID: nodeID,
|
|
})
|
|
})
|
|
|
|
ec.GET(MachineURL, func(c echo.Context) error {
|
|
list := []*apiTypes.Machine{}
|
|
|
|
online := services.AvailableNodes(ledger, 20*time.Minute)
|
|
|
|
for _, v := range ledger.CurrentData()[protocol.MachinesLedgerKey] {
|
|
machine := &types.Machine{}
|
|
v.Unmarshal(machine)
|
|
m := &apiTypes.Machine{Machine: *machine}
|
|
if e.Host().Network().Connectedness(peer.ID(machine.PeerID)) == network.Connected {
|
|
m.Connected = true
|
|
}
|
|
peers, err := e.MessageHub.ListPeers()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, p := range peers {
|
|
if p.String() == machine.PeerID {
|
|
m.OnChain = true
|
|
}
|
|
}
|
|
for _, a := range online {
|
|
if a == machine.PeerID {
|
|
m.Online = true
|
|
}
|
|
}
|
|
list = append(list, m)
|
|
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, list)
|
|
})
|
|
|
|
ec.GET(NodesURL, func(c echo.Context) error {
|
|
list := []apiTypes.Peer{}
|
|
peers, err := e.MessageHub.ListPeers()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Sum up state also from services
|
|
online := services.AvailableNodes(ledger, 10*time.Minute)
|
|
p := map[string]interface{}{}
|
|
|
|
for _, v := range online {
|
|
p[v] = nil
|
|
}
|
|
|
|
for _, v := range peers {
|
|
_, exists := p[v.String()]
|
|
if !exists {
|
|
p[v.String()] = nil
|
|
}
|
|
}
|
|
|
|
for id, _ := range p {
|
|
list = append(list, apiTypes.Peer{ID: id, Online: true})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, list)
|
|
})
|
|
|
|
ec.GET(PeerstoreURL, func(c echo.Context) error {
|
|
list := []apiTypes.Peer{}
|
|
for _, v := range e.Host().Network().Peerstore().Peers() {
|
|
list = append(list, apiTypes.Peer{ID: v.String()})
|
|
}
|
|
return c.JSON(http.StatusOK, list)
|
|
})
|
|
|
|
ec.GET(UsersURL, func(c echo.Context) error {
|
|
user := []*types.User{}
|
|
for _, v := range ledger.CurrentData()[protocol.UsersLedgerKey] {
|
|
u := &types.User{}
|
|
v.Unmarshal(u)
|
|
user = append(user, u)
|
|
}
|
|
return c.JSON(http.StatusOK, user)
|
|
})
|
|
|
|
ec.GET(ServiceURL, func(c echo.Context) error {
|
|
list := []*types.Service{}
|
|
for _, v := range ledger.CurrentData()[protocol.ServicesLedgerKey] {
|
|
srvc := &types.Service{}
|
|
v.Unmarshal(srvc)
|
|
list = append(list, srvc)
|
|
}
|
|
return c.JSON(http.StatusOK, list)
|
|
})
|
|
|
|
ec.GET("/*", echo.WrapHandler(http.StripPrefix("/", assetHandler)))
|
|
|
|
ec.GET(BlockchainURL, func(c echo.Context) error {
|
|
return c.JSON(http.StatusOK, ledger.LastBlock())
|
|
})
|
|
|
|
ec.GET(LedgerURL, func(c echo.Context) error {
|
|
return c.JSON(http.StatusOK, ledger.CurrentData())
|
|
})
|
|
|
|
ec.GET(fmt.Sprintf("%s/:bucket/:key", LedgerURL), func(c echo.Context) error {
|
|
bucket := c.Param("bucket")
|
|
key := c.Param("key")
|
|
return c.JSON(http.StatusOK, ledger.CurrentData()[bucket][key])
|
|
})
|
|
|
|
ec.GET(fmt.Sprintf("%s/:bucket", LedgerURL), func(c echo.Context) error {
|
|
bucket := c.Param("bucket")
|
|
return c.JSON(http.StatusOK, ledger.CurrentData()[bucket])
|
|
})
|
|
|
|
announcing := struct{ State string }{"Announcing"}
|
|
|
|
// Store arbitrary data
|
|
ec.PUT(fmt.Sprintf("%s/:bucket/:key/:value", LedgerURL), func(c echo.Context) error {
|
|
bucket := c.Param("bucket")
|
|
key := c.Param("key")
|
|
value := c.Param("value")
|
|
|
|
ledger.Persist(context.Background(), defaultInterval, timeout, bucket, key, value)
|
|
return c.JSON(http.StatusOK, announcing)
|
|
})
|
|
|
|
ec.GET(DNSURL, func(c echo.Context) error {
|
|
res := []apiTypes.DNS{}
|
|
for r, e := range ledger.CurrentData()[protocol.DNSKey] {
|
|
var t types.DNS
|
|
e.Unmarshal(&t)
|
|
d := map[string]string{}
|
|
|
|
for k, v := range t {
|
|
d[dns.TypeToString[uint16(k)]] = v
|
|
}
|
|
|
|
res = append(res,
|
|
apiTypes.DNS{
|
|
Regex: r,
|
|
Records: d,
|
|
})
|
|
}
|
|
return c.JSON(http.StatusOK, res)
|
|
})
|
|
|
|
// Announce dns
|
|
ec.POST(DNSURL, func(c echo.Context) error {
|
|
d := new(apiTypes.DNS)
|
|
if err := c.Bind(d); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
|
}
|
|
|
|
entry := make(types.DNS)
|
|
for r, e := range d.Records {
|
|
entry[dns.Type(dns.StringToType[r])] = e
|
|
}
|
|
services.PersistDNSRecord(context.Background(), ledger, defaultInterval, timeout, d.Regex, entry)
|
|
return c.JSON(http.StatusOK, announcing)
|
|
})
|
|
|
|
// Delete data from ledger
|
|
ec.DELETE(fmt.Sprintf("%s/:bucket", LedgerURL), func(c echo.Context) error {
|
|
bucket := c.Param("bucket")
|
|
|
|
ledger.AnnounceDeleteBucket(context.Background(), defaultInterval, timeout, bucket)
|
|
return c.JSON(http.StatusOK, announcing)
|
|
})
|
|
|
|
ec.DELETE(fmt.Sprintf("%s/:bucket/:key", LedgerURL), func(c echo.Context) error {
|
|
bucket := c.Param("bucket")
|
|
key := c.Param("key")
|
|
|
|
ledger.AnnounceDeleteBucketKey(context.Background(), defaultInterval, timeout, bucket, key)
|
|
return c.JSON(http.StatusOK, announcing)
|
|
})
|
|
|
|
ec.HideBanner = true
|
|
|
|
if err := ec.Start(l); err != nil && err != http.ErrServerClosed {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
ct, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
ec.Shutdown(ct)
|
|
cancel()
|
|
}()
|
|
|
|
return nil
|
|
}
|