Browse Source

Merge pull request #463 from gravitl/feature_v0.8.6_freebsd

Feature v0.8.6 freebsd
Alex 3 years ago
parent
commit
8ef885ca1d

+ 2 - 0
config/config.go

@@ -44,6 +44,7 @@ type ServerConfig struct {
 	GRPCPort              string `yaml:"grpcport"`
 	GRPCSecure            string `yaml:"grpcsecure"`
 	MasterKey             string `yaml:"masterkey"`
+	DNSKey                string `yaml:"dnskey"`
 	AllowedOrigin         string `yaml:"allowedorigin"`
 	NodeID                string `yaml:"nodeid"`
 	RestBackend           string `yaml:"restbackend"`
@@ -66,6 +67,7 @@ type ServerConfig struct {
 	ClientID              string `yaml:"clientid"`
 	ClientSecret          string `yaml:"clientsecret"`
 	FrontendURL           string `yaml:"frontendurl"`
+	DisplayKeys           string `yaml:"displaykeys"`
 }
 
 // Generic SQL Config

+ 48 - 8
controllers/dnsHttpController.go

@@ -3,6 +3,7 @@ package controller
 import (
 	"encoding/json"
 	"net/http"
+	"strings"
 
 	"github.com/go-playground/validator/v10"
 	"github.com/gorilla/mux"
@@ -14,14 +15,14 @@ import (
 
 func dnsHandlers(r *mux.Router) {
 
-	r.HandleFunc("/api/dns", securityCheck(true, http.HandlerFunc(getAllDNS))).Methods("GET")
-	r.HandleFunc("/api/dns/adm/{network}/nodes", securityCheck(false, http.HandlerFunc(getNodeDNS))).Methods("GET")
-	r.HandleFunc("/api/dns/adm/{network}/custom", securityCheck(false, http.HandlerFunc(getCustomDNS))).Methods("GET")
-	r.HandleFunc("/api/dns/adm/{network}", securityCheck(false, http.HandlerFunc(getDNS))).Methods("GET")
-	r.HandleFunc("/api/dns/{network}", securityCheck(false, http.HandlerFunc(createDNS))).Methods("POST")
-	r.HandleFunc("/api/dns/adm/pushdns", securityCheck(false, http.HandlerFunc(pushDNS))).Methods("POST")
-	r.HandleFunc("/api/dns/{network}/{domain}", securityCheck(false, http.HandlerFunc(deleteDNS))).Methods("DELETE")
-	r.HandleFunc("/api/dns/{network}/{domain}", securityCheck(false, http.HandlerFunc(updateDNS))).Methods("PUT")
+	r.HandleFunc("/api/dns", securityCheckDNS(true, true, http.HandlerFunc(getAllDNS))).Methods("GET")
+	r.HandleFunc("/api/dns/adm/{network}/nodes", securityCheckDNS(false, true, http.HandlerFunc(getNodeDNS))).Methods("GET")
+	r.HandleFunc("/api/dns/adm/{network}/custom", securityCheckDNS(false, true, http.HandlerFunc(getCustomDNS))).Methods("GET")
+	r.HandleFunc("/api/dns/adm/{network}", securityCheckDNS(false, true, http.HandlerFunc(getDNS))).Methods("GET")
+	r.HandleFunc("/api/dns/{network}", securityCheckDNS(false, false, http.HandlerFunc(createDNS))).Methods("POST")
+	r.HandleFunc("/api/dns/adm/pushdns", securityCheckDNS(false, false, http.HandlerFunc(pushDNS))).Methods("POST")
+	r.HandleFunc("/api/dns/{network}/{domain}", securityCheckDNS(false, false, http.HandlerFunc(deleteDNS))).Methods("DELETE")
+	r.HandleFunc("/api/dns/{network}/{domain}", securityCheckDNS(false, false, http.HandlerFunc(updateDNS))).Methods("PUT")
 }
 
 //Gets all nodes associated with network, including pending nodes
@@ -408,3 +409,42 @@ func ValidateDNSUpdate(change models.DNSEntry, entry models.DNSEntry) error {
 	}
 	return err
 }
+
+//Security check DNS is middleware for every DNS function and just checks to make sure that its the master or dns token calling
+//Only admin should have access to all these network-level actions
+//DNS token should have access to only read functions
+func securityCheckDNS(reqAdmin bool, allowDNSToken bool, next http.Handler) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var errorResponse = models.ErrorResponse{
+			Code: http.StatusUnauthorized, Message: "W1R3: It's not you it's me.",
+		}
+
+		var params = mux.Vars(r)
+		bearerToken := r.Header.Get("Authorization")
+		if allowDNSToken && authenticateDNSToken(bearerToken) {
+			r.Header.Set("user", "nameserver")
+			networks, _ := json.Marshal([]string{ALL_NETWORK_ACCESS})
+			r.Header.Set("networks", string(networks))
+			next.ServeHTTP(w, r)
+		} else {
+			err, networks, username := SecurityCheck(reqAdmin, params["networkname"], bearerToken)
+			if err != nil {
+				if strings.Contains(err.Error(), "does not exist") {
+					errorResponse.Code = http.StatusNotFound
+				}
+				errorResponse.Message = err.Error()
+				returnErrorResponse(w, r, errorResponse)
+				return
+			}
+			networksJson, err := json.Marshal(&networks)
+			if err != nil {
+				errorResponse.Message = err.Error()
+				returnErrorResponse(w, r, errorResponse)
+				return
+			}
+			r.Header.Set("user", username)
+			r.Header.Set("networks", string(networksJson))
+			next.ServeHTTP(w, r)
+		}
+	}
+}

+ 21 - 4
controllers/networkHttpController.go

