Merge branch 'develop' into GRA-1217-tests

This commit is contained in:
Matthew R Kasun 2023-03-08 16:45:30 -05:00 committed by GitHub
commit 8ab81ca2da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 970 additions and 595 deletions

View file

@ -31,6 +31,7 @@ body:
label: Version
description: What version are you running?
options:
- v0.18.3
- v0.18.2
- v0.18.1
- v0.18.0

View file

@ -17,7 +17,7 @@
<p align="center">
<a href="https://github.com/gravitl/netmaker/releases">
<img src="https://img.shields.io/badge/Version-0.18.2-informational?style=flat-square" />
<img src="https://img.shields.io/badge/Version-0.18.3-informational?style=flat-square" />
</a>
<a href="https://hub.docker.com/r/gravitl/netmaker/tags">
<img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />

View file

@ -3,6 +3,7 @@ package acl
import (
"os"
"github.com/gravitl/netmaker/cli/cmd/commons"
"github.com/gravitl/netmaker/cli/functions"
"github.com/gravitl/netmaker/logic/acls"
"github.com/guumaster/tablewriter"
@ -16,23 +17,28 @@ var aclListCmd = &cobra.Command{
Long: `List all ACLs associated with a network`,
Run: func(cmd *cobra.Command, args []string) {
aclSource := (map[acls.AclID]acls.ACL)(*functions.GetACL(args[0]))
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"From", "To", "Status"})
for id, acl := range aclSource {
for k, v := range (map[acls.AclID]byte)(acl) {
row := []string{string(id), string(k)}
switch v {
case acls.NotAllowed:
row = append(row, "Not Allowed")
case acls.NotPresent:
row = append(row, "Not Present")
case acls.Allowed:
row = append(row, "Allowed")
switch commons.OutputFormat {
case commons.JsonOutput:
functions.PrettyPrint(aclSource)
default:
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"From", "To", "Status"})
for id, acl := range aclSource {
for k, v := range (map[acls.AclID]byte)(acl) {
row := []string{string(id), string(k)}
switch v {
case acls.NotAllowed:
row = append(row, "Not Allowed")
case acls.NotPresent:
row = append(row, "Not Present")
case acls.Allowed:
row = append(row, "Allowed")
}
table.Append(row)
}
table.Append(row)
}
table.Render()
}
table.Render()
},
}

View file

@ -0,0 +1,9 @@
package commons
// OutputFormat flag defines the output format to stdout (Enum:- json)
var OutputFormat string
const (
// JsonOutput refers to json format output to stdout
JsonOutput = "json"
)

View file

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"github.com/gravitl/netmaker/cli/cmd/commons"
"github.com/gravitl/netmaker/cli/functions"
"github.com/gravitl/netmaker/models"
"github.com/guumaster/tablewriter"
@ -31,12 +32,17 @@ var dnsListCmd = &cobra.Command{
} else {
data = *functions.GetDNS()
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Name", "Network", "IPv4 Address", "IPv6 Address"})
for _, d := range data {
table.Append([]string{d.Name, d.Network, d.Address, d.Address6})
switch commons.OutputFormat {
case commons.JsonOutput:
functions.PrettyPrint(data)
default:
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Name", "Network", "IPv4 Address", "IPv6 Address"})
for _, d := range data {
table.Append([]string{d.Name, d.Network, d.Address, d.Address6})
}
table.Render()
}
table.Render()
},
}

View file

@ -0,0 +1,47 @@
package enrollment_key
import (
"strings"
"github.com/gravitl/netmaker/cli/functions"
"github.com/gravitl/netmaker/models"
"github.com/spf13/cobra"
)
var (
expiration int
usesRemaining int
networks string
unlimited bool
tags string
)
var enrollmentKeyCreateCmd = &cobra.Command{
Use: "create",
Args: cobra.NoArgs,
Short: "Create an enrollment key",
Long: `Create an enrollment key`,
Run: func(cmd *cobra.Command, args []string) {
enrollKey := &models.APIEnrollmentKey{
Expiration: int64(expiration),
UsesRemaining: usesRemaining,
Unlimited: unlimited,
}
if networks != "" {
enrollKey.Networks = strings.Split(networks, ",")
}
if tags != "" {
enrollKey.Tags = strings.Split(tags, ",")
}
functions.PrettyPrint(functions.CreateEnrollmentKey(enrollKey))
},
}
func init() {
enrollmentKeyCreateCmd.Flags().IntVar(&expiration, "expiration", 0, "Expiration time of the key in UNIX timestamp format")
enrollmentKeyCreateCmd.Flags().IntVar(&usesRemaining, "uses", 0, "Number of times this key can be used")
enrollmentKeyCreateCmd.Flags().StringVar(&networks, "networks", "", "Comma-separated list of networks which the enrollment key can access")
enrollmentKeyCreateCmd.Flags().BoolVar(&unlimited, "unlimited", false, "Should the key have unlimited uses ?")
enrollmentKeyCreateCmd.Flags().StringVar(&tags, "tags", "", "Comma-separated list of any additional tags")
rootCmd.AddCommand(enrollmentKeyCreateCmd)
}

View file

@ -0,0 +1,23 @@
package enrollment_key
import (
"fmt"
"github.com/gravitl/netmaker/cli/functions"
"github.com/spf13/cobra"
)
var enrollmentKeyDeleteCmd = &cobra.Command{
Use: "delete keyID",
Args: cobra.ExactArgs(1),
Short: "Delete an enrollment key",
Long: `Delete an enrollment key`,
Run: func(cmd *cobra.Command, args []string) {
functions.DeleteEnrollmentKey(args[0])
fmt.Println("Enrollment key ", args[0], " deleted")
},
}
func init() {
rootCmd.AddCommand(enrollmentKeyDeleteCmd)
}

View file

@ -0,0 +1,20 @@
package enrollment_key
import (
"github.com/gravitl/netmaker/cli/functions"
"github.com/spf13/cobra"
)
var enrollmentKeyListCmd = &cobra.Command{
Use: "list",
Args: cobra.NoArgs,
Short: "List enrollment keys",
Long: `List enrollment keys`,
Run: func(cmd *cobra.Command, args []string) {
functions.PrettyPrint(functions.GetEnrollmentKeys())
},
}
func init() {
rootCmd.AddCommand(enrollmentKeyListCmd)
}

View file

@ -0,0 +1,28 @@
package enrollment_key
import (
"os"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "enrollment_key",
Short: "Manage Enrollment Keys",
Long: `Manage Enrollment Keys`,
}
// GetRoot returns the root subcommand
func GetRoot() *cobra.Command {
return rootCmd
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

View file

@ -5,6 +5,7 @@ import (
"strconv"
"time"
"github.com/gravitl/netmaker/cli/cmd/commons"
"github.com/gravitl/netmaker/cli/functions"
"github.com/gravitl/netmaker/models"
"github.com/guumaster/tablewriter"
@ -25,12 +26,17 @@ var extClientListCmd = &cobra.Command{
} else {
data = *functions.GetAllExtClients()
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Client ID", "Network", "IPv4 Address", "IPv6 Address", "Enabled", "Last Modified"})
for _, d := range data {
table.Append([]string{d.ClientID, d.Network, d.Address, d.Address6, strconv.FormatBool(d.Enabled), time.Unix(d.LastModified, 0).String()})
switch commons.OutputFormat {
case commons.JsonOutput:
functions.PrettyPrint(data)
default:
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Client ID", "Network", "IPv4 Address", "IPv6 Address", "Enabled", "Last Modified"})
for _, d := range data {
table.Append([]string{d.ClientID, d.Network, d.Address, d.Address6, strconv.FormatBool(d.Enabled), time.Unix(d.LastModified, 0).String()})
}
table.Render()
}
table.Render()
},
}

View file

@ -4,6 +4,7 @@ import (
"os"
"time"
"github.com/gravitl/netmaker/cli/cmd/commons"
"github.com/gravitl/netmaker/cli/functions"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
@ -16,14 +17,19 @@ var networkListCmd = &cobra.Command{
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
networks := functions.GetNetworks()
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"NetId", "Address Range (IPv4)", "Address Range (IPv6)", "Network Last Modified", "Nodes Last Modified"})
for _, n := range *networks {
networkLastModified := time.Unix(n.NetworkLastModified, 0).Format(time.RFC3339)
nodesLastModified := time.Unix(n.NodesLastModified, 0).Format(time.RFC3339)
table.Append([]string{n.NetID, n.AddressRange, n.AddressRange6, networkLastModified, nodesLastModified})
switch commons.OutputFormat {
case commons.JsonOutput:
functions.PrettyPrint(networks)
default:
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"NetId", "Address Range (IPv4)", "Address Range (IPv6)", "Network Last Modified", "Nodes Last Modified"})
for _, n := range *networks {
networkLastModified := time.Unix(n.NetworkLastModified, 0).Format(time.RFC3339)
nodesLastModified := time.Unix(n.NodesLastModified, 0).Format(time.RFC3339)
table.Append([]string{n.NetID, n.AddressRange, n.AddressRange6, networkLastModified, nodesLastModified})
}
table.Render()
}
table.Render()
},
}

View file

