From 94ca1778847d1f316270bf52ff175e873b335892 Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Mon, 5 Apr 2021 18:38:38 +0200 Subject: [PATCH] support different interface types: client and server mode --- assets/css/custom.css | 8 + assets/tpl/admin_edit_client.html | 124 +------ assets/tpl/admin_edit_interface.html | 505 ++++++++++---------------- assets/tpl/admin_index.html | 58 ++- internal/server/handlers_common.go | 1 + internal/server/handlers_interface.go | 22 +- internal/server/routes.go | 1 + internal/server/server_helper.go | 12 +- internal/wireguard/peermanager.go | 73 +++- internal/wireguard/tpl/interface.tpl | 29 +- internal/wireguard/tpl/peer.tpl | 2 +- 11 files changed, 378 insertions(+), 457 deletions(-) diff --git a/assets/css/custom.css b/assets/css/custom.css index f450dbb..f1a7a2c 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -80,4 +80,12 @@ pre{background:#f7f7f9}iframe{overflow:hidden;border:none}@media (min-width: 768 .form-group.required label:after { content:"*"; color:red; +} + +a.advanced-settings:before { + content: "Hide"; +} + +a.advanced-settings.collapsed:before { + content: "Show"; } \ No newline at end of file diff --git a/assets/tpl/admin_edit_client.html b/assets/tpl/admin_edit_client.html index 419c601..4cf61bd 100644 --- a/assets/tpl/admin_edit_client.html +++ b/assets/tpl/admin_edit_client.html @@ -91,11 +91,11 @@
- +
- +
@@ -192,7 +192,7 @@
- +
@@ -209,124 +209,6 @@ - - Cancel - - {{end}} - - - {{if eq .Device.Type "custom"}} - {{if .Peer.IsNew}} -

Create a new peer

- {{else}} -

Edit peer: {{.Peer.Identifier}}

- {{end}} - -
- - - - - {{if .EditableKeys}} -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
- {{else}} - - -
-
- - -
-
- {{end}} -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
- - -
-
- -
-
-
- - -
-
- - -
-
-
- - Cancel
diff --git a/assets/tpl/admin_edit_interface.html b/assets/tpl/admin_edit_interface.html index 1af2c9c..4373f09 100644 --- a/assets/tpl/admin_edit_interface.html +++ b/assets/tpl/admin_edit_interface.html @@ -23,338 +23,231 @@ -
- - - -

Server's interface configuration

-
-
- - + + + +

Server's interface configuration

+
+
+ + +
-
- {{if .EditableKeys}} -
-
- - + {{if .EditableKeys}} +
+
+ + +
-
-
-
- - +
+
+ + +
-
- {{else}} - -
-
- - + {{else}} + +
+
+ + +
-
- {{end}} -
-
- - + {{end}} +
+
+ + +
+
+ + +
-
- - +

Client's global configuration

+
+
+ + +
-
-
-
- - +
+
+ + +
+
+ + +
-
- - +
+
+ + +
+
+ + +
-
-

Client's global configuration

-
-
- - +

Interface configuration hooks

+
+
+ + +
-
-
-
- - +
+
+ + +
-
- - +
+
+ + +
-
-
-
- - +
+
+ + +
-
- - -
-
-

Interface configuration hooks

-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
- - Cancel - Apply Global Settings to peers - + +
+
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
+ + + Cancel + Apply Global Settings to clients +
- - - -

Client's interface configuration

-
-
- - -
-
- {{if .EditableKeys}} -
-
- - -
-
-
-
- - -
-
- {{else}} - -
-
- - -
-
- {{end}} -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-

Interface configuration hooks

-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
- - - Cancel -
-
- - -
-
- - - -

Custom interface configuration

-
-
- - -
-
- {{if .EditableKeys}} -
-
- - -
-
-
-
- - -
-
- {{else}} - -
-
- - -
-
- {{end}} -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
-
- - + + + +

Client's interface configuration

+
+
+ + +
+
+ {{if .EditableKeys}} +
+
+ + +
+
+
+
+ + +
+
+ {{else}} + +
+
+ + +
+
+ {{end}} +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+

Interface configuration hooks

+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
-
-
-

Peer's global configuration

-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
-

Interface configuration hooks

-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
- - Cancel - Apply Global Settings to clients - + + Cancel +
diff --git a/assets/tpl/admin_index.html b/assets/tpl/admin_index.html index 2066777..8077b7f 100644 --- a/assets/tpl/admin_index.html +++ b/assets/tpl/admin_index.html @@ -18,7 +18,9 @@
- Interface status for {{.Device.DeviceName}} + Interface status for {{.Device.DeviceName}} {{if eq $.Device.Type "server"}}(server mode){{end}}{{if eq $.Device.Type "client"}}(client mode){{end}} + +         @@ -26,6 +28,7 @@
+ {{if eq $.Device.Type "server"}}
@@ -78,21 +81,60 @@
+ {{end}} + {{if eq $.Device.Type "client"}} +
+ + + + + + + + + + + + + + + +
Public Key:{{.Device.PublicKey}}
Enabled Endpoints:{{len .Device.Interface.Peers}}
Total Endpoints:{{.TotalPeers}}
+
+
+ + + + + + + + + + + + + + + +
IP Address:{{.Device.IPsStr}}
DNS servers:{{.Device.DNSStr}}
Default MTU:{{.Device.Mtu}}
+
+ {{end}}
- {{with or (eq $.Device.Type "server") (eq $.Device.Type "custom")}} + {{if eq $.Device.Type "server"}}

Current VPN Peers

{{end}} - {{with eq $.Device.Type "client"}} + {{if eq $.Device.Type "client"}}

Current VPN Endpoints

{{end}}
- {{with eq $.Device.Type "server"}} + {{if eq $.Device.Type "server"}} {{end}} M @@ -140,9 +182,11 @@ + {{if eq $.Device.Type "server"}} + {{end}} @@ -168,22 +212,28 @@

{{if $p.DeactivatedAt}}-{{else}} {{formatBytes $p.Peer.ReceiveBytes}} / {{formatBytes $p.Peer.TransmitBytes}}{{end}}

{{end}}
+ {{if eq $.Device.Type "server"}}
{{$p.Config}}
+ {{end}}
+ {{if eq $.Device.Type "server"}} + {{end}}
+ {{if eq $.Device.Type "server"}} + {{end}}
diff --git a/internal/server/handlers_common.go b/internal/server/handlers_common.go index 84e028a..2fa1087 100644 --- a/internal/server/handlers_common.go +++ b/internal/server/handlers_common.go @@ -160,6 +160,7 @@ func (s *Server) updateFormInSession(c *gin.Context, formData interface{}) error func (s *Server) setNewPeerFormInSession(c *gin.Context) (SessionData, error) { currentSession := GetSessionData(c) + // If session does not contain a peer form ignore update // If url contains a formerr parameter reset the form if currentSession.FormData == nil || c.Query("formerr") == "" { diff --git a/internal/server/handlers_interface.go b/internal/server/handlers_interface.go index cf20c15..96f477e 100644 --- a/internal/server/handlers_interface.go +++ b/internal/server/handlers_interface.go @@ -58,8 +58,6 @@ func (s *Server) PostAdminEditInterface(c *gin.Context) { formDevice.DefaultPersistentKeepalive = 0 formDevice.SaveConfig = false case wireguard.DeviceTypeServer: - formDevice.SaveConfig = false - case wireguard.DeviceTypeCustom: } // Update WireGuard device @@ -127,6 +125,21 @@ func (s *Server) GetInterfaceConfig(c *gin.Context) { return } +func (s *Server) GetSaveConfig(c *gin.Context) { + currentSession := GetSessionData(c) + + err := s.WriteWireGuardConfigFile(currentSession.DeviceName) + if err != nil { + SetFlashMessage(c, "Failed to save WireGuard config-file: "+err.Error(), "danger") + c.Redirect(http.StatusSeeOther, "/admin/") + return + } + + SetFlashMessage(c, "Updated WireGuard config-file", "success") + c.Redirect(http.StatusSeeOther, "/admin/") + return +} + func (s *Server) GetApplyGlobalConfig(c *gin.Context) { currentSession := GetSessionData(c) device := s.peers.GetDevice(currentSession.DeviceName) @@ -149,10 +162,7 @@ func (s *Server) GetApplyGlobalConfig(c *gin.Context) { peer.PersistentKeepalive = device.DefaultPersistentKeepalive peer.DNSStr = device.DNSStr peer.Mtu = device.Mtu - - if device.Type == wireguard.DeviceTypeServer { - peer.EndpointPublicKey = device.PublicKey - } + peer.EndpointPublicKey = device.PublicKey if err := s.peers.UpdatePeer(peer); err != nil { SetFlashMessage(c, err.Error(), "danger") diff --git a/internal/server/routes.go b/internal/server/routes.go index 03cf998..6eb47ac 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -32,6 +32,7 @@ func SetupRoutes(s *Server) { admin.GET("/device/edit", s.GetAdminEditInterface) admin.POST("/device/edit", s.PostAdminEditInterface) admin.GET("/device/download", s.GetInterfaceConfig) + admin.GET("/device/write", s.GetSaveConfig) admin.GET("/device/applyglobals", s.GetApplyGlobalConfig) admin.GET("/peer/edit", s.GetAdminEditPeer) admin.POST("/peer/edit", s.PostAdminEditPeer) diff --git a/internal/server/server_helper.go b/internal/server/server_helper.go index e94a04f..5eec6b3 100644 --- a/internal/server/server_helper.go +++ b/internal/server/server_helper.go @@ -46,8 +46,6 @@ func (s *Server) PrepareNewPeer(device string) (wireguard.Peer, error) { peer.UID = fmt.Sprintf("u%x", md5.Sum([]byte(peer.PublicKey))) switch dev.Type { - case wireguard.DeviceTypeCustom: - fallthrough case wireguard.DeviceTypeServer: peer.EndpointPublicKey = dev.PublicKey peer.Endpoint = dev.DefaultEndpoint @@ -121,7 +119,7 @@ func (s *Server) CreatePeer(device string, peer wireguard.Peer) error { // Create WireGuard interface if peer.DeactivatedAt == nil { - if err := s.wg.AddPeer(device, peer.GetConfig()); err != nil { + if err := s.wg.AddPeer(device, peer.GetConfig(&dev)); err != nil { return errors.WithMessage(err, "failed to add WireGuard peer") } } @@ -137,6 +135,7 @@ func (s *Server) CreatePeer(device string, peer wireguard.Peer) error { // UpdatePeer updates the physical WireGuard interface and the database. func (s *Server) UpdatePeer(peer wireguard.Peer, updateTime time.Time) error { currentPeer := s.peers.GetPeerByKey(peer.PublicKey) + dev := s.peers.GetDevice(peer.DeviceName) // Update WireGuard device var err error @@ -144,9 +143,9 @@ func (s *Server) UpdatePeer(peer wireguard.Peer, updateTime time.Time) error { case peer.DeactivatedAt == &updateTime: err = s.wg.RemovePeer(peer.DeviceName, peer.PublicKey) case peer.DeactivatedAt == nil && currentPeer.Peer != nil: - err = s.wg.UpdatePeer(peer.DeviceName, peer.GetConfig()) + err = s.wg.UpdatePeer(peer.DeviceName, peer.GetConfig(&dev)) case peer.DeactivatedAt == nil && currentPeer.Peer == nil: - err = s.wg.AddPeer(peer.DeviceName, peer.GetConfig()) + err = s.wg.AddPeer(peer.DeviceName, peer.GetConfig(&dev)) } if err != nil { return errors.WithMessage(err, "failed to update WireGuard peer") @@ -178,10 +177,11 @@ func (s *Server) DeletePeer(peer wireguard.Peer) error { // RestoreWireGuardInterface restores the state of the physical WireGuard interface from the database. func (s *Server) RestoreWireGuardInterface(device string) error { activePeers := s.peers.GetActivePeers(device) + dev := s.peers.GetDevice(device) for i := range activePeers { if activePeers[i].Peer == nil { - if err := s.wg.AddPeer(device, activePeers[i].GetConfig()); err != nil { + if err := s.wg.AddPeer(device, activePeers[i].GetConfig(&dev)); err != nil { return errors.WithMessage(err, "failed to add WireGuard peer") } } diff --git a/internal/wireguard/peermanager.go b/internal/wireguard/peermanager.go index de6bc3e..56a8dfd 100644 --- a/internal/wireguard/peermanager.go +++ b/internal/wireguard/peermanager.go @@ -126,8 +126,15 @@ func (p Peer) GetAllowedIPs() []string { return common.ParseStringList(p.AllowedIPsStr) } -func (p Peer) GetConfig() wgtypes.PeerConfig { - publicKey, _ := wgtypes.ParseKey(p.PublicKey) +func (p Peer) GetConfig(dev *Device) wgtypes.PeerConfig { + var publicKey wgtypes.Key + switch dev.Type { + case DeviceTypeServer: + publicKey, _ = wgtypes.ParseKey(p.PublicKey) + case DeviceTypeClient: + publicKey, _ = wgtypes.ParseKey(p.EndpointPublicKey) + } + var presharedKey *wgtypes.Key if p.PresharedKey != "" { presharedKeyTmp, _ := wgtypes.ParseKey(p.PresharedKey) @@ -218,7 +225,6 @@ type DeviceType string const ( DeviceTypeServer DeviceType = "server" DeviceTypeClient DeviceType = "client" - DeviceTypeCustom DeviceType = "custom" ) type Device struct { @@ -272,7 +278,6 @@ func (d Device) IsValid() bool { if len(d.GetIPAddresses()) == 0 { return false } - case DeviceTypeCustom: } return true @@ -360,6 +365,17 @@ func NewPeerManager(db *gorm.DB, wg *Manager) (*PeerManager, error) { } } + // validate and update existing peers if needed + for _, deviceName := range wg.Cfg.DeviceNames { + dev := pm.GetDevice(deviceName) + peers = pm.GetAllPeers(deviceName) + for i := range peers { + if err := pm.fixPeerDefaultData(&peers[i], &dev); err != nil { + return nil, errors.WithMessagef(err, "unable to fix peers for interface %s", deviceName) + } + } + } + return pm, nil } @@ -406,16 +422,33 @@ func (m *PeerManager) InitFromPhysicalInterface() error { // assumption: server mode is used func (m *PeerManager) validateOrCreatePeer(device string, wgPeer wgtypes.Peer) error { peer := Peer{} - m.db.Where("public_key = ?", wgPeer.PublicKey.String()).FirstOrInit(&peer) + m.db.Where("public_key = ? OR endpoint_public_key = ?", wgPeer.PublicKey.String(), wgPeer.PublicKey.String()).FirstOrInit(&peer) + + dev := m.GetDevice(device) if peer.PublicKey == "" { // peer not found, create peer.UID = fmt.Sprintf("u%x", md5.Sum([]byte(wgPeer.PublicKey.String()))) - peer.PublicKey = wgPeer.PublicKey.String() + if dev.Type == DeviceTypeServer { + peer.PublicKey = wgPeer.PublicKey.String() + peer.Identifier = "Autodetected Client (" + peer.PublicKey[0:8] + ")" + } else if dev.Type == DeviceTypeClient { + // create a new key pair, not really needed but otherwise our "client exists" detection does not work... + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + return errors.Wrap(err, "failed to generate dummy private key") + } + peer.PrivateKey = key.String() + peer.PublicKey = key.PublicKey().String() + peer.EndpointPublicKey = wgPeer.PublicKey.String() + if wgPeer.Endpoint != nil { + peer.Endpoint = wgPeer.Endpoint.String() + } + peer.Identifier = "Autodetected Endpoint (" + peer.EndpointPublicKey[0:8] + ")" + } if wgPeer.PresharedKey != (wgtypes.Key{}) { peer.PresharedKey = wgPeer.PresharedKey.String() } peer.Email = "autodetected@example.com" - peer.Identifier = "Autodetected Client (" + peer.PublicKey[0:8] + ")" peer.UpdatedAt = time.Now() peer.CreatedAt = time.Now() IPs := make([]string, len(wgPeer.AllowedIPs)) // use allowed IP's as the peer IP's @@ -424,9 +457,6 @@ func (m *PeerManager) validateOrCreatePeer(device string, wgPeer wgtypes.Peer) e } peer.SetIPAddresses(IPs...) peer.DeviceName = device - if wgPeer.Endpoint != nil { - peer.Endpoint = wgPeer.Endpoint.String() // TODO: do we need to import this for server mode? - } res := m.db.Create(&peer) if res.Error != nil { @@ -494,6 +524,29 @@ func (m *PeerManager) populatePeerData(peer *Peer) { peer.IsOnline = false } +// fixPeerDefaultData tries to fill all required fields for the given peer +func (m *PeerManager) fixPeerDefaultData(peer *Peer, device *Device) error { + updatePeer := false + + switch device.Type { + case DeviceTypeServer: + if peer.Endpoint == "" { + peer.Endpoint = device.DefaultEndpoint + updatePeer = true + } + if peer.EndpointPublicKey == "" { + peer.EndpointPublicKey = device.PublicKey + updatePeer = true + } + case DeviceTypeClient: + } + + if updatePeer { + return m.UpdatePeer(*peer) + } + return nil +} + // populateDeviceData enriches the device struct with WireGuard live data like interface information func (m *PeerManager) populateDeviceData(device *Device) { // set data from WireGuard interface diff --git a/internal/wireguard/tpl/interface.tpl b/internal/wireguard/tpl/interface.tpl index e851b14..f8aa3d2 100644 --- a/internal/wireguard/tpl/interface.tpl +++ b/internal/wireguard/tpl/interface.tpl @@ -10,13 +10,16 @@ PrivateKey = {{ .Interface.PrivateKey }} Address = {{ .Interface.IPsStr }} -# Misc. settings +# Misc. settings (optional) {{- if ne .Interface.ListenPort 0}} ListenPort = {{ .Interface.ListenPort }} {{- end}} {{- if ne .Interface.Mtu 0}} MTU = {{.Interface.Mtu}} {{- end}} +{{- if and (ne .Interface.DNSStr "") (eq $.Interface.Type "client")}} +DNS = {{ .Interface.DNSStr }} +{{- end}} {{- if ne .Interface.FirewallMark 0}} FwMark = {{.Interface.FirewallMark}} {{- end}} @@ -27,11 +30,19 @@ Table = {{.Interface.RoutingTable}} SaveConfig = true {{- end}} -# Interface hooks +# Interface hooks (optional) +{{- if .Interface.PreUp}} PreUp = {{ .Interface.PreUp }} +{{- end}} +{{- if .Interface.PostUp}} PostUp = {{ .Interface.PostUp }} +{{- end}} +{{- if .Interface.PreDown}} PreDown = {{ .Interface.PreDown }} +{{- end}} +{{- if .Interface.PostDown}} PostDown = {{ .Interface.PostDown }} +{{- end}} # # Peers @@ -43,12 +54,24 @@ PostDown = {{ .Interface.PostDown }} # -WGP- Peer email: {{.Email}} # -WGP- PrivateKey: {{.PrivateKey}} [Peer] +{{- if eq $.Interface.Type "server"}} PublicKey = {{ .PublicKey }} +{{- end}} +{{- if eq $.Interface.Type "client"}} +PublicKey = {{ .EndpointPublicKey }} +{{- end}} {{- if .PresharedKey}} PresharedKey = {{ .PresharedKey }} {{- end}} +{{- if eq $.Interface.Type "server"}} +AllowedIPs = {{ .IPsStr }} +{{- end}} +{{- if eq $.Interface.Type "client"}} +{{- if .AllowedIPsStr}} AllowedIPs = {{ .AllowedIPsStr }} -{{- if and (ne .Endpoint "") (ne $.Interface.Type "server")}} +{{- end}} +{{- end}} +{{- if and (ne .Endpoint "") (eq $.Interface.Type "client")}} Endpoint = {{ .Endpoint }} {{- end}} {{- if ne .PersistentKeepalive 0}} diff --git a/internal/wireguard/tpl/peer.tpl b/internal/wireguard/tpl/peer.tpl index 68e40c3..5899308 100644 --- a/internal/wireguard/tpl/peer.tpl +++ b/internal/wireguard/tpl/peer.tpl @@ -8,7 +8,7 @@ PrivateKey = {{ .Peer.PrivateKey }} Address = {{ .Peer.IPsStr }} -# Misc. settings +# Misc. settings (optional) {{- if .Peer.DNSStr}} DNS = {{ .Peer.DNSStr }} {{- end}}