netmaker/logic/wireguard.go

440 lines
12 KiB
Go
Raw Normal View History

package logic
import (
2021-10-13 03:44:19 +08:00
"errors"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"time"
2021-12-07 04:31:08 +08:00
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/models"
2021-10-13 03:44:19 +08:00
"github.com/gravitl/netmaker/netclient/ncutils"
2022-01-22 02:15:54 +08:00
"github.com/gravitl/netmaker/netclient/wireguard"
"golang.zx2c4.com/wireguard/wgctrl"
2021-10-13 03:44:19 +08:00
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
2022-01-18 06:44:12 +08:00
// RemoveConf - removes a configuration for a given WireGuard interface
func RemoveConf(iface string, printlog bool) error {
var err error
confPath := ncutils.GetNetclientPathSpecific() + iface + ".conf"
err = removeWGQuickConf(confPath, printlog)
return err
}
// HasPeerConnected - checks if a client node has connected over WG
func HasPeerConnected(node *models.Node) bool {
client, err := wgctrl.New()
if err != nil {
return false
}
defer client.Close()
device, err := client.Device(node.Interface)
if err != nil {
return false
}
for _, peer := range device.Peers {
if peer.PublicKey.String() == node.PublicKey {
if peer.Endpoint != nil {
return true
}
}
}
return false
}
2022-02-15 00:01:33 +08:00
// IfaceDelta - checks if the new node causes an interface change
func IfaceDelta(currentNode *models.Node, newNode *models.Node) bool {
// single comparison statements
if newNode.Endpoint != currentNode.Endpoint ||
newNode.LocalAddress != currentNode.LocalAddress ||
newNode.PublicKey != currentNode.PublicKey ||
newNode.Address != currentNode.Address ||
newNode.IsEgressGateway != currentNode.IsEgressGateway ||
newNode.IsIngressGateway != currentNode.IsIngressGateway ||
newNode.IsRelay != currentNode.IsRelay ||
newNode.UDPHolePunch != currentNode.UDPHolePunch ||
newNode.IsPending != currentNode.IsPending ||
2022-02-17 23:33:48 +08:00
newNode.ListenPort != currentNode.ListenPort ||
2022-02-17 09:26:20 +08:00
newNode.MTU != currentNode.MTU ||
2022-02-15 00:01:33 +08:00
newNode.PersistentKeepalive != currentNode.PersistentKeepalive ||
newNode.DNSOn != currentNode.DNSOn ||
len(newNode.AllowedIPs) != len(currentNode.AllowedIPs) {
return true
}
// multi-comparison statements
if newNode.IsDualStack == "yes" {
if newNode.Address6 != currentNode.Address6 {
return true
}
}
if newNode.IsEgressGateway == "yes" {
if len(currentNode.EgressGatewayRanges) != len(newNode.EgressGatewayRanges) {
return true
}
for _, address := range newNode.EgressGatewayRanges {
if !StringSliceContains(currentNode.EgressGatewayRanges, address) {
return true
}
}
}
if newNode.IsRelay == "yes" {
if len(currentNode.RelayAddrs) != len(newNode.RelayAddrs) {
return true
}
for _, address := range newNode.RelayAddrs {
if !StringSliceContains(currentNode.RelayAddrs, address) {
return true
}
}
}
for _, address := range newNode.AllowedIPs {
if !StringSliceContains(currentNode.AllowedIPs, address) {
return true
}
}
return false
}
2022-01-18 06:44:12 +08:00
// == Private Functions ==
// gets the server peers locally
func getSystemPeers(node *models.Node) (map[string]string, error) {
peers := make(map[string]string)
client, err := wgctrl.New()
if err != nil {
return peers, err
}
2021-12-11 04:01:10 +08:00
defer client.Close()
device, err := client.Device(node.Interface)
if err != nil {
return nil, err
}
for _, peer := range device.Peers {
if IsBase64(peer.PublicKey.String()) && peer.Endpoint != nil && CheckEndpoint(peer.Endpoint.String()) {
peers[peer.PublicKey.String()] = peer.Endpoint.String()
}
}
return peers, nil
}
2021-10-13 03:44:19 +08:00
func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string) error {
2021-10-13 03:44:19 +08:00
key, err := wgtypes.ParseKey(privkey)
if err != nil {
return err
}
wgclient, err := wgctrl.New()
if err != nil {
return err
}
defer wgclient.Close()
var ifacename string
if node.Interface != "" {
ifacename = node.Interface
} else {
2021-12-07 04:31:08 +08:00
logger.Log(2, "no server interface provided to configure")
2021-10-13 03:44:19 +08:00
}
if node.Address == "" {
2021-12-07 04:31:08 +08:00
logger.Log(2, "no server address to provided configure")
2021-10-13 03:44:19 +08:00
}
if ncutils.IsKernel() {
2021-12-07 04:31:08 +08:00
logger.Log(2, "setting kernel device", ifacename)
2022-02-10 04:21:51 +08:00
network, err := GetNetwork(node.Network)
if err != nil {
logger.Log(0, "failed to get network"+err.Error())
return err
}
net := strings.Split(network.AddressRange, "/")
mask := net[len(net)-1]
2022-02-10 03:41:13 +08:00
setKernelDevice(ifacename, node.Address, mask)
2021-10-13 03:44:19 +08:00
}
nodeport := int(node.ListenPort)
2021-10-28 09:36:49 +08:00
var conf = wgtypes.Config{
2021-10-13 03:44:19 +08:00
PrivateKey: &key,
ListenPort: &nodeport,
ReplacePeers: true,
Peers: peers,
}
if !ncutils.IsKernel() {
2022-01-22 02:15:54 +08:00
if err := wireguard.WriteWgConfig(node, key.String(), peers); err != nil {
logger.Log(1, "error writing wg conf file: ", err.Error())
2021-10-13 03:44:19 +08:00
return err
}
// spin up userspace + apply the conf file
2021-10-28 09:36:49 +08:00
var deviceiface = ifacename
2022-01-22 02:15:54 +08:00
confPath := ncutils.GetNetclientPathSpecific() + ifacename + ".conf"
2021-10-13 03:44:19 +08:00
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)
}
2021-10-15 11:38:01 +08:00
time.Sleep(time.Second >> 2)
2021-10-13 03:44:19 +08:00
err = applyWGQuickConf(confPath)
if err != nil {
2021-12-07 04:31:08 +08:00
logger.Log(1, "failed to create wireguard interface")
2021-10-13 03:44:19 +08:00
return err
}
} else {
ipExec, err := exec.LookPath("ip")
if err != nil {
return err
}
_, err = wgclient.Device(ifacename)
if err != nil {
if os.IsNotExist(err) {
fmt.Println("Device does not exist: ")
fmt.Println(err)
} else {
return errors.New("Unknown config error: " + err.Error())
}
}
err = wgclient.ConfigureDevice(ifacename, conf)
if err != nil {
if os.IsNotExist(err) {
fmt.Println("Device does not exist: ")
fmt.Println(err)
} else {
fmt.Printf("This is inconvenient: %v", err)
}
}
if _, err := ncutils.RunCmd(ipExec+" link set down dev "+ifacename, false); err != nil {
2021-12-07 04:31:08 +08:00
logger.Log(2, "attempted to remove interface before editing")
2021-10-13 03:44:19 +08:00
return err
}
if node.PostDown != "" {
runcmds := strings.Split(node.PostDown, "; ")
2021-11-16 00:42:52 +08:00
_ = ncutils.RunCmds(runcmds, false)
2021-10-13 03:44:19 +08:00
}
// set MTU of node interface
if _, err := ncutils.RunCmd(ipExec+" link set mtu "+strconv.Itoa(int(node.MTU))+" up dev "+ifacename, true); err != nil {
2021-12-07 04:31:08 +08:00
logger.Log(2, "failed to create interface with mtu", strconv.Itoa(int(node.MTU)), "-", ifacename)
2021-10-13 03:44:19 +08:00
return err
}
if node.PostUp != "" {
runcmds := strings.Split(node.PostUp, "; ")
_ = ncutils.RunCmds(runcmds, true)
}
if hasGateway {
for _, gateway := range gateways {
_, _ = ncutils.RunCmd(ipExec+" -4 route add "+gateway+" dev "+ifacename, true)
}
}
if node.Address6 != "" && node.IsDualStack == "yes" {
2021-12-07 04:31:08 +08:00
logger.Log(1, "adding address:", node.Address6)
2021-10-13 03:44:19 +08:00
_, _ = ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+node.Address6+"/64", true)
}
}
return err
}
2022-02-10 03:41:13 +08:00
func setKernelDevice(ifacename, address, mask string) error {
2021-10-13 03:44:19 +08:00
ipExec, err := exec.LookPath("ip")
if err != nil {
return err
}
2022-01-18 06:44:12 +08:00
// == best effort ==
ncutils.RunCmd("ip link delete dev "+ifacename, false)
ncutils.RunCmd(ipExec+" link add dev "+ifacename+" type wireguard", true)
2022-02-10 03:41:13 +08:00
ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+address+"/"+mask, true) // this was a bug waiting to happen
2021-10-13 03:44:19 +08:00
return nil
}
func applyWGQuickConf(confPath string) error {
if _, err := ncutils.RunCmd("wg-quick up "+confPath, true); err != nil {
return err
}
return nil
}
func removeWGQuickConf(confPath string, printlog bool) error {
if _, err := ncutils.RunCmd("wg-quick down "+confPath, printlog); err != nil {
return err
}
return nil
}
2021-10-13 22:02:04 +08:00
func setServerPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) error {
client, err := wgctrl.New()
if err != nil {
2021-12-07 04:31:08 +08:00
logger.Log(0, "failed to start wgctrl")
2021-10-13 22:02:04 +08:00
return err
}
2021-12-11 04:01:10 +08:00
defer client.Close()
2021-10-13 22:02:04 +08:00
device, err := client.Device(iface)
if err != nil {
2021-12-09 05:52:32 +08:00
logger.Log(1, "failed to parse interface")
2021-10-13 22:02:04 +08:00
return err
}
devicePeers := device.Peers
if len(devicePeers) > 1 && len(peers) == 0 {
2021-12-07 04:31:08 +08:00
logger.Log(1, "no peers pulled")
2021-10-13 22:02:04 +08:00
return err
}
for _, peer := range peers {
2021-12-07 04:31:08 +08:00
if len(peer.AllowedIPs) > 0 {
for _, currentPeer := range devicePeers {
if len(currentPeer.AllowedIPs) > 0 && currentPeer.AllowedIPs[0].String() == peer.AllowedIPs[0].String() &&
currentPeer.PublicKey.String() != peer.PublicKey.String() {
_, err := ncutils.RunCmd("wg set "+iface+" peer "+currentPeer.PublicKey.String()+" remove", true)
if err != nil {
logger.Log(0, "error removing peer", peer.Endpoint.String())
}
2021-10-13 22:02:04 +08:00
}
}
}
var allowedips string
var iparr []string
for _, ipaddr := range peer.AllowedIPs {
iparr = append(iparr, ipaddr.String())
}
allowedips = strings.Join(iparr, ",")
keepAliveString := strconv.Itoa(int(keepalive))
if keepAliveString == "0" {
keepAliveString = "5"
}
2022-01-31 22:58:43 +08:00
_, err = ncutils.RunCmd("wg set "+iface+" peer "+peer.PublicKey.String()+
" persistent-keepalive "+keepAliveString+
" allowed-ips "+allowedips, true)
2021-10-13 22:02:04 +08:00
if err != nil {
2021-12-07 04:31:08 +08:00
logger.Log(2, "error setting peer", peer.PublicKey.String())
2021-10-13 22:02:04 +08:00
}
}
for _, currentPeer := range devicePeers {
2021-12-07 04:31:08 +08:00
if len(currentPeer.AllowedIPs) > 0 {
shouldDelete := true
for _, peer := range peers {
2022-01-31 22:58:43 +08:00
if len(peer.AllowedIPs) > 0 &&
(peer.PublicKey.String() == currentPeer.PublicKey.String() ||
2022-01-31 23:22:39 +08:00
peer.AllowedIPs[0].String() == currentPeer.AllowedIPs[0].String()) {
2021-12-07 04:31:08 +08:00
shouldDelete = false
}
2021-10-13 22:02:04 +08:00
}
2021-12-07 04:31:08 +08:00
if shouldDelete {
output, err := ncutils.RunCmd("wg set "+iface+" peer "+currentPeer.PublicKey.String()+" remove", true)
if err != nil {
logger.Log(0, output, "error removing peer", currentPeer.PublicKey.String())
}
2021-10-13 22:02:04 +08:00
}
}
}
return nil
}
2021-10-14 03:15:20 +08:00
2021-12-11 04:01:10 +08:00
func setWGConfig(node *models.Node, peerupdate bool) error {
2021-11-13 00:24:29 +08:00
2021-12-11 04:01:10 +08:00
peers, hasGateway, gateways, err := GetServerPeers(node)
2021-11-13 00:24:29 +08:00
if err != nil {
return err
}
privkey, err := FetchPrivKey(node.ID)
if err != nil {
return err
}
if peerupdate {
2021-12-11 04:01:10 +08:00
err = setServerPeers(node.Interface, node.PersistentKeepalive, peers[:])
2021-12-07 04:31:08 +08:00
logger.Log(2, "updated peers on server", node.Name)
2021-11-13 00:24:29 +08:00
} else {
2021-12-11 04:01:10 +08:00
err = initWireguard(node, privkey, peers[:], hasGateway, gateways[:])
2021-12-07 04:31:08 +08:00
logger.Log(3, "finished setting wg config on server", node.Name)
2021-11-13 00:24:29 +08:00
}
2021-12-11 10:09:42 +08:00
peers = nil
2021-11-13 00:24:29 +08:00
return err
}
func setWGKeyConfig(node *models.Node) error {
2021-10-14 03:15:20 +08:00
privatekey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
privkeystring := privatekey.String()
publickey := privatekey.PublicKey()
node.PublicKey = publickey.String()
err = StorePrivKey(node.ID, privkeystring)
if err != nil {
return err
}
if node.Action == models.NODE_UPDATE_KEY {
node.Action = models.NODE_NOOP
}
2021-12-11 04:01:10 +08:00
return setWGConfig(node, false)
2021-10-14 03:15:20 +08:00
}
func removeLocalServer(node *models.Node) error {
var ifacename = node.Interface
var err error
if err = RemovePrivKey(node.ID); err != nil {
2021-12-07 04:31:08 +08:00
logger.Log(1, "failed to remove server conf from db", node.ID)
}
if ifacename != "" {
if !ncutils.IsKernel() {
if err = RemoveConf(ifacename, true); err == nil {
2021-12-07 04:31:08 +08:00
logger.Log(1, "removed WireGuard interface:", ifacename)
}
} else {
ipExec, err := exec.LookPath("ip")
if err != nil {
return err
}
out, err := ncutils.RunCmd(ipExec+" link del "+ifacename, false)
dontprint := strings.Contains(out, "does not exist") || strings.Contains(out, "Cannot find device")
if err != nil && !dontprint {
2021-12-07 04:31:08 +08:00
logger.Log(1, "error running command:", ipExec, "link del", ifacename)
logger.Log(1, out)
}
if node.PostDown != "" {
runcmds := strings.Split(node.PostDown, "; ")
_ = ncutils.RunCmds(runcmds, false)
}
}
}
home := ncutils.GetNetclientPathSpecific()
if ncutils.FileExists(home + "netconfig-" + node.Network) {
_ = os.Remove(home + "netconfig-" + node.Network)
}
if ncutils.FileExists(home + "nettoken-" + node.Network) {
_ = os.Remove(home + "nettoken-" + node.Network)
}
if ncutils.FileExists(home + "secret-" + node.Network) {
_ = os.Remove(home + "secret-" + node.Network)
}
if ncutils.FileExists(home + "wgkey-" + node.Network) {
_ = os.Remove(home + "wgkey-" + node.Network)
}
if ncutils.FileExists(home + "nm-" + node.Network + ".conf") {
_ = os.Remove(home + "nm-" + node.Network + ".conf")
}
return err
}