@ -4,6 +4,7 @@ import (
"os"
"strconv"
"github.com/gravitl/netmaker/cli/cmd/commons"
"github.com/gravitl/netmaker/cli/functions"
"github.com/gravitl/netmaker/models"
"github.com/guumaster/tablewriter"
@ -23,23 +24,28 @@ var nodeListCmd = &cobra.Command{
} else {
data = *functions.GetNodes()
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "Addresses", "Network", "Egress", "Ingress", "Relay"})
for _, d := range data {
addresses := ""
if d.Address != "" {
addresses += d.Address
}
if d.Address6 != "" {
switch commons.OutputFormat {
case commons.JsonOutput:
functions.PrettyPrint(data)
default:
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "Addresses", "Network", "Egress", "Ingress", "Relay"})
for _, d := range data {
addresses := ""
if d.Address != "" {
addresses += ", "
addresses += d.Address
}
addresses += d.Address6
if d.Address6 != "" {
if d.Address != "" {
addresses += ", "
}
addresses += d.Address6
}
table.Append([]string{d.ID, addresses, d.Network,
strconv.FormatBool(d.IsEgressGateway), strconv.FormatBool(d.IsIngressGateway), strconv.FormatBool(d.IsRelay)})
}
table.Append([]string{d.ID, addresses, d.Network,
strconv.FormatBool(d.IsEgressGateway), strconv.FormatBool(d.IsIngressGateway), strconv.FormatBool(d.IsRelay)})
table.Render()
}
table.Render()
},
}

View file

