diff --git a/internal/wireguard/backend_file.go b/internal/wireguard/backend_file.go index de4535a..5726415 100644 --- a/internal/wireguard/backend_file.go +++ b/internal/wireguard/backend_file.go @@ -60,15 +60,3 @@ func (f FileBackend) DeletePeer(_ PeerConfig, _ InterfaceConfig) error { return nil // the file backend will only store changed interfaces } - -func (f FileBackend) Load(identifier DeviceIdentifier) (InterfaceConfig, []PeerConfig, error) { - return InterfaceConfig{}, nil, nil -} - -func (f FileBackend) LoadAll(interfaceIdentifiers ...DeviceIdentifier) (map[InterfaceConfig][]PeerConfig, error) { - return nil, nil -} - -func (f FileBackend) GetAvailableInterfaces() ([]DeviceIdentifier, error) { - return nil, nil -} diff --git a/internal/wireguard/manager.go b/internal/wireguard/manager.go index 7c2dca8..62d6df5 100644 --- a/internal/wireguard/manager.go +++ b/internal/wireguard/manager.go @@ -1,12 +1,8 @@ package wireguard import ( - "bufio" - "fmt" "net" - "os" "sort" - "strconv" "strings" "sync" "time" @@ -45,29 +41,27 @@ PeerManager } +// ManagementUtil is a persistent management util for WireGuard configurations. type ManagementUtil struct { mux sync.RWMutex // mutex to synchronize access to maps - wg lowlevel.WireGuardClient - nl lowlevel.NetlinkClient - - unmanagedInterfaces []DeviceIdentifier // Those interfaces are completely ignored by WireGuard Portal - - // config writers and loaders are used to populate the internal config maps - cw []ConfigWriter - cl []ConfigLoader + wg lowlevel.WireGuardClient // WireGuard interface handler + nl lowlevel.NetlinkClient // Network interface handler + cs ConfigStore // Persistent backend // internal holder of interface configurations interfaces map[DeviceIdentifier]InterfaceConfig + // internal holder of peer configurations peers map[DeviceIdentifier]map[PeerIdentifier]PeerConfig } -func NewManagementUtil(wg lowlevel.WireGuardClient, nl lowlevel.NetlinkClient, opts ...Opt) (*ManagementUtil, error) { +func NewManagementUtil(wg lowlevel.WireGuardClient, nl lowlevel.NetlinkClient, cs ConfigStore, opts ...Opt) (*ManagementUtil, error) { m := &ManagementUtil{ mux: sync.RWMutex{}, wg: wg, nl: nl, + cs: cs, } for _, opt := range opts { @@ -89,18 +83,6 @@ } } -func ConfigLoaders(cl ...ConfigLoader) Opt { - return func(m *ManagementUtil) { - m.cl = cl - } -} - -func ConfigWriters(cw ...ConfigWriter) Opt { - return func(m *ManagementUtil) { - m.cw = cw - } -} - func (m *ManagementUtil) GetFreshKeypair() (KeyPair, error) { privateKey, err := wgtypes.GeneratePrivateKey() if err != nil { @@ -352,9 +334,9 @@ } // TODO: implement/think about -func (m *ManagementUtil) initialize() error { +func (m *ManagementUtil) loadFromBackend() error { // Load all interfaces from the database - backendInterfaces, err := m.getBackendInterfaces(DatabaseBackendName) + backendInterfaces, err := m.cs.GetAvailableInterfaces() if err != nil { return errors.Wrap(err, "failed to load backend interfaces") } @@ -403,30 +385,6 @@ return nil } -func (m *ManagementUtil) getBackendInterfaces(backend string) ([]DeviceIdentifier, error) { - // Load all interfaces from the config loader backends - uniqueInterfaces := make(map[DeviceIdentifier]struct{}) - for _, cl := range m.cl { - if cl.Name() != backend { - continue - } - - backendInterfaces, err := cl.GetAvailableInterfaces() - if err != nil { - return nil, errors.Wrapf(err, "failed to load available interfaces from backend %s", cl.Name()) - } - for _, iface := range backendInterfaces { - uniqueInterfaces[iface] = struct{}{} - } - } - - interfaces := make([]DeviceIdentifier, 0, len(uniqueInterfaces)) - for iface := range uniqueInterfaces { - interfaces = append(interfaces, iface) - } - return interfaces, nil -} - func (m *ManagementUtil) loadBackendInterfaces(backend string, identifiers ...DeviceIdentifier) error { for _, cl := range m.cl { if cl.Name() != backend { @@ -592,15 +550,14 @@ peers = append(peers, config) } - for _, writer := range m.cw { - if delete { - err = writer.DeleteInterface(device, peers) - } else { - err = writer.SaveInterface(device, peers) - } - if err != nil { - return errors.Wrapf(err, "failed to persist interface %s", identifier) - } + if !delete { + err = m.cs.SaveInterface(device, peers) + } else { + err = m.cs.DeleteInterface(device.DeviceName) + } + + if err != nil { + return errors.Wrapf(err, "failed to persist interface %s", identifier) } return nil @@ -619,179 +576,19 @@ } } - for _, writer := range m.cw { - if delete { - err = writer.DeletePeer(peer, device) - } else { - err = writer.SavePeer(peer, device) - } - if err != nil { - return errors.Wrapf(err, "failed to persist peer %s", identifier) - } + if !delete { + err = m.cs.SavePeer(peer, device.DeviceName) + } else { + err = m.cs.DeletePeer(peer.Uid, device.DeviceName) + } + + if err != nil { + return errors.Wrapf(err, "failed to persist peer %s", identifier) } return nil } -// TODO: fix/implement -func (m *ManagementUtil) loadExistingInterfaces() ([]InterfaceConfig, error) { - devices, err := m.wg.Devices() - if err != nil { - return nil, errors.Wrapf(err, "failed to get WireGuard device list") - } - - interfaces := make([]InterfaceConfig, len(devices)) - for i, device := range devices { - interfaces[i].DeviceName = DeviceIdentifier(device.Name) - interfaces[i].FirewallMark = int32(device.FirewallMark) - interfaces[i].KeyPair = KeyPair{ - PrivateKey: device.PrivateKey.String(), - PublicKey: device.PublicKey.String(), - } - interfaces[i].ListenPort = device.ListenPort - interfaces[i].DriverType = device.Type.String() - - parsedInterface, _, err := m.parseConfigFile(device.Name) - if err != nil { - continue - } - interfaces[i].DnsStr = parsedInterface.DnsStr - interfaces[i].DisplayName = parsedInterface.DisplayName - interfaces[i].PostDown = parsedInterface.PostDown - interfaces[i].PreDown = parsedInterface.PreDown - interfaces[i].PostUp = parsedInterface.PostUp - interfaces[i].PreUp = parsedInterface.PreUp - interfaces[i].AddressStr = parsedInterface.AddressStr - interfaces[i].RoutingTable = parsedInterface.RoutingTable - interfaces[i].Mtu = parsedInterface.Mtu - - fmt.Println(interfaces[i]) - } - - return interfaces, nil -} - -// parseConfigFile parses WireGuard configuration files (INI syntax) and some additional comments in the file -// TODO: fix/implement -func (m *ManagementUtil) parseConfigFile(interfaceName string) (InterfaceConfig, []PeerConfig, error) { - configFile := "TODO" //filepath.Join(m.configPath, interfaceName+".conf") - - file, err := os.Open(configFile) - if err != nil { - return InterfaceConfig{}, nil, errors.Wrapf(err, "unable to open config file for interface %s", interfaceName) - } - scanner := bufio.NewScanner(file) - - peerSection := false - iface := InterfaceConfig{} - for scanner.Scan() { - line := scanner.Text() - line = strings.TrimSpace(line) - - switch { - case strings.HasPrefix(line, "#"): // A comment line - line = line[1:] - commentParts := strings.SplitN(line, "=", 1) - fmt.Println(commentParts, peerSection) - case strings.HasPrefix(line, "["): // Config section - line = strings.ToLower(line[1 : len(line)-1]) - switch line { - case "peer": - peerSection = true - case "interface": - peerSection = false - default: - return InterfaceConfig{}, nil, errors.Errorf("configuration file contains unsupported section %s", line) - } - default: //Config option - optionParts := strings.SplitN(line, "=", 1) - if len(optionParts) != 2 { - return InterfaceConfig{}, nil, errors.Errorf("configuration file contains invalid line %s", line) - } - option := strings.ToLower(strings.TrimSpace(optionParts[0])) - value := strings.TrimSpace(optionParts[1]) - peerOption := false - switch option { - // Interface - case "privatekey": - key, err := wgtypes.ParseKey(value) - if err != nil { - return InterfaceConfig{}, nil, errors.Wrapf(err, "interface section has no valid private Key") - } - iface.KeyPair = KeyPair{ - PrivateKey: key.String(), - PublicKey: key.PublicKey().String(), - } - case "address": - iface.AddressStr = value - case "listenport": - port, err := strconv.Atoi(value) - if err != nil { - return InterfaceConfig{}, nil, errors.Wrapf(err, "interface section has invalid listen port Value") - } - iface.ListenPort = port - case "postup": - iface.PostUp = value - case "postdown": - iface.PostDown = value - case "preup": - iface.PreUp = value - case "predown": - iface.PreDown = value - case "mtu": - mtu, err := strconv.Atoi(value) - if err != nil { - return InterfaceConfig{}, nil, errors.Wrapf(err, "interface section has invalid MTU Value") - } - iface.Mtu = mtu - case "dns": - iface.DnsStr = value - case "table": - iface.RoutingTable = value - case "fwmark": - fwMark, err := strconv.Atoi(value) - if err != nil { - return InterfaceConfig{}, nil, errors.Wrapf(err, "interface section has invalid fwmark Value") - } - iface.FirewallMark = int32(fwMark) - case "saveconfig": - saveConfig, err := strconv.ParseBool(value) - if err != nil { - return InterfaceConfig{}, nil, errors.Wrapf(err, "interface section has invalid save-config Value") - } - iface.SaveConfig = saveConfig - // Peer - case "endpoint": - peerOption = true - case "publickey": - peerOption = true - case "allowedips": - peerOption = true - case "persistentkeepalive": - peerOption = true - case "presharedkey": - peerOption = true - } - - if peerSection != peerOption { - return InterfaceConfig{}, nil, errors.Errorf("config section contains invalid option %s", option) - } - - fmt.Println(value) - } - if strings.HasPrefix(line, "#") { - fmt.Println("comment") - } - fmt.Println(line) - } - - if err := scanner.Err(); err != nil { - return InterfaceConfig{}, nil, errors.Wrapf(err, "unable to scan config file for interface %s", interfaceName) - } - - return InterfaceConfig{}, nil, nil -} - func parseIpAddressString(addrStr string) ([]*netlink.Addr, error) { rawAddresses := strings.Split(addrStr, ",") addresses := make([]*netlink.Addr, 0, len(rawAddresses)) diff --git a/internal/wireguard/persistence.go b/internal/wireguard/persistence.go new file mode 100644 index 0000000..1c31e6d --- /dev/null +++ b/internal/wireguard/persistence.go @@ -0,0 +1,14 @@ +package wireguard + +// ConfigStore provides an interface for interacting with different configuration storage repositories. +type ConfigStore interface { + GetAvailableInterfaces() ([]DeviceIdentifier, error) + GetAllInterfaces(interfaceIdentifiers ...DeviceIdentifier) (map[InterfaceConfig][]PeerConfig, error) + GetInterface(identifier DeviceIdentifier) (InterfaceConfig, []PeerConfig, error) + + SaveInterface(cfg InterfaceConfig, peers []PeerConfig) error + SavePeer(peer PeerConfig, interfaceIdentifier DeviceIdentifier) error + + DeleteInterface(identifier DeviceIdentifier) error + DeletePeer(peer PeerIdentifier, interfaceIdentifier DeviceIdentifier) error +} diff --git a/tmp/lowlevel/doc.go b/tmp/lowlevel/doc.go new file mode 100644 index 0000000..057cf03 --- /dev/null +++ b/tmp/lowlevel/doc.go @@ -0,0 +1,6 @@ +package lowlevel + +/** +This package contains wrappers for low level api's like netlink or the WireGuard control library. +Wrapping those external libraries makes mocking and testing code easier. +*/ diff --git a/tmp/lowlevel/netlink.go b/tmp/lowlevel/netlink.go new file mode 100644 index 0000000..b86d13e --- /dev/null +++ b/tmp/lowlevel/netlink.go @@ -0,0 +1,63 @@ +package lowlevel + +import ( + "github.com/vishvananda/netlink" +) + +// A NetlinkClient is a type which can control a netlink device. +type NetlinkClient interface { + LinkAdd(link netlink.Link) error + LinkDel(link netlink.Link) error + LinkByName(name string) (netlink.Link, error) + LinkSetUp(link netlink.Link) error + LinkSetDown(link netlink.Link) error + LinkSetMTU(link netlink.Link, mtu int) error + AddrReplace(link netlink.Link, addr *netlink.Addr) error + AddrAdd(link netlink.Link, addr *netlink.Addr) error + AddrList(link netlink.Link) ([]netlink.Addr, error) +} + +type NetlinkManager struct { +} + +func (n NetlinkManager) LinkAdd(link netlink.Link) error { return netlink.LinkAdd(link) } + +func (n NetlinkManager) LinkDel(link netlink.Link) error { return netlink.LinkDel(link) } + +func (n NetlinkManager) LinkByName(name string) (netlink.Link, error) { + return netlink.LinkByName(name) +} + +func (n NetlinkManager) LinkSetUp(link netlink.Link) error { return netlink.LinkSetUp(link) } + +func (n NetlinkManager) LinkSetDown(link netlink.Link) error { return netlink.LinkSetDown(link) } + +func (n NetlinkManager) LinkSetMTU(link netlink.Link, mtu int) error { + return netlink.LinkSetMTU(link, mtu) +} + +func (n NetlinkManager) AddrReplace(link netlink.Link, addr *netlink.Addr) error { + return netlink.AddrReplace(link, addr) +} + +func (n NetlinkManager) AddrAdd(link netlink.Link, addr *netlink.Addr) error { + return netlink.AddrAdd(link, addr) +} + +func (n NetlinkManager) AddrList(link netlink.Link) ([]netlink.Addr, error) { + listIPv4, err := netlink.AddrList(link, netlink.FAMILY_V4) + if err != nil { + return nil, err + } + + listIPv6, err := netlink.AddrList(link, netlink.FAMILY_V6) + if err != nil { + return nil, err + } + + ipAddresses := make([]netlink.Addr, 0, len(listIPv4)+len(listIPv6)) + ipAddresses = append(ipAddresses, listIPv4...) + ipAddresses = append(ipAddresses, listIPv6...) + + return ipAddresses, nil +} diff --git a/tmp/lowlevel/wgctrl.go b/tmp/lowlevel/wgctrl.go new file mode 100644 index 0000000..ab6832c --- /dev/null +++ b/tmp/lowlevel/wgctrl.go @@ -0,0 +1,15 @@ +package lowlevel + +import ( + "io" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +// A WireGuardClient is a type which can control a WireGuard device. +type WireGuardClient interface { + io.Closer + Devices() ([]*wgtypes.Device, error) + Device(name string) (*wgtypes.Device, error) + ConfigureDevice(name string, cfg wgtypes.Config) error +} diff --git a/tmp/persistence/database.go b/tmp/persistence/database.go new file mode 100644 index 0000000..dc7cf83 --- /dev/null +++ b/tmp/persistence/database.go @@ -0,0 +1 @@ +package persistence diff --git a/tmp/persistence/ldap.go b/tmp/persistence/ldap.go new file mode 100644 index 0000000..90c28b3 --- /dev/null +++ b/tmp/persistence/ldap.go @@ -0,0 +1,4 @@ +package persistence + +type LdapLoader interface { +} diff --git a/tmp/persistence/models.go b/tmp/persistence/models.go new file mode 100644 index 0000000..3b37212 --- /dev/null +++ b/tmp/persistence/models.go @@ -0,0 +1,157 @@ +package persistence + +import ( + "database/sql" + "time" + + "gorm.io/gorm" +) + +type BaseModel struct { + CreatedBy string + UpdatedBy string + CreatedAt time.Time + UpdatedAt time.Time + DisabledAt sql.NullTime +} + +type InterfaceIdentifier string +type PeerIdentifier string +type UserIdentifier string + +type KeyPair struct { + PrivateKey string + PublicKey string +} + +type PreSharedKey string + +type InterfaceType string + +const ( + InterfaceTypeServer InterfaceType = "server" + InterfaceTypeClient InterfaceType = "client" +) + +type InterfaceConfig struct { + BaseModel + + // WireGuard specific (for the [interface] section of the config file) + + Identifier InterfaceIdentifier // device name, for example: wg0 + KeyPair KeyPair // private/public Key of the server interface + ListenPort int // the listening port, for example: 51820 + + AddressStr string // the interface ip addresses, comma separated + DnsStr string // the dns server that should be set if the interface is up, comma separated + + Mtu int // the device MTU + FirewallMark int32 // a firewall mark + RoutingTable string // the routing table + + PreUp string // action that is executed before the device is up + PostUp string // action that is executed after the device is up + PreDown string // action that is executed before the device is down + PostDown string // action that is executed after the device is down + + SaveConfig bool // automatically persist config changes to the wgX.conf file + + // WG Portal specific + Enabled bool // flag that specifies if the interface is enabled (up) or nor (down) + DisplayName string // a nice display name/ description for the interface + Type InterfaceType // the interface type, either InterfaceTypeServer or InterfaceTypeClient + DriverType string // the interface driver type (linux, software, ...) + + // Default settings for the peer, used for new peers, those settings will be published to ConfigOption options of + // the peer config + + PeerDefNetworkStr string // the default subnets from which peers will get their IP addresses, comma seperated + PeerDefDnsStr string // the default dns server for the peer + PeerDefEndpoint string // the default endpoint for the peer + PeerDefAllowedIPsStr string // the default allowed IP string for the peer + PeerDefMtu int // the default device MTU + PeerDefPersistentKeepalive int // the default persistent keep-alive Value + PeerDefFirewallMark int32 // default firewall mark + PeerDefRoutingTable string // the default routing table + + PeerDefPreUp string // default action that is executed before the device is up + PeerDefPostUp string // default action that is executed after the device is up + PeerDefPreDown string // default action that is executed before the device is down + PeerDefPostDown string // default action that is executed after the device is down +} + +type PeerInterfaceConfig struct { + AddressStr StringConfigOption // the interface ip addresses, comma separated + DnsStr StringConfigOption // the dns server that should be set if the interface is up, comma separated + Mtu IntConfigOption // the device MTU + FirewallMark Int32ConfigOption // a firewall mark + RoutingTable StringConfigOption // the routing table + + PreUp StringConfigOption // action that is executed before the device is up + PostUp StringConfigOption // action that is executed after the device is up + PreDown StringConfigOption // action that is executed before the device is down + PostDown StringConfigOption // action that is executed after the device is down +} + +type PeerConfig struct { + BaseModel + + // WireGuard specific (for the [peer] section of the config file) + + Endpoint StringConfigOption // the endpoint address + AllowedIPsStr StringConfigOption // all allowed ip subnets, comma seperated + ExtraAllowedIPsStr string // all allowed ip subnets on the server side, comma seperated + KeyPair KeyPair // private/public Key of the peer + PresharedKey string // the pre-shared Key of the peer + PersistentKeepalive IntConfigOption // the persistent keep-alive interval + + // WG Portal specific + + DisplayName string // a nice display name/ description for the peer + Identifier PeerIdentifier // peer unique identifier + UserIdentifier UserIdentifier // the owner + + // Interface settings for the peer, used to generate the [interface] section in the peer config file + PeerInterfaceConfig +} + +type UserSource string + +const ( + UserSourceLdap UserSource = "ldap" // LDAP / ActiveDirectory + UserSourceDatabase UserSource = "db" // sqlite / mysql database + UserSourceOIDC UserSource = "oidc" // open id connect, TODO: implement +) + +type PrivateString string + +func (PrivateString) MarshalJSON() ([]byte, error) { + return []byte(`""`), nil +} + +func (PrivateString) String() string { + return "" +} + +// User is the user model that gets linked to peer entries, by default an empty user model with only the email address is created +type User struct { + // required fields + Uid UserIdentifier `gorm:"primaryKey"` + Email string `form:"email" binding:"required,email"` + Source UserSource + IsAdmin bool + + // optional fields + Firstname string `form:"firstname" binding:"omitempty"` + Lastname string `form:"lastname" binding:"omitempty"` + Phone string `form:"phone" binding:"omitempty"` + Department string `form:"department" binding:"omitempty"` + + // optional, integrated password authentication + Password PrivateString `form:"password" binding:"omitempty"` + + // database internal fields + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index" json:",omitempty" swaggertype:"string"` +} diff --git a/tmp/persistence/options.go b/tmp/persistence/options.go new file mode 100644 index 0000000..683eae5 --- /dev/null +++ b/tmp/persistence/options.go @@ -0,0 +1,81 @@ +package persistence + +// ConfigOption is an Overridable configuration option +type ConfigOption struct { + Value interface{} + Overridable bool +} + +type StringConfigOption struct { + ConfigOption +} + +func (o StringConfigOption) GetValue() string { + if o.Value == nil { + return "" + } + return o.Value.(string) +} + +func NewStringConfigOption(value string, overridable bool) StringConfigOption { + return StringConfigOption{ConfigOption{ + Value: value, + Overridable: overridable, + }} +} + +type IntConfigOption struct { + ConfigOption +} + +func (o IntConfigOption) GetValue() int { + if o.Value == nil { + return 0 + } + return o.Value.(int) +} + +func NewIntConfigOption(value int, overridable bool) IntConfigOption { + return IntConfigOption{ConfigOption{ + Value: value, + Overridable: overridable, + }} +} + +type Int32ConfigOption struct { + ConfigOption +} + +func (o Int32ConfigOption) GetValue() int32 { + if o.Value == nil { + return 0 + } + + return o.Value.(int32) +} + +func NewInt32ConfigOption(value int32, overridable bool) Int32ConfigOption { + return Int32ConfigOption{ConfigOption{ + Value: value, + Overridable: overridable, + }} +} + +type BoolConfigOption struct { + ConfigOption +} + +func (o BoolConfigOption) GetValue() bool { + if o.Value == nil { + return false + } + + return o.Value.(bool) +} + +func NewBoolConfigOption(value bool, overridable bool) BoolConfigOption { + return BoolConfigOption{ConfigOption{ + Value: value, + Overridable: overridable, + }} +} diff --git a/tmp/persistence/users.go b/tmp/persistence/users.go new file mode 100644 index 0000000..8f3d56a --- /dev/null +++ b/tmp/persistence/users.go @@ -0,0 +1,19 @@ +package persistence + +import "gorm.io/gorm" + +type UserFilterCondition func(tx *gorm.DB) + +type UsersLoader interface { + GetUser(id UserIdentifier) (User, error) + GetUsers() ([]User, error) + GetUsersUnscoped() ([]User, error) + GetUsersFiltered(filter ...UserFilterCondition) ([]User, error) +} + +type Users interface { + UsersLoader + + SaveUser(user User) error + DeleteUser(identifier UserIdentifier) error +} diff --git a/tmp/persistence/wireguard.go b/tmp/persistence/wireguard.go new file mode 100644 index 0000000..9f320f1 --- /dev/null +++ b/tmp/persistence/wireguard.go @@ -0,0 +1,14 @@ +package persistence + +type WireGuard interface { + GetAvailableInterfaces() ([]InterfaceIdentifier, error) + + GetAllInterfaces(interfaceIdentifiers ...InterfaceIdentifier) (map[InterfaceConfig][]PeerConfig, error) + GetInterface(identifier InterfaceIdentifier) (InterfaceConfig, []PeerConfig, error) + + SaveInterface(cfg InterfaceConfig, peers []PeerConfig) error + SavePeer(peer PeerConfig, interfaceIdentifier InterfaceIdentifier) error + + DeleteInterface(identifier InterfaceIdentifier) error + DeletePeer(peer PeerIdentifier, interfaceIdentifier InterfaceIdentifier) error +} diff --git a/tmp/portal/api.go b/tmp/portal/api.go new file mode 100644 index 0000000..8d7996b --- /dev/null +++ b/tmp/portal/api.go @@ -0,0 +1 @@ +package portal diff --git a/tmp/portal/web.go b/tmp/portal/web.go new file mode 100644 index 0000000..8d7996b --- /dev/null +++ b/tmp/portal/web.go @@ -0,0 +1 @@ +package portal diff --git a/tmp/user/authentication.go b/tmp/user/authentication.go new file mode 100644 index 0000000..b200aa5 --- /dev/null +++ b/tmp/user/authentication.go @@ -0,0 +1,4 @@ +package user + +type Authenticator interface { +} diff --git a/tmp/user/manager.go b/tmp/user/manager.go new file mode 100644 index 0000000..1bfd1fc --- /dev/null +++ b/tmp/user/manager.go @@ -0,0 +1,7 @@ +package user + +import "github.com/h44z/wg-portal/tmp/persistence" + +type Manager interface { + persistence.UsersLoader +} diff --git a/tmp/wireguard/manager.go b/tmp/wireguard/manager.go new file mode 100644 index 0000000..0df314e --- /dev/null +++ b/tmp/wireguard/manager.go @@ -0,0 +1,50 @@ +package wireguard + +import ( + "io" + + "github.com/h44z/wg-portal/tmp/persistence" +) + +type KeyGenerator interface { + GetFreshKeypair() (persistence.KeyPair, error) + GetPreSharedKey() (persistence.PreSharedKey, error) +} + +// InterfaceManager provides methods to create/update/delete physical WireGuard devices. +type InterfaceManager interface { + GetInterfaces() ([]persistence.InterfaceConfig, error) + CreateInterface(id persistence.InterfaceIdentifier) error + DeleteInterface(id persistence.InterfaceIdentifier) error + UpdateInterface(id persistence.InterfaceIdentifier, cfg persistence.InterfaceConfig) error +} + +type ImportableInterface struct { + persistence.InterfaceConfig + ImportLocation string + ImportType string +} + +type ImportManager interface { + GetImportableInterfaces() (map[ImportableInterface][]persistence.PeerConfig, error) + ImportInterface(cfg ImportableInterface, peers []persistence.PeerConfig) +} + +type ConfigFileGenerator interface { + GetInterfaceConfig(cfg persistence.InterfaceConfig, peers []persistence.PeerConfig) (io.Reader, error) + GetPeerConfig(peer persistence.PeerConfig) (io.Reader, error) +} + +type PeerManager interface { + GetPeers(device persistence.InterfaceIdentifier) ([]persistence.PeerConfig, error) + SavePeers(device persistence.InterfaceIdentifier, peers ...persistence.PeerConfig) error + RemovePeer(device persistence.InterfaceIdentifier, peer persistence.PeerIdentifier) error +} + +type Manager interface { + KeyGenerator + InterfaceManager + PeerManager + ImportManager + ConfigFileGenerator +} diff --git a/tmp/wireguard/persistence.go b/tmp/wireguard/persistence.go new file mode 100644 index 0000000..0b280a9 --- /dev/null +++ b/tmp/wireguard/persistence.go @@ -0,0 +1 @@ +package wireguard diff --git a/tmp/wireguard/template.go b/tmp/wireguard/template.go new file mode 100644 index 0000000..e021d08 --- /dev/null +++ b/tmp/wireguard/template.go @@ -0,0 +1,66 @@ +package wireguard + +import ( + "bytes" + "embed" + "io" + "text/template" + + "github.com/h44z/wg-portal/tmp/persistence" + + "github.com/pkg/errors" +) + +//go:embed tpl_files/* +var TemplateFiles embed.FS + +type TemplateHandler struct { + templates *template.Template +} + +func NewTemplateHandler() (*TemplateHandler, error) { + templateCache, err := template.New("WireGuard").ParseFS(TemplateFiles, "tpl_files/*.tpl") + if err != nil { + return nil, errors.Wrapf(err, "failed to parse template files") + } + + handler := &TemplateHandler{ + templates: templateCache, + } + + return handler, nil +} + +func (c TemplateHandler) GetInterfaceConfig(cfg persistence.InterfaceConfig, peers []persistence.PeerConfig) (io.Reader, error) { + var tplBuff bytes.Buffer + + err := c.templates.ExecuteTemplate(&tplBuff, "interface.tpl", map[string]interface{}{ + "Interface": cfg, + "Peers": peers, + "Portal": map[string]interface{}{ + "Version": "unknown", + }, + }) + if err != nil { + return nil, errors.Wrapf(err, "failed to execute interface template for %s", cfg.Identifier) + } + + return &tplBuff, nil +} + +func (c TemplateHandler) GetPeerConfig(peer persistence.PeerConfig, iface persistence.InterfaceConfig) (io.Reader, error) { + var tplBuff bytes.Buffer + + err := c.templates.ExecuteTemplate(&tplBuff, "peer.tpl", map[string]interface{}{ + "Peer": peer, + "Interface": iface, + "Portal": map[string]interface{}{ + "Version": "unknown", + }, + }) + if err != nil { + return nil, errors.Wrapf(err, "failed to execute peer template for %s", peer.Uid) + } + + return &tplBuff, nil +} diff --git a/tmp/wireguard/tpl_files/interface.tpl b/tmp/wireguard/tpl_files/interface.tpl new file mode 100644 index 0000000..2abb63d --- /dev/null +++ b/tmp/wireguard/tpl_files/interface.tpl @@ -0,0 +1,82 @@ +# AUTOGENERATED FILE - DO NOT EDIT +# This file uses wg-quick format. See https://man7.org/linux/man-pages/man8/wg-quick.8.html#CONFIGURATION + +# -WGP- WIREGUARD PORTAL CONFIGURATION FILE, version {{ .Portal.Version }} +# Lines starting with the -WGP- tag are used by the WireGuard Portal configuration parser. + +[Interface] +# -WGP- Interface: {{ .Interface.DeviceName }} | Updated: {{ .Interface.UpdatedAt }} | Created: {{ .Interface.CreatedAt }} +# -WGP- Display name: {{ .Interface.DisplayName }} +# -WGP- Interface mode: {{ .Interface.Type }} +# -WGP- PublicKey = {{ .Interface.KeyPair.PublicKey }} + +# Core settings +PrivateKey = {{ .Interface.KeyPair.PrivateKey }} +Address = {{ .Interface.AddressStr }} + +# 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}} +{{- if ne .Interface.RoutingTable ""}} +Table = {{.Interface.RoutingTable}} +{{- end}} +{{- if .Interface.SaveConfig}} +SaveConfig = true +{{- end}} + +# 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 +# + +{{range .Peers}} +{{- if not .DisabledAt}} +[Peer] +# -WGP- Peer: {{.Uid}} | Updated: {{.UpdatedAt}} | Created: {{.CreatedAt}} +# -WGP- Display name: {{ .Identifier }} +{{- if .KeyPair.PrivateKey}} +# -WGP- PrivateKey: {{.KeyPair.PrivateKey}} +{{- end}} +PublicKey = {{ .KeyPair.PublicKey }} +{{- if .PresharedKey}} +PresharedKey = {{ .PresharedKey }} +{{- end}} +{{- if eq $.Interface.Type "server"}} +AllowedIPs = {{ .AddressStr }}{{if ne .ExtraAllowedIPsStr ""}}, {{ .ExtraAllowedIPsStr }}{{end}} +{{- end}} +{{- if eq $.Interface.Type "client"}} +{{- if .AllowedIPsStr}} +AllowedIPs = {{ .AllowedIPsStr }} +{{- end}} +{{- end}} +{{- if and (ne .Endpoint "") (eq $.Interface.Type "client")}} +Endpoint = {{ .Endpoint }} +{{- end}} +{{- if ne .PersistentKeepalive 0}} +PersistentKeepalive = {{ .PersistentKeepalive }} +{{- end}} +{{- end}} +{{end}} \ No newline at end of file diff --git a/tmp/wireguard/tpl_files/peer.tpl b/tmp/wireguard/tpl_files/peer.tpl new file mode 100644 index 0000000..2278023 --- /dev/null +++ b/tmp/wireguard/tpl_files/peer.tpl @@ -0,0 +1,60 @@ +# AUTOGENERATED FILE - DO NOT EDIT +# This file uses wg-quick format. See https://man7.org/linux/man-pages/man8/wg-quick.8.html#CONFIGURATION + +# -WGP- WIREGUARD PORTAL CONFIGURATION FILE, version {{ .Portal.Version }} +# Lines starting with the -WGP- tag are used by the WireGuard Portal configuration parser. + +[Interface] +# -WGP- Peer: {{.Peer.Uid}} | Updated: {{.Peer.UpdatedAt}} | Created: {{.Peer.CreatedAt}} +# -WGP- Display name: {{ .Peer.Identifier }} +# -WGP- PublicKey: {{ .Peer.KeyPair.PublicKey }} +{{- if eq $.Interface.Type "server"}} +# -WGP- Peer type: client +{{else}} +# -WGP- Peer type: server +{{- end}} + +# Core settings +PrivateKey = {{ .Peer.KeyPair.PrivateKey }} +Address = {{ .Peer.AddressStr.GetValue }} + +# Misc. settings (optional) +{{- if .Peer.DnsStr.GetValue}} +DNS = {{ .Peer.DnsStr.GetValue }} +{{- end}} +{{- if ne .Peer.Mtu.GetValue 0}} +MTU = {{ .Peer.Mtu.GetValue }} +{{- end}} +{{- if ne .Peer.FirewallMark.GetValue 0}} +FwMark = {{ .Peer.FirewallMark.GetValue }} +{{- end}} +{{- if ne .Peer.RoutingTable.GetValue ""}} +Table = {{ .Peer.RoutingTable.GetValue }} +{{- end}} + +# Interface hooks (optional) +{{- if .Peer.PreUp.GetValue}} +PreUp = {{ .Peer.PreUp.GetValue }} +{{- end}} +{{- if .Peer.PostUp.GetValue}} +PostUp = {{ .Peer.PostUp.GetValue }} +{{- end}} +{{- if .Peer.PreDown.GetValue}} +PreDown = {{ .Peer.PreDown.GetValue }} +{{- end}} +{{- if .Peer.PostDown.GetValue}} +PostDown = {{ .Peer.PostDown.GetValue }} +{{- end}} + +[Peer] +PublicKey = {{ .Interface.KeyPair.PublicKey }} +Endpoint = {{ .Peer.Endpoint.GetValue }} +{{- if .Peer.AllowedIPsStr.GetValue}} +AllowedIPs = {{ .Peer.AllowedIPsStr.GetValue }} +{{- end}} +{{- if .Peer.PresharedKey}} +PresharedKey = {{ .Peer.PresharedKey }} +{{- end}} +{{- if ne .Peer.PersistentKeepalive.GetValue 0}} +PersistentKeepalive = {{ .Peer.PersistentKeepalive.GetValue }} +{{- end}} \ No newline at end of file