extend example

This commit is contained in:
Pascal Fischer
2024-03-18 15:31:47 +01:00
parent 27c3a4c5d6
commit 0c6c5fdc70
71 changed files with 4481 additions and 216 deletions
+1
View File
@@ -0,0 +1 @@
package dns
@@ -0,0 +1 @@
package dns
@@ -0,0 +1 @@
package dns
@@ -0,0 +1,11 @@
package types
// Config represents a dns configuration that is exchanged between management and peers
type Config struct {
// ServiceEnable indicates if the service should be enabled
ServiceEnable bool
// NameServerGroups contains a list of nameserver group
NameServerGroups []*NameServerGroup
// CustomZones contains a list of custom zone
CustomZones []CustomZone
}
@@ -0,0 +1,9 @@
package types
// CustomZone represents a custom zone to be resolved by the dns server
type CustomZone struct {
// Domain is the zone's domain
Domain string
// Records custom zone records
Records []SimpleRecord
}
@@ -0,0 +1,75 @@
package types
import "net/netip"
// NameServerType nameserver type
type NameServerType int
// NameServer represents a DNS nameserver
type NameServer struct {
// IP address of nameserver
IP netip.Addr
// NSType nameserver type
NSType NameServerType
// Port nameserver listening port
Port int
}
// Copy copies a nameserver object
func (n *NameServer) Copy() *NameServer {
return &NameServer{
IP: n.IP,
NSType: n.NSType,
Port: n.Port,
}
}
// IsEqual compares one nameserver with the other
func (n *NameServer) IsEqual(other *NameServer) bool {
return other.IP == n.IP &&
other.NSType == n.NSType &&
other.Port == n.Port
}
func compareNameServerList(list, other []NameServer) bool {
if len(list) != len(other) {
return false
}
for _, ns := range list {
if !containsNameServer(ns, other) {
return false
}
}
return true
}
func containsNameServer(element NameServer, list []NameServer) bool {
for _, ns := range list {
if ns.IsEqual(&element) {
return true
}
}
return false
}
func compareGroupsList(list, other []string) bool {
if len(list) != len(other) {
return false
}
for _, id := range list {
match := false
for _, otherID := range other {
if id == otherID {
match = true
break
}
}
if !match {
return false
}
}
return true
}
@@ -0,0 +1,65 @@
package types
type NameServerGroup interface {
}
type DefaultNameServerGroup struct {
// ID identifier of group
ID string `gorm:"primaryKey"`
// AccountID is a reference to Account that this object belongs
AccountID string `gorm:"index"`
// Name group name
Name string
// Description group description
Description string
// NameServers list of nameservers
NameServers []NameServer `gorm:"serializer:json"`
// Groups list of peer group IDs to distribute the nameservers information
Groups []string `gorm:"serializer:json"`
// Primary indicates that the nameserver group is the primary resolver for any dns query
Primary bool
// Domains indicate the dns query domains to use with this nameserver group
Domains []string `gorm:"serializer:json"`
// Enabled group status
Enabled bool
// SearchDomainsEnabled indicates whether to add match domains to search domains list or not
SearchDomainsEnabled bool
}
// EventMeta returns activity event meta related to the nameserver group
func (g *DefaultNameServerGroup) EventMeta() map[string]any {
return map[string]any{"name": g.Name}
}
// Copy copies a nameserver group object
func (g *DefaultNameServerGroup) Copy() *DefaultNameServerGroup {
nsGroup := &DefaultNameServerGroup{
ID: g.ID,
Name: g.Name,
Description: g.Description,
NameServers: make([]NameServer, len(g.NameServers)),
Groups: make([]string, len(g.Groups)),
Enabled: g.Enabled,
Primary: g.Primary,
Domains: make([]string, len(g.Domains)),
SearchDomainsEnabled: g.SearchDomainsEnabled,
}
copy(nsGroup.NameServers, g.NameServers)
copy(nsGroup.Groups, g.Groups)
copy(nsGroup.Domains, g.Domains)
return nsGroup
}
// IsEqual compares one nameserver group with the other
func (g *DefaultNameServerGroup) IsEqual(other *DefaultNameServerGroup) bool {
return other.ID == g.ID &&
other.Name == g.Name &&
other.Description == g.Description &&
other.Primary == g.Primary &&
other.SearchDomainsEnabled == g.SearchDomainsEnabled &&
compareNameServerList(g.NameServers, other.NameServers) &&
compareGroupsList(g.Groups, other.Groups) &&
compareGroupsList(g.Domains, other.Domains)
}
@@ -0,0 +1,7 @@
package types
type Settings interface {
}
type DefaultSettings struct {
}
@@ -0,0 +1,53 @@
package types
import (
"fmt"
"net"
"github.com/miekg/dns"
)
// SimpleRecord provides a simple DNS record specification for CNAME, A and AAAA records
type SimpleRecord struct {
// Name domain name
Name string
// Type of record, 1 for A, 5 for CNAME, 28 for AAAA. see https://pkg.go.dev/github.com/miekg/dns@v1.1.41#pkg-constants
Type int
// Class dns class, currently use the DefaultClass for all records
Class string
// TTL time-to-live for the record
TTL int
// RData is the actual value resolved in a dns query
RData string
}
// String returns a string of the simple record formatted as:
// <Name> <TTL> <Class> <Type> <RDATA>
func (s SimpleRecord) String() string {
fqdn := dns.Fqdn(s.Name)
return fmt.Sprintf("%s %d %s %s %s", fqdn, s.TTL, s.Class, dns.Type(s.Type).String(), s.RData)
}
// Len returns the length of the RData field, based on its type
func (s SimpleRecord) Len() uint16 {
emptyString := s.RData == ""
switch s.Type {
case 1:
if emptyString {
return 0
}
return net.IPv4len
case 5:
if emptyString || s.RData == "." {
return 1
}
return uint16(len(s.RData) + 1)
case 28:
if emptyString {
return 0
}
return net.IPv6len
default:
return 0
}
}
@@ -0,0 +1 @@
package groups
@@ -0,0 +1 @@
package groups
@@ -0,0 +1 @@
package groups
@@ -0,0 +1,23 @@
package types
type Group interface {
}
type DefaultGroup struct {
// ID of the group
ID string
// AccountID is a reference to Account that this object belongs
AccountID string `json:"-" gorm:"index"`
// Name visible in the UI
Name string
// Issued of the group
Issued string
// Peers list of the group
Peers []string `gorm:"serializer:json"`
IntegrationReference IntegrationReference `gorm:"embedded;embeddedPrefix:integration_ref_"`
}
@@ -0,0 +1 @@
package network
@@ -0,0 +1,19 @@
package network
import "github.com/netbirdio/netbird/management/refactor/resources/network/types"
type Manager interface {
GetNetwork(accountID string) (types.Network, error)
}
type DefaultManager struct {
}
func NewDefaultManager() *DefaultManager {
return &DefaultManager{}
}
func (d DefaultManager) GetNetwork(accountID string) (types.Network, error) {
// TODO implement me
panic("implement me")
}
@@ -0,0 +1 @@
package network
@@ -0,0 +1,70 @@
package types
import (
"math/rand"
"net"
"sync"
"time"
"github.com/c-robinson/iplib"
"github.com/rs/xid"
)
const (
// SubnetSize is a size of the subnet of the global network, e.g. 100.77.0.0/16
SubnetSize = 16
// NetSize is a global network size 100.64.0.0/10
NetSize = 10
)
type Network struct {
Identifier string `json:"id"`
Net net.IPNet `gorm:"serializer:gob"`
Dns string
// Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added).
// Used to synchronize state to the client apps.
Serial uint64
mu sync.Mutex `json:"-" gorm:"-"`
}
// NewNetwork creates a new Network initializing it with a Serial=0
// It takes a random /16 subnet from 100.64.0.0/10 (64 different subnets)
func NewNetwork() *Network {
n := iplib.NewNet4(net.ParseIP("100.64.0.0"), NetSize)
sub, _ := n.Subnet(SubnetSize)
s := rand.NewSource(time.Now().Unix())
r := rand.New(s)
intn := r.Intn(len(sub))
return &Network{
Identifier: xid.New().String(),
Net: sub[intn].IPNet,
Dns: "",
Serial: 0}
}
// IncSerial increments Serial by 1 reflecting that the network state has been changed
func (n *Network) IncSerial() {
n.mu.Lock()
defer n.mu.Unlock()
n.Serial++
}
// CurrentSerial returns the Network.Serial of the network (latest state id)
func (n *Network) CurrentSerial() uint64 {
n.mu.Lock()
defer n.mu.Unlock()
return n.Serial
}
func (n *Network) Copy() *Network {
return &Network{
Identifier: n.Identifier,
Net: n.Net,
Dns: n.Dns,
Serial: n.Serial,
}
}
@@ -0,0 +1,18 @@
package types
import (
peerTypes "github.com/netbirdio/netbird/management/refactor/resources/peers/types"
policyTypes "github.com/netbirdio/netbird/management/refactor/resources/policies/types"
routeTypes "github.com/netbirdio/netbird/management/refactor/resources/routes/types"
nbdns "github.com/netbirdio/netbird/dns"
)
type NetworkMap struct {
Peers []*peerTypes.Peer
Network *Network
Routes []*routeTypes.Route
DNSConfig nbdns.Config
OfflinePeers []*peerTypes.Peer
FirewallRules []*policyTypes.FirewallRule
}
+317
View File
@@ -0,0 +1,317 @@
package peers
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gorilla/mux"
http2 "github.com/netbirdio/netbird/management/refactor/api/http"
peerTypes "github.com/netbirdio/netbird/management/refactor/resources/peers/types"
"github.com/netbirdio/netbird/management/refactor/store"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/jwtclaims"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status"
)
func RegisterPeersEndpoints(manager Manager, router *mux.Router) {
peersHandler := NewDefaultPeersHandler(manager, apiHandler.AuthCfg)
router.HandleFunc("/peers", peersHandler.GetAllPeers).Methods("GET", "OPTIONS")
router.HandleFunc("/peers/{peerId}", peersHandler.HandlePeer).
Methods("GET", "PUT", "DELETE", "OPTIONS")
}
// DefaultPeersHandler is a handler that returns peers of the account
type DefaultPeersHandler struct {
peersManager Manager
store store.Store
claimsExtractor *jwtclaims.ClaimsExtractor
}
// NewDefaultPeersHandler creates a new PeersHandler HTTP handler
func NewDefaultPeersHandler(manager Manager, authCfg AuthCfg) *DefaultPeersHandler {
return &DefaultPeersHandler{
peersManager: manager,
claimsExtractor: jwtclaims.NewClaimsExtractor(
jwtclaims.WithAudience(authCfg.Audience),
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
),
}
}
func (h *DefaultPeersHandler) checkPeerStatus(peer *peerTypes.Peer) (*peerTypes.Peer, error) {
peerToReturn := peer.Copy()
if peer.Status.Connected {
// Although we have online status in store we do not yet have an updated channel so have to show it as disconnected
// This may happen after server restart when not all peers are yet connected
if !h.accountManager.HasConnectedChannel(peer.ID) {
peerToReturn.Status.Connected = false
}
}
return peerToReturn, nil
}
func (h *DefaultPeersHandler) getPeer(account *server.Account, peerID, userID string, w http.ResponseWriter) {
peer, err := h.peersManager.GetPeerByID(account.Id, peerID, userID)
if err != nil {
util.WriteError(err, w)
return
}
peerToReturn, err := h.checkPeerStatus(peer)
if err != nil {
util.WriteError(err, w)
return
}
dnsDomain := h.accountManager.GetDNSDomain()
groupsInfo := toGroupsInfo(account.Groups, peer.ID)
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
util.WriteJSONObject(w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, accessiblePeers))
}
func (h *DefaultPeersHandler) updatePeer(account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) {
req := &api.PeerRequest{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
return
}
update := &nbpeer.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name,
LoginExpirationEnabled: req.LoginExpirationEnabled}
if req.ApprovalRequired != nil {
update.Status = &nbpeer.PeerStatus{RequiresApproval: *req.ApprovalRequired}
}
peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update)
if err != nil {
util.WriteError(err, w)
return
}
dnsDomain := h.accountManager.GetDNSDomain()
groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
util.WriteJSONObject(w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, accessiblePeers))
}
func (h *DefaultPeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) {
err := h.accountManager.DeletePeer(accountID, peerID, userID)
if err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, http2.EmptyObject{})
}
// HandlePeer handles all peer requests for GET, PUT and DELETE operations
func (h *DefaultPeersHandler) HandlePeer(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
vars := mux.Vars(r)
peerID := vars["peerId"]
if len(peerID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid peer ID"), w)
return
}
switch r.Method {
case http.MethodDelete:
h.deletePeer(account.Id, user.Id, peerID, w)
return
case http.MethodPut:
h.updatePeer(account, user, peerID, w, r)
return
case http.MethodGet:
h.getPeer(account, peerID, user.Id, w)
return
default:
util.WriteError(status.Errorf(status.NotFound, "unknown METHOD"), w)
}
}
// GetAllPeers returns a list of all peers associated with a provided account
func (h *DefaultPeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
claims := h.claimsExtractor.FromRequestContext(r)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
peers, err := h.accountManager.GetPeers(account.Id, user.Id)
if err != nil {
util.WriteError(err, w)
return
}
dnsDomain := h.accountManager.GetDNSDomain()
respBody := make([]*api.PeerBatch, 0, len(peers))
for _, peer := range peers {
peerToReturn, err := h.checkPeerStatus(peer)
if err != nil {
util.WriteError(err, w)
return
}
groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID)
accessiblePeerNumbers := h.accessiblePeersNumber(account, peer.ID)
respBody = append(respBody, toPeerListItemResponse(peerToReturn, groupMinimumInfo, dnsDomain, accessiblePeerNumbers))
}
util.WriteJSONObject(w, respBody)
return
default:
util.WriteError(status.Errorf(status.NotFound, "unknown METHOD"), w)
}
}
func (h *DefaultPeersHandler) accessiblePeersNumber(account *server.Account, peerID string) int {
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
return len(netMap.Peers) + len(netMap.OfflinePeers)
}
func toAccessiblePeers(netMap *server.NetworkMap, dnsDomain string) []api.AccessiblePeer {
accessiblePeers := make([]api.AccessiblePeer, 0, len(netMap.Peers)+len(netMap.OfflinePeers))
for _, p := range netMap.Peers {
ap := api.AccessiblePeer{
Id: p.ID,
Name: p.Name,
Ip: p.IP.String(),
DnsLabel: fqdn(p, dnsDomain),
UserId: p.UserID,
}
accessiblePeers = append(accessiblePeers, ap)
}
for _, p := range netMap.OfflinePeers {
ap := api.AccessiblePeer{
Id: p.ID,
Name: p.Name,
Ip: p.IP.String(),
DnsLabel: fqdn(p, dnsDomain),
UserId: p.UserID,
}
accessiblePeers = append(accessiblePeers, ap)
}
return accessiblePeers
}
func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMinimum {
var groupsInfo []api.GroupMinimum
groupsChecked := make(map[string]struct{})
for _, group := range groups {
_, ok := groupsChecked[group.ID]
if ok {
continue
}
groupsChecked[group.ID] = struct{}{}
for _, pk := range group.Peers {
if pk == peerID {
info := api.GroupMinimum{
Id: group.ID,
Name: group.Name,
PeersCount: len(group.Peers),
}
groupsInfo = append(groupsInfo, info)
break
}
}
}
return groupsInfo
}
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer {
osVersion := peer.Meta.OSVersion
if osVersion == "" {
osVersion = peer.Meta.Core
}
return &api.Peer{
Id: peer.ID,
Name: peer.Name,
Ip: peer.IP.String(),
ConnectionIp: peer.Location.ConnectionIP.String(),
Connected: peer.Status.Connected,
LastSeen: peer.Status.LastSeen,
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
KernelVersion: peer.Meta.KernelVersion,
GeonameId: int(peer.Location.GeoNameID),
Version: peer.Meta.WtVersion,
Groups: groupsInfo,
SshEnabled: peer.SSHEnabled,
Hostname: peer.Meta.Hostname,
UserId: peer.UserID,
UiVersion: peer.Meta.UIVersion,
DnsLabel: fqdn(peer, dnsDomain),
LoginExpirationEnabled: peer.LoginExpirationEnabled,
LastLogin: peer.LastLogin,
LoginExpired: peer.Status.LoginExpired,
AccessiblePeers: accessiblePeer,
ApprovalRequired: &peer.Status.RequiresApproval,
CountryCode: peer.Location.CountryCode,
CityName: peer.Location.CityName,
}
}
func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeersCount int) *api.PeerBatch {
osVersion := peer.Meta.OSVersion
if osVersion == "" {
osVersion = peer.Meta.Core
}
return &api.PeerBatch{
Id: peer.ID,
Name: peer.Name,
Ip: peer.IP.String(),
ConnectionIp: peer.Location.ConnectionIP.String(),
Connected: peer.Status.Connected,
LastSeen: peer.Status.LastSeen,
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
KernelVersion: peer.Meta.KernelVersion,
GeonameId: int(peer.Location.GeoNameID),
Version: peer.Meta.WtVersion,
Groups: groupsInfo,
SshEnabled: peer.SSHEnabled,
Hostname: peer.Meta.Hostname,
UserId: peer.UserID,
UiVersion: peer.Meta.UIVersion,
DnsLabel: fqdn(peer, dnsDomain),
LoginExpirationEnabled: peer.LoginExpirationEnabled,
LastLogin: peer.LastLogin,
LoginExpired: peer.Status.LoginExpired,
AccessiblePeersCount: accessiblePeersCount,
ApprovalRequired: &peer.Status.RequiresApproval,
CountryCode: peer.Location.CountryCode,
CityName: peer.Location.CityName,
}
}
func fqdn(peer *nbpeer.Peer, dnsDomain string) string {
fqdn := peer.FQDN(dnsDomain)
if fqdn == "" {
return peer.DNSLabel
} else {
return fqdn
}
}
@@ -0,0 +1,51 @@
package peers
import (
"github.com/netbirdio/netbird/management/refactor/resources/peers/types"
"github.com/netbirdio/netbird/management/refactor/resources/settings"
)
type Manager interface {
GetPeerByPubKey(pubKey string) (types.Peer, error)
GetPeerByID(id string) (types.Peer, error)
GetNetworkPeerByID(id string) (types.Peer, error)
GetNetworkPeersInAccount(id string) ([]types.Peer, error)
}
type DefaultManager struct {
repository Repository
settingsManager settings.Manager
}
func NewDefaultManager(repository Repository, settingsManager settings.Manager) *DefaultManager {
return &DefaultManager{
repository: repository,
settingsManager: settingsManager,
}
}
func (dm *DefaultManager) GetNetworkPeerByID(id string) (types.Peer, error) {
return dm.repository.FindPeerByID(id)
}
func (dm *DefaultManager) GetNetworkPeersInAccount(accountId string) ([]types.Peer, error) {
defaultPeers, err := dm.repository.FindAllPeersInAccount(accountId)
if err != nil {
return nil, err
}
peers := make([]types.Peer, len(defaultPeers))
for _, dp := range defaultPeers {
peers = append(peers, dp)
}
return peers, nil
}
func (dm *DefaultManager) GetPeerByPubKey(pubKey string) (types.Peer, error) {
return dm.repository.FindPeerByPubKey(pubKey)
}
func (dm *DefaultManager) GetPeerByID(id string) (types.Peer, error) {
return dm.repository.FindPeerByID(id)
}
@@ -0,0 +1,10 @@
package peers
import "github.com/netbirdio/netbird/management/refactor/resources/peers/types"
type Repository interface {
FindPeerByPubKey(pubKey string) (types.Peer, error)
FindPeerByID(id string) (types.Peer, error)
FindAllPeersInAccount(id string) ([]types.Peer, error)
UpdatePeer(peer types.Peer) error
}
@@ -0,0 +1,261 @@
package types
import (
"fmt"
"net"
"time"
)
type Peer interface {
GetID() string
SetID(string)
GetAccountID() string
SetAccountID(string)
GetKey() string
SetKey(string)
GetSetupKey() string
SetSetupKey(string)
GetIP() net.IP
SetIP(net.IP)
GetName() string
SetName(string)
GetDNSLabel() string
SetDNSLabel(string)
GetUserID() string
SetUserID(string)
GetSSHKey() string
SetSSHKey(string)
GetSSHEnabled() bool
SetSSHEnabled(bool)
AddedWithSSOLogin() bool
UpdateMetaIfNew(meta PeerSystemMeta) bool
MarkLoginExpired(expired bool)
FQDN(dnsDomain string) string
EventMeta(dnsDomain string) map[string]any
LoginExpired(expiresIn time.Duration) (bool, time.Duration)
Copy() Peer
}
// Peer represents a machine connected to the network.
// The Peer is a WireGuard peer identified by a public key
type DefaultPeer struct {
// ID is an internal ID of the peer
ID string `gorm:"primaryKey"`
// AccountID is a reference to Account that this object belongs
AccountID string `json:"-" gorm:"index;uniqueIndex:idx_peers_account_id_ip"`
// WireGuard public key
Key string `gorm:"index"`
// A setup key this peer was registered with
SetupKey string
// IP address of the Peer
IP net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip"`
// Meta is a Peer system meta data
Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"`
// Name is peer's name (machine name)
Name string
// DNSLabel is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's
// domain to the peer label. e.g. peer-dns-label.netbird.cloud
DNSLabel string
// Status peer's management connection status
Status *PeerStatus `gorm:"embedded;embeddedPrefix:peer_status_"`
// The user ID that registered the peer
UserID string
// SSHKey is a public SSH key of the peer
SSHKey string
// SSHEnabled indicates whether SSH server is enabled on the peer
SSHEnabled bool
// LoginExpirationEnabled indicates whether peer's login expiration is enabled and once expired the peer has to re-login.
// Works with LastLogin
LoginExpirationEnabled bool
// LastLogin the time when peer performed last login operation
LastLogin time.Time
// CreatedAt records the time the peer was created
CreatedAt time.Time
// Indicate ephemeral peer attribute
Ephemeral bool
// Geo location based on connection IP
Location Location `gorm:"embedded;embeddedPrefix:location_"`
}
// Location is a geo location information of a Peer based on public connection IP
type Location struct {
ConnectionIP net.IP // from grpc peer or reverse proxy headers depends on setup
CountryCode string
CityName string
GeoNameID uint // city level geoname id
}
// PeerLogin used as a data object between the gRPC API and AccountManager on Login request.
type PeerLogin struct {
// WireGuardPubKey is a peers WireGuard public key
WireGuardPubKey string
// SSHKey is a peer's ssh key. Can be empty (e.g., old version do not provide it, or this feature is disabled)
SSHKey string
// Meta is the system information passed by peer, must be always present.
Meta PeerSystemMeta
// UserID indicates that JWT was used to log in, and it was valid. Can be empty when SetupKey is used or auth is not required.
UserID string
// AccountID indicates that JWT was used to log in, and it was valid. Can be empty when SetupKey is used or auth is not required.
AccountID string
// SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required.
SetupKey string
}
// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user.
func (p *DefaultPeer) AddedWithSSOLogin() bool {
return p.UserID != ""
}
// UpdateMetaIfNew updates peer's system metadata if new information is provided
// returns true if meta was updated, false otherwise
func (p *DefaultPeer) UpdateMetaIfNew(meta PeerSystemMeta) bool {
// Avoid overwriting UIVersion if the update was triggered sole by the CLI client
if meta.UIVersion == "" {
meta.UIVersion = p.Meta.UIVersion
}
if p.Meta.isEqual(meta) {
return false
}
p.Meta = meta
return true
}
// MarkLoginExpired marks peer's status expired or not
func (p *DefaultPeer) MarkLoginExpired(expired bool) {
newStatus := p.Status.Copy()
newStatus.LoginExpired = expired
if expired {
newStatus.Connected = false
}
p.Status = newStatus
}
// LoginExpired indicates whether the peer's login has expired or not.
// If Peer.LastLogin plus the expiresIn duration has happened already; then login has expired.
// Return true if a login has expired, false otherwise, and time left to expiration (negative when expired).
// Login expiration can be disabled/enabled on a Peer level via Peer.LoginExpirationEnabled property.
// Login expiration can also be disabled/enabled globally on the Account level via Settings.PeerLoginExpirationEnabled.
// Only peers added by interactive SSO login can be expired.
func (p *DefaultPeer) LoginExpired(expiresIn time.Duration) (bool, time.Duration) {
if !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled {
return false, 0
}
expiresAt := p.LastLogin.Add(expiresIn)
now := time.Now()
timeLeft := expiresAt.Sub(now)
return timeLeft <= 0, timeLeft
}
// FQDN returns peers FQDN combined of the peer's DNS label and the system's DNS domain
func (p *DefaultPeer) FQDN(dnsDomain string) string {
if dnsDomain == "" {
return ""
}
return fmt.Sprintf("%s.%s", p.DNSLabel, dnsDomain)
}
// EventMeta returns activity event meta related to the peer
func (p *DefaultPeer) EventMeta(dnsDomain string) map[string]any {
return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP, "created_at": p.CreatedAt}
}
func (p *DefaultPeer) GetID() string {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) SetID(s string) {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) GetAccountID() string {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) SetAccountID(s string) {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) GetKey() string {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) SetKey(s string) {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) GetSetupKey() string {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) SetSetupKey(s string) {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) GetIP() net.IP {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) SetIP(ip net.IP) {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) GetName() string {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) SetName(s string) {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) GetDNSLabel() string {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) SetDNSLabel(s string) {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) GetUserID() string {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) SetUserID(s string) {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) GetSSHKey() string {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) SetSSHKey(s string) {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) GetSSHEnabled() bool {
// TODO implement me
panic("implement me")
}
func (p *DefaultPeer) SetSSHEnabled(b bool) {
// TODO implement me
panic("implement me")
}
@@ -0,0 +1,24 @@
package types
import "time"
// Copy PeerStatus
func (p *PeerStatus) Copy() *PeerStatus {
return &PeerStatus{
LastSeen: p.LastSeen,
Connected: p.Connected,
LoginExpired: p.LoginExpired,
RequiresApproval: p.RequiresApproval,
}
}
type PeerStatus struct { //nolint:revive
// LastSeen is the last time peer was connected to the management service
LastSeen time.Time
// Connected indicates whether peer is connected to the management service or not
Connected bool
// LoginExpired
LoginExpired bool
// RequiresApproval indicates whether peer requires approval or not
RequiresApproval bool
}
@@ -0,0 +1,69 @@
package types
import "net/netip"
// NetworkAddress is the IP address with network and MAC address of a network interface
type NetworkAddress struct {
NetIP netip.Prefix `gorm:"serializer:json"`
Mac string
}
// Environment is a system environment information
type Environment struct {
Cloud string
Platform string
}
// PeerSystemMeta is a metadata of a Peer machine system
type PeerSystemMeta struct { //nolint:revive
Hostname string
GoOS string
Kernel string
Core string
Platform string
OS string
OSVersion string
WtVersion string
UIVersion string
KernelVersion string
NetworkAddresses []NetworkAddress `gorm:"serializer:json"`
SystemSerialNumber string
SystemProductName string
SystemManufacturer string
Environment Environment `gorm:"serializer:json"`
}
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
if len(p.NetworkAddresses) != len(other.NetworkAddresses) {
return false
}
for _, addr := range p.NetworkAddresses {
var found bool
for _, oAddr := range other.NetworkAddresses {
if addr.Mac == oAddr.Mac && addr.NetIP == oAddr.NetIP {
found = true
continue
}
}
if !found {
return false
}
}
return p.Hostname == other.Hostname &&
p.GoOS == other.GoOS &&
p.Kernel == other.Kernel &&
p.KernelVersion == other.KernelVersion &&
p.Core == other.Core &&
p.Platform == other.Platform &&
p.OS == other.OS &&
p.OSVersion == other.OSVersion &&
p.WtVersion == other.WtVersion &&
p.UIVersion == other.UIVersion &&
p.SystemSerialNumber == other.SystemSerialNumber &&
p.SystemProductName == other.SystemProductName &&
p.SystemManufacturer == other.SystemManufacturer &&
p.Environment.Cloud == other.Environment.Cloud &&
p.Environment.Platform == other.Environment.Platform
}
@@ -0,0 +1 @@
package policies
@@ -0,0 +1,33 @@
package policies
import (
"github.com/netbirdio/netbird/management/refactor/resources/peers"
"github.com/netbirdio/netbird/management/refactor/resources/peers/types"
)
type Manager interface {
GetAccessiblePeersAndFirewallRules(peerID string) (peers []types.Peer, firewallRules []*FirewallRule)
}
type DefaultManager struct {
repository Repository
peerManager peers.Manager
}
func NewDefaultManager(repository Repository, peerManager peers.Manager) *DefaultManager {
return &DefaultManager{
repository: repository,
peerManager: peerManager,
}
}
func (dm *DefaultManager) GetAccessiblePeersAndFirewallRules(peerID string) (peers []types.Peer, firewallRules []*FirewallRule) {
peer, err := dm.peerManager.GetPeerByID(peerID)
if err != nil {
return nil, nil
}
peers, err = dm.peerManager.GetNetworkPeersInAccount(peer.GetAccountID())
return peers, nil
}
@@ -0,0 +1 @@
package posture
@@ -0,0 +1 @@
package posture
@@ -0,0 +1 @@
package posture
@@ -0,0 +1,4 @@
package policies
type Repository interface {
}
@@ -0,0 +1,19 @@
package types
// FirewallRule is a rule of the firewall.
type FirewallRule struct {
// PeerIP of the peer
PeerIP string
// Direction of the traffic
Direction int
// Action of the traffic
Action string
// Protocol of the traffic
Protocol string
// Port of the traffic
Port string
}
@@ -0,0 +1,13 @@
package types
type Policy interface {
GetID() string
}
type DefaultPolicy struct {
ID string
}
func (dp *DefaultPolicy) GetID() string {
return dp.ID
}
@@ -0,0 +1,13 @@
package types
type PolicyRule interface {
GetID() string
}
type DefaultPolicyRule struct {
ID string
}
func (dpr *DefaultPolicyRule) GetID() string {
return dpr.ID
}
@@ -0,0 +1 @@
package routes
@@ -0,0 +1,100 @@
package routes
import (
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/refactor/resources/peers"
"github.com/netbirdio/netbird/management/refactor/resources/peers/types"
routeTypes "github.com/netbirdio/netbird/management/refactor/resources/routes/types"
"github.com/netbirdio/netbird/route"
)
type Manager interface {
GetRoutesToSync(peerID string, peersToConnect []*types.Peer, accountID string) []*routeTypes.Route
}
type DefaultManager struct {
repository Repository
peersManager peers.Manager
}
func NewDefaultManager(repository Repository, peersManager peers.Manager) *DefaultManager {
return &DefaultManager{
repository: repository,
peersManager: peersManager,
}
}
func (d DefaultManager) GetRoutesToSync(peerID string, peersToConnect []*types.Peer) []*routeTypes.Route {
routes, peerDisabledRoutes := d.getRoutingPeerRoutes(peerID)
peerRoutesMembership := make(lookupMap)
for _, r := range append(routes, peerDisabledRoutes...) {
peerRoutesMembership[route.GetHAUniqueID(r)] = struct{}{}
}
groupListMap := a.getPeerGroups(peerID)
for _, peer := range aclPeers {
activeRoutes, _ := a.getRoutingPeerRoutes(peer.ID)
groupFilteredRoutes := a.filterRoutesByGroups(activeRoutes, groupListMap)
filteredRoutes := a.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership)
routes = append(routes, filteredRoutes...)
}
return routes
}
func (d DefaultManager) getRoutingPeerRoutes(accountID, peerID string) (enabledRoutes []routeTypes.Route, disabledRoutes []routeTypes.Route) {
peer, err := d.peersManager.GetPeerByID(peerID)
if err != nil {
log.Errorf("peer %s that doesn't exist under account %s", peerID, accountID)
return nil, nil
}
// currently we support only linux routing peers
if peer.Meta.GoOS != "linux" {
return enabledRoutes, disabledRoutes
}
seenRoute := make(map[string]struct{})
takeRoute := func(r routeTypes.Route, id string) {
if _, ok := seenRoute[r.GetID()]; ok {
return
}
seenRoute[r.GetID()] = struct{}{}
if r.IsEnabled() {
r.SetPeer(peer.GetKey())
enabledRoutes = append(enabledRoutes, r)
return
}
disabledRoutes = append(disabledRoutes, r)
}
for _, r := range a.Routes {
for _, groupID := range r.PeerGroups {
group := a.GetGroup(groupID)
if group == nil {
log.Errorf("route %s has peers group %s that doesn't exist under account %s", r.ID, groupID, a.Id)
continue
}
for _, id := range group.Peers {
if id != peerID {
continue
}
newPeerRoute := r.Copy()
newPeerRoute.Peer = id
newPeerRoute.PeerGroups = nil
newPeerRoute.ID = r.ID + ":" + id // we have to provide unique route id when distribute network map
takeRoute(newPeerRoute, id)
break
}
}
if r.Peer == peerID {
takeRoute(r.Copy(), peerID)
}
}
return enabledRoutes, disabledRoutes
}
@@ -0,0 +1,4 @@
package routes
type Repository interface {
}
@@ -0,0 +1,54 @@
package types
import "net/netip"
const (
// InvalidNetwork invalid network type
InvalidNetwork NetworkType = iota
// IPv4Network IPv4 network type
IPv4Network
// IPv6Network IPv6 network type
IPv6Network
)
// NetworkType route network type
type NetworkType int
type Route interface {
GetID() string
IsEnabled() bool
GetPeer() string
SetPeer(string)
}
type DefaultRoute struct {
ID string `gorm:"primaryKey"`
// AccountID is a reference to Account that this object belongs
AccountID string `gorm:"index"`
Network netip.Prefix `gorm:"serializer:gob"`
NetID string
Description string
Peer string
PeerGroups []string `gorm:"serializer:gob"`
NetworkType NetworkType
Masquerade bool
Metric int
Enabled bool
Groups []string `gorm:"serializer:json"`
}
func (r *DefaultRoute) GetID() string {
return r.ID
}
func (r *DefaultRoute) IsEnabled() bool {
return r.Enabled
}
func (r *DefaultRoute) GetPeer() string {
return r.Peer
}
func (r *DefaultRoute) SetPeer(peer string) {
r.Peer = peer
}
@@ -0,0 +1 @@
package settings
@@ -0,0 +1,21 @@
package settings
import "github.com/netbirdio/netbird/management/refactor/resources/settings/types"
type Manager interface {
GetSettings(accountID string) (types.Settings, error)
}
type DefaultManager struct {
repository Repository
}
func NewDefaultManager(repository Repository) *DefaultManager {
return &DefaultManager{
repository: repository,
}
}
func (dm *DefaultManager) GetSettings(accountID string) (types.Settings, error) {
return dm.repository.FindSettings(accountID)
}
@@ -0,0 +1,7 @@
package settings
import "github.com/netbirdio/netbird/management/refactor/resources/settings/types"
type Repository interface {
FindSettings(accountID string) (types.Settings, error)
}
@@ -0,0 +1,34 @@
package types
import "time"
type Settings interface {
GetLicense() string
GetPeerLoginExpiration() time.Duration
SetPeerLoginExpiration(duration time.Duration)
GetPeerLoginExpirationEnabled() bool
SetPeerLoginExpirationEnabled(bool)
}
type DefaultSettings struct {
}
func (s *DefaultSettings) GetLicense() string {
return "selfhosted"
}
func (s *DefaultSettings) GetPeerLoginExpiration() time.Duration {
return 0
}
func (s *DefaultSettings) SetPeerLoginExpiration(duration time.Duration) {
}
func (s *DefaultSettings) GetPeerLoginExpirationEnabled() bool {
return false
}
func (s *DefaultSettings) SetPeerLoginExpirationEnabled(bool) {
}
@@ -0,0 +1 @@
package setup_keys
@@ -0,0 +1 @@
package setup_keys
@@ -0,0 +1 @@
package setup_keys
@@ -0,0 +1,7 @@
package types
type SetupKey interface {
}
type DefaultSetupKey struct {
}
@@ -0,0 +1 @@
package users
@@ -0,0 +1,27 @@
package users
import (
"github.com/netbirdio/netbird/management/refactor/resources/peers"
"github.com/netbirdio/netbird/management/refactor/resources/users/types"
)
type Manager interface {
GetUser(id string) (types.User, error)
}
type DefaultManager struct {
repository Repository
peerManager peers.Manager
}
func NewDefaultManager(repository Repository, peerManager peers.Manager) *DefaultManager {
return &DefaultManager{
repository: repository,
peerManager: peerManager,
}
}
func (d DefaultManager) GetUser(id string) (types.User, error) {
// TODO implement me
panic("implement me")
}
@@ -0,0 +1 @@
package personal_access_tokens
@@ -0,0 +1 @@
package personal_access_tokens
@@ -0,0 +1 @@
package personal_access_tokens
@@ -0,0 +1,4 @@
package users
type Repository interface {
}
@@ -0,0 +1,35 @@
package types
import "time"
// UserRole is the role of a User
type UserRole string
type User interface {
IsBlocked() bool
}
// User represents a user of the system
type DefaultUser struct {
Id string `gorm:"primaryKey"`
// AccountID is a reference to Account that this object belongs
AccountID string `json:"-" gorm:"index"`
Role UserRole
IsServiceUser bool
// NonDeletable indicates whether the service user can be deleted
NonDeletable bool
// ServiceUserName is only set if IsServiceUser is true
ServiceUserName string
// AutoGroups is a list of Group IDs to auto-assign to peers registered by this user
AutoGroups []string `gorm:"serializer:json"`
// Blocked indicates whether the user is blocked. Blocked users can't use the system.
Blocked bool
// LastLogin is the last time the user logged in to IdP
LastLogin time.Time
// Issued of the user
Issued string `gorm:"default:api"`
}
func (u *DefaultUser) IsBlocked() bool {
return u.Blocked
}