@@ -20,6 +20,8 @@ import (
 
 const ALL_NETWORK_ACCESS = "THIS_USER_HAS_ALL"
 const NO_NETWORKS_PRESENT = "THIS_USER_HAS_NONE"
+const PLACEHOLDER_KEY_TEXT = "ACCESS_KEY"
+const PLACEHOLDER_TOKEN_TEXT = "ACCESS_TOKEN"
 
 func networkHandlers(r *mux.Router) {
 	r.HandleFunc("/api/networks", securityCheck(false, http.HandlerFunc(getNetworks))).Methods("GET")
@@ -114,10 +116,12 @@ func SecurityCheck(reqAdmin bool, netname string, token string) (error, []string
 
 //Consider a more secure way of setting master key
 func authenticateMaster(tokenString string) bool {
-	if tokenString == servercfg.GetMasterKey() {
-		return true
-	}
-	return false
+	return tokenString == servercfg.GetMasterKey()
+}
+
+//Consider a more secure way of setting master key
+func authenticateDNSToken(tokenString string) bool {
+	return tokenString == servercfg.GetDNSKey()
 }
 
 //simple get all networks function
@@ -572,6 +576,9 @@ func getAccessKeys(w http.ResponseWriter, r *http.Request) {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
+	if !servercfg.IsDisplayKeys() {
+		keys = RemoveKeySensitiveInfo(keys)
+	}
 	functions.PrintUserLog(r.Header.Get("user"), "fetched access keys on network "+network, 2)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(keys)
@@ -633,3 +640,13 @@ func DeleteKey(keyname, netname string) error {
 
 	return nil
 }
+
+func RemoveKeySensitiveInfo(keys []models.AccessKey) []models.AccessKey {
+	var returnKeys []models.AccessKey
+	for _, key := range keys {
+		key.Value = PLACEHOLDER_KEY_TEXT
+		key.AccessString = PLACEHOLDER_TOKEN_TEXT
+		returnKeys = append(returnKeys, key)
+	}
+	return returnKeys
+}

+ 19 - 4
controllers/nodeGrpcController.go

@@ -29,10 +29,14 @@ func (s *NodeServiceServer) ReadNode(ctx context.Context, req *nodepb.Object) (*
 	if err != nil {
 		return nil, err
 	}
+	node.NetworkSettings, err = logic.GetNetworkSettings(node.Network)
+	if err != nil {
+		return nil, err
+	}
 	node.SetLastCheckIn()
 	// Cast to ReadNodeRes type
-	nodeData, err := json.Marshal(&node)
-	if err != nil {
+	nodeData, errN := json.Marshal(&node)
+	if errN != nil {
 		return nil, err
 	}
 	logic.UpdateNode(&node, &node)
@@ -75,7 +79,14 @@ func (s *NodeServiceServer) CreateNode(ctx context.Context, req *nodepb.Object)
 	if err != nil {
 		return nil, err
 	}
-	nodeData, err := json.Marshal(&node)
+	node.NetworkSettings, err = logic.GetNetworkSettings(node.Network)
+	if err != nil {
+		return nil, err
+	}
+	nodeData, errN := json.Marshal(&node)
+	if errN != nil {
+		return nil, err
+	}
 	// return the node in a CreateNodeRes type
 	response := &nodepb.Object{
 		Data: string(nodeData),
@@ -107,10 +118,14 @@ func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.Object)
 	if err != nil {
 		return nil, err
 	}
-	nodeData, err := json.Marshal(&newnode)
+	newnode.NetworkSettings, err = logic.GetNetworkSettings(node.Network)
 	if err != nil {
 		return nil, err
 	}
+	nodeData, errN := json.Marshal(&newnode)
+	if errN != nil {
+		return nil, err
+	}
 	return &nodepb.Object{
 		Data: string(nodeData),
 		Type: nodepb.NODE_TYPE,

+ 1 - 1
controllers/relay.go

@@ -37,7 +37,7 @@ func createRelay(w http.ResponseWriter, r *http.Request) {
 // CreateRelay - creates a relay
 func CreateRelay(relay models.RelayRequest) (models.Node, error) {
 	node, err := logic.GetNodeByMacAddress(relay.NetID, relay.NodeID)
-	if node.OS == "windows" || node.OS == "macos" { // add in darwin later
+	if node.OS == "macos" { // add in darwin later
 		return models.Node{}, errors.New(node.OS + " is unsupported for relay")
 	}
 	if err != nil {

+ 1 - 0
go.mod

@@ -3,6 +3,7 @@ module github.com/gravitl/netmaker
 go 1.15
 
 require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/go-playground/validator/v10 v10.9.0
 	github.com/golang-jwt/jwt/v4 v4.1.0
 	github.com/golang/protobuf v1.5.2 // indirect

+ 15 - 0
logic/networks.go

@@ -51,6 +51,21 @@ func GetParentNetwork(networkname string) (models.Network, error) {
 	return network, nil
 }
 
+// GetParentNetwork - get parent network
+func GetNetworkSettings(networkname string) (models.Network, error) {
+
+	var network models.Network
+	networkData, err := database.FetchRecord(database.NETWORKS_TABLE_NAME, networkname)
+	if err != nil {
+		return network, err
+	}
+	if err = json.Unmarshal([]byte(networkData), &network); err != nil {
+		return models.Network{}, err
+	}
+	network.AccessKeys = []models.AccessKey{}
+	return network, nil
+}
+
 // UniqueAddress - see if address is unique
 func UniqueAddress(networkName string) (string, error) {
 

+ 1 - 1
logic/server.go

@@ -136,7 +136,7 @@ func ServerJoin(network string, serverID string, privateKey string) error {
 		return err
 	}
 
-	err = initWireguard(node, privateKey, peers, hasGateway, gateways)
+	err = initWireguard(node, privateKey, peers, hasGateway, gateways, 0)
 	if err != nil {
 		return err
 	}

+ 36 - 27
logic/wireguard.go

@@ -45,32 +45,9 @@ func RemoveConf(iface string, printlog bool) error {
 	return err
 }
 
-// == Private Methods ==
+// Private Functions
 
-func setWGConfig(node models.Node, network string, peerupdate bool) error {
-
-	node.SetID()
-	peers, hasGateway, gateways, err := GetServerPeers(node.MacAddress, node.Network, node.IsDualStack == "yes", node.IsIngressGateway == "yes")
-	if err != nil {
-		return err
-	}
-	privkey, err := FetchPrivKey(node.ID)
-	if err != nil {
-		return err
-	}
-	if peerupdate {
-		var iface string
-		iface = node.Interface
-		err = setServerPeers(iface, node.PersistentKeepalive, peers)
-		Log("updated peers on server "+node.Name, 2)
-	} else {
-		err = initWireguard(&node, privkey, peers, hasGateway, gateways)
-		Log("finished setting wg config on server "+node.Name, 3)
-	}
-	return err
-}
-
-func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string) error {
+func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string, fwmark int32) error {
 
 	key, err := wgtypes.ParseKey(privkey)
 	if err != nil {
@@ -108,7 +85,7 @@ func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 
 	if !ncutils.IsKernel() {
 		var newConf string
-		newConf, _ = ncutils.CreateUserSpaceConf(node.Address, key.String(), strconv.FormatInt(int64(node.ListenPort), 10), node.MTU, node.PersistentKeepalive, peers)
+		newConf, _ = ncutils.CreateUserSpaceConf(node.Address, key.String(), strconv.FormatInt(int64(node.ListenPort), 10), node.MTU, fwmark, node.PersistentKeepalive, peers)
 		confPath := ncutils.GetNetclientPathSpecific() + ifacename + ".conf"
 		Log("writing wg conf file to: "+confPath, 1)
 		err = ioutil.WriteFile(confPath, []byte(newConf), 0644)
@@ -116,6 +93,16 @@ func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 			Log("error writing wg conf file to "+confPath+": "+err.Error(), 1)
 			return err
 		}
+		if ncutils.IsWindows() {
+			wgConfPath := ncutils.GetWGPathSpecific() + ifacename + ".conf"
+			Log("writing wg conf file to: "+confPath, 1)
+			err = ioutil.WriteFile(wgConfPath, []byte(newConf), 0644)
+			if err != nil {
+				Log("error writing wg conf file to "+wgConfPath+": "+err.Error(), 1)
+				return err
+			}
+			confPath = wgConfPath
+		}
 		// spin up userspace + apply the conf file
 		var deviceiface = ifacename
 		d, _ := wgclient.Device(deviceiface)
@@ -163,7 +150,7 @@ func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 
 		if node.PostDown != "" {
 			runcmds := strings.Split(node.PostDown, "; ")
-			_ = ncutils.RunCmds(runcmds, true)
+			_ = ncutils.RunCmds(runcmds, false)
 		}
 		// set MTU of node interface
 		if _, err := ncutils.RunCmd(ipExec+" link set mtu "+strconv.Itoa(int(node.MTU))+" up dev "+ifacename, true); err != nil {
@@ -290,6 +277,28 @@ func setServerPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) e
 	return nil
 }
 
+func setWGConfig(node models.Node, network string, peerupdate bool) error {
+
+	node.SetID()
+	peers, hasGateway, gateways, err := GetServerPeers(node.MacAddress, node.Network, node.IsDualStack == "yes", node.IsIngressGateway == "yes")
+	if err != nil {
+		return err
+	}
+	privkey, err := FetchPrivKey(node.ID)
+	if err != nil {
+		return err
+	}
+	if peerupdate {
+		var iface string = node.Interface
+		err = setServerPeers(iface, node.PersistentKeepalive, peers)
+		Log("updated peers on server "+node.Name, 2)
+	} else {
+		err = initWireguard(&node, privkey, peers, hasGateway, gateways, 0)
+		Log("finished setting wg config on server "+node.Name, 3)
+	}
+	return err
+}
+
 func setWGKeyConfig(node models.Node) error {
 
 	node.SetID()

+ 3 - 1
models/node.go

@@ -13,6 +13,7 @@ import (
 
 const charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
 const TEN_YEARS_IN_SECONDS = 300000000
+const MAX_NAME_LENGTH = 62
 
 // == ACTIONS == (can only be set by GRPC)
 const NODE_UPDATE_KEY = "updatekey"
@@ -30,7 +31,8 @@ type Node struct {
 	Address             string   `json:"address" bson:"address" yaml:"address" validate:"omitempty,ipv4"`
 	Address6            string   `json:"address6" bson:"address6" yaml:"address6" validate:"omitempty,ipv6"`
 	LocalAddress        string   `json:"localaddress" bson:"localaddress" yaml:"localaddress" validate:"omitempty,ip"`
-	Name                string   `json:"name" bson:"name" yaml:"name" validate:"omitempty,max=32,in_charset"`
+	Name                string   `json:"name" bson:"name" yaml:"name" validate:"omitempty,max=62,in_charset"`
+	NetworkSettings     Network  `json:"networksettings" bson:"networksettings" yaml:"networksettings" validate:"-"`
 	ListenPort          int32    `json:"listenport" bson:"listenport" yaml:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
 	PublicKey           string   `json:"publickey" bson:"publickey" yaml:"publickey" validate:"required,base64"`
 	Endpoint            string   `json:"endpoint" bson:"endpoint" yaml:"endpoint" validate:"required,ip"`

+ 10 - 6
netclient/config/config.go

@@ -24,12 +24,14 @@ type GlobalConfig struct {
 
 // ClientConfig - struct for dealing with client configuration
 type ClientConfig struct {
-	Server          ServerConfig `yaml:"server"`
-	Node            models.Node  `yaml:"node"`
-	Network         string       `yaml:"network"`
-	Daemon          string       `yaml:"daemon"`
-	OperatingSystem string       `yaml:"operatingsystem"`
-	DebugJoin       bool         `yaml:"debugjoin"`
+	Server          ServerConfig   `yaml:"server"`
+	Node            models.Node    `yaml:"node"`
+	NetworkSettings models.Network `yaml:"networksettings"`
+	Network         string         `yaml:"network"`
+	Daemon          string         `yaml:"daemon"`
+	OperatingSystem string         `yaml:"operatingsystem"`
+	DebugJoin       bool           `yaml:"debugjoin"`
+	FWMark          int32          `yaml:"fwmark"`
 }
 
 // ServerConfig - struct for dealing with the server information for a netclient
@@ -191,6 +193,7 @@ func (config *ClientConfig) ReadConfig() {
 // ModConfig - overwrites the node inside client config on disk
 func ModConfig(node *models.Node) error {
 	network := node.Network
+	networksettings := node.NetworkSettings
 	if network == "" {
 		return errors.New("no network provided")
 	}
@@ -205,6 +208,7 @@ func ModConfig(node *models.Node) error {
 	}
 
 	modconfig.Node = (*node)
+	modconfig.NetworkSettings = (networksettings)
 	err = Write(&modconfig, network)
 	return err
 }

+ 3 - 2
netclient/daemon/macos.go

@@ -2,11 +2,12 @@ package daemon
 
 import (
 	"fmt"
-	"github.com/gravitl/netmaker/netclient/ncutils"
 	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
+
+	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
 const MAC_SERVICE_NAME = "com.gravitl.netclient"
@@ -20,7 +21,7 @@ func SetupMacDaemon(interval string) error {
 	binarypath := dir + "/netclient"
 
 	if !ncutils.FileExists("/etc/netclient/netclient") {
-		_, err = ncutils.Copy(binarypath, "/etc/netclient/netclient")
+		err = ncutils.Copy(binarypath, "/etc/netclient/netclient")
 		if err != nil {
 			log.Println(err)
 			return err

+ 1 - 1
netclient/daemon/systemd.go

@@ -35,7 +35,7 @@ func SetupSystemDDaemon(interval string) error {
 		os.Symlink("/etc/netclient/netclient", "/usr/local/bin/netclient")
 	}
 	if !ncutils.FileExists("/etc/netclient/netclient") {
-		_, err = ncutils.Copy(binarypath, "/etc/netclient/netclient")
+		err = ncutils.Copy(binarypath, "/etc/netclient/netclient")
 		if err != nil {
 			log.Println(err)
 			return err

+ 1 - 1
netclient/functions/checkin.go

@@ -234,7 +234,7 @@ func Pull(network string, manual bool) (*models.Node, error) {
 		}
 	} else {
 		if err = wireguard.SetWGConfig(network, true); err != nil {
-			if errors.Is(err, os.ErrNotExist) {
+			if errors.Is(err, os.ErrNotExist) && !ncutils.IsFreeBSD() {
 				return Pull(network, true)
 			} else {
 				return nil, err

+ 10 - 0
netclient/functions/common.go

@@ -206,6 +206,16 @@ func LeaveNetwork(network string) error {
 			}
 		}
 	}
+	//extra network route setting required for freebsd and windows
+	if ncutils.IsWindows() {
+		ip, mask, err := ncutils.GetNetworkIPMask(node.NetworkSettings.AddressRange)
+		if err != nil {
+			ncutils.PrintLog(err.Error(), 1)
+		}
+		_, _ = ncutils.RunCmd("route delete "+ip+" mask "+mask+" "+node.Address, true)
+	} else if ncutils.IsFreeBSD() {
+		_, _ = ncutils.RunCmd("route del -net "+node.NetworkSettings.AddressRange+" -interface "+node.Interface, true)
+	}
 	return RemoveLocalInstance(cfg, network)
 }
 

+ 38 - 2
netclient/functions/join.go

@@ -6,7 +6,9 @@ import (
 	"errors"
 	"fmt"
 	"log"
+	"math/rand"
 	"os/exec"
+	"time"
 
 	nodepb "github.com/gravitl/netmaker/grpc"
 	"github.com/gravitl/netmaker/models"
@@ -23,7 +25,6 @@ import (
 
 // JoinNetwork - helps a client join a network
 func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
-
 	if cfg.Node.Network == "" {
 		return errors.New("no network provided")
 	}
@@ -34,6 +35,13 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 			err := errors.New("ALREADY_INSTALLED. Netclient appears to already be installed for " + cfg.Network + ". To re-install, please remove by executing 'sudo netclient leave -n " + cfg.Network + "'. Then re-run the install command.")
 			return err
 		}
+		if cfg.FWMark == 0 {
+			rand.Seed(time.Now().UnixNano())
+			var min int32 = 1000
+			var max int32 = 9999
+			cfg.FWMark = rand.Int31n(max-min) + min
+		}
+
 		err = config.Write(&cfg, cfg.Network)
 		if err != nil {
 			return err
@@ -91,12 +99,18 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 		}
 	}
 
+	if ncutils.IsFreeBSD() {
+		cfg.Node.UDPHolePunch = "no"
+	}
+	// make sure name is appropriate, if not, give blank name
+	cfg.Node.Name = formatName(cfg.Node)
 	// differentiate between client/server here
 	var node models.Node // fill this node with appropriate calls
 	postnode := &models.Node{
 		Password:            cfg.Node.Password,
 		MacAddress:          cfg.Node.MacAddress,
 		AccessKey:           cfg.Server.AccessKey,
+		IsStatic:            cfg.Node.IsStatic,
 		Network:             cfg.Network,
 		ListenPort:          cfg.Node.ListenPort,
 		PostUp:              cfg.Node.PostUp,
@@ -165,6 +179,11 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 		}
 		node.Endpoint = node.LocalAddress
 	}
+	if ncutils.IsFreeBSD() {
+		node.UDPHolePunch = "no"
+		cfg.Node.IsStatic = "yes"
+	}
+
 	if node.IsServer != "yes" { // == handle client side ==
 		err = config.ModConfig(&node)
 		if err != nil {
@@ -200,7 +219,7 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 	}
 
 	ncutils.Log("starting wireguard")
-	err = wireguard.InitWireguard(&node, privateKey, peers, hasGateway, gateways)
+	err = wireguard.InitWireguard(&node, privateKey, peers, hasGateway, gateways, false)
 	if err != nil {
 		return err
 	}
@@ -213,3 +232,20 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 
 	return err
 }
+
+// format name appropriately. Set to blank on failure
+func formatName(node models.Node) string {
+	// Logic to properly format name
+	if !node.NameInNodeCharSet() {
+		node.Name = ncutils.DNSFormatString(node.Name)
+	}
+	if len(node.Name) > models.MAX_NAME_LENGTH {
+		node.Name = ncutils.ShortenString(node.Name, models.MAX_NAME_LENGTH)
+	}
+	if !node.NameInNodeCharSet() || len(node.Name) > models.MAX_NAME_LENGTH {
+		ncutils.PrintLog("could not properly format name: "+node.Name, 1)
+		ncutils.PrintLog("setting name to blank", 1)
+		node.Name = ""
+	}
+	return node.Name
+}

+ 23 - 2
netclient/local/local.go

@@ -19,7 +19,9 @@ func SetIPForwarding() error {
 	var err error
 	switch os {
 	case "linux":
-		err = SetIPForwardingLinux()
+		err = SetIPForwardingUnix()
+	case "freebsd":
+		err = SetIPForwardingFreeBSD()
 	case "darwin":
 		err = SetIPForwardingMac()
 	default:
@@ -29,7 +31,7 @@ func SetIPForwarding() error {
 }
 
 // SetIPForwardingLinux - sets the ipforwarding for linux
-func SetIPForwardingLinux() error {
+func SetIPForwardingUnix() error {
 	out, err := ncutils.RunCmd("sysctl net.ipv4.ip_forward", true)
 	if err != nil {
 		log.Println("WARNING: Error encountered setting ip forwarding. This can break functionality.")
@@ -47,6 +49,25 @@ func SetIPForwardingLinux() error {
 	return nil
 }
 
+// SetIPForwardingLinux - sets the ipforwarding for linux
+func SetIPForwardingFreeBSD() error {
+	out, err := ncutils.RunCmd("sysctl net.inet.ip.forwarding", true)
+	if err != nil {
+		log.Println("WARNING: Error encountered setting ip forwarding. This can break functionality.")
+		return err
+	} else {
+		s := strings.Fields(string(out))
+		if s[1] != "1" {
+			_, err = ncutils.RunCmd("sysctl -w net.inet.ip.forwarding=1", true)
+			if err != nil {
+				log.Println("WARNING: Error encountered setting ip forwarding. You may want to investigate this.")
+				return err
+			}
+		}
+	}
+	return nil
+}
+
 // SetIPForwardingMac - sets ip forwarding for mac
 func SetIPForwardingMac() error {
 	_, err := ncutils.RunCmd("sysctl -w net.inet.ip.forwarding=1", true)

+ 0 - 2
netclient/main.go

@@ -10,7 +10,6 @@ import (
 	"os/signal"
 	"runtime/debug"
 	"strconv"
-	"strings"
 	"syscall"
 
 	"github.com/gravitl/netmaker/netclient/command"
@@ -31,7 +30,6 @@ func main() {
 	if err != nil {
 		hostname = ""
 	}
-	hostname = strings.Split(hostname, ".")[0]
 
 	cliFlags := []cli.Flag{
 		&cli.StringFlag{

+ 62 - 11
netclient/ncutils/netclientutils.go

@@ -12,6 +12,7 @@ import (
 	"net/http"
 	"os"
 	"os/exec"
+	"regexp"
 	"runtime"
 	"strconv"
 	"strings"
@@ -35,6 +36,9 @@ const LINUX_APP_DATA_PATH = "/etc/netclient"
 // WINDOWS_APP_DATA_PATH - windows path
 const WINDOWS_APP_DATA_PATH = "C:\\ProgramData\\Netclient"
 
+// WINDOWS_APP_DATA_PATH - windows path
+const WINDOWS_WG_DATA_PATH = "C:\\Program Files\\WireGuard\\Data\\Configurations"
+
 // WINDOWS_SVC_NAME - service name
 const WINDOWS_SVC_NAME = "netclient"
 
@@ -65,6 +69,11 @@ func IsLinux() bool {
 	return runtime.GOOS == "linux"
 }
 
+// IsLinux - checks if is linux
+func IsFreeBSD() bool {
+	return runtime.GOOS == "freebsd"
+}
+
 // GetWireGuard - checks if wg is installed
 func GetWireGuard() string {
 	userspace := os.Getenv("WG_QUICK_USERSPACE_IMPLEMENTATION")
@@ -182,15 +191,19 @@ PersistentKeepAlive = %s
 }
 
 // CreateUserSpaceConf - creates a user space WireGuard conf
-func CreateUserSpaceConf(address string, privatekey string, listenPort string, mtu int32, perskeepalive int32, peers []wgtypes.PeerConfig) (string, error) {
+func CreateUserSpaceConf(address string, privatekey string, listenPort string, mtu int32, fwmark int32, perskeepalive int32, peers []wgtypes.PeerConfig) (string, error) {
 	peersString, err := parsePeers(perskeepalive, peers)
-	listenPortString := ""
+	var listenPortString string
+	var fwmarkString string
 	if mtu <= 0 {
 		mtu = 1280
 	}
 	if listenPort != "" {
 		listenPortString += "ListenPort = " + listenPort
 	}
+	if fwmark != 0 {
+		fwmarkString += "FWMark = " + strconv.Itoa(int(fwmark))
+	}
 	if err != nil {
 		return "", err
 	}
@@ -199,6 +212,7 @@ Address = %s
 PrivateKey = %s
 MTU = %s
 %s
+%s
 
 %s
 
@@ -207,6 +221,7 @@ MTU = %s
 		privatekey,
 		strconv.Itoa(int(mtu)),
 		listenPortString,
+		fwmarkString,
 		peersString)
 	return config, nil
 }
@@ -258,6 +273,16 @@ func GetLocalIP(localrange string) (string, error) {
 	return local, nil
 }
 
+func GetNetworkIPMask(networkstring string) (string, string, error) {
+	ip, ipnet, err := net.ParseCIDR(networkstring)
+	if err != nil {
+		return "", "", err
+	}
+	ipstring := ip.String()
+	maskstring := ipnet.Mask.String()
+	return ipstring, maskstring, err
+}
+
 // GetFreePort - gets free port of machine
 func GetFreePort(rangestart int32) (int32, error) {
 	if rangestart == 0 {
@@ -324,6 +349,15 @@ func GetNetclientPathSpecific() string {
 	}
 }
 
+// GetNetclientPathSpecific - gets specific netclient config path
+func GetWGPathSpecific() string {
+	if IsWindows() {
+		return WINDOWS_WG_DATA_PATH + "\\"
+	} else {
+		return "/etc/wireguard/"
+	}
+}
+
 // GRPCRequestOpts - gets grps request opts
 func GRPCRequestOpts(isSecure string) grpc.DialOption {
 	var requestOpts grpc.DialOption
@@ -336,33 +370,33 @@ func GRPCRequestOpts(isSecure string) grpc.DialOption {
 }
 
 // Copy - copies a src file to dest
-func Copy(src, dst string) (int64, error) {
+func Copy(src, dst string) error {
 	sourceFileStat, err := os.Stat(src)
 	if err != nil {
-		return 0, err
+		return err
 	}
 
 	if !sourceFileStat.Mode().IsRegular() {
-		return 0, errors.New(src + " is not a regular file")
+		return errors.New(src + " is not a regular file")
 	}
 
 	source, err := os.Open(src)
 	if err != nil {
-		return 0, err
+		return err
 	}
 	defer source.Close()
 
 	destination, err := os.Create(dst)
 	if err != nil {
-		return 0, err
+		return err
 	}
 	defer destination.Close()
-	nBytes, err := io.Copy(destination, source)
-	err = os.Chmod(dst, 0755)
+	_, err = io.Copy(destination, source)
 	if err != nil {
-		log.Println(err)
+		return err
 	}
-	return nBytes, err
+	err = os.Chmod(dst, 0755)
+	return err
 }
 
 // RunCmd - runs a local command
@@ -443,3 +477,20 @@ func stringAfter(original string, substring string) string {
 	}
 	return original[adjustedPosition:]
 }
+
+func ShortenString(input string, length int) string {
+	output := input
+	if len(input) > length {
+		output = input[0:length]
+	}
+	return output
+}
+
+func DNSFormatString(input string) string {
+	reg, err := regexp.Compile("[^a-zA-Z0-9-]+")
+	if err != nil {
+		Log("error with regex: " + err.Error())
+		return ""
+	}
+	return reg.ReplaceAllString(input, "")
+}

+ 46 - 0
netclient/ncutils/netclientutils_freebsd.go

@@ -0,0 +1,46 @@
+package ncutils
+
+import (
+	"context"
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"math/rand"
+	"net"
+	"net/http"
+	"os"
+	"os/exec"
+	"regexp"
+	"runtime"
+	"strconv"
+	"strings"
+	"syscall"
+	"time"
+
+	"golang.zx2c4.com/wireguard/wgctrl"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+)
+
+// Runs Commands for FreeBSD
+func RunCmd(command string, printerr bool) (string, error) {
+	args := strings.Fields(command)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+	cmd := exec.Command(args[0], args[1:]...)
+	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+	go func() {
+		<-ctx.Done()
+		_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
+	}()
+	out, err := cmd.CombinedOutput()
+	if err != nil && printerr {
+		log.Println("error running command:", command)
+		log.Println(strings.TrimSuffix(string(out), "\n"))
+	}
+	return string(out), err
+}

+ 95 - 0
netclient/ncutils/peerhelper.go

@@ -0,0 +1,95 @@
+package ncutils
+
+import (
+	"strconv"
+	"strings"
+	"net"
+	"time"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+)
+
+func GetPeers(iface string) ([]wgtypes.Peer, error) {
+
+	var peers []wgtypes.Peer
+	output, err := RunCmd("wg show "+iface+" dump",true)
+	if err != nil {
+		return peers, err
+	}
+	for i, line := range strings.Split(strings.TrimSuffix(output, "\n"), "\n") {
+		if i == 0 {
+			continue
+		}
+		var allowedIPs []net.IPNet
+		fields := strings.Fields(line)
+		if len(fields) < 4 {
+			Log("error parsing peer: "+line)
+			continue
+		}
+		pubkeystring := fields[0]
+		endpointstring := fields[2]
+		allowedipstring := fields[3]
+		var pkeepalivestring string
+		if len(fields) > 7 {
+			pkeepalivestring = fields[7]
+		}
+		// AllowedIPs = private IP + defined networks
+
+		pubkey, err := wgtypes.ParseKey(pubkeystring)
+		if err != nil {
+			Log("error parsing peer key "+pubkeystring)
+			continue
+		}
+		ipstrings := strings.Split(allowedipstring, ",")
+		for _, ipstring := range ipstrings {
+			var netip net.IP
+			if netip = net.ParseIP(strings.Split(ipstring,"/")[0]); netip != nil {
+				allowedIPs = append(
+					allowedIPs,
+					net.IPNet{
+						IP:   netip,
+						Mask: netip.DefaultMask(),
+					},
+				)
+			}
+		}
+		if len(allowedIPs) == 0 {
+			Log("error parsing peer "+pubkeystring+", no allowedips found")
+			continue
+		}
+		var endpointarr []string
+		var endpointip net.IP
+		if endpointarr = strings.Split(endpointstring,":"); len(endpointarr) != 2 {
+			Log("error parsing peer "+pubkeystring+", could not parse endpoint: "+endpointstring)
+			continue
+		}
+		if endpointip = net.ParseIP(endpointarr[0]); endpointip == nil {
+			Log("error parsing peer "+pubkeystring+", could not parse endpoint: "+endpointarr[0])
+			continue
+		}
+		var port int
+		if port, err = strconv.Atoi(endpointarr[1]); err != nil {
+			Log("error parsing peer "+pubkeystring+", could not parse port: "+err.Error())
+			continue
+		}
+		var endpoint = net.UDPAddr {
+			IP: endpointip,
+			Port: port,
+		}
+		var dur time.Duration
+		if pkeepalivestring != "" {
+			if dur, err = time.ParseDuration(pkeepalivestring+"s"); err != nil {
+				Log("error parsing peer "+pubkeystring+", could not parse keepalive: "+err.Error())
+			}
+		}
+
+
+		peers = append(peers, wgtypes.Peer{
+			PublicKey:         pubkey,
+			Endpoint:          &endpoint,
+			AllowedIPs:        allowedIPs,
+			PersistentKeepaliveInterval: dur,
+		})
+	}
+
+	return peers, err
+}

+ 64 - 29
netclient/wireguard/common.go

@@ -23,23 +23,29 @@ import (
 // SetPeers - sets peers on a given WireGuard interface
 func SetPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) error {
 
-	client, err := wgctrl.New()
-	if err != nil {
-		ncutils.PrintLog("failed to start wgctrl", 0)
-		return err
-	}
-
-	device, err := client.Device(iface)
-	if err != nil {
-		ncutils.PrintLog("failed to parse interface", 0)
-		return err
+	var devicePeers []wgtypes.Peer
+	var err error
+	if ncutils.IsFreeBSD() {
+		if devicePeers, err = ncutils.GetPeers(iface); err != nil {
+			return err
+		}
+	} else {
+		client, err := wgctrl.New()
+		if err != nil {
+			ncutils.PrintLog("failed to start wgctrl", 0)
+			return err
+		}
+		device, err := client.Device(iface)
+		if err != nil {
+			ncutils.PrintLog("failed to parse interface", 0)
+			return err
+		}
+		devicePeers = device.Peers
 	}
-	devicePeers := device.Peers
 	if len(devicePeers) > 1 && len(peers) == 0 {
 		ncutils.PrintLog("no peers pulled", 1)
 		return err
 	}
-
 	for _, peer := range peers {
 
 		for _, currentPeer := range devicePeers {
@@ -60,7 +66,7 @@ func SetPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) error {
 		allowedips = strings.Join(iparr, ",")
 		keepAliveString := strconv.Itoa(int(keepalive))
 		if keepAliveString == "0" {
-			keepAliveString = "5"
+			keepAliveString = "15"
 		}
 		if peer.Endpoint != nil {
 			_, err = ncutils.RunCmd("wg set "+iface+" peer "+peer.PublicKey.String()+
@@ -96,7 +102,7 @@ func SetPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) error {
 }
 
 // Initializes a WireGuard interface
-func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string) error {
+func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string, syncconf bool) error {
 
 	key, err := wgtypes.ParseKey(privkey)
 	if err != nil {
@@ -112,7 +118,8 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 	if err != nil {
 		return err
 	}
-
+	fwmarkint32 := modcfg.FWMark
+	fwmarkint := int(fwmarkint32)
 	nodecfg := modcfg.Node
 	servercfg := modcfg.Server
 
@@ -160,6 +167,7 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 		conf = wgtypes.Config{
 			PrivateKey:   &key,
 			ListenPort:   &nodeport,
+			FirewallMark: &fwmarkint,
 			ReplacePeers: true,
 			Peers:        peers,
 		}
@@ -167,9 +175,9 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 	if !ncutils.IsKernel() {
 		var newConf string
 		if node.UDPHolePunch != "yes" {
-			newConf, _ = ncutils.CreateUserSpaceConf(node.Address, key.String(), strconv.FormatInt(int64(node.ListenPort), 10), node.MTU, node.PersistentKeepalive, peers)
+			newConf, _ = ncutils.CreateUserSpaceConf(node.Address, key.String(), strconv.FormatInt(int64(node.ListenPort), 10), node.MTU, fwmarkint32, node.PersistentKeepalive, peers)
 		} else {
-			newConf, _ = ncutils.CreateUserSpaceConf(node.Address, key.String(), "", node.MTU, node.PersistentKeepalive, peers)
+			newConf, _ = ncutils.CreateUserSpaceConf(node.Address, key.String(), "", node.MTU, fwmarkint32, node.PersistentKeepalive, peers)
 		}
 		confPath := ncutils.GetNetclientPathSpecific() + ifacename + ".conf"
 		ncutils.PrintLog("writing wg conf file to: "+confPath, 1)
@@ -178,6 +186,16 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 			ncutils.PrintLog("error writing wg conf file to "+confPath+": "+err.Error(), 1)
 			return err
 		}
+		if ncutils.IsWindows() {
+			wgConfPath := ncutils.GetWGPathSpecific() + ifacename + ".conf"
+			ncutils.PrintLog("error writing wg conf file to "+confPath+": "+err.Error(), 1)
+			err = ioutil.WriteFile(wgConfPath, []byte(newConf), 0644)
+			if err != nil {
+				ncutils.PrintLog("error writing wg conf file to "+confPath+": "+err.Error(), 1)
+				return err
+			}
+			confPath = wgConfPath
+		}
 		// spin up userspace / windows interface + apply the conf file
 		var deviceiface string
 		if ncutils.IsMac() {
@@ -186,16 +204,20 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 				deviceiface = ifacename
 			}
 		}
-		d, _ := wgclient.Device(deviceiface)
-		for d != nil && d.Name == deviceiface {
-			_ = RemoveConf(ifacename, false) // remove interface first
-			time.Sleep(time.Second >> 2)
-			d, _ = wgclient.Device(deviceiface)
-		}
-		err = ApplyConf(confPath)
-		if err != nil {
-			ncutils.PrintLog("failed to create wireguard interface", 1)
-			return err
+		if syncconf {
+			err = SyncWGQuickConf(ifacename, confPath)
+		} else {
+			d, _ := wgclient.Device(deviceiface)
+			for d != nil && d.Name == deviceiface {
+				_ = RemoveConf(ifacename, false) // remove interface first
+				time.Sleep(time.Second >> 2)
+				d, _ = wgclient.Device(deviceiface)
+			}
+			err = ApplyConf(confPath)
+			if err != nil {
+				ncutils.PrintLog("failed to create wireguard interface", 1)
+				return err
+			}
 		}
 	} else {
 		ipExec, err := exec.LookPath("ip")
@@ -258,6 +280,17 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 		}
 	}
 
+	//extra network route setting required for freebsd and windows
+	if ncutils.IsWindows() {
+		ip, mask, err := ncutils.GetNetworkIPMask(nodecfg.NetworkSettings.AddressRange)
+		if err != nil {
+			return err
+		}
+		_, _ = ncutils.RunCmd("route add "+ip+" mask "+mask+" "+node.Address, true)
+	} else if ncutils.IsFreeBSD() {
+		_, _ = ncutils.RunCmd("route add -net "+nodecfg.NetworkSettings.AddressRange+" -interface "+ifacename, true)
+	}
+
 	return err
 }
 
@@ -279,7 +312,7 @@ func SetWGConfig(network string, peerupdate bool) error {
 	if err != nil {
 		return err
 	}
-	if peerupdate {
+	if peerupdate && !ncutils.IsFreeBSD() {
 		var iface string
 		iface = nodecfg.Interface
 		if ncutils.IsMac() {
@@ -289,8 +322,10 @@ func SetWGConfig(network string, peerupdate bool) error {
 			}
 		}
 		err = SetPeers(iface, nodecfg.PersistentKeepalive, peers)
+	} else if peerupdate {
+		err = InitWireguard(&nodecfg, privkey, peers, hasGateway, gateways, true)
 	} else {
-		err = InitWireguard(&nodecfg, privkey, peers, hasGateway, gateways)
+		err = InitWireguard(&nodecfg, privkey, peers, hasGateway, gateways, false)
 	}
 	return err
 }

+ 32 - 6
netclient/wireguard/unix.go

@@ -2,6 +2,9 @@ package wireguard
 
 import (
 	"io/ioutil"
+	"log"
+	"os"
+	"regexp"
 
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/config"
@@ -50,18 +53,41 @@ func SetWGKeyConfig(network string, serveraddr string) error {
 
 // ApplyWGQuickConf - applies wg-quick commands if os supports
 func ApplyWGQuickConf(confPath string) error {
-	if _, err := ncutils.RunCmd("wg-quick up "+confPath, true); err != nil {
+	_, _ = ncutils.RunCmd("wg-quick down "+confPath, false)
+	_, err := ncutils.RunCmd("wg-quick up "+confPath, false)
+	return err
+}
+
+// SyncWGQuickConf - formats config file and runs sync command
+func SyncWGQuickConf(iface string, confPath string) error {
+	var tmpConf = confPath + ".sync.tmp"
+	confRaw, err := ncutils.RunCmd("wg-quick strip "+confPath, false)
+	if err != nil {
+		return err
+	}
+	regex := regexp.MustCompile(".*Warning.*\n")
+	conf := regex.ReplaceAllString(confRaw, "")
+	err = ioutil.WriteFile(tmpConf, []byte(conf), 0644)
+	if err != nil {
 		return err
 	}
-	return nil
+	_, err = ncutils.RunCmd("wg syncconf "+iface+" "+tmpConf, true)
+	if err != nil {
+		log.Println(err.Error())
+		ncutils.Log("error syncing conf, resetting")
+		err = ApplyWGQuickConf(confPath)
+	}
+	errN := os.Remove(tmpConf)
+	if errN != nil {
+		ncutils.Log(errN.Error())
+	}
+	return err
 }
 
 // RemoveWGQuickConf - calls wg-quick down
 func RemoveWGQuickConf(confPath string, printlog bool) error {
-	if _, err := ncutils.RunCmd("wg-quick down "+confPath, printlog); err != nil {
-		return err
-	}
-	return nil
+	_, err := ncutils.RunCmd("wg-quick down "+confPath, printlog)
+	return err
 }
 
 // StorePrivKey - stores wg priv key on disk locally

+ 62 - 0
scripts/netclient-install.ps1

@@ -0,0 +1,62 @@
+param ($version='latest', $token)
+
+function Quit {
+    param(
+        $Text
+    )
+    Write-Host "Exiting: " $Text
+    Break Script
+}
+
+if($token -eq $null -or $token -eq ""){
+    Quit "-token required"
+}
+
+$software = "WireGuard";
+$installed = (Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where { $_.DisplayName -eq $software }) -ne $null
+
+If(-Not $installed) {
+    Write-Host "'$software' is NOT installed. installing...";
+    $url = "https://download.wireguard.com/windows-client/wireguard-installer.exe"
+    $outpath = "$PSScriptRoot/wireguard-installer.exe"
+    Invoke-WebRequest -Uri $url -OutFile $outpath
+    $args = @("Comma","Separated","Arguments")
+    Start-Process -Filepath "$PSScriptRoot/wireguard-installer.exe" -ArgumentList $args
+    $software = "WireGuard";
+    $installed = (Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where { $_.DisplayName -eq $software }) -ne $null
+    If(-Not $installed) {
+        Quit "Could not install WireGuard"
+    } else {
+        Write-Host "'$software' is installed."
+    }
+} else {
+	Write-Host "'$software' is installed."
+}
+$outpath = "";
+if (Test-Path -Path "C:\ProgramData\Netclient\bin\netclient.exe") {
+    $outpath = "C:\ProgramData\Netclient\bin\netclient.exe";
+} else {
+    $outpath = "$PSScriptRoot/netclient.exe"
+    Write-Host "'netclient.exe' is NOT installed. installing...";
+    Write-Host "https://github.com/gravitl/netmaker/releases/download/$version/netclient.exe";
+    $url = "https://github.com/gravitl/netmaker/releases/download/$version/netclient.exe"
+    Invoke-WebRequest -Uri $url -OutFile $outpath
+}
+$NetArgs = @("join","-t",$token)
+Start-Process -Filepath $outpath -ArgumentList $NetArgs
+
+if ((Get-Command "netclient.exe" -ErrorAction SilentlyContinue) -eq $null) { 
+    if (-not (Test-Path -Path "C:\ProgramData\Netclient\bin\netclient.exe")) {
+        New-Item -Path "C:\ProgramData\Netclient" -Name "bin" -ItemType "directory"
+        Move-Item -Path "$PSScriptRoot/netclient.exe" -Destination "C:\ProgramData\Netclient\bin\netclient.exe"
+    }
+    '''
+    Please add netclient.exe to your path to make it executable from powershell:
+        1. Open "Edit environment variables for your account"
+        2. Double click on "Path"
+        3. On a new line, paste the following: %USERPROFILE%\AppData\Netclient\bin
+        4. Click "Ok"
+    '''
+}
+
+Write-Host "'netclient' is installed."

+ 97 - 13
scripts/netclient-install.sh

@@ -7,6 +7,8 @@ fi
 
 echo "checking dependencies..."
 
+OS=$(uname)
+
 if [ -f /etc/debian_version ]; then
 	install_cmd='apt-get install -y'
 elif [ -f /etc/alpine-release ]; then
@@ -15,6 +17,8 @@ elif [ -f /etc/centos-release ]; then
 	install_cmd='yum install -y'
 elif [ -f /etc/fedora-release ]; then
 	install_cmd='dnf install -y'
+elif [ OS == "FreeBSD" ]; then
+	install_cmd='pkg install -y'
 else
 	install_cmd=''
 fi
@@ -27,21 +31,41 @@ dependencies="wireguard"
 set -- $dependencies
 while [ -n "$1" ]; do
     echo $1
-	is_installed=$(dpkg-query -W --showformat='${Status}\n' $1 | grep "install ok installed")
-	if [ "${is_installed}" = "install ok installed" ]; then
-		echo "    " $1 is installed
+	if [ OS == "FreeBSD" ]; then
+		is_installed=$(pkg check -d $1 | grep '100%')
+		if [ "${is_installed}" = '100%' ]; then
+			echo "    " $1 is installed
+		else
+			echo "    " $1 is not installed. Attempting install.
+			${install_cmd} $1
+			sleep 5
+			is_installed=$(pkg check -d $1 | grep '100%')
+			if [ "${is_installed}" = '100%' ]; then
+				echo "    " $1 is installed
+			elif [ -x "$(command -v $1)" ]; then
+				echo "    " $1 is installed
+			else
+				echo "    " FAILED TO INSTALL $1
+				echo "    " This may break functionality.
+			fi
+		fi	
 	else
-		echo "    " $1 is not installed. Attempting install.
-		${install_cmd} $1
-		sleep 5
 		is_installed=$(dpkg-query -W --showformat='${Status}\n' $1 | grep "install ok installed")
-          	if [ "${is_installed}" = "install ok installed" ]; then
-			echo "    " $1 is installed
-		elif [ -x "$(command -v $1)" ]; then
+		if [ "${is_installed}" = "install ok installed" ]; then
 			echo "    " $1 is installed
 		else
-			echo "    " FAILED TO INSTALL $1
-			echo "    " This may break functionality.
+			echo "    " $1 is not installed. Attempting install.
+			${install_cmd} $1
+			sleep 5
+			is_installed=$(dpkg-query -W --showformat='${Status}\n' $1 | grep "install ok installed")
+				if [ "${is_installed}" = "install ok installed" ]; then
+				echo "    " $1 is installed
+			elif [ -x "$(command -v $1)" ]; then
+				echo "    " $1 is installed
+			else
+				echo "    " FAILED TO INSTALL $1
+				echo "    " This may break functionality.
+			fi
 		fi
 	fi
 	shift
@@ -93,6 +117,36 @@ case $(uname | tr '[:upper:]' '[:lower:]') in
 	darwin)
         	dist=netclient-darwin
 	;;
+	freebsd*)
+		if [ -z "$CPU_ARCH" ]; then
+			CPU_ARCH=$(uname -m)
+		fi
+		case $CPU_ARCH in
+			amd64)
+				dist=netclient-freebsd
+			;;
+			x86_64)
+				dist=netclient-freebsd
+			;;
+                        x86_32)
+                                dist=netclient-freebsd-32
+                        ;;
+ 			arm64)
+				dist=netclient-freebsd-arm64
+			;;
+			aarch64)
+                                dist=netclient-freebsd-arm64
+			;;
+			armv7l)
+                                dist=netclient-freebsd-armv7
+			;;
+			arm*)
+				dist=netclient-freebsd-$CPU_ARCH
+            		;;
+			*)
+				fatal "$CPU_ARCH : cpu architecture not supported"
+    		esac
+	;;
 esac
 
 echo "Binary = $dist"
@@ -107,10 +161,40 @@ else
 fi
 chmod +x netclient
 
+EXTRA_ARGS = ""
+if [ OS == "FreeBSD" ]; then
+	EXTRA_ARGS = "--daemon=off"
+fi
+
 if [ -z "${NAME}" ]; then
-sudo ./netclient join -t $KEY
+sudo ./netclient join -t $KEY EXTRA_ARGS
 else
-sudo ./netclient join -t $KEY --name $NAME
+sudo ./netclient join -t $KEY --name $NAME EXTRA_ARGS
 fi
 
 rm -f netclient
+
+if [ OS == "FreeBSD" ]; then
+	tee /usr/local/etc/rc.d/netclient <<'EOF' >/dev/null
+#!/bin/sh
+
+# PROVIDE: netclient
+# REQUIRE: LOGIN DAEMON NETWORKING SERVERS FILESYSTEM
+# BEFORE:  
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="netclient"
+rcvar=netclient_enable
+pidfile="/var/run/${name}.pid"
+command="/usr/sbin/daemon"
+command_args="-c -f -P ${pidfile} -R 10 -t "Netclient" -u root -o /etc/netclient/netclient.log /etc/netclient/netclient checkin -n all"
+
+load_rc_config $name
+run_rc_command "$1"
+
+EOF
+	/usr/local/etc/rc.d/netclient enable
+	/usr/local/etc/rc.d/netclient start
+fi

+ 17 - 0
scripts/netclient-rc-freebsd

@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# PROVIDE: netclient
+# REQUIRE: LOGIN DAEMON NETWORKING SERVERS FILESYSTEM
+# BEFORE:  
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="netclient"
+rcvar=netclient_enable
+pidfile="/var/run/${name}.pid"
+command="/usr/sbin/daemon"
+command_args="-c -f -P ${pidfile} -R 10 -t "Netclient" -u root -o /etc/netclient/netclient.log /etc/netclient/netclient checkin -n all"
+
+load_rc_config $name
+run_rc_command "$1"

+ 31 - 0
servercfg/serverconf.go

@@ -33,6 +33,7 @@ func GetServerConfig() config.ServerConfig {
 	cfg.GRPCHost = GetGRPCHost()
 	cfg.GRPCPort = GetGRPCPort()
 	cfg.MasterKey = "(hidden)"
+	cfg.DNSKey = "(hidden)"
 	cfg.AllowedOrigin = GetAllowedOrigin()
 	cfg.RestBackend = "off"
 	cfg.Verbosity = GetVerbose()
@@ -54,6 +55,10 @@ func GetServerConfig() config.ServerConfig {
 	if IsDNSMode() {
 		cfg.DNSMode = "on"
 	}
+	cfg.DisplayKeys = "off"
+	if IsDisplayKeys() {
+		cfg.DisplayKeys = "on"
+	}
 	cfg.GRPCSSL = "off"
 	if IsGRPCSSL() {
 		cfg.GRPCSSL = "on"
@@ -246,6 +251,17 @@ func GetMasterKey() string {
 	return key
 }
 
+// GetDNSKey - gets the configured dns key of server
+func GetDNSKey() string {
+	key := "secretkey"
+	if os.Getenv("DNS_KEY") != "" {
+		key = os.Getenv("DNS_KEY")
+	} else if config.Config.Server.DNSKey != "" {
+		key = config.Config.Server.DNSKey
+	}
+	return key
+}
+
 // GetAllowedOrigin - get the allowed origin
 func GetAllowedOrigin() string {
 	allowedorigin := "*"
@@ -323,6 +339,21 @@ func IsDNSMode() bool {
 	return isdns
 }
 
+// IsDisplayKeys - should server be able to display keys?
+func IsDisplayKeys() bool {
+	isdisplay := true
+	if os.Getenv("DISPLAY_KEYS") != "" {
+		if os.Getenv("DISPLAY_KEYS") == "off" {
+			isdisplay = false
+		}
+	} else if config.Config.Server.DisplayKeys != "" {
+		if config.Config.Server.DisplayKeys == "off" {
+			isdisplay = false
+		}
+	}
+	return isdisplay
+}
+
 // IsGRPCSSL - ssl grpc on or off
 func IsGRPCSSL() bool {
 	isssl := false