Merge pull request #2901 from gravitl/release-v0.24.0

v0.24.0
This commit is contained in:
Abhishek K 2024-04-19 19:23:20 +05:30 committed by GitHub
commit 5693384ec3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 1559 additions and 447 deletions

View file

@ -31,6 +31,7 @@ body:
label: Version
description: What version are you running?
options:
- v0.24.0
- v0.23.0
- v0.22.0
- v0.21.2

1
.gitignore vendored
View file

@ -25,3 +25,4 @@ data/
netmaker.exe
netmaker.code-workspace
dist/
nmctl

View file

@ -16,7 +16,7 @@
<p align="center">
<a href="https://github.com/gravitl/netmaker/releases">
<img src="https://img.shields.io/badge/Version-0.23.0-informational?style=flat-square" />
<img src="https://img.shields.io/badge/Version-0.24.0-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

@ -32,7 +32,6 @@ const (
github_provider_name = "github"
oidc_provider_name = "oidc"
verify_user = "verifyuser"
auth_key = "netmaker_auth"
user_signin_length = 16
node_signin_length = 64
headless_signin_length = 32
@ -75,10 +74,10 @@ func InitializeAuthProvider() string {
if functions == nil {
return ""
}
var _, err = fetchPassValue(logic.RandomString(64))
logger.Log(0, "setting oauth secret")
var err = logic.SetAuthSecret(logic.RandomString(64))
if err != nil {
logger.Log(0, err.Error())
return ""
logger.FatalLog("failed to set auth_secret", err.Error())
}
var authInfo = servercfg.GetAuthProviderInfo()
var serverConn = servercfg.GetAPIHost()
@ -156,7 +155,7 @@ func HandleAuthLogin(w http.ResponseWriter, r *http.Request) {
// IsOauthUser - returns
func IsOauthUser(user *models.User) error {
var currentValue, err = fetchPassValue("")
var currentValue, err = FetchPassValue("")
if err != nil {
return err
}
@ -246,8 +245,9 @@ func addUser(email string) error {
slog.Error("error checking for existence of admin user during OAuth login for", "email", email, "error", err)
return err
} // generate random password to adapt to current model
var newPass, fetchErr = fetchPassValue("")
var newPass, fetchErr = FetchPassValue("")
if fetchErr != nil {
slog.Error("failed to get password", "error", err.Error())
return fetchErr
}
var newUser = models.User{
@ -255,6 +255,7 @@ func addUser(email string) error {
Password: newPass,
}
if !hasSuperAdmin { // must be first attempt, create a superadmin
logger.Log(0, "creating superadmin")
if err = logic.CreateSuperAdmin(&newUser); err != nil {
slog.Error("error creating super admin from user", "email", email, "error", err)
} else {
@ -264,7 +265,7 @@ func addUser(email string) error {
// TODO: add ability to add users with preemptive permissions
newUser.IsAdmin = false
if err = logic.CreateUser(&newUser); err != nil {
logger.Log(1, "error creating user,", email, "; user not added")
logger.Log(0, "error creating user,", email, "; user not added", "error", err.Error())
} else {
logger.Log(0, "user created from ", email)
}
@ -272,25 +273,17 @@ func addUser(email string) error {
return nil
}
func fetchPassValue(newValue string) (string, error) {
func FetchPassValue(newValue string) (string, error) {
type valueHolder struct {
Value string `json:"value" bson:"value"`
}
var b64NewValue = base64.StdEncoding.EncodeToString([]byte(newValue))
var newValueHolder = &valueHolder{
Value: b64NewValue,
}
var data, marshalErr = json.Marshal(newValueHolder)
if marshalErr != nil {
return "", marshalErr
}
var currentValue, err = logic.FetchAuthSecret(auth_key, string(data))
newValueHolder := valueHolder{}
var currentValue, err = logic.FetchAuthSecret()
if err != nil {
return "", err
}
var unmarshErr = json.Unmarshal([]byte(currentValue), newValueHolder)
var unmarshErr = json.Unmarshal([]byte(currentValue), &newValueHolder)
if unmarshErr != nil {
return "", unmarshErr
}
@ -334,3 +327,23 @@ func isStateCached(state string) bool {
_, err := netcache.Get(state)
return err == nil || strings.Contains(err.Error(), "expired")
}
// isEmailAllowed - checks if email is allowed to signup
func isEmailAllowed(email string) bool {
allowedDomains := servercfg.GetAllowedEmailDomains()
domains := strings.Split(allowedDomains, ",")
if len(domains) == 1 && domains[0] == "*" {
return true
}
emailParts := strings.Split(email, "@")
if len(emailParts) < 2 {
return false
}
baseDomainOfEmail := emailParts[1]
for _, domain := range domains {
if domain == baseDomainOfEmail {
return true
}
}
return false
}

View file

@ -7,6 +7,7 @@ import (
"io"
"net/http"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
@ -60,9 +61,29 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
handleOauthNotConfigured(w)
return
}
if !isEmailAllowed(content.UserPrincipalName) {
handleOauthUserNotAllowedToSignUp(w)
return
}
// check if user approval is already pending
if logic.IsPendingUser(content.UserPrincipalName) {
handleOauthUserSignUpApprovalPending(w)
return
}
_, err = logic.GetUser(content.UserPrincipalName)
if err != nil { // user must not exists, so try to make one
if err = addUser(content.UserPrincipalName); err != nil {
if err != nil {
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
err = logic.InsertPendingUser(&models.User{
UserName: content.UserPrincipalName,
})
if err != nil {
handleSomethingWentWrong(w)
return
}
handleFirstTimeOauthUserSignUp(w)
return
} else {
handleSomethingWentWrong(w)
return
}
}
@ -75,7 +96,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
handleOauthUserNotAllowed(w)
return
}
var newPass, fetchErr = fetchPassValue("")
var newPass, fetchErr = FetchPassValue("")
if fetchErr != nil {
return
}

View file

@ -12,17 +12,44 @@ const oauthNotConfigured = `<!DOCTYPE html><html>
const userNotAllowed = `<!DOCTYPE html><html>
<body>
<h3>Only Admins are allowed to access Dashboard.</h3>
<h3>Only administrators can access the Dashboard. Please contact your administrator to elevate your account.</h3>
<p>Non-Admins can access the netmaker networks using <a href="https://docs.netmaker.io/pro/rac.html" target="_blank" rel="noopener">RemoteAccessClient.</a></p>
</body>
</html>
`
const userFirstTimeSignUp = `<!DOCTYPE html><html>
<body>
<h3>Thank you for signing up. Please contact your administrator for access.</h3>
</body>
</html>
`
const userSignUpApprovalPending = `<!DOCTYPE html><html>
<body>
<h3>Your account is yet to be approved. Please contact your administrator for access.</h3>
</body>
</html>
`
const userNotFound = `<!DOCTYPE html><html>
<body>
<h3>User Not Found.</h3>
</body>
</html>`
const somethingwentwrong = `<!DOCTYPE html><html>
<body>
<h3>Something went wrong. Contact Admin.</h3>
</body>
</html>`
const notallowedtosignup = `<!DOCTYPE html><html>
<body>
<h3>Your email is not allowed. Please contact your administrator.</h3>
</body>
</html>`
func handleOauthUserNotFound(response http.ResponseWriter) {
response.Header().Set("Content-Type", "text/html; charset=utf-8")
response.WriteHeader(http.StatusNotFound)
@ -34,6 +61,23 @@ func handleOauthUserNotAllowed(response http.ResponseWriter) {
response.WriteHeader(http.StatusForbidden)
response.Write([]byte(userNotAllowed))
}
func handleFirstTimeOauthUserSignUp(response http.ResponseWriter) {
response.Header().Set("Content-Type", "text/html; charset=utf-8")
response.WriteHeader(http.StatusForbidden)
response.Write([]byte(userFirstTimeSignUp))
}
func handleOauthUserSignUpApprovalPending(response http.ResponseWriter) {
response.Header().Set("Content-Type", "text/html; charset=utf-8")
response.WriteHeader(http.StatusForbidden)
response.Write([]byte(userSignUpApprovalPending))
}
func handleOauthUserNotAllowedToSignUp(response http.ResponseWriter) {
response.Header().Set("Content-Type", "text/html; charset=utf-8")
response.WriteHeader(http.StatusForbidden)
response.Write([]byte(notallowedtosignup))
}
// handleOauthNotConfigured - returns an appropriate html page when oauth is not configured on netmaker server but an oauth login was attempted
func handleOauthNotConfigured(response http.ResponseWriter) {
@ -41,3 +85,9 @@ func handleOauthNotConfigured(response http.ResponseWriter) {
response.WriteHeader(http.StatusInternalServerError)
response.Write([]byte(oauthNotConfigured))
}
func handleSomethingWentWrong(response http.ResponseWriter) {
response.Header().Set("Content-Type", "text/html; charset=utf-8")
response.WriteHeader(http.StatusInternalServerError)
response.Write([]byte(somethingwentwrong))
}

View file

@ -7,6 +7,7 @@ import (
"io"
"net/http"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
@ -60,9 +61,29 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
handleOauthNotConfigured(w)
return
}
if !isEmailAllowed(content.Login) {
handleOauthUserNotAllowedToSignUp(w)
return
}
// check if user approval is already pending
if logic.IsPendingUser(content.Login) {
handleOauthUserSignUpApprovalPending(w)
return
}
_, err = logic.GetUser(content.Login)
if err != nil { // user must not exist, so try to make one
if err = addUser(content.Login); err != nil {
if err != nil {
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
err = logic.InsertPendingUser(&models.User{
UserName: content.Login,
})
if err != nil {
handleSomethingWentWrong(w)
return
}
handleFirstTimeOauthUserSignUp(w)
return
} else {
handleSomethingWentWrong(w)
return
}
}
@ -75,7 +96,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
handleOauthUserNotAllowed(w)
return
}
var newPass, fetchErr = fetchPassValue("")
var newPass, fetchErr = FetchPassValue("")
if fetchErr != nil {
return
}

View file

@ -8,6 +8,7 @@ import (
"net/http"
"time"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
@ -62,14 +63,35 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
handleOauthNotConfigured(w)
return
}
if !isEmailAllowed(content.Email) {
handleOauthUserNotAllowedToSignUp(w)
return
}
// check if user approval is already pending
if logic.IsPendingUser(content.Email) {
handleOauthUserSignUpApprovalPending(w)
return
}
_, err = logic.GetUser(content.Email)
if err != nil { // user must not exists, so try to make one
if err = addUser(content.Email); err != nil {
if err != nil {
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
err = logic.InsertPendingUser(&models.User{
UserName: content.Email,
})
if err != nil {
handleSomethingWentWrong(w)
return
}
handleFirstTimeOauthUserSignUp(w)
return
} else {
handleSomethingWentWrong(w)
return
}
}
user, err := logic.GetUser(content.Email)
if err != nil {
logger.Log(0, "error fetching user: ", err.Error())
handleOauthUserNotFound(w)
return
}
@ -77,7 +99,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
handleOauthUserNotAllowed(w)
return
}
var newPass, fetchErr = fetchPassValue("")
var newPass, fetchErr = FetchPassValue("")
if fetchErr != nil {
return
}

View file

@ -50,19 +50,24 @@ func HandleHeadlessSSOCallback(w http.ResponseWriter, r *http.Request) {
return
}
_, err = logic.GetUser(userClaims.getUserName())
if err != nil { // user must not exists, so try to make one
if err = addUser(userClaims.getUserName()); err != nil {
logger.Log(1, "could not create new user: ", userClaims.getUserName())
return
}
// check if user approval is already pending
if logic.IsPendingUser(userClaims.getUserName()) {
handleOauthUserNotAllowed(w)
return
}
newPass, fetchErr := fetchPassValue("")
user, err := logic.GetUser(userClaims.getUserName())
if err != nil {
response := returnErrTemplate("", "user not found", state, reqKeyIf)
w.WriteHeader(http.StatusForbidden)
w.Write(response)
return
}
newPass, fetchErr := FetchPassValue("")
if fetchErr != nil {
return
}
jwt, jwtErr := logic.VerifyAuthRequest(models.UserAuthParams{
UserName: userClaims.getUserName(),
UserName: user.UserName,
Password: newPass,
})
if jwtErr != nil {

View file

@ -248,12 +248,20 @@ func CheckNetRegAndHostUpdate(networks []string, h *models.Host, relayNodeId uui
// check if relay node exists and acting as relay
relaynode, err := logic.GetNodeByID(relayNodeId.String())
if err == nil && relaynode.IsRelay {
slog.Info(fmt.Sprintf("adding relayed node %s to relay %s on network %s", newNode.ID.String(), relayNodeId.String(), network))
newNode.IsRelayed = true
newNode.RelayedBy = relayNodeId.String()
slog.Info(fmt.Sprintf("adding relayed node %s to relay %s on network %s", newNode.ID.String(), relayNodeId.String(), network))
updatedRelayNode := relaynode
updatedRelayNode.RelayedNodes = append(updatedRelayNode.RelayedNodes, newNode.ID.String())
logic.UpdateRelayed(&relaynode, &updatedRelayNode)
if err := logic.UpsertNode(&updatedRelayNode); err != nil {
slog.Error("failed to update node", "nodeid", relayNodeId.String())
}
if err := logic.UpsertNode(newNode); err != nil {
slog.Error("failed to update node", "nodeid", relayNodeId.String())
}
} else {
slog.Error("failed to relay node. maybe specified relay node is actually not a relay?", "err", err)
}
}
logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)

View file

@ -7,6 +7,7 @@ import (
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
@ -73,9 +74,29 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
handleOauthNotConfigured(w)
return
}
if !isEmailAllowed(content.Email) {
handleOauthUserNotAllowedToSignUp(w)
return
}
// check if user approval is already pending
if logic.IsPendingUser(content.Email) {
handleOauthUserSignUpApprovalPending(w)
return
}
_, err = logic.GetUser(content.Email)
if err != nil { // user must not exists, so try to make one
if err = addUser(content.Email); err != nil {
if err != nil {
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
err = logic.InsertPendingUser(&models.User{
UserName: content.Email,
})
if err != nil {
handleSomethingWentWrong(w)
return
}
handleFirstTimeOauthUserSignUp(w)
return
} else {
handleSomethingWentWrong(w)
return
}
}
@ -88,7 +109,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
handleOauthUserNotAllowed(w)
return
}
var newPass, fetchErr = fetchPassValue("")
var newPass, fetchErr = FetchPassValue("")
if fetchErr != nil {
return
}

View file

@ -0,0 +1,20 @@
package failover
import (
"github.com/gravitl/netmaker/cli/functions"
"github.com/spf13/cobra"
)
var disableFailoverCmd = &cobra.Command{
Use: "disable [NODE ID]",
Args: cobra.ExactArgs(1),
Short: "Disable failover for a given Node",
Long: `Disable failover for a given Node`,
Run: func(cmd *cobra.Command, args []string) {
functions.PrettyPrint(functions.DisableNodeFailover(args[0]))
},
}
func init() {
rootCmd.AddCommand(disableFailoverCmd)
}

View file

@ -0,0 +1,20 @@
package failover
import (
"github.com/gravitl/netmaker/cli/functions"
"github.com/spf13/cobra"
)
var enableFailoverCmd = &cobra.Command{
Use: "enable [NODE ID]",
Args: cobra.ExactArgs(1),
Short: "Enable failover for a given Node",
Long: `Enable failover for a given Node`,
Run: func(cmd *cobra.Command, args []string) {
functions.PrettyPrint(functions.EnableNodeFailover(args[0]))
},
}
func init() {
rootCmd.AddCommand(enableFailoverCmd)
}

28
cli/cmd/failover/root.go Normal file
View file

@ -0,0 +1,28 @@
package failover
import (
"os"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "failover",
Short: "Enable/Disable failover for a node associated with a network",
Long: `Enable/Disable failover for a node associated with a network`,
}
// 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

@ -14,6 +14,7 @@ import (
var (
apiHostFilePath string
endpoint string
endpoint6 string
name string
listenPort int
mtu int
@ -40,6 +41,7 @@ var hostUpdateCmd = &cobra.Command{
} else {
apiHost.ID = args[0]
apiHost.EndpointIP = endpoint
apiHost.EndpointIPv6 = endpoint6
apiHost.Name = name
apiHost.ListenPort = listenPort
apiHost.MTU = mtu
@ -54,6 +56,7 @@ var hostUpdateCmd = &cobra.Command{
func init() {
hostUpdateCmd.Flags().StringVar(&apiHostFilePath, "file", "", "Path to host_definition.json")
hostUpdateCmd.Flags().StringVar(&endpoint, "endpoint", "", "Endpoint of the Host")
hostUpdateCmd.Flags().StringVar(&endpoint6, "endpoint6", "", "IPv6 Endpoint of the Host")
hostUpdateCmd.Flags().StringVar(&name, "name", "", "Host name")
hostUpdateCmd.Flags().IntVar(&listenPort, "listen_port", 0, "Listen port of the host")
hostUpdateCmd.Flags().IntVar(&mtu, "mtu", 0, "Host MTU size")

View file

@ -32,6 +32,9 @@ var networkCreateCmd = &cobra.Command{
network.AddressRange6 = address6
network.IsIPv6 = "yes"
}
if address == "" {
network.IsIPv4 = "no"
}
if udpHolePunch {
network.DefaultUDPHolePunch = "yes"
}

View file

@ -6,10 +6,11 @@ import (
)
var nodeCreateIngressCmd = &cobra.Command{
Use: "create_ingress [NETWORK NAME] [NODE ID]",
Args: cobra.ExactArgs(2),
Short: "Turn a Node into a Ingress",
Long: `Turn a Node into a Ingress`,
Use: "create_remote_access_gateway [NETWORK NAME] [NODE ID]",
Args: cobra.ExactArgs(2),
Short: "Turn a Node into a Remote Access Gateway (Ingress)",
Long: `Turn a Node into a Remote Access Gateway (Ingress) for a Network.`,
Aliases: []string{"create_rag"},
Run: func(cmd *cobra.Command, args []string) {
functions.PrettyPrint(functions.CreateIngress(args[0], args[1], failover))
},

View file

@ -6,10 +6,11 @@ import (
)
var nodeDeleteIngressCmd = &cobra.Command{
Use: "delete_ingress [NETWORK NAME] [NODE ID]",
Args: cobra.ExactArgs(2),
Short: "Delete Ingress role from a Node",
Long: `Delete Ingress role from a Node`,
Use: "delete_remote_access_gateway [NETWORK NAME] [NODE ID]",
Args: cobra.ExactArgs(2),
Short: "Delete Remote Access Gateway role from a Node",
Long: `Delete Remote Access Gateway role from a Node`,
Aliases: []string{"delete_rag"},
Run: func(cmd *cobra.Command, args []string) {
functions.PrettyPrint(functions.DeleteIngress(args[0], args[1]))
},

View file

@ -29,7 +29,7 @@ var nodeListCmd = &cobra.Command{
functions.PrettyPrint(data)
default:
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "Addresses", "Network", "Egress", "Ingress", "Relay"})
table.SetHeader([]string{"ID", "Addresses", "Network", "Egress", "Remote Access Gateway", "Relay"})
for _, d := range data {
addresses := ""
if d.Address != "" {

View file

@ -9,6 +9,7 @@ import (
"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/failover"
"github.com/gravitl/netmaker/cli/cmd/host"
"github.com/gravitl/netmaker/cli/cmd/metrics"
"github.com/gravitl/netmaker/cli/cmd/network"
@ -53,4 +54,5 @@ func init() {
rootCmd.AddCommand(metrics.GetRoot())
rootCmd.AddCommand(host.GetRoot())
rootCmd.AddCommand(enrollment_key.GetRoot())
rootCmd.AddCommand(failover.GetRoot())
}

View file

@ -14,5 +14,5 @@ func GetACL(networkName string) *acls.ACLContainer {
// UpdateACL - update an ACL
func UpdateACL(networkName string, payload *acls.ACLContainer) *acls.ACLContainer {
return request[acls.ACLContainer](http.MethodPut, fmt.Sprintf("/api/networks/%s/acls", networkName), payload)
return request[acls.ACLContainer](http.MethodPut, fmt.Sprintf("/api/networks/%s/acls/v2", networkName), payload)
}

18
cli/functions/failover.go Normal file
View file

@ -0,0 +1,18 @@
package functions
import (
"fmt"
"net/http"
"github.com/gravitl/netmaker/models"
)
// EnableNodeFailover - Enable failover for a given Node
func EnableNodeFailover(nodeID string) *models.SuccessResponse {
return request[models.SuccessResponse](http.MethodPost, fmt.Sprintf("/api/v1/node/%s/failover", nodeID), nil)
}
// DisableNodeFailover - Disable failover for a given Node
func DisableNodeFailover(nodeID string) *models.SuccessResponse {
return request[models.SuccessResponse](http.MethodDelete, fmt.Sprintf("/api/v1/node/%s/failover", nodeID), nil)
}

View file

@ -3,7 +3,7 @@ version: "3.4"
services:
netclient:
container_name: netclient
image: 'gravitl/netclient:v0.23.0'
image: 'gravitl/netclient:v0.24.0'
hostname: netmaker-1
network_mode: host
restart: on-failure

View file

@ -92,6 +92,8 @@ type ServerConfig struct {
JwtValidityDuration time.Duration `yaml:"jwt_validity_duration"`
RacAutoDisable bool `yaml:"rac_auto_disable"`
CacheEnabled string `yaml:"caching_enabled"`
EndpointDetection bool `json:"endpoint_detection"`
AllowedEmailDomains string `yaml:"allowed_email_domains"`
}
// SQLConfig - Generic SQL Config

View file

@ -10,7 +10,7 @@
//
// Schemes: https
// BasePath: /
// Version: 0.23.0
// Version: 0.24.0
// Host: api.demo.netmaker.io
//
// Consumes:
@ -49,6 +49,12 @@ type hasAdmin struct {
Admin bool
}
// swagger:response apiHostSliceResponse
type apiHostSliceResponse struct {
// in: body
Host []models.ApiHost
}
// swagger:response apiHostResponse
type apiHostResponse struct {
// in: body
@ -251,7 +257,7 @@ type networkBodyResponse struct {
Network models.Network `json:"network"`
}
// swagger:parameters updateNetworkACL getNetworkACL
// swagger:parameters updateNetworkACL
type aclContainerBodyParam struct {
// ACL Container
// in: body
@ -269,7 +275,7 @@ type aclContainerResponse struct {
type nodeSliceResponse struct {
// Nodes
// in: body
Nodes []models.LegacyNode `json:"nodes"`
Nodes []models.ApiNode `json:"nodes"`
}
// swagger:response nodeResponse
@ -348,8 +354,26 @@ type HostFromNetworkParams struct {
Network string `json:"network"`
}
// swagger:parameters createEnrollmentKey
type createEnrollmentKeyParams struct {
// APIEnrollmentKey
// in: body
Body models.APIEnrollmentKey `json:"body"`
}
// swagger:parameters updateEnrollmentKey
type updateEnrollmentKeyParams struct {
// KeyID
// in: path
KeyID string `json:"keyid"`
// APIEnrollmentKey
// in: body
Body models.APIEnrollmentKey `json:"body"`
}
// swagger:parameters deleteEnrollmentKey
type DeleteEnrollmentKeyParam struct {
type deleteEnrollmentKeyParam struct {
// in: path
KeyID string `json:"keyid"`
}
@ -435,6 +459,7 @@ func useUnused() bool {
_ = userAuthBodyParam{}
_ = usernamePathParam{}
_ = hasAdmin{}
_ = apiHostSliceResponse{}
_ = apiHostResponse{}
_ = fileResponse{}
_ = extClientConfParams{}
@ -443,5 +468,8 @@ func useUnused() bool {
_ = signal{}
_ = filenameToGet{}
_ = dnsNetworkPathParam{}
_ = createEnrollmentKeyParams{}
_ = updateEnrollmentKeyParams{}
_ = deleteEnrollmentKeyParam{}
return false
}

View file

@ -180,7 +180,7 @@ func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(newEnrollmentKey)
}
// swagger:route PUT /api/v1/enrollment-keys/:id enrollmentKeys updateEnrollmentKey
// swagger:route PUT /api/v1/enrollment-keys/{keyid} enrollmentKeys updateEnrollmentKey
//
// Updates an EnrollmentKey for hosts to use on Netmaker server. Updates only the relay to use.
//
@ -308,7 +308,7 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
if !hostExists {
newHost.PersistentKeepalive = models.DefaultPersistentKeepAlive
// register host
logic.CheckHostPorts(&newHost)
//logic.CheckHostPorts(&newHost)
// create EMQX credentials and ACLs for host
if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
if err := mq.GetEmqxHandler().CreateEmqxUser(newHost.ID.String(), newHost.HostPass); err != nil {

View file

@ -394,9 +394,9 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
}
for _, extclient := range extclients {
if extclient.RemoteAccessClientID != "" &&
extclient.RemoteAccessClientID == customExtClient.RemoteAccessClientID && nodeid == extclient.IngressGatewayID {
extclient.RemoteAccessClientID == customExtClient.RemoteAccessClientID && extclient.OwnerID == caller.UserName && nodeid == extclient.IngressGatewayID {
// extclient on the gw already exists for the remote access client
err = errors.New("remote client config already exists on the gateway. it may have been created by another user with this same remote client machine")
err = errors.New("remote client config already exists on the gateway")
slog.Error("failed to create extclient", "user", userName, "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
@ -436,15 +436,14 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
return
}
if err := logic.SetClientDefaultACLs(&extclient); err != nil {
slog.Error("failed to set default acls for extclient", "user", r.Header.Get("user"), "network", node.Network, "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
slog.Info("created extclient", "user", r.Header.Get("user"), "network", node.Network, "clientid", extclient.ClientID)
w.WriteHeader(http.StatusOK)
go func() {
if err := logic.SetClientDefaultACLs(&extclient); err != nil {
slog.Error("failed to set default acls for extclient", "user", r.Header.Get("user"), "network", node.Network, "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
if err := mq.PublishPeerUpdate(false); err != nil {
logger.Log(1, "error setting ext peers on "+nodeid+": "+err.Error())
}

View file

@ -32,6 +32,7 @@ func hostHandlers(r *mux.Router) {
r.HandleFunc("/api/v1/host", Authorize(true, false, "host", http.HandlerFunc(pull))).Methods(http.MethodGet)
r.HandleFunc("/api/v1/host/{hostid}/signalpeer", Authorize(true, false, "host", http.HandlerFunc(signalPeer))).Methods(http.MethodPost)
r.HandleFunc("/api/v1/fallback/host/{hostid}", Authorize(true, false, "host", http.HandlerFunc(hostUpdateFallback))).Methods(http.MethodPut)
r.HandleFunc("/api/emqx/hosts", logic.SecurityCheck(true, http.HandlerFunc(delEmqxHosts))).Methods(http.MethodDelete)
r.HandleFunc("/api/v1/auth-register/host", socketHandler)
}
@ -61,7 +62,7 @@ func upgradeHost(w http.ResponseWriter, r *http.Request) {
// oauth
//
// Responses:
// 200: apiHostResponse
// 200: apiHostSliceResponse
func getHosts(w http.ResponseWriter, r *http.Request) {
currentHosts, err := logic.GetAllHosts()
if err != nil {
@ -133,17 +134,18 @@ func pull(w http.ResponseWriter, r *http.Request) {
serverConf.TrafficKey = key
response := models.HostPull{
Host: *host,
Nodes: logic.GetHostNodes(host),
ServerConfig: serverConf,
Peers: hPU.Peers,
PeerIDs: hPU.PeerIDs,
HostNetworkInfo: hPU.HostNetworkInfo,
EgressRoutes: hPU.EgressRoutes,
FwUpdate: hPU.FwUpdate,
ChangeDefaultGw: hPU.ChangeDefaultGw,
DefaultGwIp: hPU.DefaultGwIp,
IsInternetGw: hPU.IsInternetGw,
Host: *host,
Nodes: logic.GetHostNodes(host),
ServerConfig: serverConf,
Peers: hPU.Peers,
PeerIDs: hPU.PeerIDs,
HostNetworkInfo: hPU.HostNetworkInfo,
EgressRoutes: hPU.EgressRoutes,
FwUpdate: hPU.FwUpdate,
ChangeDefaultGw: hPU.ChangeDefaultGw,
DefaultGwIp: hPU.DefaultGwIp,
IsInternetGw: hPU.IsInternetGw,
EndpointDetection: servercfg.IsEndpointDetectionEnabled(),
}
logger.Log(1, hostID, "completed a pull")
@ -552,26 +554,27 @@ func authenticateHost(response http.ResponseWriter, request *http.Request) {
logic.ReturnErrorResponse(response, request, errorResponse)
return
}
// Create EMQX creds and ACLs if not found
if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
if err := mq.GetEmqxHandler().CreateEmqxUser(host.ID.String(), authRequest.Password); err != nil {
slog.Error("failed to create host credentials for EMQX: ", err.Error())
} else {
if err := mq.GetEmqxHandler().CreateHostACL(host.ID.String(), servercfg.GetServerInfo().Server); err != nil {
slog.Error("failed to add host ACL rules to EMQX: ", err.Error())
}
for _, nodeID := range host.Nodes {
if node, err := logic.GetNodeByID(nodeID); err == nil {
if err = mq.GetEmqxHandler().AppendNodeUpdateACL(host.ID.String(), node.Network, node.ID.String(), servercfg.GetServer()); err != nil {
slog.Error("failed to add ACLs for EMQX node", "error", err)
go func() {
// Create EMQX creds and ACLs if not found
if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
if err := mq.GetEmqxHandler().CreateEmqxUser(host.ID.String(), authRequest.Password); err != nil {
slog.Error("failed to create host credentials for EMQX: ", err.Error())
} else {
if err := mq.GetEmqxHandler().CreateHostACL(host.ID.String(), servercfg.GetServerInfo().Server); err != nil {
slog.Error("failed to add host ACL rules to EMQX: ", err.Error())
}
for _, nodeID := range host.Nodes {
if node, err := logic.GetNodeByID(nodeID); err == nil {
if err = mq.GetEmqxHandler().AppendNodeUpdateACL(host.ID.String(), node.Network, node.ID.String(), servercfg.GetServer()); err != nil {
slog.Error("failed to add ACLs for EMQX node", "error", err)
}
} else {
slog.Error("failed to get node", "nodeid", nodeID, "error", err)
}
} else {
slog.Error("failed to get node", "nodeid", nodeID, "error", err)
}
}
}
}
}()
response.WriteHeader(http.StatusOK)
response.Header().Set("Content-Type", "application/json")
@ -749,3 +752,34 @@ func syncHost(w http.ResponseWriter, r *http.Request) {
slog.Info("requested host pull", "user", r.Header.Get("user"), "host", host.ID)
w.WriteHeader(http.StatusOK)
}
// swagger:route DELETE /api/emqx/hosts hosts delEmqxHosts
//
// Lists all hosts.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: apiHostResponse
func delEmqxHosts(w http.ResponseWriter, r *http.Request) {
currentHosts, err := logic.GetAllHosts()
if err != nil {
logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
for _, host := range currentHosts {
// delete EMQX credentials for host
if err := mq.GetEmqxHandler().DeleteEmqxUser(host.ID.String()); err != nil {
slog.Error("failed to remove host credentials from EMQX", "id", host.ID, "error", err)
}
}
err = mq.GetEmqxHandler().DeleteEmqxUser(servercfg.GetMqUserName())
if err != nil {
slog.Error("failed to remove server credentials from EMQX", "user", servercfg.GetMqUserName(), "error", err)
}
logic.ReturnSuccessResponse(w, r, "deleted hosts data on emqx")
}

View file

@ -55,6 +55,7 @@ func getUsage(w http.ResponseWriter, _ *http.Request) {
Egresses int `json:"egresses"`
Relays int `json:"relays"`
InternetGateways int `json:"internet_gateways"`
FailOvers int `json:"fail_overs"`
}
var serverUsage usage
hosts, err := logic.GetAllHosts()
@ -90,6 +91,10 @@ func getUsage(w http.ResponseWriter, _ *http.Request) {
if err == nil {
serverUsage.InternetGateways = len(gateways)
}
failOvers, err := logic.GetAllFailOvers()
if err == nil {
serverUsage.FailOvers = len(failOvers)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(models.SuccessResponse{
Code: http.StatusOK,

View file

@ -9,6 +9,7 @@ import (
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/gravitl/netmaker/auth"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
@ -35,6 +36,11 @@ func userHandlers(r *mux.Router) {
r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods(http.MethodGet)
r.HandleFunc("/api/oauth/headless", auth.HandleHeadlessSSO)
r.HandleFunc("/api/oauth/register/{regKey}", auth.RegisterHostSSO).Methods(http.MethodGet)
r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(getPendingUsers))).Methods(http.MethodGet)
r.HandleFunc("/api/users_pending", logic.SecurityCheck(true, http.HandlerFunc(deleteAllPendingUsers))).Methods(http.MethodDelete)
r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(deletePendingUser))).Methods(http.MethodDelete)
r.HandleFunc("/api/users_pending/user/{username}", logic.SecurityCheck(true, http.HandlerFunc(approvePendingUser))).Methods(http.MethodPost)
}
// swagger:route POST /api/users/adm/authenticate authenticate authenticateUser
@ -583,3 +589,136 @@ func socketHandler(w http.ResponseWriter, r *http.Request) {
// Start handling the session
go auth.SessionHandler(conn)
}
// swagger:route GET /api/users_pending user getPendingUsers
//
// Get all pending users.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: userBodyResponse
func getPendingUsers(w http.ResponseWriter, r *http.Request) {
// set header.
w.Header().Set("Content-Type", "application/json")
users, err := logic.ListPendingUsers()
if err != nil {
logger.Log(0, "failed to fetch users: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
logic.SortUsers(users[:])
logger.Log(2, r.Header.Get("user"), "fetched pending users")
json.NewEncoder(w).Encode(users)
}
// swagger:route POST /api/users_pending/user/{username} user approvePendingUser
//
// approve pending user.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: userBodyResponse
func approvePendingUser(w http.ResponseWriter, r *http.Request) {
// set header.
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
username := params["username"]
users, err := logic.ListPendingUsers()
if err != nil {
logger.Log(0, "failed to fetch users: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
for _, user := range users {
if user.UserName == username {
var newPass, fetchErr = auth.FetchPassValue("")
if fetchErr != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(fetchErr, "internal"))
return
}
if err = logic.CreateUser(&models.User{
UserName: user.UserName,
Password: newPass,
}); err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to create user: %s", err), "internal"))
return
}
err = logic.DeletePendingUser(username)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete pending user: %s", err), "internal"))
return
}
break
}
}
logic.ReturnSuccessResponse(w, r, "approved "+username)
}
// swagger:route DELETE /api/users_pending/user/{username} user deletePendingUser
//
// delete pending user.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: userBodyResponse
func deletePendingUser(w http.ResponseWriter, r *http.Request) {
// set header.
w.Header().Set("Content-Type", "application/json")
var params = mux.Vars(r)
username := params["username"]
users, err := logic.ListPendingUsers()
if err != nil {
logger.Log(0, "failed to fetch users: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
for _, user := range users {
if user.UserName == username {
err = logic.DeletePendingUser(username)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete pending user: %s", err), "internal"))
return
}
break
}
}
logic.ReturnSuccessResponse(w, r, "deleted pending "+username)
}
// swagger:route DELETE /api/users_pending/{username}/pending user deleteAllPendingUsers
//
// delete all pending users.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: userBodyResponse
func deleteAllPendingUsers(w http.ResponseWriter, r *http.Request) {
// set header.
w.Header().Set("Content-Type", "application/json")
err := database.DeleteAllRecords(database.PENDING_USERS_TABLE_NAME)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to delete all pending users "+err.Error()), "internal"))
return
}
logic.ReturnSuccessResponse(w, r, "cleared all pending users")
}

View file

@ -61,7 +61,8 @@ const (
ENROLLMENT_KEYS_TABLE_NAME = "enrollmentkeys"
// HOST_ACTIONS_TABLE_NAME - table name for enrollmentkeys
HOST_ACTIONS_TABLE_NAME = "hostactions"
// PENDING_USERS_TABLE_NAME - table name for pending users
PENDING_USERS_TABLE_NAME = "pending_users"
// == ERROR CONSTS ==
// NO_RECORD - no singular result found
NO_RECORD = "no result found"
@ -144,6 +145,7 @@ func createTables() {
CreateTable(HOSTS_TABLE_NAME)
CreateTable(ENROLLMENT_KEYS_TABLE_NAME)
CreateTable(HOST_ACTIONS_TABLE_NAME)
CreateTable(PENDING_USERS_TABLE_NAME)
}
func CreateTable(tableName string) error {

14
go.mod
View file

@ -4,7 +4,7 @@ go 1.19
require (
github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/go-playground/validator/v10 v10.18.0
github.com/go-playground/validator/v10 v10.19.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
@ -13,12 +13,12 @@ require (
github.com/mattn/go-sqlite3 v1.14.22
github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/txn2/txeh v1.5.5
golang.org/x/crypto v0.19.0
golang.org/x/net v0.21.0 // indirect
golang.org/x/oauth2 v0.17.0
golang.org/x/sys v0.17.0 // indirect
golang.org/x/crypto v0.22.0
golang.org/x/net v0.22.0 // indirect
golang.org/x/oauth2 v0.18.0
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
google.golang.org/protobuf v1.31.0 // indirect
@ -38,7 +38,7 @@ require (
)
require (
github.com/go-jose/go-jose/v3 v3.0.1
github.com/go-jose/go-jose/v3 v3.0.3
github.com/guumaster/tablewriter v0.0.10
github.com/matryer/is v1.4.1
github.com/olekukonko/tablewriter v0.0.5

44
go.sum
View file

@ -20,24 +20,24 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
@ -85,9 +85,9 @@ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyh
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/txn2/txeh v1.5.5 h1:UN4e/lCK5HGw/gGAi2GCVrNKg0GTCUWs7gs5riaZlz4=
github.com/txn2/txeh v1.5.5/go.mod h1:qYzGG9kCzeVEI12geK4IlanHWY8X4uy/I3NcW7mk8g4=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@ -95,44 +95,54 @@ github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEAB
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb h1:9aqVcYEDHmSNb0uOWukxV5lHV09WqiSiCuhEgWNETLY=

View file

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

View file

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

View file

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

View file

@ -64,9 +64,9 @@ func (acl ACL) Save(containerID ContainerID, ID AclID) (ACL, error) {
// ACL.IsAllowed - sees if ID is allowed in referring ACL
func (acl ACL) IsAllowed(ID AclID) (allowed bool) {
AclMutex.RLock()
AclMutex.Lock()
allowed = acl[ID] == Allowed
AclMutex.RUnlock()
AclMutex.Unlock()
return
}
@ -88,6 +88,8 @@ func (aclContainer ACLContainer) RemoveACL(ID AclID) ACLContainer {
// ACLContainer.ChangeAccess - changes the relationship between two nodes in memory
func (networkACL ACLContainer) ChangeAccess(ID1, ID2 AclID, value byte) {
AclMutex.Lock()
defer AclMutex.Unlock()
if _, ok := networkACL[ID1]; !ok {
slog.Error("ACL missing for ", "id", ID1)
return

View file

@ -3,21 +3,26 @@ package nodeacls
import (
"encoding/json"
"fmt"
"sync"
"github.com/gravitl/netmaker/logic/acls"
)
var NodesAllowedACLMutex = &sync.Mutex{}
// AreNodesAllowed - checks if nodes are allowed to communicate in their network ACL
func AreNodesAllowed(networkID NetworkID, node1, node2 NodeID) bool {
NodesAllowedACLMutex.Lock()
defer NodesAllowedACLMutex.Unlock()
var currentNetworkACL, err = FetchAllACLs(networkID)
if err != nil {
return false
}
var allowed bool
acls.AclMutex.RLock()
acls.AclMutex.Lock()
currNetworkACLNode1 := currentNetworkACL[acls.AclID(node1)]
currNetworkACLNode2 := currentNetworkACL[acls.AclID(node2)]
acls.AclMutex.RUnlock()
acls.AclMutex.Unlock()
allowed = currNetworkACLNode1.IsAllowed(acls.AclID(node2)) && currNetworkACLNode2.IsAllowed(acls.AclID(node1))
return allowed
}

View file

@ -1,6 +1,7 @@
package logic
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@ -15,6 +16,10 @@ import (
"github.com/gravitl/netmaker/models"
)
const (
auth_key = "netmaker_auth"
)
// HasSuperAdmin - checks if server has an superadmin/owner
func HasSuperAdmin() (bool, error) {
@ -96,12 +101,14 @@ func CreateUser(user *models.User) error {
}
var err = ValidateUser(user)
if err != nil {
logger.Log(0, "failed to validate user", err.Error())
return err
}
// encrypt that password so we never see it again
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5)
if err != nil {
logger.Log(0, "error encrypting pass", err.Error())
return err
}
// set password to encrypted password
@ -109,6 +116,7 @@ func CreateUser(user *models.User) error {
tokenString, _ := CreateUserJWT(user.UserName, user.IsSuperAdmin, user.IsAdmin)
if tokenString == "" {
logger.Log(0, "failed to generate token", err.Error())
return err
}
@ -117,10 +125,12 @@ func CreateUser(user *models.User) error {
// connect db
data, err := json.Marshal(user)
if err != nil {
logger.Log(0, "failed to marshal", err.Error())
return err
}
err = database.Insert(user.UserName, string(data), database.USERS_TABLE_NAME)
if err != nil {
logger.Log(0, "failed to insert user", err.Error())
return err
}
@ -279,16 +289,32 @@ func DeleteUser(user string) (bool, error) {
return true, nil
}
// FetchAuthSecret - manages secrets for oauth
func FetchAuthSecret(key string, secret string) (string, error) {
var record, err = database.FetchRecord(database.GENERATED_TABLE_NAME, key)
if err != nil {
if err = database.Insert(key, secret, database.GENERATED_TABLE_NAME); err != nil {
return "", err
} else {
return secret, nil
func SetAuthSecret(secret string) error {
type valueHolder struct {
Value string `json:"value" bson:"value"`
}
record, err := FetchAuthSecret()
if err == nil {
v := valueHolder{}
json.Unmarshal([]byte(record), &v)
if v.Value != "" {
return nil
}
}
var b64NewValue = base64.StdEncoding.EncodeToString([]byte(secret))
newValueHolder := valueHolder{
Value: b64NewValue,
}
d, _ := json.Marshal(newValueHolder)
return database.Insert(auth_key, string(d), database.GENERATED_TABLE_NAME)
}
// FetchAuthSecret - manages secrets for oauth
func FetchAuthSecret() (string, error) {
var record, err = database.FetchRecord(database.GENERATED_TABLE_NAME, auth_key)
if err != nil {
return "", err
}
return record, nil
}

View file

@ -4,8 +4,8 @@ import (
"encoding/json"
"net/http"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/models"
"golang.org/x/exp/slog"
)
// FormatError - takes ErrorResponse and uses correct code
@ -62,7 +62,7 @@ func ReturnErrorResponse(response http.ResponseWriter, request *http.Request, er
if err != nil {
panic(err)
}
logger.Log(1, "processed request error:", errorMessage.Message)
slog.Debug("processed request error", "err", errorMessage.Message)
response.Header().Set("Content-Type", "application/json")
response.WriteHeader(errorMessage.Code)
response.Write(jsonResponse)

View file

@ -164,6 +164,11 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
node.IngressGatewayRange = network.AddressRange
node.IngressGatewayRange6 = network.AddressRange6
node.IngressDNS = ingress.ExtclientDNS
if servercfg.IsPro {
if _, exists := FailOverExists(node.Network); exists {
ResetFailedOverPeer(&node)
}
}
node.SetLastModified()
if node.Metadata == "" {
node.Metadata = "This host can be used for remote access"

View file

@ -217,6 +217,7 @@ func UpdateHost(newHost, currentHost *models.Host) {
newHost.Nodes = currentHost.Nodes
newHost.PublicKey = currentHost.PublicKey
newHost.TrafficKeyPublic = currentHost.TrafficKeyPublic
newHost.EndpointIPv6 = currentHost.EndpointIPv6
// changeable fields
if len(newHost.Version) == 0 {
newHost.Version = currentHost.Version
@ -258,6 +259,10 @@ func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool)
currHost.EndpointIP = newHost.EndpointIP
sendPeerUpdate = true
}
if currHost.EndpointIPv6.String() != newHost.EndpointIPv6.String() {
currHost.EndpointIPv6 = newHost.EndpointIPv6
sendPeerUpdate = true
}
currHost.DaemonInstalled = newHost.DaemonInstalled
currHost.Debug = newHost.Debug
currHost.Verbosity = newHost.Verbosity

View file

@ -106,7 +106,6 @@ func VerifyUserToken(tokenString string) (username string, issuperadmin, isadmin
if err != nil {
return "", false, false, err
}
if user.UserName != "" {
return user.UserName, user.IsSuperAdmin, user.IsAdmin, nil
}

View file

@ -138,7 +138,7 @@ func GetParentNetwork(networkname string) (models.Network, error) {
return network, nil
}
// GetParentNetwork - get parent network
// GetNetworkSettings - get parent network
func GetNetworkSettings(networkname string) (models.Network, error) {
var network models.Network

View file

@ -625,3 +625,18 @@ func ValidateParams(nodeid, netid string) (models.Node, error) {
}
return node, nil
}
// GetAllFailOvers - gets all the nodes that are failovers
func GetAllFailOvers() ([]models.Node, error) {
nodes, err := GetAllNodes()
if err != nil {
return nil, err
}
igs := make([]models.Node, 0)
for _, node := range nodes {
if node.IsFailOver {
igs = append(igs, node)
}
}
return igs, nil
}

View file

@ -25,6 +25,10 @@ var (
ResetFailedOverPeer = func(failedOverNode *models.Node) error {
return nil
}
// FailOverExists - check if failover node existed or not
FailOverExists = func(network string) (failOverNode models.Node, exists bool) {
return failOverNode, exists
}
// GetFailOverPeerIps - gets failover peerips
GetFailOverPeerIps = func(peer, node *models.Node) []net.IPNet {
return []net.IPNet{}
@ -72,10 +76,11 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
FwUpdate: models.FwUpdate{
EgressInfo: make(map[string]models.EgressInfo),
},
PeerIDs: make(models.PeerMap, 0),
Peers: []wgtypes.PeerConfig{},
NodePeers: []wgtypes.PeerConfig{},
HostNetworkInfo: models.HostInfoMap{},
PeerIDs: make(models.PeerMap, 0),
Peers: []wgtypes.PeerConfig{},
NodePeers: []wgtypes.PeerConfig{},
HostNetworkInfo: models.HostInfoMap{},
EndpointDetection: servercfg.IsEndpointDetectionEnabled(),
}
slog.Debug("peer update for host", "hostId", host.ID.String())
@ -168,7 +173,8 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
}
if peer.IsEgressGateway {
hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, models.EgressNetworkRoutes{
NodeAddr: node.PrimaryAddressIPNet(),
NodeAddr: node.Address,
NodeAddr6: node.Address6,
EgressRanges: peer.EgressGatewayRanges,
})
}
@ -206,8 +212,21 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
uselocal = false
}
}
//if host is ipv4 only or ipv4+ipv6, set the peer endpoint to ipv4 address, if host is ipv6 only, set the peer endpoint to ipv6 address
peerEndpoint := peerHost.EndpointIP
if ipv4 := host.EndpointIP.To4(); ipv4 != nil {
peerEndpoint = peerHost.EndpointIP
} else {
//if peer host's ipv6 address is empty, it means that peer is an IPv4 only host
//IPv4 only host could not communicate with IPv6 only host
if peerHost.EndpointIPv6 != nil && peerHost.EndpointIPv6.String() != "" {
peerEndpoint = peerHost.EndpointIPv6
}
}
peerConfig.Endpoint = &net.UDPAddr{
IP: peerHost.EndpointIP,
IP: peerEndpoint,
Port: GetPeerListenPort(peerHost),
}
@ -371,6 +390,7 @@ func GetPeerListenPort(host *models.Host) int {
// GetAllowedIPs - calculates the wireguard allowedip field for a peer of a node based on the peer and node settings
func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet {
var allowedips []net.IPNet
allowedips = getNodeAllowedIPs(peer, node)
if peer.IsInternetGateway && node.InternetGwID == peer.ID.String() {
allowedips = append(allowedips, GetAllowedIpForInetNodeClient(node, peer)...)
return allowedips
@ -381,7 +401,6 @@ func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet
return allowedips
}
}
allowedips = append(allowedips, getNodeAllowedIPs(peer, node)...)
// handle ingress gateway peers
if peer.IsIngressGateway {

View file

@ -39,10 +39,7 @@ func sendTelemetry() error {
return err
}
// get telemetry data
d, err := FetchTelemetryData()
if err != nil {
slog.Error("error fetching telemetry data", "error", err)
}
d := FetchTelemetryData()
// get tenant admin email
adminEmail := os.Getenv("NM_EMAIL")
client, err := posthog.NewWithConfig(posthog_pub_key, posthog.Config{Endpoint: posthog_endpoint})
@ -82,7 +79,7 @@ func sendTelemetry() error {
}
// FetchTelemetryData - fetches telemetry data: count of various object types in DB
func FetchTelemetryData() (telemetryData, error) {
func FetchTelemetryData() telemetryData {
var data telemetryData
data.IsPro = servercfg.IsPro
@ -92,21 +89,16 @@ func FetchTelemetryData() (telemetryData, error) {
data.Hosts = getDBLength(database.HOSTS_TABLE_NAME)
data.Version = servercfg.GetVersion()
data.Servers = getServerCount()
nodes, err := GetAllNodes()
if err == nil {
data.Nodes = len(nodes)
data.Count = getClientCount(nodes)
}
endDate, err := GetTrialEndDate()
if err != nil {
logger.Log(0, "error getting trial end date", err.Error())
}
nodes, _ := GetAllNodes()
data.Nodes = len(nodes)
data.Count = getClientCount(nodes)
endDate, _ := GetTrialEndDate()
data.ProTrialEndDate = endDate
if endDate.After(time.Now()) {
data.IsProTrial = true
}
data.IsSaasTenant = servercfg.DeployedByOperator()
return data, err
return data
}
// getServerCount returns number of servers from database

View file

@ -75,3 +75,47 @@ func GetSuperAdmin() (models.ReturnUser, error) {
}
return models.ReturnUser{}, errors.New("superadmin not found")
}
func InsertPendingUser(u *models.User) error {
data, err := json.Marshal(u)
if err != nil {
return err
}
return database.Insert(u.UserName, string(data), database.PENDING_USERS_TABLE_NAME)
}
func DeletePendingUser(username string) error {
return database.DeleteRecord(database.PENDING_USERS_TABLE_NAME, username)
}
func IsPendingUser(username string) bool {
records, err := database.FetchRecords(database.PENDING_USERS_TABLE_NAME)
if err != nil {
return false
}
for _, record := range records {
u := models.ReturnUser{}
err := json.Unmarshal([]byte(record), &u)
if err == nil && u.UserName == username {
return true
}
}
return false
}
func ListPendingUsers() ([]models.ReturnUser, error) {
pendingUsers := []models.ReturnUser{}
records, err := database.FetchRecords(database.PENDING_USERS_TABLE_NAME)
if err != nil && !database.IsEmptyRecord(err) {
return pendingUsers, err
}
for _, record := range records {
u := models.ReturnUser{}
err = json.Unmarshal([]byte(record), &u)
if err == nil {
pendingUsers = append(pendingUsers, u)
}
}
return pendingUsers, nil
}

View file

@ -89,7 +89,7 @@ func StringSliceContains(slice []string, item string) bool {
return false
}
// NormalCIDR - returns the first address of CIDR
// NormalizeCIDR - returns the first address of CIDR
func NormalizeCIDR(address string) (string, error) {
ip, IPNet, err := net.ParseCIDR(address)
if err != nil {

View file

@ -76,7 +76,7 @@ func checkForZombieHosts(h *models.Host) {
// ManageZombies - goroutine which adds/removes/deletes nodes from the zombie node quarantine list
func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
logger.Log(2, "Zombie management started")
InitializeZombies()
go InitializeZombies()
// Zombie Nodes Cleanup Four Times a Day
ticker := time.NewTicker(time.Hour * ZOMBIE_TIMEOUT)

View file

@ -28,7 +28,7 @@ import (
"golang.org/x/exp/slog"
)
var version = "v0.23.0"
var version = "v0.24.0"
// Start DB Connection and start API Request Handler
func main() {
@ -155,7 +155,7 @@ func runMessageQueue(wg *sync.WaitGroup, ctx context.Context) {
defer wg.Done()
brokerHost, _ := servercfg.GetMessageQueueEndpoint()
logger.Log(0, "connecting to mq broker at", brokerHost)
mq.SetupMQTT()
mq.SetupMQTT(true)
if mq.IsConnected() {
logger.Log(0, "connected to MQ Broker")
} else {

View file

@ -177,7 +177,7 @@ func removeInterGw(egressRanges []string) ([]string, bool) {
func updateAcls() {
// get all networks
networks, err := logic.GetNetworks()
if err != nil {
if err != nil && !database.IsEmptyRecord(err) {
slog.Error("acls migration failed. error getting networks", "error", err)
return
}
@ -287,7 +287,7 @@ func updateAcls() {
}
// save new acls
slog.Info(fmt.Sprintf("(migration) saving new acls for network: %s", network.NetID), "networkAcl", networkAcl)
slog.Debug(fmt.Sprintf("(migration) saving new acls for network: %s", network.NetID), "networkAcl", networkAcl)
if _, err := networkAcl.Save(acls.ContainerID(network.NetID)); err != nil {
slog.Error(fmt.Sprintf("error during acls migration. error saving new acls for network: %s", network.NetID), "error", err)
continue

View file

@ -8,27 +8,35 @@ import (
// ApiHost - the host struct for API usage
type ApiHost struct {
ID string `json:"id"`
Verbosity int `json:"verbosity"`
FirewallInUse string `json:"firewallinuse"`
Version string `json:"version"`
Name string `json:"name"`
OS string `json:"os"`
Debug bool `json:"debug"`
IsStatic bool `json:"isstatic"`
ListenPort int `json:"listenport"`
WgPublicListenPort int `json:"wg_public_listen_port" yaml:"wg_public_listen_port"`
MTU int `json:"mtu" yaml:"mtu"`
Interfaces []Iface `json:"interfaces" yaml:"interfaces"`
DefaultInterface string `json:"defaultinterface" yaml:"defautlinterface"`
EndpointIP string `json:"endpointip" yaml:"endpointip"`
PublicKey string `json:"publickey"`
MacAddress string `json:"macaddress"`
Nodes []string `json:"nodes"`
IsDefault bool `json:"isdefault" yaml:"isdefault"`
NatType string `json:"nat_type" yaml:"nat_type"`
PersistentKeepalive int `json:"persistentkeepalive" yaml:"persistentkeepalive"`
AutoUpdate bool `json:"autoupdate" yaml:"autoupdate"`
ID string `json:"id"`
Verbosity int `json:"verbosity"`
FirewallInUse string `json:"firewallinuse"`
Version string `json:"version"`
Name string `json:"name"`
OS string `json:"os"`
Debug bool `json:"debug"`
IsStatic bool `json:"isstatic"`
ListenPort int `json:"listenport"`
WgPublicListenPort int `json:"wg_public_listen_port" yaml:"wg_public_listen_port"`
MTU int `json:"mtu" yaml:"mtu"`
Interfaces []ApiIface `json:"interfaces" yaml:"interfaces"`
DefaultInterface string `json:"defaultinterface" yaml:"defautlinterface"`
EndpointIP string `json:"endpointip" yaml:"endpointip"`
EndpointIPv6 string `json:"endpointipv6" yaml:"endpointipv6"`
PublicKey string `json:"publickey"`
MacAddress string `json:"macaddress"`
Nodes []string `json:"nodes"`
IsDefault bool `json:"isdefault" yaml:"isdefault"`
NatType string `json:"nat_type" yaml:"nat_type"`
PersistentKeepalive int `json:"persistentkeepalive" yaml:"persistentkeepalive"`
AutoUpdate bool `json:"autoupdate" yaml:"autoupdate"`
}
// ApiIface - the interface struct for API usage
// The original Iface struct contains a net.Address, which does not get marshalled correctly
type ApiIface struct {
Name string `json:"name"`
AddressString string `json:"addressString"`
}
// Host.ConvertNMHostToAPI - converts a Netmaker host to an API editable host
@ -36,11 +44,15 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost {
a := ApiHost{}
a.Debug = h.Debug
a.EndpointIP = h.EndpointIP.String()
a.EndpointIPv6 = h.EndpointIPv6.String()
a.FirewallInUse = h.FirewallInUse
a.ID = h.ID.String()
a.Interfaces = h.Interfaces
a.Interfaces = make([]ApiIface, len(h.Interfaces))
for i := range a.Interfaces {
a.Interfaces[i].AddressString = a.Interfaces[i].Address.String()
a.Interfaces[i] = ApiIface{
Name: h.Interfaces[i].Name,
AddressString: h.Interfaces[i].Address.String(),
}
}
a.DefaultInterface = h.DefaultInterface
a.IsStatic = h.IsStatic
@ -73,6 +85,11 @@ func (a *ApiHost) ConvertAPIHostToNMHost(currentHost *Host) *Host {
} else {
h.EndpointIP = net.ParseIP(a.EndpointIP)
}
if len(a.EndpointIPv6) == 0 || strings.Contains(a.EndpointIPv6, "nil") {
h.EndpointIPv6 = currentHost.EndpointIPv6
} else {
h.EndpointIPv6 = net.ParseIP(a.EndpointIPv6)
}
h.Debug = a.Debug
h.FirewallInUse = a.FirewallInUse
h.IPForwarding = currentHost.IPForwarding

View file

@ -63,6 +63,7 @@ type Host struct {
Interfaces []Iface `json:"interfaces" yaml:"interfaces"`
DefaultInterface string `json:"defaultinterface" yaml:"defaultinterface"`
EndpointIP net.IP `json:"endpointip" yaml:"endpointip"`
EndpointIPv6 net.IP `json:"endpointipv6" yaml:"endpointipv6"`
IsDocker bool `json:"isdocker" yaml:"isdocker"`
IsK8S bool `json:"isk8s" yaml:"isk8s"`
IsStatic bool `json:"isstatic" yaml:"isstatic"`

View file

@ -8,21 +8,22 @@ import (
// HostPeerUpdate - struct for host peer updates
type HostPeerUpdate struct {
Host Host `json:"host" bson:"host" yaml:"host"`
ChangeDefaultGw bool `json:"change_default_gw"`
DefaultGwIp net.IP `json:"default_gw_ip"`
IsInternetGw bool `json:"is_inet_gw"`
NodeAddrs []net.IPNet `json:"nodes_addrs" yaml:"nodes_addrs"`
Server string `json:"server" bson:"server" yaml:"server"`
ServerVersion string `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
ServerAddrs []ServerAddr `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
NodePeers []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
Peers []wgtypes.PeerConfig
PeerIDs PeerMap `json:"peerids" bson:"peerids" yaml:"peerids"`
HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"`
EgressRoutes []EgressNetworkRoutes `json:"egress_network_routes"`
FwUpdate FwUpdate `json:"fw_update"`
ReplacePeers bool `json:"replace_peers"`
Host Host `json:"host" bson:"host" yaml:"host"`
ChangeDefaultGw bool `json:"change_default_gw"`
DefaultGwIp net.IP `json:"default_gw_ip"`
IsInternetGw bool `json:"is_inet_gw"`
NodeAddrs []net.IPNet `json:"nodes_addrs" yaml:"nodes_addrs"`
Server string `json:"server" bson:"server" yaml:"server"`
ServerVersion string `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
ServerAddrs []ServerAddr `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
NodePeers []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
Peers []wgtypes.PeerConfig
PeerIDs PeerMap `json:"peerids" bson:"peerids" yaml:"peerids"`
HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"`
EgressRoutes []EgressNetworkRoutes `json:"egress_network_routes"`
FwUpdate FwUpdate `json:"fw_update"`
ReplacePeers bool `json:"replace_peers"`
EndpointDetection bool `json:"endpoint_detection"`
}
// IngressInfo - struct for ingress info
@ -42,6 +43,7 @@ type EgressInfo struct {
// EgressNetworkRoutes - struct for egress network routes for adding routes to peer's interface
type EgressNetworkRoutes struct {
NodeAddr net.IPNet `json:"node_addr"`
NodeAddr6 net.IPNet `json:"node_addr6"`
EgressRanges []string `json:"egress_ranges"`
}

View file

@ -205,7 +205,7 @@ func (extPeer *ExtClient) AddressIPNet4() net.IPNet {
// ExtClient.AddressIPNet6 - return ipv6 IPNet format
func (extPeer *ExtClient) AddressIPNet6() net.IPNet {
return net.IPNet{
IP: net.ParseIP(extPeer.Address),
IP: net.ParseIP(extPeer.Address6),
Mask: net.CIDRMask(128, 128),
}
}

View file

@ -232,17 +232,18 @@ type TrafficKeys struct {
// HostPull - response of a host's pull
type HostPull struct {
Host Host `json:"host" yaml:"host"`
Nodes []Node `json:"nodes" yaml:"nodes"`
Peers []wgtypes.PeerConfig `json:"peers" yaml:"peers"`
ServerConfig ServerConfig `json:"server_config" yaml:"server_config"`
PeerIDs PeerMap `json:"peer_ids,omitempty" yaml:"peer_ids,omitempty"`
HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" yaml:"host_network_info,omitempty"`
EgressRoutes []EgressNetworkRoutes `json:"egress_network_routes"`
FwUpdate FwUpdate `json:"fw_update"`
ChangeDefaultGw bool `json:"change_default_gw"`
DefaultGwIp net.IP `json:"default_gw_ip"`
IsInternetGw bool `json:"is_inet_gw"`
Host Host `json:"host" yaml:"host"`
Nodes []Node `json:"nodes" yaml:"nodes"`
Peers []wgtypes.PeerConfig `json:"peers" yaml:"peers"`
ServerConfig ServerConfig `json:"server_config" yaml:"server_config"`
PeerIDs PeerMap `json:"peer_ids,omitempty" yaml:"peer_ids,omitempty"`
HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" yaml:"host_network_info,omitempty"`
EgressRoutes []EgressNetworkRoutes `json:"egress_network_routes"`
FwUpdate FwUpdate `json:"fw_update"`
ChangeDefaultGw bool `json:"change_default_gw"`
DefaultGwIp net.IP `json:"default_gw_ip"`
IsInternetGw bool `json:"is_inet_gw"`
EndpointDetection bool `json:"endpoint_detection"`
}
type DefaultGwInfo struct {

View file

@ -22,13 +22,6 @@ type userCreateReq struct {
Password string `json:"password"`
}
type cloudAcl struct {
UserName string `json:"username"`
Topic string `json:"topic"`
Action string `json:"action"`
Access string `json:"access"`
}
func (e *EmqxCloud) GetType() servercfg.Emqxdeploy { return servercfg.EmqxCloudDeploy }
func (e *EmqxCloud) CreateEmqxUser(username, pass string) error {
@ -89,54 +82,7 @@ func (e *EmqxCloud) CreateEmqxUserforServer() error {
if res.StatusCode != http.StatusOK {
return errors.New("request failed " + string(body))
}
// add acls
acls := []cloudAcl{
{
UserName: servercfg.GetMqUserName(),
Topic: fmt.Sprintf("update/%s/#", servercfg.GetServer()),
Access: "allow",
Action: "sub",
},
{
UserName: servercfg.GetMqUserName(),
Topic: fmt.Sprintf("host/serverupdate/%s/#", servercfg.GetServer()),
Access: "allow",
Action: "sub",
},
{
UserName: servercfg.GetMqUserName(),
Topic: fmt.Sprintf("signal/%s/#", servercfg.GetServer()),
Access: "allow",
Action: "sub",
},
{
UserName: servercfg.GetMqUserName(),
Topic: fmt.Sprintf("metrics/%s/#", servercfg.GetServer()),
Access: "allow",
Action: "sub",
},
{
UserName: servercfg.GetMqUserName(),
Topic: "peers/host/#",
Access: "allow",
Action: "pub",
},
{
UserName: servercfg.GetMqUserName(),
Topic: "node/update/#",
Access: "allow",
Action: "pub",
},
{
UserName: servercfg.GetMqUserName(),
Topic: "host/update/#",
Access: "allow",
Action: "pub",
},
}
return e.createacls(acls)
return nil
}
func (e *EmqxCloud) CreateEmqxDefaultAuthenticator() error { return nil } // ignore
@ -147,94 +93,13 @@ func (e *EmqxCloud) CreateDefaultDenyRule() error {
return nil
}
func (e *EmqxCloud) createacls(acls []cloudAcl) error {
payload, err := json.Marshal(acls)
if err != nil {
return err
}
client := &http.Client{}
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/acl", e.URL), strings.NewReader(string(payload)))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
req.SetBasicAuth(e.AppID, e.AppSecret)
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return err
}
if res.StatusCode != http.StatusOK {
return errors.New("request failed " + string(body))
}
func (e *EmqxCloud) CreateHostACL(hostID, serverName string) error {
return nil
}
func (e *EmqxCloud) CreateHostACL(hostID, serverName string) error {
acls := []cloudAcl{
{
UserName: hostID,
Topic: fmt.Sprintf("peers/host/%s/%s", hostID, serverName),
Access: "allow",
Action: "sub",
},
{
UserName: hostID,
Topic: fmt.Sprintf("host/update/%s/%s", hostID, serverName),
Access: "allow",
Action: "sub",
},
{
UserName: hostID,
Topic: fmt.Sprintf("host/serverupdate/%s/%s", serverName, hostID),
Access: "allow",
Action: "pub",
},
}
return e.createacls(acls)
}
func (e *EmqxCloud) AppendNodeUpdateACL(hostID, nodeNetwork, nodeID, serverName string) error {
acls := []cloudAcl{
{
UserName: hostID,
Topic: fmt.Sprintf("node/update/%s/%s", nodeNetwork, nodeID),
Access: "allow",
Action: "sub",
},
{
UserName: hostID,
Topic: fmt.Sprintf("ping/%s/%s", serverName, nodeID),
Access: "allow",
Action: "pubsub",
},
{
UserName: hostID,
Topic: fmt.Sprintf("update/%s/%s", serverName, nodeID),
Access: "allow",
Action: "pubsub",
},
{
UserName: hostID,
Topic: fmt.Sprintf("signal/%s/%s", serverName, nodeID),
Access: "allow",
Action: "pubsub",
},
{
UserName: hostID,
Topic: fmt.Sprintf("metrics/%s/%s", serverName, nodeID),
Access: "allow",
Action: "pubsub",
},
}
return nil
return e.createacls(acls)
}
func (e *EmqxCloud) GetUserACL(username string) (*aclObject, error) { return nil, nil } // ununsed on cloud since it doesn't overwrite acls list

View file

@ -92,7 +92,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
}
decrypted, decryptErr := decryptMsgWithHost(currentHost, msg.Payload())
if decryptErr != nil {
slog.Error("failed to decrypt message for host", "id", id, "error", decryptErr)
slog.Error("failed to decrypt message for host", "id", id, "name", currentHost.Name, "error", decryptErr)
return
}
var hostUpdate models.HostUpdate
@ -198,7 +198,21 @@ func signalPeer(signal models.Signal) {
signal.IsPro = servercfg.IsPro
peerHost, err := logic.GetHost(signal.ToHostID)
if err != nil {
slog.Error("failed to signal, peer not found", "error", err)
slog.Error("failed to signal, peer host not found", "error", err)
return
}
peerNode, err := logic.GetNodeByID(signal.ToNodeID)
if err != nil {
slog.Error("failed to signal, node not found", "error", err)
return
}
node, err := logic.GetNodeByID(signal.FromNodeID)
if err != nil {
slog.Error("failed to signal, peer node not found", "error", err)
return
}
if peerNode.IsIngressGateway || node.IsIngressGateway || peerNode.IsInternetGateway || node.IsInternetGateway {
signal.Action = ""
return
}
err = HostUpdate(&models.HostUpdate{
@ -282,9 +296,11 @@ func HandleHostCheckin(h, currentHost *models.Host) bool {
!h.EndpointIP.Equal(currentHost.EndpointIP) ||
(len(h.NatType) > 0 && h.NatType != currentHost.NatType) ||
h.DefaultInterface != currentHost.DefaultInterface ||
(h.ListenPort != 0 && h.ListenPort != currentHost.ListenPort) || (h.WgPublicListenPort != 0 && h.WgPublicListenPort != currentHost.WgPublicListenPort)
(h.ListenPort != 0 && h.ListenPort != currentHost.ListenPort) ||
(h.WgPublicListenPort != 0 && h.WgPublicListenPort != currentHost.WgPublicListenPort) || (!h.EndpointIPv6.Equal(currentHost.EndpointIPv6))
if ifaceDelta { // only save if something changes
currentHost.EndpointIP = h.EndpointIP
currentHost.EndpointIPv6 = h.EndpointIPv6
currentHost.Interfaces = h.Interfaces
currentHost.DefaultInterface = h.DefaultInterface
currentHost.NatType = h.NatType

View file

@ -10,6 +10,7 @@ import (
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/servercfg"
"golang.org/x/exp/slog"
)
// KEEPALIVE_TIMEOUT - time in seconds for timeout
@ -27,19 +28,20 @@ var mqclient mqtt.Client
func setMqOptions(user, password string, opts *mqtt.ClientOptions) {
broker, _ := servercfg.GetMessageQueueEndpoint()
opts.AddBroker(broker)
id := logic.RandomString(23)
opts.ClientID = id
opts.ClientID = logic.RandomString(23)
opts.SetUsername(user)
opts.SetPassword(password)
opts.SetAutoReconnect(true)
opts.SetConnectRetry(true)
opts.SetConnectRetryInterval(time.Second << 2)
opts.SetCleanSession(true)
opts.SetConnectRetryInterval(time.Second * 4)
opts.SetKeepAlive(time.Minute)
opts.SetCleanSession(true)
opts.SetWriteTimeout(time.Minute)
}
// SetupMQTT creates a connection to broker and return client
func SetupMQTT() {
func SetupMQTT(fatal bool) {
if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
if emqx.GetType() == servercfg.EmqxOnPremDeploy {
time.Sleep(10 * time.Second) // wait for the REST endpoint to be ready
@ -69,6 +71,7 @@ func SetupMQTT() {
opts := mqtt.NewClientOptions()
setMqOptions(servercfg.GetMqUserName(), servercfg.GetMqPassword(), opts)
logger.Log(0, "Mq Client Connecting with Random ID: ", opts.ClientID)
opts.SetOnConnectHandler(func(client mqtt.Client) {
serverName := servercfg.GetServer()
if token := client.Subscribe(fmt.Sprintf("update/%s/#", serverName), 0, mqtt.MessageHandler(UpdateNode)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
@ -91,6 +94,13 @@ func SetupMQTT() {
opts.SetOrderMatters(false)
opts.SetResumeSubs(true)
})
opts.SetConnectionLostHandler(func(c mqtt.Client, e error) {
slog.Warn("detected broker connection lost", "err", e.Error())
c.Disconnect(250)
slog.Info("re-initiating MQ connection")
SetupMQTT(false)
})
mqclient = mqtt.NewClient(opts)
tperiod := time.Now().Add(10 * time.Second)
for {
@ -98,9 +108,16 @@ func SetupMQTT() {
logger.Log(2, "unable to connect to broker, retrying ...")
if time.Now().After(tperiod) {
if token.Error() == nil {
logger.FatalLog("could not connect to broker, token timeout, exiting ...")
if fatal {
logger.FatalLog("could not connect to broker, token timeout, exiting ...")
}
logger.Log(0, "could not connect to broker, token timeout, exiting ...")
} else {
logger.FatalLog("could not connect to broker, exiting ...", token.Error().Error())
if fatal {
logger.FatalLog("could not connect to broker, exiting ...", token.Error().Error())
}
logger.Log(0, "could not connect to broker, exiting ...", token.Error().Error())
}
}
} else {
@ -124,7 +141,7 @@ func Keepalive(ctx context.Context) {
// IsConnected - function for determining if the mqclient is connected or not
func IsConnected() bool {
return mqclient != nil && mqclient.IsConnected()
return mqclient != nil && mqclient.IsConnectionOpen()
}
// CloseClient - function to close the mq connection from server

View file

@ -134,10 +134,15 @@ func failOverME(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
host, err := logic.GetHost(node.HostID.String())
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
failOverNode, exists := proLogic.FailOverExists(node.Network)
if !exists {
logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failover node doesn't exist in the network"), "badrequest"))
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("req-from: %s, failover node doesn't exist in the network", host.Name), "badrequest"))
return
}
var failOverReq models.FailOverMeReq

View file

@ -11,6 +11,7 @@ import (
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/mq"
proLogic "github.com/gravitl/netmaker/pro/logic"
"github.com/gravitl/netmaker/servercfg"
)
// InetHandlers - handlers for internet gw
@ -66,6 +67,14 @@ func createInternetGw(w http.ResponseWriter, r *http.Request) {
return
}
proLogic.SetInternetGw(&node, request)
if servercfg.IsPro {
if _, exists := proLogic.FailOverExists(node.Network); exists {
go func() {
proLogic.ResetFailedOverPeer(&node)
mq.PublishPeerUpdate(false)
}()
}
}
err = logic.UpsertNode(&node)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))

View file

@ -2,9 +2,11 @@ package controllers
import (
"encoding/json"
proLogic "github.com/gravitl/netmaker/pro/logic"
"net/http"
proLogic "github.com/gravitl/netmaker/pro/logic"
"golang.org/x/exp/slog"
"github.com/gorilla/mux"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
@ -122,12 +124,13 @@ func getNetworkExtMetrics(w http.ResponseWriter, r *http.Request) {
continue
}
// if metrics for that client have been reported, append them
if len(ingressMetrics.Connectivity[clients[j].ClientID].NodeName) > 0 {
if _, ok := ingressMetrics.Connectivity[clients[j].ClientID]; ok {
networkMetrics.Connectivity[clients[j].ClientID] = ingressMetrics.Connectivity[clients[j].ClientID]
}
}
}
slog.Debug("sending collected client metrics", "metrics", networkMetrics.Connectivity)
logger.Log(1, r.Header.Get("user"), "fetched ext client metrics for network", network)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(networkMetrics.Connectivity)

View file

@ -1,16 +1,25 @@
package controllers
import (
"net/http"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/servercfg"
"net/http"
)
var limitedApis = map[string]struct{}{
"/api/server/status": {},
"/api/emqx/hosts": {},
"/api/users/adm/authenticate": {},
}
func OnlyServerAPIWhenUnlicensedMiddleware(handler http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if servercfg.ErrLicenseValidation != nil && request.URL.Path != "/api/server/status" {
logic.ReturnErrorResponse(writer, request, logic.FormatError(servercfg.ErrLicenseValidation, "forbidden"))
return
if servercfg.ErrLicenseValidation != nil {
if _, ok := limitedApis[request.URL.Path]; !ok {
logic.ReturnErrorResponse(writer, request, logic.FormatError(servercfg.ErrLicenseValidation, "forbidden"))
return
}
}
handler.ServeHTTP(writer, request)
})

View file

@ -84,6 +84,7 @@ func InitPro() {
})
logic.ResetFailOver = proLogic.ResetFailOver
logic.ResetFailedOverPeer = proLogic.ResetFailedOverPeer
logic.FailOverExists = proLogic.FailOverExists
logic.CreateFailOver = proLogic.CreateFailOver
logic.GetFailOverPeerIps = proLogic.GetFailOverPeerIps
logic.DenyClientNodeAccess = proLogic.DenyClientNode

View file

@ -11,6 +11,9 @@ import (
)
func SetFailOverCtx(failOverNode, victimNode, peerNode models.Node) error {
if victimNode.IsIngressGateway || peerNode.IsIngressGateway || victimNode.IsInternetGateway || peerNode.IsInternetGateway {
return nil
}
if peerNode.FailOverPeers == nil {
peerNode.FailOverPeers = make(map[string]struct{})
}
@ -119,6 +122,9 @@ func GetFailOverPeerIps(peer, node *models.Node) []net.IPNet {
}
allowedips = append(allowedips, allowed)
}
if failOverpeer.IsEgressGateway {
allowedips = append(allowedips, logic.GetEgressIPs(&failOverpeer)...)
}
}
}

View file

@ -104,7 +104,6 @@ func MQUpdateMetrics(client mqtt.Client, msg mqtt.Message) {
}
func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) {
oldMetrics, err := logic.GetMetrics(currentNode.ID.String())
if err != nil {
slog.Error("error finding old metrics for node", "id", currentNode.ID, "error", err)
@ -121,21 +120,13 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) {
if newMetrics.Connectivity == nil {
newMetrics.Connectivity = make(map[string]models.Metric)
}
if len(attachedClients) > 0 {
// associate ext clients with IDs
for i := range attachedClients {
extMetric := newMetrics.Connectivity[attachedClients[i].PublicKey]
if len(extMetric.NodeName) == 0 &&
len(newMetrics.Connectivity[attachedClients[i].ClientID].NodeName) > 0 { // cover server clients
extMetric = newMetrics.Connectivity[attachedClients[i].ClientID]
if extMetric.TotalReceived > 0 && extMetric.TotalSent > 0 {
extMetric.Connected = true
}
}
extMetric.NodeName = attachedClients[i].ClientID
delete(newMetrics.Connectivity, attachedClients[i].PublicKey)
newMetrics.Connectivity[attachedClients[i].ClientID] = extMetric
}
for i := range attachedClients {
slog.Debug("[metrics] processing attached client", "client", attachedClients[i].ClientID, "public key", attachedClients[i].PublicKey)
clientMetric := newMetrics.Connectivity[attachedClients[i].PublicKey]
clientMetric.NodeName = attachedClients[i].ClientID
newMetrics.Connectivity[attachedClients[i].ClientID] = clientMetric
delete(newMetrics.Connectivity, attachedClients[i].PublicKey)
slog.Debug("[metrics] attached client metric", "metric", clientMetric)
}
// run through metrics for each peer
@ -168,7 +159,5 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) {
}
for k := range oldMetrics.Connectivity { // cleanup any left over data, self healing
delete(newMetrics.Connectivity, k)
}
slog.Debug("[metrics] node metrics data", "node ID", currentNode.ID, "metrics", newMetrics)
}

View file

@ -10,6 +10,11 @@ import (
"golang.org/x/exp/slog"
)
const (
IPv4Network = "0.0.0.0/0"
IPv6Network = "::/0"
)
func ValidateInetGwReq(inetNode models.Node, req models.InetNodeReq, update bool) error {
inetHost, err := logic.GetHost(inetNode.HostID.String())
if err != nil {
@ -104,6 +109,9 @@ func SetDefaultGwForRelayedUpdate(relayed, relay models.Node, peerUpdate models.
if relay.InternetGwID != "" {
peerUpdate.ChangeDefaultGw = true
peerUpdate.DefaultGwIp = relay.Address.IP
if peerUpdate.DefaultGwIp == nil {
peerUpdate.DefaultGwIp = relay.Address6.IP
}
}
return peerUpdate
@ -118,7 +126,9 @@ func SetDefaultGw(node models.Node, peerUpdate models.HostPeerUpdate) models.Hos
}
peerUpdate.ChangeDefaultGw = true
peerUpdate.DefaultGwIp = inetNode.Address.IP
if peerUpdate.DefaultGwIp == nil {
peerUpdate.DefaultGwIp = inetNode.Address6.IP
}
}
return peerUpdate
}
@ -140,6 +150,18 @@ func GetNetworkIngresses(network string) ([]models.Node, error) {
// GetAllowedIpsForInet - get inet cidr for node using a inet gw
func GetAllowedIpForInetNodeClient(node, peer *models.Node) []net.IPNet {
_, ipnet, _ := net.ParseCIDR("0.0.0.0/0")
return []net.IPNet{*ipnet}
var allowedips = []net.IPNet{}
if peer.Address.IP != nil {
_, ipnet, _ := net.ParseCIDR(IPv4Network)
allowedips = append(allowedips, *ipnet)
return allowedips
}
if peer.Address6.IP != nil {
_, ipnet, _ := net.ParseCIDR(IPv6Network)
allowedips = append(allowedips, *ipnet)
}
return allowedips
}

View file

@ -238,7 +238,7 @@ func getRelayedAddresses(id string) []net.IPNet {
addrs = append(addrs, node.Address)
}
if node.Address6.IP != nil {
node.Address.Mask = net.CIDRMask(128, 128)
node.Address6.Mask = net.CIDRMask(128, 128)
addrs = append(addrs, node.Address6)
}
return addrs

View file

@ -42,7 +42,7 @@ const trial_data_key = "trialdata"
// stores trial end date
func initTrial() error {
telData, _ := logic.FetchTelemetryData()
telData := logic.FetchTelemetryData()
if telData.Hosts > 0 || telData.Networks > 0 || telData.Users > 0 {
return nil // database is already populated, so skip creating trial
}

View file

@ -63,6 +63,7 @@ type Usage struct {
Egresses int `json:"egresses"`
Relays int `json:"relays"`
InternetGateways int `json:"internet_gateways"`
FailOvers int `json:"fail_overs"`
}
// Usage.SetDefaults - sets the default values for usage

View file

@ -59,5 +59,9 @@ func getCurrentServerUsage() (limits Usage) {
if err == nil {
limits.InternetGateways = len(gateways)
}
failovers, err := logic.GetAllFailOvers()
if err == nil {
limits.FailOvers = len(failovers)
}
return
}

View file

@ -1,19 +1,24 @@
# Netmaker v0.23.0
# Netmaker v0.24.0
## Whats New ✨
- Revamped Internet Gateways: hosts and clients can now use internet gateways! More info [here](https://docs.netmaker.io/pro/internet-gateways.html)
On community edition, internet gateways for clients can be accessed via the Remote Access tab.
- PostUp and PostDown commands for clients
- EMQX cloud support
- Metadata for Remote Access Gateways
- IPv6 and Dual Stack Networks Support Across Platform
- Endpoint Detection Can Now Be Turned Off By Setting `ENDPOINT_DETECTION=false` On Server Config
- New SignUp Flow For Oauth Users, With Admin Approval Process.
- Added Failover Commands to nmctl
## What's Fixed/Improved 🛠
- Allow creation of gateways, relays and egress without clients, relayed hosts and external ranges respectively
- Make default host a remote access gateway and a failover host on joining a new network
- Stability fixes with ACLs
- Fixed issues with install/upgrade scripts
- Fixed issues with CoreDNS
- Scalability Fixes around Mq connection, ACLs
- Fixed Zombie Node Logic To Avoid Choking On the Channel
- Fixed Egress Routes In Dual Stack Netmaker Overlay Networks
- Fixed Client Connectivity Metrics Data
- Fixed auto-relay with enrollment key
- Imporved Logic Around Oauth Sceret Management
- Improved Oauth Message Templates
## Known Issues 🐞
- Erratic Traffic Data In Metrics
- `netclient server leave` Leaves a Stale Node Record In At Least One Network When Part Of Multiple Networks, But Can Be Deleted From The UI.
- On Darwin Stale Egress Route Entries Remain On The Machine After Removing Egress Range Or Removing The Egress Server

View file

@ -53,6 +53,8 @@ TELEMETRY=on
# OAuth section
#
###
# only mentioned domains will be allowded to signup using oauth, by default all domains are allowed
ALLOWED_EMAIL_DOMAINS=*
# "<azure-ad|github|google|oidc>"
AUTH_PROVIDER=
# "<client id of your oauth provider>"
@ -71,3 +73,5 @@ JWT_VALIDITY_DURATION=43200
RAC_AUTO_DISABLE=true
# if turned on data will be cached on to improve performance significantly (IMPORTANT: If HA set to `false` )
CACHING_ENABLED=true
# if turned on netclient checks if peers are reachable over private/LAN address, and choose that as peer endpoint
ENDPOINT_DETECTION=true

View file

@ -248,8 +248,8 @@ save_config() { (
local toCopy=("SERVER_HOST" "MASTER_KEY" "MQ_USERNAME" "MQ_PASSWORD"
"INSTALL_TYPE" "NODE_ID" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT"
"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "VERBOSITY"
"DEBUG_MODE" "REST_BACKEND" "DISABLE_REMOTE_IP_CHECK" "TELEMETRY" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE" "CACHING_ENABLED")
"DEBUG_MODE" "REST_BACKEND" "DISABLE_REMOTE_IP_CHECK" "TELEMETRY" "ALLOWED_EMAIL_DOMAINS" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE" "CACHING_ENABLED" "ENDPOINT_DETECTION")
for name in "${toCopy[@]}"; do
save_config_item $name "${!name}"
done
@ -694,7 +694,12 @@ upgrade() {
unset IMAGE_TAG
unset BUILD_TAG
IMAGE_TAG=$UI_IMAGE_TAG
BUILD_TAG=$UI_IMAGE_TAG
semver=$(chsv_check_version_ex "$UI_IMAGE_TAG")
if [[ ! "$semver" ]]; then
BUILD_TAG=$LATEST
else
BUILD_TAG=$UI_IMAGE_TAG
fi
echo "-----------------------------------------------------"
echo "Provide Details for pro installation:"
echo " 1. Log into https://app.netmaker.io"
@ -720,7 +725,13 @@ downgrade () {
unset IMAGE_TAG
unset BUILD_TAG
IMAGE_TAG=$UI_IMAGE_TAG
BUILD_TAG=$UI_IMAGE_TAG
semver=$(chsv_check_version_ex "$UI_IMAGE_TAG")
if [[ ! "$semver" ]]; then
BUILD_TAG=$LATEST
else
BUILD_TAG=$UI_IMAGE_TAG
fi
save_config
if [ -a "$SCRIPT_DIR"/docker-compose.override.yml ]; then
rm -f "$SCRIPT_DIR"/docker-compose.override.yml
@ -730,6 +741,23 @@ downgrade () {
install_netmaker
}
function chsv_check_version() {
if [[ $1 =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$ ]]; then
echo "$1"
else
echo ""
fi
}
function chsv_check_version_ex() {
if [[ $1 =~ ^v.+$ ]]; then
chsv_check_version "${1:1}"
else
chsv_check_version "${1}"
fi
}
main (){

View file

@ -441,7 +441,7 @@ func GetPublicIP() (string, error) {
endpoint := ""
var err error
iplist := []string{"https://ip.server.gravitl.com", "https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"}
iplist := []string{"https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"}
publicIpService := os.Getenv("PUBLIC_IP_SERVICE")
if publicIpService != "" {
// prepend the user-specified service so it's checked first
@ -674,6 +674,15 @@ func DeployedByOperator() bool {
return config.Config.Server.DeployedByOperator
}
// IsEndpointDetectionEnabled - returns true if endpoint detection enabled
func IsEndpointDetectionEnabled() bool {
var enabled = true //default
if os.Getenv("ENDPOINT_DETECTION") != "" {
enabled = os.Getenv("ENDPOINT_DETECTION") == "true"
}
return enabled
}
// GetEnvironment returns the environment the server is running in (e.g. dev, staging, prod...)
func GetEnvironment() string {
if env := os.Getenv("ENVIRONMENT"); env != "" {
@ -703,3 +712,14 @@ func GetEmqxAppID() string {
func GetEmqxAppSecret() string {
return os.Getenv("EMQX_APP_SECRET")
}
// GetAllowedEmailDomains - gets the allowed email domains for oauth signup
func GetAllowedEmailDomains() string {
allowedDomains := "*"
if os.Getenv("ALLOWED_EMAIL_DOMAINS") != "" {
allowedDomains = os.Getenv("ALLOWED_EMAIL_DOMAINS")
} else if config.Config.Server.AllowedEmailDomains != "" {
allowedDomains = config.Config.Server.AllowedEmailDomains
}
return allowedDomains
}

View file

@ -15,9 +15,43 @@ definitions:
description: ACLContainer - the total list of all node's ACL in a given network
type: object
x-go-package: github.com/gravitl/netmaker/logic/acls
APIEnrollmentKey:
description: APIEnrollmentKey - used to create enrollment keys via API
properties:
expiration:
format: int64
type: integer
x-go-name: Expiration
networks:
items:
type: string
type: array
x-go-name: Networks
relay:
type: string
x-go-name: Relay
tags:
items:
type: string
type: array
x-go-name: Tags
type:
$ref: '#/definitions/KeyType'
unlimited:
type: boolean
x-go-name: Unlimited
uses_remaining:
format: int64
type: integer
x-go-name: UsesRemaining
type: object
x-go-package: github.com/gravitl/netmaker/models
ApiHost:
description: ApiHost - the host struct for API usage
properties:
autoupdate:
type: boolean
x-go-name: AutoUpdate
debug:
type: boolean
x-go-name: Debug
@ -35,18 +69,12 @@ definitions:
x-go-name: ID
interfaces:
items:
$ref: '#/definitions/Iface'
$ref: '#/definitions/ApiIface'
type: array
x-go-name: Interfaces
isdefault:
type: boolean
x-go-name: IsDefault
isrelay:
type: boolean
x-go-name: IsRelay
isrelayed:
type: boolean
x-go-name: IsRelayed
isstatic:
type: boolean
x-go-name: IsStatic
@ -75,17 +103,13 @@ definitions:
os:
type: string
x-go-name: OS
persistentkeepalive:
format: int64
type: integer
x-go-name: PersistentKeepalive
publickey:
type: string
x-go-name: PublicKey
relay_hosts:
items:
type: string
type: array
x-go-name: RelayedHosts
relayed_by:
type: string
x-go-name: RelayedBy
verbosity:
format: int64
type: integer
@ -99,6 +123,139 @@ definitions:
x-go-name: WgPublicListenPort
type: object
x-go-package: github.com/gravitl/netmaker/models
ApiIface:
description: |-
ApiIface - the interface struct for API usage
The original Iface struct contains a net.Address, which does not get marshalled correctly
properties:
addressString:
type: string
x-go-name: AddressString
name:
type: string
x-go-name: Name
type: object
x-go-package: github.com/gravitl/netmaker/models
ApiNode:
description: ApiNode is a stripped down Node DTO that exposes only required fields to external systems
properties:
address:
type: string
x-go-name: Address
address6:
type: string
x-go-name: Address6
allowedips:
items:
type: string
type: array
x-go-name: AllowedIPs
connected:
type: boolean
x-go-name: Connected
defaultacl:
description: == PRO ==
type: string
x-go-name: DefaultACL
dnson:
type: boolean
x-go-name: DNSOn
egressgatewaynatenabled:
type: boolean
x-go-name: EgressGatewayNatEnabled
egressgatewayranges:
items:
type: string
type: array
x-go-name: EgressGatewayRanges
expdatetime:
format: int64
type: integer
x-go-name: ExpirationDateTime
fail_over_peers:
additionalProperties:
type: object
type: object
x-go-name: FailOverPeers
failed_over_by:
format: uuid
type: string
x-go-name: FailedOverBy
hostid:
type: string
x-go-name: HostID
id:
type: string
x-go-name: ID
inet_node_req:
$ref: '#/definitions/InetNodeReq'
ingressdns:
type: string
x-go-name: IngressDns
internetgw_node_id:
type: string
x-go-name: InternetGwID
is_fail_over:
type: boolean
x-go-name: IsFailOver
isegressgateway:
type: boolean
x-go-name: IsEgressGateway
isingressgateway:
type: boolean
x-go-name: IsIngressGateway
isinternetgateway:
type: boolean
x-go-name: IsInternetGateway
isrelay:
type: boolean
x-go-name: IsRelay
isrelayed:
type: boolean
x-go-name: IsRelayed
lastcheckin:
format: int64
type: integer
x-go-name: LastCheckIn
lastmodified:
format: int64
type: integer
x-go-name: LastModified
lastpeerupdate:
format: int64
type: integer
x-go-name: LastPeerUpdate
localaddress:
type: string
x-go-name: LocalAddress
metadata:
type: string
x-go-name: Metadata
network:
type: string
x-go-name: Network
networkrange:
type: string
x-go-name: NetworkRange
networkrange6:
type: string
x-go-name: NetworkRange6
pendingdelete:
type: boolean
x-go-name: PendingDelete
relayedby:
type: string
x-go-name: RelayedBy
relaynodes:
items:
type: string
type: array
x-go-name: RelayedNodes
server:
type: string
x-go-name: Server
type: object
x-go-package: github.com/gravitl/netmaker/models
AuthParams:
description: AuthParams - struct for auth params
properties:
@ -135,6 +292,12 @@ definitions:
type: string
type: array
x-go-name: ExtraAllowedIPs
postdown:
type: string
x-go-name: PostDown
postup:
type: string
x-go-name: PostUp
publickey:
type: string
x-go-name: PublicKey
@ -187,6 +350,32 @@ definitions:
x-go-name: Ranges
type: object
x-go-package: github.com/gravitl/netmaker/models
EgressInfo:
description: EgressInfo - struct for egress info
properties:
egress_gateway_cfg:
$ref: '#/definitions/EgressGatewayRequest'
egress_gw_addr:
$ref: '#/definitions/IPNet'
egress_id:
type: string
x-go-name: EgressID
network:
$ref: '#/definitions/IPNet'
type: object
x-go-package: github.com/gravitl/netmaker/models
EgressNetworkRoutes:
description: EgressNetworkRoutes - struct for egress network routes for adding routes to peer's interface
properties:
egress_ranges:
items:
type: string
type: array
x-go-name: EgressRanges
node_addr:
$ref: '#/definitions/IPNet'
type: object
x-go-package: github.com/gravitl/netmaker/models
EnrollmentKey:
description: EnrollmentKey - the key used to register hosts and join them to specific networks
properties:
@ -199,6 +388,10 @@ definitions:
type: string
type: array
x-go-name: Networks
relay:
format: uuid
type: string
x-go-name: Relay
tags:
items:
type: string
@ -230,6 +423,11 @@ definitions:
address6:
type: string
x-go-name: Address6
allowed_ips:
items:
type: string
type: array
x-go-name: AllowedIPs
clientid:
type: string
x-go-name: ClientID
@ -265,6 +463,12 @@ definitions:
ownerid:
type: string
x-go-name: OwnerID
postdown:
type: string
x-go-name: PostDown
postup:
type: string
x-go-name: PostUp
privatekey:
type: string
x-go-name: PrivateKey
@ -280,6 +484,19 @@ definitions:
title: File represents an open file descriptor.
type: object
x-go-package: os
FwUpdate:
description: FwUpdate - struct for firewall updates
properties:
egress_info:
additionalProperties:
$ref: '#/definitions/EgressInfo'
type: object
x-go-name: EgressInfo
is_egress_gw:
type: boolean
x-go-name: IsEgressGw
type: object
x-go-package: github.com/gravitl/netmaker/models
HardwareAddr:
items:
format: uint8
@ -362,6 +579,8 @@ definitions:
os:
type: string
x-go-name: OS
persistentkeepalive:
$ref: '#/definitions/Duration'
publickey:
$ref: '#/definitions/Key'
traffickeypublic:
@ -386,11 +605,55 @@ definitions:
x-go-name: WgPublicListenPort
type: object
x-go-package: github.com/gravitl/netmaker/models
HostInfoMap:
additionalProperties:
$ref: '#/definitions/HostNetworkInfo'
description: HostInfoMap - map of host public keys to host networking info
type: object
x-go-package: github.com/gravitl/netmaker/models
HostNetworkInfo:
description: HostNetworkInfo - holds info related to host networking (used for client side peer calculations)
properties:
interfaces:
items:
$ref: '#/definitions/Iface'
type: array
x-go-name: Interfaces
is_static:
type: boolean
x-go-name: IsStatic
listen_port:
format: int64
type: integer
x-go-name: ListenPort
type: object
x-go-package: github.com/gravitl/netmaker/models
HostPull:
description: HostPull - response of a host's pull
properties:
change_default_gw:
type: boolean
x-go-name: ChangeDefaultGw
default_gw_ip:
type: string
x-go-name: DefaultGwIp
egress_network_routes:
items:
$ref: '#/definitions/EgressNetworkRoutes'
type: array
x-go-name: EgressRoutes
endpoint_detection:
type: boolean
x-go-name: EndpointDetection
fw_update:
$ref: '#/definitions/FwUpdate'
host:
$ref: '#/definitions/Host'
host_network_info:
$ref: '#/definitions/HostInfoMap'
is_inet_gw:
type: boolean
x-go-name: IsInternetGw
nodes:
items:
$ref: '#/definitions/Node'
@ -413,6 +676,9 @@ definitions:
address:
type: string
x-go-name: Address
host_id:
type: string
x-go-name: HostID
id:
type: string
x-go-name: ID
@ -466,6 +732,16 @@ definitions:
x-go-name: Name
type: object
x-go-package: github.com/gravitl/netmaker/models
InetNodeReq:
description: InetNodeReq - exit node request struct
properties:
inet_node_client_ids:
items:
type: string
type: array
x-go-name: InetNodeClientIDs
type: object
x-go-package: github.com/gravitl/netmaker/models
Key:
description: |-
A Key is a public, private, or pre-shared secret key. The Key constructor
@ -758,13 +1034,15 @@ definitions:
format: date-time
type: string
x-go-name: ExpirationDateTime
failover:
type: boolean
x-go-name: Failover
failovernode:
fail_over_peers:
additionalProperties:
type: object
type: object
x-go-name: FailOverPeers
failed_over_by:
format: uuid
type: string
x-go-name: FailoverNode
x-go-name: FailedOverBy
hostid:
format: uuid
type: string
@ -773,6 +1051,8 @@ definitions:
format: uuid
type: string
x-go-name: ID
inet_node_req:
$ref: '#/definitions/InetNodeReq'
ingressdns:
type: string
x-go-name: IngressDNS
@ -782,14 +1062,21 @@ definitions:
ingressgatewayrange6:
type: string
x-go-name: IngressGatewayRange6
internetgateway:
$ref: '#/definitions/UDPAddr'
internetgw_node_id:
type: string
x-go-name: InternetGwID
is_fail_over:
type: boolean
x-go-name: IsFailOver
isegressgateway:
type: boolean
x-go-name: IsEgressGateway
isingressgateway:
type: boolean
x-go-name: IsIngressGateway
isinternetgateway:
type: boolean
x-go-name: IsInternetGateway
isrelay:
type: boolean
x-go-name: IsRelay
@ -810,6 +1097,9 @@ definitions:
x-go-name: LastPeerUpdate
localaddress:
$ref: '#/definitions/IPNet'
metadata:
type: string
x-go-name: Metadata
network:
type: string
x-go-name: Network
@ -823,8 +1113,6 @@ definitions:
pendingdelete:
type: boolean
x-go-name: PendingDelete
persistentkeepalive:
$ref: '#/definitions/Duration'
relayedby:
type: string
x-go-name: RelayedBy
@ -918,6 +1206,8 @@ definitions:
type: string
APIPort:
type: string
AllowedEmailDomains:
type: string
AllowedOrigin:
type: string
AuthProvider:
@ -930,6 +1220,8 @@ definitions:
type: string
BrokerType:
type: string
CacheEnabled:
type: string
ClientID:
type: string
ClientSecret:
@ -965,6 +1257,8 @@ definitions:
IsEE:
type: string
x-go-name: IsPro
JwtValidityDuration:
$ref: '#/definitions/Duration'
LicenseValue:
type: string
MQPassword:
@ -997,6 +1291,8 @@ definitions:
type: string
PublicIPService:
type: string
RacAutoDisable:
type: boolean
RestBackend:
type: string
SQLConn:
@ -1033,6 +1329,9 @@ definitions:
type: integer
Version:
type: string
endpoint_detection:
type: boolean
x-go-name: EndpointDetection
type: object
x-go-package: github.com/gravitl/netmaker/config
Signal:
@ -1040,9 +1339,18 @@ definitions:
properties:
action:
$ref: '#/definitions/SignalAction'
from_host_id:
type: string
x-go-name: FromHostID
from_host_pubkey:
type: string
x-go-name: FromHostPubKey
from_node_id:
type: string
x-go-name: FromNodeID
is_pro:
type: boolean
x-go-name: IsPro
reply:
type: boolean
x-go-name: Reply
@ -1053,12 +1361,15 @@ definitions:
format: int64
type: integer
x-go-name: TimeStamp
to_host_id:
type: string
x-go-name: ToHostID
to_host_pubkey:
type: string
x-go-name: ToHostPubKey
turn_relay_addr:
to_node_id:
type: string
x-go-name: TurnRelayEndpoint
x-go-name: ToNodeID
type: object
x-go-package: github.com/gravitl/netmaker/models
SignalAction:
@ -1114,6 +1425,10 @@ definitions:
issuperadmin:
type: boolean
x-go-name: IsSuperAdmin
last_login_time:
format: date-time
type: string
x-go-name: LastLoginTime
password:
type: string
x-go-name: Password
@ -1149,7 +1464,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.23.0
version: 0.24.0
paths:
/api/dns:
get:
@ -1277,6 +1592,17 @@ paths:
summary: Push DNS entries to nameserver.
tags:
- dns
/api/emqx/hosts:
delete:
operationId: delEmqxHosts
responses:
"200":
$ref: '#/responses/apiHostResponse'
schemes:
- https
summary: Lists all hosts.
tags:
- hosts
/api/extclients:
get:
operationId: getAllExtClients
@ -1468,7 +1794,7 @@ paths:
operationId: getHosts
responses:
"200":
$ref: '#/responses/apiHostResponse'
$ref: '#/responses/apiHostSliceResponse'
schemes:
- https
summary: Lists all hosts.
@ -1728,12 +2054,6 @@ paths:
required: true
type: string
x-go-name: Networkname
- description: ACL Container
in: body
name: acl_container
schema:
$ref: '#/definitions/ACLContainer'
x-go-name: ACLContainer
responses:
"200":
$ref: '#/responses/aclContainerResponse'
@ -1765,6 +2085,41 @@ paths:
summary: Update a network ACL (Access Control List).
tags:
- networks
/api/networks/{networkname}/acls/v2:
put:
operationId: updateNetworkACL
parameters:
- description: 'name: network name'
in: path
name: networkname
required: true
type: string
x-go-name: Networkname
- description: ACL Container
in: body
name: acl_container
schema:
$ref: '#/definitions/ACLContainer'
x-go-name: ACLContainer
responses:
"200":
$ref: '#/responses/aclContainerResponse'
schemes:
- https
summary: Update a network ACL (Access Control List).
tags:
- networks
/api/node/{nodeid}/failOverME:
post:
operationId: failOver_me
responses:
"200":
$ref: '#/responses/nodeResponse'
schemes:
- https
summary: Create a relay.
tags:
- node
/api/nodes:
get:
operationId: getAllNodes
@ -2014,6 +2369,37 @@ paths:
summary: Remove a relay.
tags:
- nodes
/api/nodes/{network}/{nodeid}/inet_gw:
delete:
operationId: deleteInternetGw
responses:
"200":
$ref: '#/responses/nodeResponse'
schemes:
- https
summary: Delete an internet gw.
tags:
- nodes
post:
operationId: createInternetGw
responses:
"200":
$ref: '#/responses/nodeResponse'
schemes:
- https
summary: Create an inet node.
tags:
- nodes
put:
operationId: updateInternetGw
responses:
"200":
$ref: '#/responses/nodeResponse'
schemes:
- https
summary: update an inet node.
tags:
- nodes
/api/nodes/{network}/{nodeid}/ingress/users:
get:
operationId: ingressGatewayUsers
@ -2287,6 +2673,49 @@ paths:
summary: Transfers superadmin role to an admin user.
tags:
- user
/api/users_pending:
get:
operationId: getPendingUsers
responses:
"200":
$ref: '#/responses/userBodyResponse'
schemes:
- https
summary: Get all pending users.
tags:
- user
/api/users_pending/{username}/pending:
delete:
operationId: deleteAllPendingUsers
responses:
"200":
$ref: '#/responses/userBodyResponse'
schemes:
- https
summary: delete all pending users.
tags:
- user
/api/users_pending/user/{username}:
delete:
operationId: deletePendingUser
responses:
"200":
$ref: '#/responses/userBodyResponse'
schemes:
- https
summary: delete pending user.
tags:
- user
post:
operationId: approvePendingUser
responses:
"200":
$ref: '#/responses/userBodyResponse'
schemes:
- https
summary: approve pending user.
tags:
- user
/api/v1/enrollment-keys:
get:
operationId: getEnrollmentKeys
@ -2300,6 +2729,13 @@ paths:
- enrollmentKeys
post:
operationId: createEnrollmentKey
parameters:
- description: APIEnrollmentKey
in: body
name: body
schema:
$ref: '#/definitions/APIEnrollmentKey'
x-go-name: Body
responses:
"200":
$ref: '#/responses/EnrollmentKey'
@ -2325,6 +2761,29 @@ paths:
summary: Deletes an EnrollmentKey from Netmaker server.
tags:
- enrollmentKeys
put:
operationId: updateEnrollmentKey
parameters:
- description: KeyID
in: path
name: keyid
required: true
type: string
x-go-name: KeyID
- description: APIEnrollmentKey
in: body
name: body
schema:
$ref: '#/definitions/APIEnrollmentKey'
x-go-name: Body
responses:
"200":
$ref: '#/responses/EnrollmentKey'
schemes:
- https
summary: Updates an EnrollmentKey for hosts to use on Netmaker server. Updates only the relay to use.
tags:
- enrollmentKeys
/api/v1/enrollment-keys/{token}:
post:
operationId: handleHostRegister
@ -2347,6 +2806,17 @@ paths:
summary: Handles a Netclient registration with server and add nodes accordingly.
tags:
- enrollmentKeys
/api/v1/fallback/host/{hostid}:
put:
operationId: hostUpdateFallback
responses:
"200":
$ref: '#/responses/apiHostResponse'
schemes:
- https
summary: Updates a Netclient host on Netmaker server.
tags:
- hosts
/api/v1/host:
get:
description: Used by clients for "pull" command
@ -2369,6 +2839,27 @@ paths:
summary: Delete all legacy nodes from DB.
tags:
- nodes
/api/v1/node/failover:
delete:
operationId: deletefailOver
responses:
"200":
$ref: '#/responses/nodeResponse'
schemes:
- https
summary: Create a relay.
tags:
- node
post:
operationId: createfailOver
responses:
"200":
$ref: '#/responses/nodeResponse'
schemes:
- https
summary: Create a relay.
tags:
- node
/api/v1/nodes/migrate:
put:
operationId: migrateData
@ -2423,6 +2914,12 @@ responses:
description: ""
schema:
$ref: '#/definitions/ApiHost'
apiHostSliceResponse:
description: ""
schema:
items:
$ref: '#/definitions/ApiHost'
type: array
byteArrayResponse:
description: ""
schema:
@ -2474,7 +2971,7 @@ responses:
description: ""
schema:
items:
$ref: '#/definitions/LegacyNode'
$ref: '#/definitions/ApiNode'
type: array
okResponse:
description: ""