Browse Source

Merge pull request #603 from gravitl/feature_v0.9.4_mac_wg_quick

Feature v0.9.4 mac wg quick
Alex 3 years ago
parent
commit
fce744be9a

+ 39 - 0
docker/Dockerfile-netclient-kernel

@@ -0,0 +1,39 @@
+FROM debian:buster as builder
+# add glib support daemon manager
+
+RUN apt update -y && apt install -y wget bash gcc musl-dev openssl golang git build-essential libmnl-dev iptables
+
+RUN wget -O go.tgz https://dl.google.com/go/go1.17.1.linux-amd64.tar.gz
+
+RUN tar -C /usr/local -xzf go.tgz
+
+WORKDIR /usr/local/go/src
+
+RUN chmod +x make.bash
+
+RUN ./make.bash
+
+ENV PATH="/usr/local/go/bin:$PATH"
+
+ENV GOPATH=/opt/go/
+
+ENV PATH=$PATH:$GOPATH/bin
+
+WORKDIR /app
+
+COPY . .
+
+ENV GO111MODULE=auto
+
+RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 /usr/local/go/bin/go build -ldflags="-w -s" -o netclient-app netclient/main.go
+
+FROM debian:buster
+
+WORKDIR /root/
+
+RUN apt update -y && apt install -y bash curl wget traceroute procps dnsutils iptables openresolv iproute2
+COPY --from=builder /app/netclient-app ./netclient
+COPY --from=builder /app/scripts/netclient.sh .
+RUN chmod 0755 netclient && chmod 0755 netclient.sh
+
+ENTRYPOINT ["/bin/sh", "./netclient.sh"]

BIN
docs/images/egress1.png


+ 1 - 0
netclient/daemon/common.go

@@ -7,6 +7,7 @@ import (
 	"github.com/gravitl/netmaker/netclient/config"
 )
 
