diff --git a/README.md b/README.md index be99d3c..c0414f4 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ A detailed description for using this software with a raspberry pi can be found ## What is out of scope * Generation or application of any `iptables` or `nftables` rules - * Setting up or changing IP-addresses of the WireGuard interface + * Setting up or changing IP-addresses of the WireGuard interface on operating systems other than linux ## Application stack diff --git a/go.mod b/go.mod index bb8afa5..8eea560 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/gorilla/sessions v1.2.1 // indirect github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible github.com/kelseyhightower/envconfig v1.4.0 + github.com/milosgajdos/tenus v0.0.3 github.com/sirupsen/logrus v1.7.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e diff --git a/internal/common/configuration.go b/internal/common/configuration.go index b4640f8..aadedba 100644 --- a/internal/common/configuration.go +++ b/internal/common/configuration.go @@ -4,6 +4,7 @@ import ( "errors" "os" "reflect" + "runtime" "github.com/h44z/wg-portal/internal/wireguard" @@ -91,6 +92,7 @@ func NewConfig() *Config { cfg.LDAP.BindPass = "SuperSecret" cfg.WG.DeviceName = "wg0" cfg.WG.WireGuardConfig = "/etc/wireguard/wg0.conf" + cfg.WG.ManageIPAddresses = true cfg.AdminLdapGroup = "CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL" cfg.Email.Host = "127.0.0.1" cfg.Email.Port = 25 @@ -109,5 +111,10 @@ func NewConfig() *Config { log.Warnf("unable to load environment config: %v", err) } + if cfg.WG.ManageIPAddresses && runtime.GOOS != "linux" { + log.Warnf("Managing IP addresses only works on linux! Feature disabled.") + cfg.WG.ManageIPAddresses = false + } + return cfg } diff --git a/internal/server/handlers_interface.go b/internal/server/handlers_interface.go index bd199aa..5d2b3f9 100644 --- a/internal/server/handlers_interface.go +++ b/internal/server/handlers_interface.go @@ -82,8 +82,24 @@ func (s *Server) PostAdminEditInterface(c *gin.Context) { return } + // Update interface IP address + if s.config.WG.ManageIPAddresses { + if err := s.wg.SetIPAddress(formDevice.IPs); err != nil { + _ = s.updateFormInSession(c, formDevice) + s.setFlashMessage(c, "Failed to update ip address: "+err.Error(), "danger") + c.Redirect(http.StatusSeeOther, "/admin/device/edit?formerr=update") + } + if err := s.wg.SetMTU(formDevice.Mtu); err != nil { + _ = s.updateFormInSession(c, formDevice) + s.setFlashMessage(c, "Failed to update MTU: "+err.Error(), "danger") + c.Redirect(http.StatusSeeOther, "/admin/device/edit?formerr=update") + } + } + s.setFlashMessage(c, "Changes applied successfully!", "success") - s.setFlashMessage(c, "WireGuard must be restarted to apply ip changes.", "warning") + if !s.config.WG.ManageIPAddresses { + s.setFlashMessage(c, "WireGuard must be restarted to apply ip changes.", "warning") + } c.Redirect(http.StatusSeeOther, "/admin/device/edit") } diff --git a/internal/server/usermanager.go b/internal/server/usermanager.go index c4db8b9..a4070ba 100644 --- a/internal/server/usermanager.go +++ b/internal/server/usermanager.go @@ -319,6 +319,18 @@ func (u *UserManager) InitFromCurrentInterface() error { log.Errorf("failed to init user-manager from device: %v", err) return err } + var ipAddresses []string + var mtu int + if u.wg.Cfg.ManageIPAddresses { + if ipAddresses, err = u.wg.GetIPAddress(); err != nil { + log.Errorf("failed to init user-manager from device: %v", err) + return err + } + if mtu, err = u.wg.GetMTU(); err != nil { + log.Errorf("failed to init user-manager from device: %v", err) + return err + } + } // Check if entries already exist in database, if not create them for _, peer := range peers { @@ -326,7 +338,7 @@ func (u *UserManager) InitFromCurrentInterface() error { return err } } - if err := u.validateOrCreateDevice(*device); err != nil { + if err := u.validateOrCreateDevice(*device, ipAddresses, mtu); err != nil { return err } @@ -366,7 +378,7 @@ func (u *UserManager) validateOrCreateUserForPeer(peer wgtypes.Peer) error { return nil } -func (u *UserManager) validateOrCreateDevice(dev wgtypes.Device) error { +func (u *UserManager) validateOrCreateDevice(dev wgtypes.Device, ipAddresses []string, mtu int) error { device := Device{} u.db.Where("device_name = ?", dev.Name).FirstOrInit(&device) @@ -377,6 +389,8 @@ func (u *UserManager) validateOrCreateDevice(dev wgtypes.Device) error { device.ListenPort = dev.ListenPort device.Mtu = 0 device.PersistentKeepalive = 16 // Default + device.IPsStr = strings.Join(ipAddresses, ", ") + device.Mtu = mtu res := u.db.Create(&device) if res.Error != nil { diff --git a/internal/wireguard/config.go b/internal/wireguard/config.go index adbeaf7..5b9d4c6 100644 --- a/internal/wireguard/config.go +++ b/internal/wireguard/config.go @@ -1,6 +1,7 @@ package wireguard type Config struct { - DeviceName string `yaml:"device" envconfig:"WG_DEVICE"` - WireGuardConfig string `yaml:"configFile" envconfig:"WG_CONFIG_FILE"` // optional, if set, updates will be written to this file + DeviceName string `yaml:"device" envconfig:"WG_DEVICE"` + WireGuardConfig string `yaml:"configFile" envconfig:"WG_CONFIG_FILE"` // optional, if set, updates will be written to this file + ManageIPAddresses bool `yaml:"manageIPAddresses" envconfig:"MANAGE_IPS"` // handle ip-address setup of interface } diff --git a/internal/wireguard/net.go b/internal/wireguard/net.go new file mode 100644 index 0000000..3eca03c --- /dev/null +++ b/internal/wireguard/net.go @@ -0,0 +1,118 @@ +package wireguard + +import ( + "fmt" + "net" + + "github.com/milosgajdos/tenus" +) + +func (m *Manager) GetIPAddress() ([]string, error) { + wgInterface, err := tenus.NewLinkFrom(m.Cfg.DeviceName) + if err != nil { + return nil, fmt.Errorf("could not retrieve WireGuard interface %s: %w", m.Cfg.DeviceName, err) + } + + // Get golang net.interface + iface := wgInterface.NetInterface() + if iface == nil { // Not sure if this check is really necessary + return nil, fmt.Errorf("could not retrieve WireGuard net.interface: %w", err) + } + + addrs, err := iface.Addrs() + if err != nil { + return nil, fmt.Errorf("could not retrieve WireGuard ip addresses: %w", err) + } + + ipAddresses := make([]string, 0, len(addrs)) + for _, addr := range addrs { + var ip net.IP + var mask net.IPMask + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + mask = v.Mask + case *net.IPAddr: + ip = v.IP + mask = ip.DefaultMask() + } + if ip == nil { + continue // something is wrong? + } + + maskSize, _ := mask.Size() + cidr := fmt.Sprintf("%s/%d", ip.String(), maskSize) + ipAddresses = append(ipAddresses, cidr) + } + + return ipAddresses, nil +} + +func (m *Manager) SetIPAddress(cidrs []string) error { + wgInterface, err := tenus.NewLinkFrom(m.Cfg.DeviceName) + if err != nil { + return fmt.Errorf("could not retrieve WireGuard interface %s: %w", m.Cfg.DeviceName, err) + } + + // First remove existing IP addresses + existingIPs, err := m.GetIPAddress() + if err != nil { + return err + } + for _, cidr := range existingIPs { + wgIp, wgIpNet, err := net.ParseCIDR(cidr) + if err != nil { + return fmt.Errorf("unable to parse cidr %s: %w", cidr, err) + } + + if err := wgInterface.UnsetLinkIp(wgIp, wgIpNet); err != nil { + return fmt.Errorf("failed to unset ip %s: %w", cidr, err) + } + } + + // Next set new IP adrresses + for _, cidr := range cidrs { + wgIp, wgIpNet, err := net.ParseCIDR(cidr) + if err != nil { + return fmt.Errorf("unable to parse cidr %s: %w", cidr, err) + } + + if err := wgInterface.SetLinkIp(wgIp, wgIpNet); err != nil { + return fmt.Errorf("failed to set ip %s: %w", cidr, err) + } + } + + return nil +} + +func (m *Manager) GetMTU() (int, error) { + wgInterface, err := tenus.NewLinkFrom(m.Cfg.DeviceName) + if err != nil { + return 0, fmt.Errorf("could not retrieve WireGuard interface %s: %w", m.Cfg.DeviceName, err) + } + + // Get golang net.interface + iface := wgInterface.NetInterface() + if iface == nil { // Not sure if this check is really necessary + return 0, fmt.Errorf("could not retrieve WireGuard net.interface: %w", err) + } + + return iface.MTU, nil +} + +func (m *Manager) SetMTU(mtu int) error { + wgInterface, err := tenus.NewLinkFrom(m.Cfg.DeviceName) + if err != nil { + return fmt.Errorf("could not retrieve WireGuard interface %s: %w", m.Cfg.DeviceName, err) + } + + if mtu == 0 { + mtu = 1420 // WireGuard default MTU + } + + if err := wgInterface.SetLinkMTU(mtu); err != nil { + return fmt.Errorf("could not set MTU on interface %s: %w", m.Cfg.DeviceName, err) + } + + return nil +}