feature: Support live peer list update (#51)

* created InitializePeer and ClosePeerConnection functions

* feature: simplify peer stopping

* chore: remove unused code

* feature: basic management service implementation (#44)

* feat: basic management service implementation [FAILING TESTS]

* test: fix healthcheck test

* test: #39 add peer registration endpoint test

* feat: #39 add setup key handling

* feat: #39 add peer management store persistence

* refactor: extract config read/write to the utility package

* refactor: move file contents copy to the utility package

* refactor: use Accounts instead of Users in the Store

* feature: add management server Docker file

* refactor: introduce datadir instead of config

* chore: use filepath.Join to concat filepaths instead of string concat

* refactor: move stop channel to the root

* refactor: move stop channel to the root

* review: fix PR review notes

Co-authored-by: braginini <hello@wiretrustee.com>

* Handle read config file errors

* feature: add letsencrypt support to the management service

* fix: lint warnings

* chore: change default datadir

* refactor: set default flags in code not Dockerfile

* chore: remove unused code

* Added RemovePeer and centralized configureDevice code

* remove peer from the wg interface when closing proxy

* remove config file

* add iface tests

* fix tests, validate if file exists before removing it

* removed unused functions UpdateListenPort and ConfigureWithKeyGen

* Ensure we don't wait for timeout when closing

* Rename ClosePeerConnection to RemovePeerConnection

* Avoid returning on uapi Accept failures

* Added engine tests

* Remove extra add address code

* Adding iface.Close

* Ensure Close the interface and disable parallel test execution

* check err var when listing interfaces

* chore: add synchronisation to peer management

* chore: add connection status to track peer connection

* refactor: remove unused code

Co-authored-by: braginini <hello@wiretrustee.com>
Co-authored-by: Mikhail Bragin <bangvalo@gmail.com>
This commit is contained in:
Maycon Santos
2021-07-19 15:02:11 +02:00
committed by GitHub
co-authored by braginini Mikhail Bragin
parent 4e17890597
commit 1a8c03bef0
12 changed files with 512 additions and 127 deletions
+45 -85
View File
@@ -41,8 +41,8 @@ func CreateWithUserspace(iface string, address string) error {
for {
uapiConn, err := uapi.Accept()
if err != nil {
log.Debugln(err)
return
log.Debugln("uapi Accept failed with error: ", err)
continue
}
go tunDevice.IpcHandle(uapiConn)
}
@@ -57,13 +57,21 @@ func CreateWithUserspace(iface string, address string) error {
return nil
}
// ConfigureWithKeyGen Extends the functionality of Configure(iface string, privateKey string) by generating a new Wireguard private key
func ConfigureWithKeyGen(iface string) (*wgtypes.Key, error) {
key, err := wgtypes.GeneratePrivateKey()
// configure peer for the wireguard device
func configureDevice(iface string, config wgtypes.Config) error {
wg, err := wgctrl.New()
if err != nil {
return nil, err
return err
}
return &key, Configure(iface, key.String())
defer wg.Close()
_, err = wg.Device(iface)
if err != nil {
return err
}
log.Debugf("got Wireguard device %s", iface)
return wg.ConfigureDevice(iface, config)
}
// Configure configures a Wireguard interface
@@ -71,11 +79,6 @@ func ConfigureWithKeyGen(iface string) (*wgtypes.Key, error) {
func Configure(iface string, privateKey string) error {
log.Debugf("configuring Wireguard interface %s", iface)
wg, err := wgctrl.New()
if err != nil {
return err
}
defer wg.Close()
log.Debugf("adding Wireguard private key")
key, err := wgtypes.ParseKey(privateKey)
@@ -84,18 +87,14 @@ func Configure(iface string, privateKey string) error {
}
fwmark := 0
p := WgPort
cfg := wgtypes.Config{
config := wgtypes.Config{
PrivateKey: &key,
ReplacePeers: false,
FirewallMark: &fwmark,
ListenPort: &p,
}
err = wg.ConfigureDevice(iface, cfg)
if err != nil {
return err
}
return nil
return configureDevice(iface, config)
}
// GetListenPort returns the listening port of the Wireguard endpoint
@@ -118,55 +117,12 @@ func GetListenPort(iface string) (*int, error) {
return &d.ListenPort, nil
}
// UpdateListenPort updates a Wireguard interface listen port
func UpdateListenPort(iface string, newPort int) error {
log.Debugf("updating Wireguard listen port of interface %s, new port %d", iface, newPort)
//discover Wireguard current configuration
wg, err := wgctrl.New()
if err != nil {
return err
}
defer wg.Close()
_, err = wg.Device(iface)
if err != nil {
return err
}
log.Debugf("got Wireguard device %s", iface)
config := wgtypes.Config{
ListenPort: &newPort,
ReplacePeers: false,
}
err = wg.ConfigureDevice(iface, config)
if err != nil {
return err
}
log.Debugf("updated Wireguard listen port of interface %s, new port %d", iface, newPort)
return nil
}
// UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist
// Endpoint is optional
func UpdatePeer(iface string, peerKey string, allowedIps string, keepAlive time.Duration, endpoint string) error {
log.Debugf("updating interface %s peer %s: endpoint %s ", iface, peerKey, endpoint)
wg, err := wgctrl.New()
if err != nil {
return err
}
defer wg.Close()
_, err = wg.Device(iface)
if err != nil {
return err
}
log.Debugf("got Wireguard device %s", iface)
//parse allowed ips
_, ipNet, err := net.ParseCIDR(allowedIps)
if err != nil {
@@ -177,20 +133,18 @@ func UpdatePeer(iface string, peerKey string, allowedIps string, keepAlive time.
if err != nil {
return err
}
peers := make([]wgtypes.PeerConfig, 0)
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
ReplaceAllowedIPs: true,
AllowedIPs: []net.IPNet{*ipNet},
PersistentKeepaliveInterval: &keepAlive,
}
peers = append(peers, peer)
config := wgtypes.Config{
ReplacePeers: false,
Peers: peers,
Peers: []wgtypes.PeerConfig{peer},
}
err = wg.ConfigureDevice(iface, config)
err = configureDevice(iface, config)
if err != nil {
return err
}
@@ -208,18 +162,6 @@ func UpdatePeerEndpoint(iface string, peerKey string, newEndpoint string) error
log.Debugf("updating peer %s endpoint %s ", peerKey, newEndpoint)
wg, err := wgctrl.New()
if err != nil {
return err
}
defer wg.Close()
_, err = wg.Device(iface)
if err != nil {
return err
}
log.Debugf("got Wireguard device %s", iface)
peerAddr, err := net.ResolveUDPAddr("udp4", newEndpoint)
if err != nil {
return err
@@ -231,23 +173,41 @@ func UpdatePeerEndpoint(iface string, peerKey string, newEndpoint string) error
if err != nil {
return err
}
peers := make([]wgtypes.PeerConfig, 0)
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
ReplaceAllowedIPs: false,
UpdateOnly: true,
Endpoint: peerAddr,
}
peers = append(peers, peer)
config := wgtypes.Config{
ReplacePeers: false,
Peers: peers,
Peers: []wgtypes.PeerConfig{peer},
}
err = wg.ConfigureDevice(iface, config)
return configureDevice(iface, config)
}
// RemovePeer removes a Wireguard Peer from the interface iface
func RemovePeer(iface string, peerKey string) error {
log.Debugf("Removing peer %s from interface %s ", peerKey, iface)
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
return err
}
return nil
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
Remove: true,
}
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{peer},
}
return configureDevice(iface, config)
}
// Closes the User Space tunnel interface
func CloseWithUserspace() error {
return tunIface.Close()
}
+5
View File
@@ -37,3 +37,8 @@ func addRoute(iface string, ipNet *net.IPNet) error {
}
return nil
}
// Closes the tunnel interface
func Close() error {
return CloseWithUserspace()
}
+37 -8
View File
@@ -1,8 +1,10 @@
package iface
import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
"golang.zx2c4.com/wireguard/wgctrl"
"os"
)
@@ -36,14 +38,6 @@ func CreateWithKernel(iface string, address string) error {
return err
}
log.Debugf("adding address %s to interface: %s", address, iface)
addr, _ := netlink.ParseAddr(address)
err = netlink.AddrAdd(&link, addr)
if os.IsExist(err) {
log.Infof("interface %s already has the address: %s", iface, address)
} else if err != nil {
return err
}
err = assignAddr(address, iface)
if err != nil {
return err
@@ -103,3 +97,38 @@ func (w *wgLink) Attrs() *netlink.LinkAttrs {
func (w *wgLink) Type() string {
return "wireguard"
}
// Closes the tunnel interface
func Close() error {
if tunIface != nil {
return CloseWithUserspace()
} else {
var iface = ""
wg, err := wgctrl.New()
if err != nil {
return err
}
defer wg.Close()
devList, err := wg.Devices()
if err != nil {
return err
}
for _, wgDev := range devList {
if wgDev.ListenPort == WgPort {
iface = wgDev.Name
break
}
}
if iface == "" {
return fmt.Errorf("Wireguard Interface not found")
}
attrs := netlink.NewLinkAttrs()
attrs.Name = iface
link := wgLink{
attrs: &attrs,
}
return netlink.LinkDel(&link)
}
}
+148
View File
@@ -0,0 +1,148 @@
package iface
import (
"fmt"
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"net"
"testing"
"time"
)
// keep darwin compability
const (
ifaceName = "utun999"
key = "0PMI6OkB5JmB+Jj/iWWHekuQRx+bipZirWCWKFXexHc="
peerPubKey = "Ok0mC0qlJyXEPKh2UFIpsI2jG0L7LRpC3sLAusSJ5CQ="
)
func Test_CreateInterface(t *testing.T) {
level, _ := log.ParseLevel("Debug")
log.SetLevel(level)
wgIP := "10.99.99.1/24"
err := Create(ifaceName, wgIP)
if err != nil {
t.Fatal(err)
}
wg, err := wgctrl.New()
if err != nil {
t.Fatal(err)
}
defer wg.Close()
_, err = wg.Device(ifaceName)
if err != nil {
t.Fatal(err)
}
}
func Test_ConfigureInterface(t *testing.T) {
err := Configure(ifaceName, key)
if err != nil {
t.Fatal(err)
}
wg, err := wgctrl.New()
if err != nil {
t.Fatal(err)
}
defer wg.Close()
wgDevice, err := wg.Device(ifaceName)
if err != nil {
t.Fatal(err)
}
if wgDevice.PrivateKey.String() != key {
t.Fatalf("Private keys don't match after configure: %s != %s", key, wgDevice.PrivateKey.String())
}
}
func Test_UpdatePeer(t *testing.T) {
keepAlive := 15 * time.Second
allowedIP := "10.99.99.2/32"
endpoint := "127.0.0.1:9900"
err := UpdatePeer(ifaceName, peerPubKey, allowedIP, keepAlive, endpoint)
if err != nil {
t.Fatal(err)
}
peer, err := getPeer()
if err != nil {
t.Fatal(err)
}
if peer.PersistentKeepaliveInterval != keepAlive {
t.Fatal("configured peer with mismatched keepalive interval value")
}
resolvedEndpoint, err := net.ResolveUDPAddr("udp", endpoint)
if err != nil {
t.Fatal(err)
}
if peer.Endpoint.String() != resolvedEndpoint.String() {
t.Fatal("configured peer with mismatched endpoint")
}
var foundAllowedIP bool
for _, aip := range peer.AllowedIPs {
if aip.String() == allowedIP {
foundAllowedIP = true
break
}
}
if !foundAllowedIP {
t.Fatal("configured peer with mismatched Allowed IPs")
}
}
func Test_UpdatePeerEndpoint(t *testing.T) {
newEndpoint := "127.0.0.1:9999"
err := UpdatePeerEndpoint(ifaceName, peerPubKey, newEndpoint)
if err != nil {
t.Fatal(err)
}
peer, err := getPeer()
if err != nil {
t.Fatal(err)
}
if peer.Endpoint.String() != newEndpoint {
t.Fatal("configured peer with mismatched endpoint")
}
}
func Test_RemovePeer(t *testing.T) {
err := RemovePeer(ifaceName, peerPubKey)
if err != nil {
t.Fatal(err)
}
_, err = getPeer()
if err.Error() != "peer not found" {
t.Fatal(err)
}
}
func Test_Close(t *testing.T) {
err := Close()
if err != nil {
t.Fatal(err)
}
}
func getPeer() (wgtypes.Peer, error) {
emptyPeer := wgtypes.Peer{}
wg, err := wgctrl.New()
if err != nil {
return emptyPeer, err
}
defer wg.Close()
wgDevice, err := wg.Device(ifaceName)
if err != nil {
return emptyPeer, err
}
for _, peer := range wgDevice.Peers {
if peer.PublicKey.String() == peerPubKey {
return peer, nil
}
}
return emptyPeer, fmt.Errorf("peer not found")
}
+5
View File
@@ -39,3 +39,8 @@ func assignAddr(address string, ifaceName string) error {
func getUAPI(iface string) (net.Listener, error) {
return ipc.UAPIListen(iface)
}
// Closes the tunnel interface
func Close() error {
return CloseWithUserspace()
}