+// InstallDaemon - Calls the correct function to install the netclient as a daemon service on the given operating system.
 func InstallDaemon(cfg config.ClientConfig) error {
 	os := runtime.GOOS
 	var err error

+ 13 - 11
netclient/daemon/macos.go

@@ -4,32 +4,30 @@ import (
 	"fmt"
 	"log"
 	"os"
-	"path/filepath"
 
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
 const MAC_SERVICE_NAME = "com.gravitl.netclient"
 
+// SetupMacDaemon - Creates a daemon service from the netclient under LaunchAgents for MacOS
 func SetupMacDaemon(interval string) error {
 
-	ex, err := os.Executable()
-	if err != nil {
-		return err
-	}
-
-	exPath := filepath.Dir(ex)
-	ncutils.PrintLog("installing netclient from "+exPath, 0)
 	if !ncutils.FileExists("/etc/netclient/netclient") {
-		err = ncutils.Copy(exPath, "/etc/netclient/netclient")
+		binarypath, err := os.Executable()
+		if err != nil {
+			return err
+		}
+		ncutils.PrintLog("installing binary from "+binarypath, 0)
+		err = ncutils.Copy(binarypath, "/etc/netclient/netclient")
 		if err != nil {
 			log.Println(err)
 			return err
 		}
 	}
 
-	_, errN := os.Stat("~/Library/LaunchAgents")
-	if os.IsNotExist(errN) {
+	_, err := os.Stat("~/Library/LaunchAgents")
+	if os.IsNotExist(err) {
 		os.Mkdir("~/Library/LaunchAgents", 0755)
 	}
 	err = CreateMacService(MAC_SERVICE_NAME, interval)
@@ -40,6 +38,7 @@ func SetupMacDaemon(interval string) error {
 	return err
 }
 
+// CleanupMac - Removes the netclient checkin daemon from LaunchDaemons
 func CleanupMac() {
 	_, err := ncutils.RunCmd("launchctl unload /Library/LaunchDaemons/"+MAC_SERVICE_NAME+".plist", true)
 	if ncutils.FileExists("/Library/LaunchDaemons/" + MAC_SERVICE_NAME + ".plist") {
@@ -52,6 +51,7 @@ func CleanupMac() {
 	os.RemoveAll(ncutils.GetNetclientPath())
 }
 
+// CreateMacService - Creates the mac service file for LaunchDaemons
 func CreateMacService(servicename string, interval string) error {
 	_, err := os.Stat("/Library/LaunchDaemons")
 	if os.IsNotExist(err) {
@@ -69,6 +69,7 @@ func CreateMacService(servicename string, interval string) error {
 	return err
 }
 
+// MacDaemonString - the file contents for the mac netclient daemon service (launchdaemon)
 func MacDaemonString(interval string) string {
 	return fmt.Sprintf(`<?xml version='1.0' encoding='UTF-8'?>
 <!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\" >
@@ -97,6 +98,7 @@ func MacDaemonString(interval string) string {
 `, interval)
 }
 
+// MacTemplateData - struct to represent the mac service
 type MacTemplateData struct {
 	Label    string
 	Interval string

+ 3 - 0
netclient/functions/common.go

@@ -6,6 +6,7 @@ import (
 	"log"
 	"net"
 	"os"
+	"strings"
 
 	nodepb "github.com/gravitl/netmaker/grpc"
 	"github.com/gravitl/netmaker/models"
@@ -251,6 +252,8 @@ func WipeLocal(network string) error {
 	if ifacename != "" {
 		if err = wireguard.RemoveConf(ifacename, true); err == nil {
 			ncutils.PrintLog("removed WireGuard interface: "+ifacename, 1)
+		} else if strings.Contains(err.Error(), "does not exist") {
+			err = nil
 		}
 	}
 

+ 38 - 0
netclient/ncutils/netclientutils.go

@@ -239,6 +239,7 @@ func GetLocalIP(localrange string) (string, error) {
 	return local, nil
 }
 
+//GetNetworkIPMask - Pulls the netmask out of the network
 func GetNetworkIPMask(networkstring string) (string, string, error) {
 	ip, ipnet, err := net.ParseCIDR(networkstring)
 	if err != nil {
@@ -318,6 +319,39 @@ func GetNetclientPathSpecific() string {
 	}
 }
 
+// GetNewIface - Gets the name of the real interface created on Mac
+func GetNewIface(dir string) (string, error) {
+	files, _ := os.ReadDir(dir)
+	var newestFile string
+	var newestTime int64 = 0
+	var err error
+	for _, f := range files {
+		fi, err := os.Stat(dir + f.Name())
+		if err != nil {
+			return "", err
+		}
+		currTime := fi.ModTime().Unix()
+		if currTime > newestTime && strings.Contains(f.Name(), ".sock") {
+			newestTime = currTime
+			newestFile = f.Name()
+		}
+	}
+	resultArr := strings.Split(newestFile, ".")
+	if resultArr[0] == "" {
+		err = errors.New("sock file does not exist")
+	}
+	return resultArr[0], err
+}
+
+// GetFileAsString - returns the string contents of a given file
+func GetFileAsString(path string) (string, error) {
+	content, err := os.ReadFile(path)
+	if err != nil {
+		return "", err
+	}
+	return string(content), err
+}
+
 // GetNetclientPathSpecific - gets specific netclient config path
 func GetWGPathSpecific() string {
 	if IsWindows() {
@@ -434,6 +468,7 @@ func stringAfter(original string, substring string) string {
 	return original[adjustedPosition:]
 }
 
+// ShortenString - Brings string down to specified length. Stops names from being too long
 func ShortenString(input string, length int) string {
 	output := input
 	if len(input) > length {
@@ -442,6 +477,7 @@ func ShortenString(input string, length int) string {
 	return output
 }
 
+// DNSFormatString - Formats a string with correct usage for DNS
 func DNSFormatString(input string) string {
 	reg, err := regexp.Compile("[^a-zA-Z0-9-]+")
 	if err != nil {
@@ -451,6 +487,7 @@ func DNSFormatString(input string) string {
 	return reg.ReplaceAllString(input, "")
 }
 
+// GetHostname - Gets hostname of machine
 func GetHostname() string {
 	hostname, err := os.Hostname()
 	if err != nil {
@@ -462,6 +499,7 @@ func GetHostname() string {
 	return hostname
 }
 
+// CheckUID - Checks to make sure user has root privileges
 func CheckUID() {
 	// start our application
 	out, err := RunCmd("id -u", true)

+ 18 - 9
netclient/wireguard/common.go

@@ -96,6 +96,10 @@ func SetPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) error {
 			}
 		}
 	}
+	if ncutils.IsMac() {
+		err = SetMacPeerRoutes(iface)
+		return err
+	}
 
 	return nil
 }
@@ -167,14 +171,16 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 	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)
+		if !ncutils.IsMac() {
+			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)
+			}
 		}
 		if !ncutils.IsWindows() {
-			err = ApplyConf(confPath)
+			err = ApplyConf(*node, ifacename, confPath)
 			if err != nil {
 				ncutils.PrintLog("failed to create wireguard interface", 1)
 				return err
@@ -187,7 +193,7 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 			ncutils.PrintLog("waiting for interface...", 1)
 			for !strings.Contains(output, ifacename) && !(time.Now().After(starttime.Add(time.Duration(10) * time.Second))) {
 				output, _ = ncutils.RunCmd("wg", false)
-				err = ApplyConf(confPath)
+				err = ApplyConf(*node, ifacename, confPath)
 				time.Sleep(time.Second)
 			}
 			if !strings.Contains(output, ifacename) {
@@ -259,8 +265,9 @@ func RemoveConf(iface string, printlog bool) error {
 	var err error
 	switch os {
 	case "windows":
-
 		err = RemoveWindowsConf(iface, printlog)
+	case "darwin":
+		err = RemoveConfMac(iface)
 	default:
 		confPath := ncutils.GetNetclientPathSpecific() + iface + ".conf"
 		err = RemoveWGQuickConf(confPath, printlog)
@@ -269,12 +276,14 @@ func RemoveConf(iface string, printlog bool) error {
 }
 
 // ApplyConf - applys a conf on disk to WireGuard interface
-func ApplyConf(confPath string) error {
+func ApplyConf(node models.Node, ifacename string, confPath string) error {
 	os := runtime.GOOS
 	var err error
 	switch os {
 	case "windows":
 		_ = ApplyWindowsConf(confPath)
+	case "darwin":
+		_ = ApplyMacOSConf(node, ifacename, confPath)
 	default:
 		err = ApplyWGQuickConf(confPath)
 	}

+ 263 - 0
netclient/wireguard/mac.go

@@ -0,0 +1,263 @@
+package wireguard
+
+import (
+	"bufio"
+	"errors"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+)
+
+// WgQuickDownMac - bring down mac interface, remove routes, and run post-down commands
+func WgQuickDownMac(node models.Node, iface string) error {
+	if err := RemoveConfMac(iface); err != nil {
+		return err
+	}
+	if node.PostDown != "" {
+		runcmds := strings.Split(node.PostDown, "; ")
+		ncutils.RunCmds(runcmds, true)
+	}
+	return nil
+}
+
+// RemoveConfMac - bring down mac interface and remove routes
+func RemoveConfMac(iface string) error {
+	realIface, err := getRealIface(iface)
+	if realIface != "" {
+		err = deleteInterface(iface, realIface)
+	}
+	return err
+}
+
+// WgQuickUpMac - bring up mac interface and set routes
+func WgQuickUpMac(node models.Node, iface string, confPath string) error {
+	var err error
+	var realIface string
+	realIface, err = getRealIface(iface)
+	if realIface != "" && err == nil {
+		deleteInterface(iface, realIface)
+		deleteRoutes(realIface)
+	}
+	realIface, err = addInterface(iface)
+	if err != nil {
+		ncutils.PrintLog("error creating wg interface", 1)
+		return err
+	}
+	time.Sleep(time.Second / 2)
+	err = setConfig(realIface, confPath)
+	if err != nil {
+		ncutils.PrintLog("error setting config for "+realIface, 1)
+		return err
+	}
+	var ips = append(node.AllowedIPs, node.Address, node.Address6)
+	peerIPs := getPeerIPs(realIface)
+	if len(peerIPs) > 0 {
+		ips = append(ips, peerIPs...)
+	}
+	for _, i := range ips {
+		if i != "" {
+			err = addAddress(realIface, i)
+			if err != nil {
+				ncutils.PrintLog("error adding address "+i+" on interface "+realIface, 1)
+				return err
+			}
+		}
+	}
+	setMTU(realIface, int(node.MTU))
+	err = upInterface(realIface)
+	if err != nil {
+		ncutils.PrintLog("error turning on interface "+iface, 1)
+		return err
+	}
+	for _, i := range ips {
+		if i != "" {
+			err = addRoute(i, realIface)
+			if err != nil {
+				ncutils.PrintLog("error adding route to "+realIface+" for "+i, 1)
+				return err
+			}
+		}
+	}
+	//next, wg-quick runs set_endpoint_direct_route
+	//next, wg-quick runs monitor_daemon
+	time.Sleep(time.Second / 2)
+	if node.PostUp != "" {
+		runcmds := strings.Split(node.PostUp, "; ")
+		ncutils.RunCmds(runcmds, true)
+	}
+	return err
+}
+
+// addInterface - adds mac interface and creates reference file to match iface name with tun iface
+func addInterface(iface string) (string, error) {
+	ncutils.RunCmd("mkdir -p /var/run/wireguard/", true)
+	ncutils.RunCmd("wireguard-go utun", true)
+	realIface, err := ncutils.GetNewIface("/var/run/wireguard/")
+	if iface != "" && err == nil {
+		ifacePath := "/var/run/wireguard/" + iface + ".name"
+		err = os.WriteFile(ifacePath, []byte(realIface), 0644)
+	}
+	return realIface, err
+}
+
+// getRealIface - retrieves tun iface based on reference iface name from config file
+func getRealIface(iface string) (string, error) {
+	ncutils.RunCmd("wg show interfaces", false)
+	ifacePath := "/var/run/wireguard/" + iface + ".name"
+	if !(ncutils.FileExists(ifacePath)) {
+		return "", errors.New(ifacePath + " does not exist")
+	}
+	realIfaceName, err := ncutils.GetFileAsString(ifacePath)
+	if err != nil {
+		return "", err
+	}
+	if !(ncutils.FileExists("/var/run/wireguard/" + realIfaceName + ".sock")) {
+		return "", errors.New("interface file does not exist")
+	}
+	return realIfaceName, nil
+}
+
+// deleteRoutes - deletes network routes associated with interface
+func deleteRoutes(iface string) error {
+	realIface, err := getRealIface(iface)
+	if err != nil {
+		return err
+	}
+	var inets = [2]string{"inet", "inet6"}
+	for _, inet := range inets {
+		ifaceList, err := ncutils.RunCmd("netstat -nr -f "+inet+" | grep -e "+realIface+" | awk '{print $1}'", true)
+		if err != nil {
+			return err
+		}
+		destinations := strings.Split(ifaceList, "\n")
+
+		for _, i := range destinations {
+			ncutils.RunCmd("route -q -n delete -"+inet+" "+i, true)
+		}
+	}
+	// wg-quick deletes ENDPOINTS here (runs 'route -q delete' for each peer endpoint on the interface.)
+	// We don't believe this is necessary.
+	return nil
+}
+
+// deleteInterface - deletes the real interface and the referance file
+func deleteInterface(iface string, realIface string) error {
+	var err error
+	var out string
+	if iface != "" {
+		os.Remove("/var/run/wireguard/" + realIface + ".sock")
+		os.Remove("/var/run/wireguard/" + iface + ".name")
+	}
+	out, err = ncutils.RunCmd("ifconfig "+realIface+" down", false)
+	if strings.Contains(err.Error(), "does not exist") {
+		err = nil
+	} else if err != nil && out != "" {
+		err = errors.New(out)
+	}
+	return err
+}
+
+// upInterface - bring up the interface with ifconfig
+func upInterface(iface string) error {
+	var err error
+	_, err = ncutils.RunCmd("ifconfig "+iface+" up", true)
+	return err
+}
+
+// addAddress - adds private address to the interface
+func addAddress(iface string, addr string) error {
+	var err error
+	if strings.Contains(addr, ":") {
+		_, err = ncutils.RunCmd("ifconfig "+iface+" inet6 "+addr+" alias", true)
+	} else {
+		_, err = ncutils.RunCmd("ifconfig "+iface+" inet "+addr+" 255.255.255.0 alias", true)
+	}
+	return err
+}
+
+// setMTU - sets MTU for the interface
+func setMTU(iface string, mtu int) error {
+	var err error
+	if mtu == 0 {
+		mtu = 1280
+	}
+	_, err = ncutils.RunCmd("ifconfig "+iface+" mtu "+strconv.Itoa(mtu), true)
+	return err
+}
+
+// addRoute - adds network route to the interface if it does not already exist
+func addRoute(addr string, iface string) error {
+	var err error
+	var out string
+	var inetx = "inet"
+	if strings.Contains(addr, ":") {
+		inetx = "inet6"
+	}
+	out, err = ncutils.RunCmd("route -n get -"+inetx+" "+addr, true)
+	if err != nil {
+		return err
+	}
+	if !(strings.Contains(out, iface)) {
+		_, err = ncutils.RunCmd("route -q -n add -"+inetx+" "+addr+" -interface "+iface, true)
+	}
+	return err
+}
+
+// setConfig - sets configuration of the wireguard interface from the config file
+func setConfig(realIface string, confPath string) error {
+	confString := getConfig(confPath)
+	err := os.WriteFile(confPath+".tmp", []byte(confString), 0644)
+	if err != nil {
+		return err
+	}
+	_, err = ncutils.RunCmd("wg setconf "+realIface+" "+confPath+".tmp", true)
+	os.Remove(confPath + ".tmp")
+	return err
+}
+
+// getConfig - gets config from config file and strips out incompatible fields
+func getConfig(path string) string {
+	var confCmd = "grep -v -e Address -e MTU -e PostUp -e PostDown "
+	confRaw, _ := ncutils.RunCmd(confCmd+path, false)
+	return confRaw
+}
+
+// SetMacPeerRoutes - sets routes for interface from the peer list for all AllowedIps
+func SetMacPeerRoutes(realIface string) error {
+	var err error
+	peerIPs := getPeerIPs(realIface)
+	if len(peerIPs) == 0 {
+		return err
+	}
+	for _, i := range peerIPs {
+		if i != "" {
+			err = addRoute(i, realIface)
+			if err != nil {
+				ncutils.PrintLog("error adding route to "+realIface+" for "+i, 1)
+				return err
+			}
+		}
+	}
+	return err
+}
+
+// getPeerIPs - retrieves peer AllowedIPs from WireGuard interface
+func getPeerIPs(realIface string) []string {
+	allowedIps := []string{}
+	out, err := ncutils.RunCmd("wg show "+realIface+" allowed-ips", false)
+	if err != nil {
+		return allowedIps
+	}
+	scanner := bufio.NewScanner(strings.NewReader(out))
+	for scanner.Scan() {
+		fields := strings.Fields(scanner.Text())
+		if len(fields) > 1 {
+			allowedIps = append(allowedIps, fields[1:]...)
+		}
+	}
+	return allowedIps
+}

+ 13 - 1
netclient/wireguard/unix.go

@@ -58,10 +58,22 @@ func ApplyWGQuickConf(confPath string) error {
 	return err
 }
 
+// ApplyMacOSConf - applies system commands similar to wg-quick using golang for MacOS
+func ApplyMacOSConf(node models.Node, ifacename string, confPath string) error {
+	var err error
+	_ = WgQuickDownMac(node, ifacename)
+	err = WgQuickUpMac(node, ifacename, confPath)
+	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)
+	var confCmd = "wg-quick strip "
+	if ncutils.IsMac() {
+		confCmd = "grep -v -e Address -e MTU -e PostUp -e PostDown "
+	}
+	confRaw, err := ncutils.RunCmd(confCmd+confPath, false)
 	if err != nil {
 		return err
 	}

+ 14 - 0
scripts/token-convert.sh

@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -e
+
+token=$1
+
+token_json=$(echo $token | base64 -d)
+
+api_addr=$(echo $token_json | jq -r '.apiconn')
+grpc_addr=$(echo $token_json | jq -r '.grpcconn')
+network=$(echo $token_json | jq -r '.network')
+key=$(echo $token_json | jq -r '.key')
+
+echo ./netclient join -k $key -n $network --apiserver $api_addr --grpcserver $grpc_addr