package domain import ( "fmt" "net" "regexp" "strings" "time" "github.com/h44z/wg-portal/internal" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) type PeerIdentifier string func (i PeerIdentifier) IsPublicKey() bool { _, err := wgtypes.ParseKey(string(i)) if err != nil { return false } return true } func (i PeerIdentifier) ToPublicKey() wgtypes.Key { publicKey, _ := wgtypes.ParseKey(string(i)) return publicKey } type Peer struct { BaseModel // WireGuard specific (for the [peer] section of the config file) Endpoint ConfigOption[string] `gorm:"embedded;embeddedPrefix:endpoint_"` // the endpoint address EndpointPublicKey ConfigOption[string] `gorm:"embedded;embeddedPrefix:endpoint_pubkey_"` // the endpoint public key AllowedIPsStr ConfigOption[string] `gorm:"embedded;embeddedPrefix:allowed_ips_str_"` // all allowed ip subnets, comma seperated ExtraAllowedIPsStr string // all allowed ip subnets on the server side, comma seperated PresharedKey PreSharedKey // the pre-shared Key of the peer PersistentKeepalive ConfigOption[int] `gorm:"embedded;embeddedPrefix:persistent_keep_alive_"` // the persistent keep-alive interval // WG Portal specific DisplayName string // a nice display name/ description for the peer Identifier PeerIdentifier `gorm:"primaryKey;column:identifier"` // peer unique identifier UserIdentifier UserIdentifier `gorm:"index;column:user_identifier"` // the owner InterfaceIdentifier InterfaceIdentifier `gorm:"index;column:interface_identifier"` // the interface id Disabled *time.Time `gorm:"column:disabled"` // if this field is set, the peer is disabled DisabledReason string // the reason why the peer has been disabled ExpiresAt *time.Time `gorm:"column:expires_at"` // expiry dates for peers Notes string `form:"notes" binding:"omitempty"` // a note field for peers AutomaticallyCreated bool `gorm:"column:auto_created"` // specifies if the peer was automatically created // Interface settings for the peer, used to generate the [interface] section in the peer config file Interface PeerInterfaceConfig `gorm:"embedded"` } func (p *Peer) IsDisabled() bool { return p.Disabled != nil } func (p *Peer) IsExpired() bool { if p.ExpiresAt == nil { return false } if p.ExpiresAt.Before(time.Now()) { return true } return false } func (p *Peer) CheckAliveAddress() string { if p.Interface.CheckAliveAddress != "" { return p.Interface.CheckAliveAddress } if len(p.Interface.Addresses) > 0 { return p.Interface.Addresses[0].Addr // take the first peer address } return "" } func (p *Peer) CopyCalculatedAttributes(src *Peer) { p.BaseModel = src.BaseModel } func (p *Peer) GetConfigFileName() string { filename := "" reg := regexp.MustCompile("[^a-zA-Z0-9-_]+") if p.DisplayName != "" { filename = p.DisplayName filename = strings.ReplaceAll(filename, " ", "_") filename = reg.ReplaceAllString(filename, "") filename = internal.TruncateString(filename, 16) filename += ".conf" } else { filename = fmt.Sprintf("wg_%s", internal.TruncateString(string(p.Identifier), 8)) filename = reg.ReplaceAllString(filename, "") filename += ".conf" } return filename } func (p *Peer) ApplyInterfaceDefaults(in *Interface) { p.Endpoint.TrySetValue(in.PeerDefEndpoint) p.EndpointPublicKey.TrySetValue(in.PublicKey) p.AllowedIPsStr.TrySetValue(in.PeerDefAllowedIPsStr) p.PersistentKeepalive.TrySetValue(in.PeerDefPersistentKeepalive) p.Interface.DnsStr.TrySetValue(in.PeerDefDnsStr) p.Interface.DnsSearchStr.TrySetValue(in.PeerDefDnsSearchStr) p.Interface.Mtu.TrySetValue(in.PeerDefMtu) p.Interface.FirewallMark.TrySetValue(in.PeerDefFirewallMark) p.Interface.RoutingTable.TrySetValue(in.PeerDefRoutingTable) p.Interface.PreUp.TrySetValue(in.PeerDefPreUp) p.Interface.PostUp.TrySetValue(in.PeerDefPostUp) p.Interface.PreDown.TrySetValue(in.PeerDefPreDown) p.Interface.PostDown.TrySetValue(in.PeerDefPostDown) } func (p *Peer) GenerateDisplayName(prefix string) { if prefix != "" { prefix = fmt.Sprintf("%s ", strings.TrimSpace(prefix)) // add a space after the prefix } p.DisplayName = fmt.Sprintf("%sPeer %s", prefix, internal.TruncateString(string(p.Identifier), 8)) } // OverwriteUserEditableFields overwrites the user editable fields of the peer with the values from the userPeer func (p *Peer) OverwriteUserEditableFields(userPeer *Peer) { p.DisplayName = userPeer.DisplayName p.Interface.PublicKey = userPeer.Interface.PublicKey p.Interface.PrivateKey = userPeer.Interface.PrivateKey p.Interface.Mtu = userPeer.Interface.Mtu p.PersistentKeepalive = userPeer.PersistentKeepalive p.ExpiresAt = userPeer.ExpiresAt p.Disabled = userPeer.Disabled p.DisabledReason = userPeer.DisabledReason p.PresharedKey = userPeer.PresharedKey } type PeerInterfaceConfig struct { KeyPair // private/public Key of the peer Type InterfaceType `gorm:"column:iface_type"` // the interface type (server, client, any) Addresses []Cidr `gorm:"many2many:peer_addresses;"` // the interface ip addresses CheckAliveAddress string `gorm:"column:check_alive_address"` // optional ip address or DNS name that is used for ping checks DnsStr ConfigOption[string] `gorm:"embedded;embeddedPrefix:iface_dns_str_"` // the dns server that should be set if the interface is up, comma separated DnsSearchStr ConfigOption[string] `gorm:"embedded;embeddedPrefix:iface_dns_search_str_"` // the dns search option string that should be set if the interface is up, will be appended to DnsStr Mtu ConfigOption[int] `gorm:"embedded;embeddedPrefix:iface_mtu_"` // the device MTU FirewallMark ConfigOption[uint32] `gorm:"embedded;embeddedPrefix:iface_firewall_mark_"` // a firewall mark RoutingTable ConfigOption[string] `gorm:"embedded;embeddedPrefix:iface_routing_table_"` // the routing table PreUp ConfigOption[string] `gorm:"embedded;embeddedPrefix:iface_pre_up_"` // action that is executed before the device is up PostUp ConfigOption[string] `gorm:"embedded;embeddedPrefix:iface_post_up_"` // action that is executed after the device is up PreDown ConfigOption[string] `gorm:"embedded;embeddedPrefix:iface_pre_down_"` // action that is executed before the device is down PostDown ConfigOption[string] `gorm:"embedded;embeddedPrefix:iface_post_down_"` // action that is executed after the device is down } func (p *PeerInterfaceConfig) AddressStr() string { return CidrsToString(p.Addresses) } type PhysicalPeer struct { Identifier PeerIdentifier // peer unique identifier Endpoint string // the endpoint address AllowedIPs []Cidr // all allowed ip subnets KeyPair // private/public Key of the peer, for imports it only contains the public key as the private key is not known to the server PresharedKey PreSharedKey // the pre-shared Key of the peer PersistentKeepalive int // the persistent keep-alive interval LastHandshake time.Time ProtocolVersion int BytesUpload uint64 // upload bytes are the number of bytes that the remote peer has sent to the server BytesDownload uint64 // upload bytes are the number of bytes that the remote peer has received from the server } func (p PhysicalPeer) GetPresharedKey() *wgtypes.Key { if p.PresharedKey == "" { return nil } key, err := wgtypes.ParseKey(string(p.PresharedKey)) if err != nil { return nil } return &key } func (p PhysicalPeer) GetEndpointAddress() *net.UDPAddr { if p.Endpoint == "" { return nil } addr, err := net.ResolveUDPAddr("udp", p.Endpoint) if err != nil { return nil } return addr } func (p PhysicalPeer) GetPersistentKeepaliveTime() *time.Duration { if p.PersistentKeepalive == 0 { return nil } keepAliveDuration := time.Duration(p.PersistentKeepalive) * time.Second return &keepAliveDuration } func (p PhysicalPeer) GetAllowedIPs() []net.IPNet { allowedIPs := make([]net.IPNet, len(p.AllowedIPs)) for i, ip := range p.AllowedIPs { allowedIPs[i] = *ip.IpNet() } return allowedIPs } func ConvertPhysicalPeer(pp *PhysicalPeer) *Peer { peer := &Peer{ Endpoint: NewConfigOption(pp.Endpoint, true), EndpointPublicKey: NewConfigOption("", true), AllowedIPsStr: NewConfigOption("", true), ExtraAllowedIPsStr: "", PresharedKey: pp.PresharedKey, PersistentKeepalive: NewConfigOption(pp.PersistentKeepalive, true), DisplayName: string(pp.Identifier), Identifier: pp.Identifier, UserIdentifier: "", InterfaceIdentifier: "", Disabled: nil, Interface: PeerInterfaceConfig{ KeyPair: pp.KeyPair, }, } return peer } func MergeToPhysicalPeer(pp *PhysicalPeer, p *Peer) { pp.Identifier = p.Identifier pp.Endpoint = p.Endpoint.GetValue() if p.Interface.Type == InterfaceTypeServer { allowedIPs, _ := CidrsFromString(p.AllowedIPsStr.GetValue()) extraAllowedIPs, _ := CidrsFromString(p.ExtraAllowedIPsStr) pp.AllowedIPs = append(allowedIPs, extraAllowedIPs...) } else { allowedIPs := make([]Cidr, len(p.Interface.Addresses)) for i, ip := range p.Interface.Addresses { allowedIPs[i] = ip.HostAddr() } extraAllowedIPs, _ := CidrsFromString(p.ExtraAllowedIPsStr) pp.AllowedIPs = append(allowedIPs, extraAllowedIPs...) } pp.PresharedKey = p.PresharedKey pp.PublicKey = p.Interface.PublicKey pp.PersistentKeepalive = p.PersistentKeepalive.GetValue() } type PeerCreationRequest struct { UserIdentifiers []string Suffix string }