@ -4,8 +4,10 @@ import (
"os"
"github.com/gravitl/netmaker/cli/cmd/acl"
"github.com/gravitl/netmaker/cli/cmd/commons"
"github.com/gravitl/netmaker/cli/cmd/context"
"github.com/gravitl/netmaker/cli/cmd/dns"
"github.com/gravitl/netmaker/cli/cmd/enrollment_key"
"github.com/gravitl/netmaker/cli/cmd/ext_client"
"github.com/gravitl/netmaker/cli/cmd/host"
"github.com/gravitl/netmaker/cli/cmd/keys"
@ -41,6 +43,7 @@ func Execute() {
}
func init() {
rootCmd.PersistentFlags().StringVarP(&commons.OutputFormat, "output", "o", "", "List output in specific format (Enum:- json)")
// Bind subcommands here
rootCmd.AddCommand(network.GetRoot())
rootCmd.AddCommand(context.GetRoot())
@ -55,4 +58,5 @@ func init() {
rootCmd.AddCommand(metrics.GetRoot())
rootCmd.AddCommand(network_user.GetRoot())
rootCmd.AddCommand(host.GetRoot())
rootCmd.AddCommand(enrollment_key.GetRoot())
}

View file

@ -5,6 +5,7 @@ import (
"strconv"
"strings"
"github.com/gravitl/netmaker/cli/cmd/commons"
"github.com/gravitl/netmaker/cli/functions"
"github.com/guumaster/tablewriter"
"github.com/spf13/cobra"
@ -16,12 +17,18 @@ var userListCmd = &cobra.Command{
Short: "List all users",
Long: `List all users`,
Run: func(cmd *cobra.Command, args []string) {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Name", "Admin", "Networks", "Groups"})
for _, d := range *functions.ListUsers() {
table.Append([]string{d.UserName, strconv.FormatBool(d.IsAdmin), strings.Join(d.Networks, ", "), strings.Join(d.Groups, ", ")})
data := functions.ListUsers()
switch commons.OutputFormat {
case commons.JsonOutput:
functions.PrettyPrint(data)
default:
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Name", "Admin", "Networks", "Groups"})
for _, d := range *data {
table.Append([]string{d.UserName, strconv.FormatBool(d.IsAdmin), strings.Join(d.Networks, ", "), strings.Join(d.Groups, ", ")})
}
table.Render()
}
table.Render()
},
}

View file

@ -0,0 +1,22 @@
package functions
import (
"net/http"
"github.com/gravitl/netmaker/models"
)
// CreateEnrollmentKey - create an enrollment key
func CreateEnrollmentKey(key *models.APIEnrollmentKey) *models.EnrollmentKey {
return request[models.EnrollmentKey](http.MethodPost, "/api/v1/enrollment-keys", key)
}
// GetEnrollmentKeys - gets all enrollment keys
func GetEnrollmentKeys() *[]models.EnrollmentKey {
return request[[]models.EnrollmentKey](http.MethodGet, "/api/v1/enrollment-keys", nil)
}
// DeleteEnrollmentKey - delete an enrollment key
func DeleteEnrollmentKey(keyID string) {
request[any](http.MethodDelete, "/api/v1/enrollment-keys/"+keyID, nil)
}

View file

@ -13,7 +13,7 @@ services:
BROKER_TYPE: "emqx"
EMQX_REST_ENDPOINT: "http://mq:18083"
SERVER_NAME: "NETMAKER_BASE_DOMAIN"
STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
STUN_LIST: "stun.NETMAKER_BASE_DOMAIN:3478,stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302"
SERVER_HOST: "SERVER_PUBLIC_IP"
SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
COREDNS_ADDR: "SERVER_PUBLIC_IP"

View file

@ -13,7 +13,7 @@ services:
BROKER_TYPE: "emqx"
EMQX_REST_ENDPOINT: "http://mq:18083"
SERVER_NAME: "NETMAKER_BASE_DOMAIN"
STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
STUN_LIST: "stun.NETMAKER_BASE_DOMAIN:3478,stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302"
SERVER_HOST: "SERVER_PUBLIC_IP"
SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
COREDNS_ADDR: "SERVER_PUBLIC_IP"

View file

@ -11,7 +11,7 @@ services:
environment:
BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN"
SERVER_NAME: "NETMAKER_BASE_DOMAIN"
STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
STUN_LIST: "stun.NETMAKER_BASE_DOMAIN:3478,stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302"
SERVER_HOST: "SERVER_PUBLIC_IP"
SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
COREDNS_ADDR: "SERVER_PUBLIC_IP"
@ -24,10 +24,10 @@ services:
DATABASE: "sqlite"
NODE_ID: "netmaker-server-1"
SERVER_BROKER_ENDPOINT: "ws://mq:1883"
STUN_PORT: "3478"
VERBOSITY: "1"
MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
MQ_USERNAME: "REPLACE_MQ_USERNAME"
STUN_PORT: "3478"
ports:
- "3478:3478/udp"
netmaker-ui:

View file

@ -32,48 +32,48 @@ type EnvironmentConfig struct {
// ServerConfig - server conf struct
type ServerConfig struct {
CoreDNSAddr string `yaml:"corednsaddr"`
APIConnString string `yaml:"apiconn"`
APIHost string `yaml:"apihost"`
APIPort string `yaml:"apiport"`
Broker string `yam:"broker"`
ServerBrokerEndpoint string `yaml:"serverbrokerendpoint"`
BrokerType string `yaml:"brokertype"`
EmqxRestEndpoint string `yaml:"emqxrestendpoint"`
MasterKey string `yaml:"masterkey"`
DNSKey string `yaml:"dnskey"`
AllowedOrigin string `yaml:"allowedorigin"`
NodeID string `yaml:"nodeid"`
RestBackend string `yaml:"restbackend"`
MessageQueueBackend string `yaml:"messagequeuebackend"`
DNSMode string `yaml:"dnsmode"`
DisableRemoteIPCheck string `yaml:"disableremoteipcheck"`
Version string `yaml:"version"`
SQLConn string `yaml:"sqlconn"`
Platform string `yaml:"platform"`
Database string `yaml:"database"`
Verbosity int32 `yaml:"verbosity"`
AuthProvider string `yaml:"authprovider"`
OIDCIssuer string `yaml:"oidcissuer"`
ClientID string `yaml:"clientid"`
ClientSecret string `yaml:"clientsecret"`
FrontendURL string `yaml:"frontendurl"`
DisplayKeys string `yaml:"displaykeys"`
AzureTenant string `yaml:"azuretenant"`
Telemetry string `yaml:"telemetry"`
HostNetwork string `yaml:"hostnetwork"`
Server string `yaml:"server"`
PublicIPService string `yaml:"publicipservice"`
MQPassword string `yaml:"mqpassword"`
MQUserName string `yaml:"mqusername"`
MetricsExporter string `yaml:"metrics_exporter"`
BasicAuth string `yaml:"basic_auth"`
LicenseValue string `yaml:"license_value"`
NetmakerAccountID string `yaml:"netmaker_account_id"`
IsEE string `yaml:"is_ee"`
StunPort int `yaml:"stun_port"`
StunHost string `yaml:"stun_host"`
Proxy string `yaml:"proxy"`
CoreDNSAddr string `yaml:"corednsaddr"`
APIConnString string `yaml:"apiconn"`
APIHost string `yaml:"apihost"`
APIPort string `yaml:"apiport"`
Broker string `yam:"broker"`
ServerBrokerEndpoint string `yaml:"serverbrokerendpoint"`
BrokerType string `yaml:"brokertype"`
EmqxRestEndpoint string `yaml:"emqxrestendpoint"`
MasterKey string `yaml:"masterkey"`
DNSKey string `yaml:"dnskey"`
AllowedOrigin string `yaml:"allowedorigin"`
NodeID string `yaml:"nodeid"`
RestBackend string `yaml:"restbackend"`
MessageQueueBackend string `yaml:"messagequeuebackend"`
DNSMode string `yaml:"dnsmode"`
DisableRemoteIPCheck string `yaml:"disableremoteipcheck"`
Version string `yaml:"version"`
SQLConn string `yaml:"sqlconn"`
Platform string `yaml:"platform"`
Database string `yaml:"database"`
Verbosity int32 `yaml:"verbosity"`
AuthProvider string `yaml:"authprovider"`
OIDCIssuer string `yaml:"oidcissuer"`
ClientID string `yaml:"clientid"`
ClientSecret string `yaml:"clientsecret"`
FrontendURL string `yaml:"frontendurl"`
DisplayKeys string `yaml:"displaykeys"`
AzureTenant string `yaml:"azuretenant"`
Telemetry string `yaml:"telemetry"`
HostNetwork string `yaml:"hostnetwork"`
Server string `yaml:"server"`
PublicIPService string `yaml:"publicipservice"`
MQPassword string `yaml:"mqpassword"`
MQUserName string `yaml:"mqusername"`
MetricsExporter string `yaml:"metrics_exporter"`
BasicAuth string `yaml:"basic_auth"`
LicenseValue string `yaml:"license_value"`
NetmakerAccountID string `yaml:"netmaker_account_id"`
IsEE string `yaml:"is_ee"`
StunPort int `yaml:"stun_port"`
StunList string `yaml:"stun_list"`
Proxy string `yaml:"proxy"`
}
// SQLConfig - Generic SQL Config

View file

@ -10,7 +10,7 @@
//
// Schemes: https
// BasePath: /
// Version: 0.18.2
// Version: 0.18.3
// Host: netmaker.io
//
// Consumes:

View file

@ -17,6 +17,7 @@ import (
"github.com/gravitl/netmaker/models/promodels"
"github.com/gravitl/netmaker/mq"
"github.com/skip2/go-qrcode"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
func extClientHandlers(r *mux.Router) {
@ -317,16 +318,22 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
}
var extclient models.ExtClient
var CustomExtClient models.CustomExtClient
err := json.NewDecoder(r.Body).Decode(&CustomExtClient)
var customExtClient models.CustomExtClient
err := json.NewDecoder(r.Body).Decode(&customExtClient)
if err == nil {
if CustomExtClient.ClientID != "" && !validName(CustomExtClient.ClientID) {
if customExtClient.ClientID != "" && !validName(customExtClient.ClientID) {
logic.ReturnErrorResponse(w, r, logic.FormatError(errInvalidExtClientID, "badrequest"))
return
}
extclient.ClientID = CustomExtClient.ClientID
extclient.ClientID = customExtClient.ClientID
if len(customExtClient.PublicKey) > 0 {
if _, err := wgtypes.ParseKey(customExtClient.PublicKey); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errInvalidExtClientPubKey, "badrequest"))
return
}
extclient.PublicKey = customExtClient.PublicKey
}
}
extclient.Network = networkName
@ -350,16 +357,13 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
listenPort = host.ProxyListenPort
}
extclient.IngressGatewayEndpoint = host.EndpointIP.String() + ":" + strconv.FormatInt(int64(listenPort), 10)
extclient.Enabled = true
parentNetwork, err := logic.GetNetwork(networkName)
if err == nil { // check if parent network default ACL is enabled (yes) or not (no)
extclient.Enabled = parentNetwork.DefaultACL == "yes"
}
// check pro settings
err = logic.CreateExtClient(&extclient)
if err != nil {
if err = logic.CreateExtClient(&extclient); err != nil {
logger.Log(0, r.Header.Get("user"),
fmt.Sprintf("failed to create new ext client on network [%s]: %v", networkName, err))
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
@ -389,8 +393,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
logger.Log(0, r.Header.Get("user"), "created new ext client on network", networkName)
w.WriteHeader(http.StatusOK)
go func() {
err = mq.PublishPeerUpdate()
if err != nil {
if err := mq.PublishPeerUpdate(); err != nil {
logger.Log(1, "error setting ext peers on "+nodeid+": "+err.Error())
}
if err := mq.PublishExtCLientDNS(&extclient); err != nil {

View file

@ -1,6 +1,7 @@
package controller
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -433,7 +434,7 @@ func getNode(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
hostPeerUpdate, err := logic.GetPeerUpdateForHost(node.Network, host, nil)
hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), node.Network, host, nil)
if err != nil && !database.IsEmptyRecord(err) {
logger.Log(0, r.Header.Get("user"),
fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))
@ -622,7 +623,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
return
}
}
hostPeerUpdate, err := logic.GetPeerUpdateForHost(networkName, &data.Host, nil)
hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), networkName, &data.Host, nil)
if err != nil && !database.IsEmptyRecord(err) {
logger.Log(0, r.Header.Get("user"),
fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", data.Host.ID.String(), err))
@ -908,7 +909,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
relayedUpdate = true
}
ifaceDelta := logic.IfaceDelta(&currentNode, newNode)
aclUpdate := currentNode.DefaultACL != newNode.DefaultACL
if ifaceDelta && servercfg.Is_EE {
if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID, currentNode.Network); err != nil {
logger.Log(0, "failed to reset failover lists during node update for node", currentNode.ID.String(), currentNode.Network)
@ -941,13 +942,17 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
logger.Log(1, r.Header.Get("user"), "updated node", currentNode.ID.String(), "on network", currentNode.Network)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(apiNode)
runUpdates(newNode, ifaceDelta)
go func() {
go func(aclUpdate bool, newNode *models.Node) {
if aclUpdate {
if err := mq.PublishPeerUpdate(); err != nil {
logger.Log(0, "error during node ACL update for node", newNode.ID.String())
}
}
if err := mq.PublishReplaceDNS(&currentNode, newNode, host); err != nil {
logger.Log(1, "failed to publish dns update", err.Error())
}
}()
}(aclUpdate, newNode)
}
// swagger:route DELETE /api/nodes/{network}/{nodeid} nodes deleteNode

View file

@ -5,7 +5,10 @@ import (
"regexp"
)
var errInvalidExtClientID = errors.New("ext client ID must be alphanumderic and/or dashes")
var (
errInvalidExtClientPubKey = errors.New("incorrect ext client public key")
errInvalidExtClientID = errors.New("ext client ID must be alphanumderic and/or dashes")
)
// allow only dashes and alphaneumeric for ext client and node names
func validName(name string) bool {

View file

@ -59,6 +59,8 @@ const (
HOSTS_TABLE_NAME = "hosts"
// ENROLLMENT_KEYS_TABLE_NAME - table name for enrollmentkeys
ENROLLMENT_KEYS_TABLE_NAME = "enrollmentkeys"
// HOST_ACTIONS_TABLE_NAME - table name for enrollmentkeys
HOST_ACTIONS_TABLE_NAME = "hostactions"
// == ERROR CONSTS ==
// NO_RECORD - no singular result found
@ -141,6 +143,7 @@ func createTables() {
createTable(CACHE_TABLE_NAME)
createTable(HOSTS_TABLE_NAME)
createTable(ENROLLMENT_KEYS_TABLE_NAME)
createTable(HOST_ACTIONS_TABLE_NAME)
}
func createTable(tableName string) error {

5
go.mod
View file

@ -13,7 +13,7 @@ require (
github.com/mattn/go-sqlite3 v1.14.16
github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.2
github.com/txn2/txeh v1.3.0
golang.org/x/crypto v0.6.0
golang.org/x/net v0.6.0 // indirect
@ -42,8 +42,7 @@ require (
require (
github.com/guumaster/tablewriter v0.0.10
github.com/kr/pretty v0.3.0
github.com/matryer/is v1.4.0
github.com/matryer/is v1.4.1
github.com/olekukonko/tablewriter v0.0.5
github.com/spf13/cobra v1.6.1
)

8
go.sum
View file

@ -80,8 +80,8 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
@ -140,8 +140,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/txn2/txeh v1.3.0 h1:vnbv63htVMZCaQgLqVBxKvj2+HHHFUzNW7I183zjg3E=
github.com/txn2/txeh v1.3.0/go.mod h1:O7M6gUTPeMF+vsa4c4Ipx3JDkOYrruB1Wry8QRsMcw8=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=

View file

@ -16,7 +16,7 @@ spec:
hostNetwork: true
containers:
- name: netclient
image: gravitl/netclient:v0.18.2
image: gravitl/netclient:v0.18.3
env:
- name: TOKEN
value: "TOKEN_VALUE"

View file

@ -28,7 +28,7 @@ spec:
# - "<node label value>"
containers:
- name: netclient
image: gravitl/netclient:v0.18.2
image: gravitl/netclient:v0.18.3
env:
- name: TOKEN
value: "TOKEN_VALUE"

View file

@ -79,7 +79,7 @@ spec:
value: "Kubernetes"
- name: VERBOSITY
value: "3"
image: gravitl/netmaker:v0.18.2
image: gravitl/netmaker:v0.18.3
imagePullPolicy: Always
name: netmaker
ports:

View file

@ -15,7 +15,7 @@ spec:
spec:
containers:
- name: netmaker-ui
image: gravitl/netmaker-ui:v0.18.2
image: gravitl/netmaker-ui:v0.18.3
ports:
- containerPort: 443
env:

View file

@ -117,14 +117,15 @@ func GetExtClient(clientid string, network string) (models.ExtClient, error) {
// CreateExtClient - creates an extclient
func CreateExtClient(extclient *models.ExtClient) error {
if extclient.PrivateKey == "" {
if len(extclient.PublicKey) == 0 {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
extclient.PrivateKey = privateKey.String()
extclient.PublicKey = privateKey.PublicKey().String()
} else {
extclient.PrivateKey = "[ENTER PRIVATE KEY]"
}
parentNetwork, err := GetNetwork(extclient.Network)
@ -156,7 +157,6 @@ func CreateExtClient(extclient *models.ExtClient) error {
}
extclient.LastModified = time.Now().Unix()
key, err := GetRecordKey(extclient.ClientID, extclient.Network)
if err != nil {
return err

View file

@ -1,37 +1,55 @@
package hostactions
import (
"sync"
"encoding/json"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/models"
)
// nodeActionHandler - handles the storage of host action updates
var nodeActionHandler sync.Map
// AddAction - adds a host action to a host's list to be retrieved from broker update
func AddAction(hu models.HostUpdate) {
currentRecords, ok := nodeActionHandler.Load(hu.Host.ID.String())
if !ok { // no list exists yet
nodeActionHandler.Store(hu.Host.ID.String(), []models.HostUpdate{hu})
} else { // list exists, append to it
currentList := currentRecords.([]models.HostUpdate)
currentList = append(currentList, hu)
nodeActionHandler.Store(hu.Host.ID.String(), currentList)
hostID := hu.Host.ID.String()
currentRecords, err := database.FetchRecord(database.HOST_ACTIONS_TABLE_NAME, hostID)
if err != nil {
if database.IsEmptyRecord(err) { // no list exists yet
newEntry, err := json.Marshal([]models.HostUpdate{hu})
if err != nil {
return
}
_ = database.Insert(hostID, string(newEntry), database.HOST_ACTIONS_TABLE_NAME)
}
return
}
var currentList []models.HostUpdate
if err := json.Unmarshal([]byte(currentRecords), &currentList); err != nil {
return
}
currentList = append(currentList, hu)
newData, err := json.Marshal(currentList)
if err != nil {
return
}
_ = database.Insert(hostID, string(newData), database.HOST_ACTIONS_TABLE_NAME)
}
// GetAction - gets an action if exists
// TODO: may need to move to DB rather than sync map for HA
func GetAction(id string) *models.HostUpdate {
currentRecords, ok := nodeActionHandler.Load(id)
if !ok {
currentRecords, err := database.FetchRecord(database.HOST_ACTIONS_TABLE_NAME, id)
if err != nil {
return nil
}
var currentList []models.HostUpdate
if err = json.Unmarshal([]byte(currentRecords), &currentList); err != nil {
return nil
}
currentList := currentRecords.([]models.HostUpdate)
if len(currentList) > 0 {
hu := currentList[0]
nodeActionHandler.Store(hu.Host.ID.String(), currentList[1:])
newData, err := json.Marshal(currentList[1:])
if err != nil {
newData, _ = json.Marshal([]models.HostUpdate{})
}
_ = database.Insert(id, string(newData), database.HOST_ACTIONS_TABLE_NAME)
return &hu
}
return nil

View file

@ -395,6 +395,21 @@ func HostExists(h *models.Host) bool {
return (err != nil && !database.IsEmptyRecord(err)) || (err == nil)
}
// GetHostByNodeID - returns a host if found to have a node's ID, else nil
func GetHostByNodeID(id string) *models.Host {
hosts, err := GetAllHosts()
if err != nil {
return nil
}
for i := range hosts {
h := hosts[i]
if StringSliceContains(h.Nodes, id) {
return &h
}
}
return nil
}
func updatePort(p *int) {
*p++
if *p > maxPort {

View file

@ -32,17 +32,24 @@ const (
// GetNetworkNodes - gets the nodes of a network
func GetNetworkNodes(network string) ([]models.Node, error) {
var nodes []models.Node
allnodes, err := GetAllNodes()
if err != nil {
return []models.Node{}, err
}
for _, node := range allnodes {
return GetNetworkNodesMemory(allnodes, network), nil
}
// GetNetworkNodesMemory - gets all nodes belonging to a network from list in memory
func GetNetworkNodesMemory(allNodes []models.Node, network string) []models.Node {
var nodes = []models.Node{}
for i := range allNodes {
node := allNodes[i]
if node.Network == network {
nodes = append(nodes, node)
}
}
return nodes, nil
return nodes
}
// UpdateNode - takes a node and updates another node with it's values
@ -83,8 +90,9 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
// DeleteNode - marks node for deletion (and adds to zombie list) if called by UI or deletes node if called by node
func DeleteNode(node *models.Node, purge bool) error {
alreadyDeleted := node.PendingDelete || node.Action == models.NODE_DELETE
node.Action = models.NODE_DELETE
if !purge {
if !purge && !alreadyDeleted {
newnode := *node
newnode.PendingDelete = true
if err := UpdateNode(node, &newnode); err != nil {
@ -93,8 +101,15 @@ func DeleteNode(node *models.Node, purge bool) error {
newZombie <- node.ID
return nil
}
if alreadyDeleted {
logger.Log(1, "forcibly deleting node", node.ID.String())
}
host, err := GetHost(node.HostID.String())
if err != nil {
logger.Log(1, "no host found for node", node.ID.String(), "deleting..")
if delErr := deleteNodeByID(node); delErr != nil {
logger.Log(0, "failed to delete node", node.ID.String(), delErr.Error())
}
return err
}
if err := DissasociateNodeFromHost(node, host); err != nil {

View file

@ -1,9 +1,9 @@
package logic
import (
"context"
"errors"
"fmt"
"log"
"net"
"net/netip"
@ -16,8 +16,15 @@ import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var (
// PeerUpdateCtx context to send to host peer updates
PeerUpdateCtx context.Context
// PeerUpdateStop - the cancel for PeerUpdateCtx
PeerUpdateStop context.CancelFunc
)
// GetProxyUpdateForHost - gets the proxy update for host
func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error) {
func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.ProxyManagerPayload, error) {
proxyPayload := models.ProxyManagerPayload{
Action: models.ProxyUpdate,
}
@ -40,7 +47,7 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
relayPeersMap := make(map[string]models.RelayedConf)
for _, relayedHost := range relayedHosts {
relayedHost := relayedHost
payload, err := GetPeerUpdateForHost("", &relayedHost, nil)
payload, err := GetPeerUpdateForHost(ctx, "", &relayedHost, nil)
if err == nil {
relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, GetPeerListenPort(&relayedHost)))
if udpErr == nil {
@ -116,11 +123,24 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
return proxyPayload, nil
}
// ResetPeerUpdateContext - kills any current peer updates and resets the context
func ResetPeerUpdateContext() {
if PeerUpdateCtx != nil && PeerUpdateStop != nil {
PeerUpdateStop() // tell any current peer updates to stop
}
PeerUpdateCtx, PeerUpdateStop = context.WithCancel(context.Background())
}
// GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
func GetPeerUpdateForHost(network string, host *models.Host, deletedNode *models.Node) (models.HostPeerUpdate, error) {
func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host, deletedNode *models.Node) (models.HostPeerUpdate, error) {
if host == nil {
return models.HostPeerUpdate{}, errors.New("host is nil")
}
allNodes, err := GetAllNodes()
if err != nil {
return models.HostPeerUpdate{}, err
}
// track which nodes are deleted
// after peer calculation, if peer not in list, add delete config of peer
hostPeerUpdate := models.HostPeerUpdate{
@ -137,10 +157,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, deletedNode *models
Peers: []wgtypes.PeerConfig{},
NodePeers: []wgtypes.PeerConfig{},
}
var deletedNodes = []models.Node{} // used to track deleted nodes
if deletedNode != nil {
deletedNodes = append(deletedNodes, *deletedNode)
}
logger.Log(1, "peer update for host", host.ID.String())
peerIndexMap := make(map[string]int)
for _, nodeID := range host.Nodes {
@ -152,229 +169,219 @@ func GetPeerUpdateForHost(network string, host *models.Host, deletedNode *models
if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE {
continue
}
currentPeers, err := GetNetworkNodes(node.Network)
if err != nil {
log.Println("no network nodes")
return models.HostPeerUpdate{}, err
}
currentPeers := GetNetworkNodesMemory(allNodes, node.Network)
var nodePeerMap map[string]models.PeerRouteInfo
if node.IsIngressGateway || node.IsEgressGateway {
nodePeerMap = make(map[string]models.PeerRouteInfo)
}
for _, peer := range currentPeers {
peer := peer
if peer.ID.String() == node.ID.String() {
logger.Log(2, "peer update, skipping self")
//skip yourself
continue
}
if peer.Action == models.NODE_DELETE || peer.PendingDelete {
deletedNodes = append(deletedNodes, peer) // track deleted node for peer update
continue
}
var peerConfig wgtypes.PeerConfig
peerHost, err := GetHost(peer.HostID.String())
if err != nil {
logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
return models.HostPeerUpdate{}, err
}
if !peer.Connected {
logger.Log(2, "peer update, skipping unconnected node", peer.ID.String())
//skip unconnected nodes
continue
}
if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) {
logger.Log(2, "peer update, skipping node for acl")
//skip if not permitted by acl
continue
}
peerConfig.PublicKey = peerHost.PublicKey
peerConfig.PersistentKeepaliveInterval = &peer.PersistentKeepalive
peerConfig.ReplaceAllowedIPs = true
uselocal := false
if host.EndpointIP.String() == peerHost.EndpointIP.String() {
//peer is on same network
// set to localaddress
uselocal = true
if node.LocalAddress.IP == nil {
// use public endpint
uselocal = false
select {
case <-ctx.Done():
logger.Log(2, "cancelled peer update for host", host.Name, host.ID.String())
return models.HostPeerUpdate{}, fmt.Errorf("peer update cancelled")
default:
peer := peer
if peer.ID.String() == node.ID.String() {
logger.Log(2, "peer update, skipping self")
//skip yourself
continue
}
if node.LocalAddress.String() == peer.LocalAddress.String() {
uselocal = false
var peerConfig wgtypes.PeerConfig
peerHost, err := GetHost(peer.HostID.String())
if err != nil {
logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
return models.HostPeerUpdate{}, err
}
}
peerConfig.Endpoint = &net.UDPAddr{
IP: peerHost.EndpointIP,
Port: GetPeerListenPort(peerHost),
}
if uselocal {
peerConfig.Endpoint.IP = peer.LocalAddress.IP
}
allowedips := GetAllowedIPs(&node, &peer, nil)
if peer.IsIngressGateway {
for _, entry := range peer.IngressGatewayRange {
_, cidr, err := net.ParseCIDR(string(entry))
if err == nil {
allowedips = append(allowedips, *cidr)
peerConfig.PublicKey = peerHost.PublicKey
peerConfig.PersistentKeepaliveInterval = &peer.PersistentKeepalive
peerConfig.ReplaceAllowedIPs = true
uselocal := false
if host.EndpointIP.String() == peerHost.EndpointIP.String() {
//peer is on same network
// set to localaddress
uselocal = true
if node.LocalAddress.IP == nil {
// use public endpint
uselocal = false
}
if node.LocalAddress.String() == peer.LocalAddress.String() {
uselocal = false
}
}
}
if peer.IsEgressGateway {
allowedips = append(allowedips, getEgressIPs(&node, &peer)...)
}
peerConfig.AllowedIPs = allowedips
if node.IsIngressGateway || node.IsEgressGateway {
peerConfig.Endpoint = &net.UDPAddr{
IP: peerHost.EndpointIP,
Port: GetPeerListenPort(peerHost),
}
if uselocal {
peerConfig.Endpoint.IP = peer.LocalAddress.IP
}
allowedips := GetAllowedIPs(&node, &peer, nil)
if peer.IsIngressGateway {
_, extPeerIDAndAddrs, err := getExtPeers(&peer)
if err == nil {
for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
extPeerIdAndAddr := extPeerIdAndAddr
nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
PeerAddr: net.IPNet{
IP: net.ParseIP(extPeerIdAndAddr.Address),
Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
},
PeerKey: extPeerIdAndAddr.ID,
Allow: true,
}
for _, entry := range peer.IngressGatewayRange {
_, cidr, err := net.ParseCIDR(string(entry))
if err == nil {
allowedips = append(allowedips, *cidr)
}
}
}
if node.IsIngressGateway && peer.IsEgressGateway {
hostPeerUpdate.IngressInfo.EgressRanges = append(hostPeerUpdate.IngressInfo.EgressRanges,
peer.EgressGatewayRanges...)
if peer.IsEgressGateway {
allowedips = append(allowedips, getEgressIPs(&node, &peer)...)
}
nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
PeerAddr: net.IPNet{
IP: net.ParseIP(peer.PrimaryAddress()),
Mask: getCIDRMaskFromAddr(peer.PrimaryAddress()),
},
PeerKey: peerHost.PublicKey.String(),
Allow: true,
if peer.Action != models.NODE_DELETE &&
!peer.PendingDelete &&
peer.Connected &&
nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) &&
(deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) {
peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection
}
}
var nodePeer wgtypes.PeerConfig
if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; !ok {
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
}
nodePeer = peerConfig
} else {
peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
peerAllowedIPs = append(peerAllowedIPs, allowedips...)
hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
}
nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
}
if node.Network == network { // add to peers map for metrics
hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
}
hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, nodePeer)
}
}
var extPeers []wgtypes.PeerConfig
var extPeerIDAndAddrs []models.IDandAddr
if node.IsIngressGateway {
extPeers, extPeerIDAndAddrs, err = getExtPeers(&node)
if err == nil {
for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
extPeerIdAndAddr := extPeerIdAndAddr
nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
if node.IsIngressGateway || node.IsEgressGateway {
if peer.IsIngressGateway {
_, extPeerIDAndAddrs, err := getExtPeers(&peer)
if err == nil {
for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
extPeerIdAndAddr := extPeerIdAndAddr
nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
PeerAddr: net.IPNet{
IP: net.ParseIP(extPeerIdAndAddr.Address),
Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
},
PeerKey: extPeerIdAndAddr.ID,
Allow: true,
}
}
}
}
if node.IsIngressGateway && peer.IsEgressGateway {
hostPeerUpdate.IngressInfo.EgressRanges = append(hostPeerUpdate.IngressInfo.EgressRanges,
peer.EgressGatewayRanges...)
}
nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
PeerAddr: net.IPNet{
IP: net.ParseIP(extPeerIdAndAddr.Address),
Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
IP: net.ParseIP(peer.PrimaryAddress()),
Mask: getCIDRMaskFromAddr(peer.PrimaryAddress()),
},
PeerKey: extPeerIdAndAddr.ID,
PeerKey: peerHost.PublicKey.String(),
Allow: true,
}
}
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
extPeerIdAndAddr := extPeerIdAndAddr
hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID] = make(map[string]models.IDandAddr)
hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID][extPeerIdAndAddr.ID] = models.IDandAddr{
ID: extPeerIdAndAddr.ID,
Address: extPeerIdAndAddr.Address,
Name: extPeerIdAndAddr.Name,
Network: node.Network,
var nodePeer wgtypes.PeerConfig
if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; !ok {
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
}
hostPeerUpdate.IngressInfo.ExtPeers[extPeerIdAndAddr.ID] = models.ExtClientInfo{
Masquerade: true,
IngGwAddr: net.IPNet{
IP: net.ParseIP(node.PrimaryAddress()),
Mask: getCIDRMaskFromAddr(node.PrimaryAddress()),
},
Network: node.PrimaryNetworkRange(),
ExtPeerAddr: net.IPNet{
IP: net.ParseIP(extPeerIdAndAddr.Address),
Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
},
ExtPeerKey: extPeerIdAndAddr.ID,
Peers: nodePeerMap,
}
if node.Network == network {
hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = extPeerIdAndAddr
hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, extPeers...)
nodePeer = peerConfig
} else {
peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
peerAllowedIPs = append(peerAllowedIPs, allowedips...)
hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
}
nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
}
if node.Network == network { // add to peers map for metrics
hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = models.IDandAddr{
ID: peer.ID.String(),
Address: peer.PrimaryAddress(),
Name: peerHost.Name,
Network: peer.Network,
}
hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, nodePeer)
}
} else if !database.IsEmptyRecord(err) {
logger.Log(1, "error retrieving external clients:", err.Error())
}
}
if node.IsEgressGateway {
hostPeerUpdate.EgressInfo[node.ID.String()] = models.EgressInfo{
EgressID: node.ID.String(),
Network: node.PrimaryNetworkRange(),
EgressGwAddr: net.IPNet{
IP: net.ParseIP(node.PrimaryAddress()),
Mask: getCIDRMaskFromAddr(node.PrimaryAddress()),
},
GwPeers: nodePeerMap,
EgressGWCfg: node.EgressGatewayRequest,
var extPeers []wgtypes.PeerConfig
var extPeerIDAndAddrs []models.IDandAddr
if node.IsIngressGateway {
extPeers, extPeerIDAndAddrs, err = getExtPeers(&node)
if err == nil {
for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
extPeerIdAndAddr := extPeerIdAndAddr
nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
PeerAddr: net.IPNet{
IP: net.ParseIP(extPeerIdAndAddr.Address),
Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
},
PeerKey: extPeerIdAndAddr.ID,
Allow: true,
}
}
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
extPeerIdAndAddr := extPeerIdAndAddr
hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID] = make(map[string]models.IDandAddr)
hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID][extPeerIdAndAddr.ID] = models.IDandAddr{
ID: extPeerIdAndAddr.ID,
Address: extPeerIdAndAddr.Address,
Name: extPeerIdAndAddr.Name,
Network: node.Network,
}
hostPeerUpdate.IngressInfo.ExtPeers[extPeerIdAndAddr.ID] = models.ExtClientInfo{
Masquerade: true,
IngGwAddr: net.IPNet{
IP: net.ParseIP(node.PrimaryAddress()),
Mask: getCIDRMaskFromAddr(node.PrimaryAddress()),
},
Network: node.PrimaryNetworkRange(),
ExtPeerAddr: net.IPNet{
IP: net.ParseIP(extPeerIdAndAddr.Address),
Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
},
ExtPeerKey: extPeerIdAndAddr.ID,
Peers: nodePeerMap,
}
if node.Network == network {
hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = extPeerIdAndAddr
hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, extPeers...)
}
}
} else if !database.IsEmptyRecord(err) {
logger.Log(1, "error retrieving external clients:", err.Error())
}
}
if node.IsEgressGateway {
hostPeerUpdate.EgressInfo[node.ID.String()] = models.EgressInfo{
EgressID: node.ID.String(),
Network: node.PrimaryNetworkRange(),
EgressGwAddr: net.IPNet{
IP: net.ParseIP(node.PrimaryAddress()),
Mask: getCIDRMaskFromAddr(node.PrimaryAddress()),
},
GwPeers: nodePeerMap,
EgressGWCfg: node.EgressGatewayRequest,
}
}
}
}
// run through delete nodes
if len(deletedNodes) > 0 {
for i := range deletedNodes {
delNode := deletedNodes[i]
delHost, err := GetHost(delNode.HostID.String())
if err != nil {
continue
}
if _, ok := hostPeerUpdate.HostPeerIDs[delHost.PublicKey.String()]; !ok {
var peerConfig = wgtypes.PeerConfig{}
peerConfig.PublicKey = delHost.PublicKey
peerConfig.Endpoint = &net.UDPAddr{
IP: delHost.EndpointIP,
Port: GetPeerListenPort(delHost),
}
peerConfig.Remove = true
peerConfig.AllowedIPs = []net.IPNet{delNode.Address, delNode.Address6}
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
}
// == post peer calculations ==
// indicate removal if no allowed IPs were calculated
for i := range hostPeerUpdate.Peers {
peer := hostPeerUpdate.Peers[i]
if len(peer.AllowedIPs) == 0 {
peer.Remove = true
}
hostPeerUpdate.Peers[i] = peer
}
for i := range hostPeerUpdate.NodePeers {
peer := hostPeerUpdate.NodePeers[i]
if len(peer.AllowedIPs) == 0 {
peer.Remove = true
}
hostPeerUpdate.NodePeers[i] = peer
}
return hostPeerUpdate, nil

View file

@ -27,7 +27,7 @@ import (
stunserver "github.com/gravitl/netmaker/stun-server"
)
var version = "v0.18.2"
var version = "v0.18.3"
// Start DB Connection and start API Request Handler
func main() {

View file

@ -8,12 +8,17 @@ import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
const PLACEHOLDER_KEY_TEXT = "ACCESS_KEY"
const PLACEHOLDER_TOKEN_TEXT = "ACCESS_TOKEN"
const (
// PLACEHOLDER_KEY_TEXT - access key placeholder text if option turned off
PLACEHOLDER_KEY_TEXT = "ACCESS_KEY"
// PLACEHOLDER_TOKEN_TEXT - access key token placeholder text if option turned off
PLACEHOLDER_TOKEN_TEXT = "ACCESS_TOKEN"
)
// CustomExtClient - struct for CustomExtClient params
type CustomExtClient struct {
ClientID string `json:"clientid"`
ClientID string `json:"clientid"`
PublicKey string `json:"publickey,omitempty"`
}
// AuthParams - struct for auth params
@ -223,20 +228,20 @@ type NodeJoinResponse struct {
// ServerConfig - struct for dealing with the server information for a netclient
type ServerConfig struct {
CoreDNSAddr string `yaml:"corednsaddr"`
API string `yaml:"api"`
APIPort string `yaml:"apiport"`
DNSMode string `yaml:"dnsmode"`
Version string `yaml:"version"`
MQPort string `yaml:"mqport"`
MQUserName string `yaml:"mq_username"`
MQPassword string `yaml:"mq_password"`
Server string `yaml:"server"`
Broker string `yaml:"broker"`
Is_EE bool `yaml:"isee"`
StunPort int `yaml:"stun_port"`
StunHost string `yaml:"stun_host"`
TrafficKey []byte `yaml:"traffickey"`
CoreDNSAddr string `yaml:"corednsaddr"`
API string `yaml:"api"`
APIPort string `yaml:"apiport"`
DNSMode string `yaml:"dnsmode"`
Version string `yaml:"version"`
MQPort string `yaml:"mqport"`
MQUserName string `yaml:"mq_username"`
MQPassword string `yaml:"mq_password"`
Server string `yaml:"server"`
Broker string `yaml:"broker"`
Is_EE bool `yaml:"isee"`
StunPort int `yaml:"stun_port"`
StunList []StunServer `yaml:"stun_list"`
TrafficKey []byte `yaml:"traffickey"`
}
// User.NameInCharset - returns if name is in charset below or not
@ -261,3 +266,9 @@ type JoinData struct {
Node Node `json:"node" yaml:"node"`
Key string `json:"key" yaml:"key"`
}
// StunServer - struct to hold data required for using stun server
type StunServer struct {
Domain string `json:"domain" yaml:"domain"`
Port int `json:"port" yaml:"port"`
}

View file

@ -1,11 +1,13 @@
package mq
import (
"context"
"encoding/json"
"fmt"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/google/uuid"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
@ -22,22 +24,29 @@ func DefaultHandler(client mqtt.Client, msg mqtt.Message) {
// Ping message Handler -- handles ping topic from client nodes
func Ping(client mqtt.Client, msg mqtt.Message) {
go func() {
id, err := getID(msg.Topic())
if err != nil {
logger.Log(0, "error getting node.ID sent on ping topic ")
return
}
id, err := getID(msg.Topic())
if err != nil {
logger.Log(0, "error getting node.ID sent on ping topic ")
return
}
node, err := logic.GetNodeByID(id)
if err != nil {
logger.Log(3, "mq-ping error getting node: ", err.Error())
node, err := logic.GetNodeByID(id)
if err != nil {
logger.Log(3, "mq-ping error getting node: ", err.Error())
record, err := database.FetchRecord(database.NODES_TABLE_NAME, id)
if err != nil {
logger.Log(3, "error reading database ", err.Error())
return
if database.IsEmptyRecord(err) {
h := logic.GetHostByNodeID(id) // check if a host is still associated
if h != nil { // inform host that node should be removed
fakeNode := models.Node{}
fakeNode.ID, _ = uuid.Parse(id)
fakeNode.Action = models.NODE_DELETE
fakeNode.PendingDelete = true
if err := NodeUpdate(&fakeNode); err != nil {
logger.Log(0, "failed to inform host", h.Name, h.ID.String(), "to remove node", id, err.Error())
}
}
}
logger.Log(3, "record from database")
logger.Log(3, record)
return
}
decrypted, decryptErr := decryptMsg(&node, msg.Payload())
@ -67,15 +76,163 @@ func Ping(client mqtt.Client, msg mqtt.Message) {
return
}
logger.Log(3, "ping processed for node", node.ID.String())
// --TODO --set client version once feature is implemented.
//node.SetClientVersion(msg.Payload())
}()
return
}
decrypted, decryptErr := decryptMsg(&node, msg.Payload())
if decryptErr != nil {
logger.Log(0, "error decrypting when updating node ", node.ID.String(), decryptErr.Error())
return
}
var checkin models.NodeCheckin
if err := json.Unmarshal(decrypted, &checkin); err != nil {
logger.Log(1, "error unmarshaling payload ", err.Error())
return
}
host, err := logic.GetHost(node.HostID.String())
if err != nil {
logger.Log(0, "error retrieving host for node ", node.ID.String(), err.Error())
return
}
node.SetLastCheckIn()
host.Version = checkin.Version
node.Connected = checkin.Connected
host.Interfaces = checkin.Ifaces
for i := range host.Interfaces {
host.Interfaces[i].AddressString = host.Interfaces[i].Address.String()
}
if err := logic.UpdateNode(&node, &node); err != nil {
logger.Log(0, "error updating node", node.ID.String(), " on checkin", err.Error())
return
}
logger.Log(3, "ping processed for node", node.ID.String())
// --TODO --set client version once feature is implemented.
//node.SetClientVersion(msg.Payload())
}
// UpdateNode message Handler -- handles updates from client nodes
func UpdateNode(client mqtt.Client, msg mqtt.Message) {
go func() {
id, err := getID(msg.Topic())
if err != nil {
logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
return
}
currentNode, err := logic.GetNodeByID(id)
if err != nil {
logger.Log(1, "error getting node ", id, err.Error())
return
}
decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
if decryptErr != nil {
logger.Log(1, "failed to decrypt message for node ", id, decryptErr.Error())
return
}
var newNode models.Node
if err := json.Unmarshal(decrypted, &newNode); err != nil {
logger.Log(1, "error unmarshaling payload ", err.Error())
return
}
ifaceDelta := logic.IfaceDelta(&currentNode, &newNode)
if servercfg.Is_EE && ifaceDelta {
if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID, currentNode.Network); err != nil {
logger.Log(1, "failed to reset failover list during node update", currentNode.ID.String(), currentNode.Network)
}
}
newNode.SetLastCheckIn()
if err := logic.UpdateNode(&currentNode, &newNode); err != nil {
logger.Log(1, "error saving node", err.Error())
return
}
if ifaceDelta { // reduce number of unneeded updates, by only sending on iface changes
if err = PublishPeerUpdate(); err != nil {
logger.Log(0, "error updating peers when node", currentNode.ID.String(), "informed the server of an interface change", err.Error())
}
}
logger.Log(1, "updated node", id, newNode.ID.String())
}
// UpdateHost message Handler -- handles host updates from clients
func UpdateHost(client mqtt.Client, msg mqtt.Message) {
id, err := getID(msg.Topic())
if err != nil {
logger.Log(1, "error getting host.ID sent on ", msg.Topic(), err.Error())
return
}
currentHost, err := logic.GetHost(id)
if err != nil {
logger.Log(1, "error getting host ", id, err.Error())
return
}
decrypted, decryptErr := decryptMsgWithHost(currentHost, msg.Payload())
if decryptErr != nil {
logger.Log(1, "failed to decrypt message for host ", id, decryptErr.Error())
return
}
var hostUpdate models.HostUpdate
if err := json.Unmarshal(decrypted, &hostUpdate); err != nil {
logger.Log(1, "error unmarshaling payload ", err.Error())
return
}
logger.Log(3, fmt.Sprintf("recieved host update: %s\n", hostUpdate.Host.ID.String()))
var sendPeerUpdate bool
switch hostUpdate.Action {
case models.Acknowledgement:
hu := hostactions.GetAction(currentHost.ID.String())
if hu != nil {
if err = HostUpdate(hu); err != nil {
logger.Log(0, "failed to send new node to host", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
return
} else {
if err = PublishSingleHostPeerUpdate(context.Background(), currentHost, nil); err != nil {
logger.Log(0, "failed peers publish after join acknowledged", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
return
}
}
}
case models.UpdateHost:
sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
err := logic.UpsertHost(currentHost)
if err != nil {
logger.Log(0, "failed to update host: ", currentHost.ID.String(), err.Error())
return
}
case models.DeleteHost:
if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
// delete EMQX credentials for host
if err := DeleteEmqxUser(currentHost.ID.String()); err != nil {
logger.Log(0, "failed to remove host credentials from EMQX: ", currentHost.ID.String(), err.Error())
return
}
}
if err := logic.DisassociateAllNodesFromHost(currentHost.ID.String()); err != nil {
logger.Log(0, "failed to delete all nodes of host: ", currentHost.ID.String(), err.Error())
return
}
if err := logic.RemoveHostByID(currentHost.ID.String()); err != nil {
logger.Log(0, "failed to delete host: ", currentHost.ID.String(), err.Error())
return
}
sendPeerUpdate = true
}
if sendPeerUpdate {
err := PublishPeerUpdate()
if err != nil {
logger.Log(0, "failed to pulish peer update: ", err.Error())
}
}
// if servercfg.Is_EE && ifaceDelta {
// if err = logic.EnterpriseResetAllPeersFailovers(currentHost.ID.String(), currentHost.Network); err != nil {
// logger.Log(1, "failed to reset failover list during node update", currentHost.ID.String(), currentHost.Network)
// }
// }
}
// UpdateMetrics message Handler -- handles updates from client nodes for metrics
func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
if servercfg.Is_EE {
id, err := getID(msg.Topic())
if err != nil {
logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
@ -91,209 +248,75 @@ func UpdateNode(client mqtt.Client, msg mqtt.Message) {
logger.Log(1, "failed to decrypt message for node ", id, decryptErr.Error())
return
}
var newNode models.Node
if err := json.Unmarshal(decrypted, &newNode); err != nil {
var newMetrics models.Metrics
if err := json.Unmarshal(decrypted, &newMetrics); err != nil {
logger.Log(1, "error unmarshaling payload ", err.Error())
return
}
ifaceDelta := logic.IfaceDelta(&currentNode, &newNode)
if servercfg.Is_EE && ifaceDelta {
if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID, currentNode.Network); err != nil {
logger.Log(1, "failed to reset failover list during node update", currentNode.ID.String(), currentNode.Network)
}
}
newNode.SetLastCheckIn()
if err := logic.UpdateNode(&currentNode, &newNode); err != nil {
logger.Log(1, "error saving node", err.Error())
shouldUpdate := updateNodeMetrics(&currentNode, &newMetrics)
if err = logic.UpdateMetrics(id, &newMetrics); err != nil {
logger.Log(1, "faield to update node metrics", id, err.Error())
return
}
if ifaceDelta { // reduce number of unneeded updates, by only sending on iface changes
if err = PublishPeerUpdate(); err != nil {
logger.Log(0, "error updating peers when node", currentNode.ID.String(), "informed the server of an interface change", err.Error())
if servercfg.IsMetricsExporter() {
if err := pushMetricsToExporter(newMetrics); err != nil {
logger.Log(2, fmt.Sprintf("failed to push node: [%s] metrics to exporter, err: %v",
currentNode.ID, err))
}
}
logger.Log(1, "updated node", id, newNode.ID.String())
}()
}
// UpdateHost message Handler -- handles host updates from clients
func UpdateHost(client mqtt.Client, msg mqtt.Message) {
go func(msg mqtt.Message) {
id, err := getID(msg.Topic())
if err != nil {
logger.Log(1, "error getting host.ID sent on ", msg.Topic(), err.Error())
return
}
currentHost, err := logic.GetHost(id)
if err != nil {
logger.Log(1, "error getting host ", id, err.Error())
return
}
decrypted, decryptErr := decryptMsgWithHost(currentHost, msg.Payload())
if decryptErr != nil {
logger.Log(1, "failed to decrypt message for host ", id, decryptErr.Error())
return
}
var hostUpdate models.HostUpdate
if err := json.Unmarshal(decrypted, &hostUpdate); err != nil {
logger.Log(1, "error unmarshaling payload ", err.Error())
return
}
logger.Log(3, fmt.Sprintf("recieved host update: %s\n", hostUpdate.Host.ID.String()))
var sendPeerUpdate bool
switch hostUpdate.Action {
case models.Acknowledgement:
hu := hostactions.GetAction(currentHost.ID.String())
if hu != nil {
if err = HostUpdate(hu); err != nil {
logger.Log(0, "failed to send new node to host", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
return
} else {
if err = PublishSingleHostPeerUpdate(currentHost, nil); err != nil {
logger.Log(0, "failed peers publish after join acknowledged", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
return
}
}
}
case models.UpdateHost:
sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
err := logic.UpsertHost(currentHost)
if newMetrics.Connectivity != nil {
err := logic.EnterpriseFailoverFunc(&currentNode)
if err != nil {
logger.Log(0, "failed to update host: ", currentHost.ID.String(), err.Error())
return
}
case models.DeleteHost:
if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
// delete EMQX credentials for host
if err := DeleteEmqxUser(currentHost.ID.String()); err != nil {
logger.Log(0, "failed to remove host credentials from EMQX: ", currentHost.ID.String(), err.Error())
return
}
}
if err := logic.DisassociateAllNodesFromHost(currentHost.ID.String()); err != nil {
logger.Log(0, "failed to delete all nodes of host: ", currentHost.ID.String(), err.Error())
return
}
if err := logic.RemoveHostByID(currentHost.ID.String()); err != nil {
logger.Log(0, "failed to delete host: ", currentHost.ID.String(), err.Error())
return
}
sendPeerUpdate = true
}
if sendPeerUpdate {
err := PublishPeerUpdate()
if err != nil {
logger.Log(0, "failed to pulish peer update: ", err.Error())
logger.Log(0, "failed to failover for node", currentNode.ID.String(), "on network", currentNode.Network, "-", err.Error())
}
}
// if servercfg.Is_EE && ifaceDelta {
// if err = logic.EnterpriseResetAllPeersFailovers(currentHost.ID.String(), currentHost.Network); err != nil {
// logger.Log(1, "failed to reset failover list during node update", currentHost.ID.String(), currentHost.Network)
// }
// }
}(msg)
}
// UpdateMetrics message Handler -- handles updates from client nodes for metrics
func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
if servercfg.Is_EE {
go func() {
id, err := getID(msg.Topic())
if err != nil {
logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
return
}
currentNode, err := logic.GetNodeByID(id)
if err != nil {
logger.Log(1, "error getting node ", id, err.Error())
return
}
decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
if decryptErr != nil {
logger.Log(1, "failed to decrypt message for node ", id, decryptErr.Error())
return
}
var newMetrics models.Metrics
if err := json.Unmarshal(decrypted, &newMetrics); err != nil {
logger.Log(1, "error unmarshaling payload ", err.Error())
return
}
shouldUpdate := updateNodeMetrics(&currentNode, &newMetrics)
if err = logic.UpdateMetrics(id, &newMetrics); err != nil {
logger.Log(1, "faield to update node metrics", id, err.Error())
return
}
if servercfg.IsMetricsExporter() {
if err := pushMetricsToExporter(newMetrics); err != nil {
logger.Log(2, fmt.Sprintf("failed to push node: [%s] metrics to exporter, err: %v",
currentNode.ID, err))
if shouldUpdate {
logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
host, err := logic.GetHost(currentNode.HostID.String())
if err == nil {
if err = PublishSingleHostPeerUpdate(context.Background(), host, nil); err != nil {
logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
}
}
}
if newMetrics.Connectivity != nil {
err := logic.EnterpriseFailoverFunc(&currentNode)
if err != nil {
logger.Log(0, "failed to failover for node", currentNode.ID.String(), "on network", currentNode.Network, "-", err.Error())
}
}
if shouldUpdate {
logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
host, err := logic.GetHost(currentNode.HostID.String())
if err == nil {
if err = PublishSingleHostPeerUpdate(host, nil); err != nil {
logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
}
}
}
logger.Log(1, "updated node metrics", id)
}()
logger.Log(1, "updated node metrics", id)
}
}
// ClientPeerUpdate message handler -- handles updating peers after signal from client nodes
func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) {
go func() {
id, err := getID(msg.Topic())
if err != nil {
logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
return
}
currentNode, err := logic.GetNodeByID(id)
if err != nil {
logger.Log(1, "error getting node ", id, err.Error())
return
}
decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
if decryptErr != nil {
logger.Log(1, "failed to decrypt message during client peer update for node ", id, decryptErr.Error())
return
}
switch decrypted[0] {
case ncutils.ACK:
//do we still need this
case ncutils.DONE:
updateNodePeers(&currentNode)
}
logger.Log(1, "sent peer updates after signal received from", id)
}()
}
func updateNodePeers(currentNode *models.Node) {
if err := PublishPeerUpdate(); err != nil {
logger.Log(1, "error publishing peer update ", err.Error())
id, err := getID(msg.Topic())
if err != nil {
logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
return
}
currentNode, err := logic.GetNodeByID(id)
if err != nil {
logger.Log(1, "error getting node ", id, err.Error())
return
}
decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
if decryptErr != nil {
logger.Log(1, "failed to decrypt message during client peer update for node ", id, decryptErr.Error())
return
}
switch decrypted[0] {
case ncutils.ACK:
// do we still need this
case ncutils.DONE:
if err = PublishPeerUpdate(); err != nil {
logger.Log(1, "error publishing peer update for node", currentNode.ID.String(), err.Error())
return
}
}
logger.Log(1, "sent peer updates after signal received from", id)
}
func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) bool {

View file

@ -1,6 +1,7 @@
package mq
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -23,10 +24,10 @@ func PublishPeerUpdate() error {
logger.Log(1, "err getting all hosts", err.Error())
return err
}
logic.ResetPeerUpdateContext()
for _, host := range hosts {
host := host
err = PublishSingleHostPeerUpdate(&host, nil)
if err != nil {
if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil); err != nil {
logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
}
}
@ -45,9 +46,10 @@ func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
logger.Log(1, "err getting all hosts", err.Error())
return err
}
logic.ResetPeerUpdateContext()
for _, host := range hosts {
host := host
if err = PublishSingleHostPeerUpdate(&host, delNode); err != nil {
if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, delNode); err != nil {
logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
}
}
@ -55,9 +57,9 @@ func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
}
// PublishSingleHostPeerUpdate --- determines and publishes a peer update to one host
func PublishSingleHostPeerUpdate(host *models.Host, deletedNode *models.Node) error {
func PublishSingleHostPeerUpdate(ctx context.Context, host *models.Host, deletedNode *models.Node) error {
peerUpdate, err := logic.GetPeerUpdateForHost("", host, deletedNode)
peerUpdate, err := logic.GetPeerUpdateForHost(ctx, "", host, deletedNode)
if err != nil {
return err
}
@ -65,7 +67,7 @@ func PublishSingleHostPeerUpdate(host *models.Host, deletedNode *models.Node) er
return nil
}
if host.ProxyEnabled {
proxyUpdate, err := logic.GetProxyUpdateForHost(host)
proxyUpdate, err := logic.GetProxyUpdateForHost(ctx, host)
if err != nil {
return err
}
@ -422,13 +424,12 @@ func sendPeers() {
//collectServerMetrics(networks[:])
}
for _, host := range hosts {
if force {
if force {
logic.ResetPeerUpdateContext()
for _, host := range hosts {
host := host
logger.Log(2, "sending scheduled peer update (5 min)")
err = PublishSingleHostPeerUpdate(&host, nil)
if err != nil {
if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil); err != nil {
logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
}
}

View file

@ -1,7 +1,27 @@
# Netmaker v0.18.1
# Netmaker v0.18.3
## **Do not attempt upgrade from 0.17.x quite yet**
## whats new
- Enrollment Keys, give the ability for an admin to enroll clients into multiple networks, can be unlimited, time, or usage based
- EMQX broker support and better MQTT support in general
- Now you must specify BROKER_ENDPOINT
- Also specify SERVER_BROKER_ENDPOINT, if not provided server will connect to broker over BROKER_ENDPOINT
- Thsi gives ability for user to specify any broker endpoint and use any protocal on clients desired, such as, `mqtts://mybroker.com:8083`
(we will still default to wss)
## whats fixed
- Fixed default ACL behavior, should work as expected
- Peer calculations enhancement
- main routines share a context and docker stop/ctrl+c give expected results now
- Github workflow edits
- Removed Deprecated Local Network Range from client + server
## known issues
- EnrollmentKeys may not function as intended in an HA setup
- If a host does not receive a message to delete a node, it could become orphaned and un-deletable
- Network interface routes may be removed after sometime/unintended network update
- Upgrade script does not handle clients
- Caddy does not handle netmaker exporter well for EE
- Incorrect latency on metrics (EE)
- Swagger docs not up to date

View file

@ -1,6 +1,6 @@
#!/bin/bash
LATEST="v0.18.2"
LATEST="v0.18.3"
print_logo() {(
cat << "EOF"
@ -40,7 +40,7 @@ usage () {(
echo " \"branch\": - will install a specific branch using remote git and dockerhub "
echo " -t tag of build; if buildtype=version, tag=version. If builtype=branch or builtype=local, tag=branch"
echo "examples:"
echo " nm-quick.sh -e -b version -t v0.18.2"
echo " nm-quick.sh -e -b version -t v0.18.3"
echo " nm-quick.sh -e -b local -t feature_v0.17.2_newfeature"
echo " nm-quick.sh -e -b branch -t develop"
exit 1

View file

@ -187,7 +187,7 @@ collect_server_settings() {
STUN_NAME="stun.$SERVER_NAME"
echo "-----------------------------------------------------"
echo "Netmaker v0.18.2 requires a new DNS entry for $STUN_NAME."
echo "Netmaker v0.18.3 requires a new DNS entry for $STUN_NAME."
echo "Please confirm this is added to your DNS provider before continuing"
echo "(note: this is not required if using an nip.io address)"
echo "-----------------------------------------------------"
@ -245,7 +245,7 @@ set_compose() {
sed -i "s/v0.17.1/testing/g" /root/docker-compose.yml
# RELEASE_REPLACE - Use this once release is ready
#sed -i "s/v0.17.1/v0.18.2/g" /root/docker-compose.yml
#sed -i "s/v0.17.1/v0.18.3/g" /root/docker-compose.yml
yq ".services.netmaker.environment.SERVER_NAME = \"$SERVER_NAME\"" -i /root/docker-compose.yml
yq ".services.netmaker.environment += {\"BROKER_NAME\": \"$BROKER_NAME\"}" -i /root/docker-compose.yml
yq ".services.netmaker.environment += {\"STUN_NAME\": \"$STUN_NAME\"}" -i /root/docker-compose.yml
@ -416,7 +416,7 @@ join_networks() {
# create an egress if necessary
if [[ $HAS_EGRESS == "yes" ]]; then
echo "Egress is currently unimplemented. Wait for 0.18.2"
echo "Egress is currently unimplemented. Wait for 0.18.3"
fi
echo "HAS INGRESS: $HAS_INGRESS"
@ -447,7 +447,7 @@ join_networks() {
cat << "EOF"
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The Netmaker Upgrade Script: Upgrading to v0.18.2 so you don't have to!
The Netmaker Upgrade Script: Upgrading to v0.18.3 so you don't have to!
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EOF

View file

@ -43,7 +43,6 @@ func GetServerConfig() config.ServerConfig {
cfg.AllowedOrigin = GetAllowedOrigin()
cfg.RestBackend = "off"
cfg.NodeID = GetNodeID()
cfg.StunHost = GetStunAddr()
cfg.StunPort = GetStunPort()
cfg.BrokerType = GetBrokerType()
cfg.EmqxRestEndpoint = GetEmqxRestEndpoint()
@ -74,6 +73,7 @@ func GetServerConfig() config.ServerConfig {
cfg.FrontendURL = GetFrontendURL()
cfg.Telemetry = Telemetry()
cfg.Server = GetServer()
cfg.StunList = GetStunListString()
cfg.Verbosity = GetVerbosity()
cfg.IsEE = "no"
if Is_EE {
@ -99,8 +99,8 @@ func GetServerInfo() models.ServerConfig {
}
cfg.Version = GetVersion()
cfg.Is_EE = Is_EE
cfg.StunHost = GetStunAddr()
cfg.StunPort = GetStunPort()
cfg.StunList = GetStunList()
return cfg
}
@ -177,15 +177,44 @@ func GetAPIPort() string {
return apiport
}
// GetStunAddr - gets the stun host address
func GetStunAddr() string {
stunAddr := ""
if os.Getenv("STUN_DOMAIN") != "" {
stunAddr = os.Getenv("STUN_DOMAIN")
} else if config.Config.Server.StunHost != "" {
stunAddr = config.Config.Server.StunHost
// GetStunList - gets the stun servers
func GetStunList() []models.StunServer {
stunList := []models.StunServer{
models.StunServer{
Domain: "stun1.netmaker.io",
Port: 3478,
},
models.StunServer{
Domain: "stun2.netmaker.io",
Port: 3478,
},
}
return stunAddr
parsed := false
if os.Getenv("STUN_LIST") != "" {
stuns, err := parseStunList(os.Getenv("STUN_LIST"))
if err == nil {
parsed = true
stunList = stuns
}
}
if !parsed && config.Config.Server.StunList != "" {
stuns, err := parseStunList(config.Config.Server.StunList)
if err == nil {
stunList = stuns
}
}
return stunList
}
// GetStunList - gets the stun servers w/o parsing to struct
func GetStunListString() string {
stunList := "stun1.netmaker.io:3478,stun2.netmaker.io:3478"
if os.Getenv("STUN_LIST") != "" {
stunList = os.Getenv("STUN_LIST")
} else if config.Config.Server.StunList != "" {
stunList = config.Config.Server.StunList
}
return stunList
}
// GetCoreDNSAddr - gets the core dns address
@ -582,6 +611,7 @@ func GetNetmakerAccountID() string {
return netmakerAccountID
}
// GetStunPort - Get the port to run the stun server on
func GetStunPort() int {
port := 3478 //default
if os.Getenv("STUN_PORT") != "" {
@ -595,6 +625,7 @@ func GetStunPort() int {
return port
}
// IsProxyEnabled - is proxy on or off
func IsProxyEnabled() bool {
var enabled = false //default
if os.Getenv("PROXY") != "" {
@ -604,3 +635,33 @@ func IsProxyEnabled() bool {
}
return enabled
}
// parseStunList - turn string into slice of StunServers
func parseStunList(stunString string) ([]models.StunServer, error) {
var err error
stunServers := []models.StunServer{}
stuns := strings.Split(stunString, ",")
if len(stuns) == 0 {
return stunServers, errors.New("no stun servers provided")
}
for _, stun := range stuns {
stun = strings.Trim(stun, " ")
stunInfo := strings.Split(stun, ":")
if len(stunInfo) != 2 {
continue
}
port, err := strconv.Atoi(stunInfo[1])
if err != nil || port == 0 {
continue
}
stunServers = append(stunServers, models.StunServer{
Domain: stunInfo[0],
Port: port,
})
}
if len(stunServers) == 0 {
err = errors.New("no stun entries parsable")
}
return stunServers, err
}

View file

@ -704,7 +704,7 @@ info:
API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
title: Netmaker
version: 0.18.2
version: 0.18.3
paths:
/api/dns:
get: