From 0a27c5e25320e09b79c0ac35b51ccd1b91dc201c Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Wed, 21 Jun 2023 23:22:27 +0200 Subject: [PATCH] wip: prepare peer --- internal/app/api/v0/model/model_options.go | 136 +++++++++++++++++++++ internal/app/api/v0/model/models_peer.go | 125 +++++++++++-------- internal/app/repos.go | 1 + internal/app/wireguard/wireguard.go | 65 ++++++++++ internal/domain/peer.go | 2 - 5 files changed, 277 insertions(+), 52 deletions(-) create mode 100644 internal/app/api/v0/model/model_options.go diff --git a/internal/app/api/v0/model/model_options.go b/internal/app/api/v0/model/model_options.go new file mode 100644 index 0000000..b40e9db --- /dev/null +++ b/internal/app/api/v0/model/model_options.go @@ -0,0 +1,136 @@ +package model + +import ( + "github.com/h44z/wg-portal/internal" + "github.com/h44z/wg-portal/internal/domain" +) + +type StringConfigOption struct { + Value string `json:"value"` + Overridable bool `json:"overridable"` +} + +func NewStringConfigOption(value string, overridable bool) StringConfigOption { + return StringConfigOption{ + Value: value, + Overridable: overridable, + } +} + +func StringConfigOptionFromDomain(opt domain.StringConfigOption) StringConfigOption { + return StringConfigOption{ + Value: opt.Value, + Overridable: opt.Overridable, + } +} + +func StringConfigOptionToDomain(opt StringConfigOption) domain.StringConfigOption { + return domain.StringConfigOption{ + Value: opt.Value, + Overridable: opt.Overridable, + } +} + +type StringSliceConfigOption struct { + Value []string `json:"value"` + Overridable bool `json:"overridable"` +} + +func NewStringSliceConfigOption(value []string, overridable bool) StringSliceConfigOption { + return StringSliceConfigOption{ + Value: value, + Overridable: overridable, + } +} + +func StringSliceConfigOptionFromDomain(opt domain.StringConfigOption) StringSliceConfigOption { + return StringSliceConfigOption{ + Value: internal.SliceString(opt.Value), + Overridable: opt.Overridable, + } +} + +func StringSliceConfigOptionToDomain(opt StringSliceConfigOption) domain.StringConfigOption { + return domain.StringConfigOption{ + Value: internal.SliceToString(opt.Value), + Overridable: opt.Overridable, + } +} + +type IntConfigOption struct { + Value int `json:"value"` + Overridable bool `json:"overridable"` +} + +func NewIntConfigOption(value int, overridable bool) IntConfigOption { + return IntConfigOption{ + Value: value, + Overridable: overridable, + } +} + +func IntConfigOptionFromDomain(opt domain.IntConfigOption) IntConfigOption { + return IntConfigOption{ + Value: opt.Value, + Overridable: opt.Overridable, + } +} + +func IntConfigOptionToDomain(opt IntConfigOption) domain.IntConfigOption { + return domain.IntConfigOption{ + Value: opt.Value, + Overridable: opt.Overridable, + } +} + +type Int32ConfigOption struct { + Value int32 `json:"value"` + Overridable bool `json:"overridable"` +} + +func NewInt32ConfigOption(value int32, overridable bool) Int32ConfigOption { + return Int32ConfigOption{ + Value: value, + Overridable: overridable, + } +} + +func Int32ConfigOptionFromDomain(opt domain.Int32ConfigOption) Int32ConfigOption { + return Int32ConfigOption{ + Value: opt.Value, + Overridable: opt.Overridable, + } +} + +func Int32ConfigOptionToDomain(opt Int32ConfigOption) domain.Int32ConfigOption { + return domain.Int32ConfigOption{ + Value: opt.Value, + Overridable: opt.Overridable, + } +} + +type BoolConfigOption struct { + Value bool `json:"value"` + Overridable bool `json:"overridable"` +} + +func NewBoolConfigOption(value bool, overridable bool) BoolConfigOption { + return BoolConfigOption{ + Value: value, + Overridable: overridable, + } +} + +func BoolConfigOptionFromDomain(opt domain.BoolConfigOption) BoolConfigOption { + return BoolConfigOption{ + Value: opt.Value, + Overridable: opt.Overridable, + } +} + +func BoolConfigOptionToDomain(opt BoolConfigOption) domain.BoolConfigOption { + return domain.BoolConfigOption{ + Value: opt.Value, + Overridable: opt.Overridable, + } +} diff --git a/internal/app/api/v0/model/models_peer.go b/internal/app/api/v0/model/models_peer.go index baa2300..fdd4a40 100644 --- a/internal/app/api/v0/model/models_peer.go +++ b/internal/app/api/v0/model/models_peer.go @@ -1,41 +1,45 @@ package model import ( + "github.com/h44z/wg-portal/internal" "github.com/h44z/wg-portal/internal/domain" "time" ) type Peer struct { - Identifier string `json:"Identifier" example:"super_nice_peer"` // peer unique identifier - DisplayName string `json:"DisplayName"` // a nice display name/ description for the peer - UserIdentifier string `json:"UserIdentifier"` // the owner - InterfaceIdentifier string `json:"InterfaceIdentifier"` // the interface id - Disabled bool `json:"Disabled"` // flag that specifies if the peer is enabled (up) or not (down) - DisabledReason string `json:"DisabledReason"` // the reason why the peer has been disabled + Identifier string `json:"Identifier" example:"super_nice_peer"` // peer unique identifier + DisplayName string `json:"DisplayName"` // a nice display name/ description for the peer + UserIdentifier string `json:"UserIdentifier"` // the owner + InterfaceIdentifier string `json:"InterfaceIdentifier"` // the interface id + Disabled bool `json:"Disabled"` // flag that specifies if the peer is enabled (up) or not (down) + DisabledReason string `json:"DisabledReason"` // the reason why the peer has been disabled + ExpiresAt *time.Time `json:"column:expires_at"` // expiry dates for peers + Notes string `json:"notes"` // a note field for peers - Endpoint string `json:"Endpoint"` // the endpoint address - EndpointPublicKey string `json:"EndpointPublicKey"` // the endpoint public key - AllowedIPs string `json:"AllowedIPs"` // all allowed ip subnets, comma seperated - ExtraAllowedIPs string `json:"ExtraAllowedIPs"` // all allowed ip subnets on the server side, comma seperated - PresharedKey string `json:"PresharedKey"` // the pre-shared Key of the peer - PersistentKeepalive int `json:"PersistentKeepalive"` // the persistent keep-alive interval + Endpoint StringConfigOption `json:"Endpoint"` // the endpoint address + EndpointPublicKey string `json:"EndpointPublicKey"` // the endpoint public key + AllowedIPs StringSliceConfigOption `json:"AllowedIPs"` // all allowed ip subnets, comma seperated + ExtraAllowedIPs []string `json:"ExtraAllowedIPs"` // all allowed ip subnets on the server side, comma seperated + PresharedKey string `json:"PresharedKey"` // the pre-shared Key of the peer + PersistentKeepalive IntConfigOption `json:"PersistentKeepalive"` // the persistent keep-alive interval PrivateKey string `json:"PrivateKey" example:"abcdef=="` // private Key of the server peer PublicKey string `json:"PublicKey" example:"abcdef=="` // public Key of the server peer Mode string // the peer interface type (server, client, any) - Addresses []string `json:"Addresses"` // the interface ip addresses - Dns string `json:"Dns"` // the dns server that should be set if the interface is up, comma separated - DnsSearch string `json:"DnsSearch"` // the dns search option string that should be set if the interface is up, will be appended to DnsStr - Mtu int `json:"Mtu"` // the device MTU - FirewallMark int32 `json:"FirewallMark"` // a firewall mark - RoutingTable string `json:"RoutingTable"` // the routing table + Addresses []string `json:"Addresses"` // the interface ip addresses + CheckAliveAddress string `json:"CheckAliveAddress"` // optional ip address or DNS name that is used for ping checks + Dns StringSliceConfigOption `json:"Dns"` // the dns server that should be set if the interface is up, comma separated + DnsSearch StringSliceConfigOption `json:"DnsSearch"` // the dns search option string that should be set if the interface is up, will be appended to DnsStr + Mtu IntConfigOption `json:"Mtu"` // the device MTU + FirewallMark Int32ConfigOption `json:"FirewallMark"` // a firewall mark + RoutingTable StringConfigOption `json:"RoutingTable"` // the routing table - PreUp string `json:"PreUp"` // action that is executed before the device is up - PostUp string `json:"PostUp"` // action that is executed after the device is up - PreDown string `json:"PreDown"` // action that is executed before the device is down - PostDown string `json:"PostDown"` // action that is executed after the device is down + PreUp StringConfigOption `json:"PreUp"` // action that is executed before the device is up + PostUp StringConfigOption `json:"PostUp"` // action that is executed after the device is up + PreDown StringConfigOption `json:"PreDown"` // action that is executed before the device is down + PostDown StringConfigOption `json:"PostDown"` // action that is executed after the device is down } func NewPeer(src *domain.Peer) *Peer { @@ -46,25 +50,28 @@ func NewPeer(src *domain.Peer) *Peer { InterfaceIdentifier: string(src.InterfaceIdentifier), Disabled: src.IsDisabled(), DisabledReason: src.DisabledReason, - Endpoint: src.Endpoint.GetValue(), + ExpiresAt: src.ExpiresAt, + Notes: src.Notes, + Endpoint: StringConfigOptionFromDomain(src.Endpoint), EndpointPublicKey: src.EndpointPublicKey, - AllowedIPs: src.AllowedIPsStr.GetValue(), - ExtraAllowedIPs: src.ExtraAllowedIPsStr, + AllowedIPs: StringSliceConfigOptionFromDomain(src.AllowedIPsStr), + ExtraAllowedIPs: internal.SliceString(src.ExtraAllowedIPsStr), PresharedKey: string(src.PresharedKey), - PersistentKeepalive: src.PersistentKeepalive.GetValue(), + PersistentKeepalive: IntConfigOptionFromDomain(src.PersistentKeepalive), PrivateKey: src.Interface.PrivateKey, PublicKey: src.Interface.PublicKey, Mode: string(src.Interface.Type), Addresses: domain.CidrsToStringSlice(src.Interface.Addresses), - Dns: src.Interface.DnsStr.GetValue(), - DnsSearch: src.Interface.DnsSearchStr.GetValue(), - Mtu: src.Interface.Mtu.GetValue(), - FirewallMark: src.Interface.FirewallMark.GetValue(), - RoutingTable: src.Interface.RoutingTable.GetValue(), - PreUp: src.Interface.PreUp.GetValue(), - PostUp: src.Interface.PostUp.GetValue(), - PreDown: src.Interface.PreDown.GetValue(), - PostDown: src.Interface.PostDown.GetValue(), + CheckAliveAddress: src.Interface.CheckAliveAddress, + Dns: StringSliceConfigOptionFromDomain(src.Interface.DnsStr), + DnsSearch: StringSliceConfigOptionFromDomain(src.Interface.DnsSearchStr), + Mtu: IntConfigOptionFromDomain(src.Interface.Mtu), + FirewallMark: Int32ConfigOptionFromDomain(src.Interface.FirewallMark), + RoutingTable: StringConfigOptionFromDomain(src.Interface.RoutingTable), + PreUp: StringConfigOptionFromDomain(src.Interface.PreUp), + PostUp: StringConfigOptionFromDomain(src.Interface.PostUp), + PreDown: StringConfigOptionFromDomain(src.Interface.PreDown), + PostDown: StringConfigOptionFromDomain(src.Interface.PostDown), } } @@ -80,24 +87,42 @@ func NewPeers(src []domain.Peer) []Peer { func NewDomainPeer(src *Peer) *domain.Peer { now := time.Now() + cidrs, _ := domain.CidrsFromArray(src.Addresses) + res := &domain.Peer{ BaseModel: domain.BaseModel{}, - Endpoint: domain.StringConfigOption{}, + Endpoint: StringConfigOptionToDomain(src.Endpoint), EndpointPublicKey: src.EndpointPublicKey, - AllowedIPsStr: domain.StringConfigOption{}, - ExtraAllowedIPsStr: "", - PresharedKey: "", - PersistentKeepalive: domain.IntConfigOption{}, - DisplayName: "", - Identifier: "", - UserIdentifier: "", - InterfaceIdentifier: "", - Temporary: nil, - Disabled: nil, - DisabledReason: "", - ExpiresAt: nil, - Notes: "", - Interface: domain.PeerInterfaceConfig{}, + AllowedIPsStr: StringSliceConfigOptionToDomain(src.AllowedIPs), + ExtraAllowedIPsStr: internal.SliceToString(src.ExtraAllowedIPs), + PresharedKey: domain.PreSharedKey(src.PresharedKey), + PersistentKeepalive: IntConfigOptionToDomain(src.PersistentKeepalive), + DisplayName: src.DisplayName, + Identifier: domain.PeerIdentifier(src.Identifier), + UserIdentifier: domain.UserIdentifier(src.UserIdentifier), + InterfaceIdentifier: domain.InterfaceIdentifier(src.InterfaceIdentifier), + Disabled: nil, // set below + DisabledReason: src.DisabledReason, + ExpiresAt: src.ExpiresAt, + Notes: src.Notes, + Interface: domain.PeerInterfaceConfig{ + KeyPair: domain.KeyPair{ + PrivateKey: src.PrivateKey, + PublicKey: src.PublicKey, + }, + Type: domain.InterfaceType(src.Mode), + Addresses: cidrs, + CheckAliveAddress: src.CheckAliveAddress, + DnsStr: StringSliceConfigOptionToDomain(src.Dns), + DnsSearchStr: StringSliceConfigOptionToDomain(src.DnsSearch), + Mtu: IntConfigOptionToDomain(src.Mtu), + FirewallMark: Int32ConfigOptionToDomain(src.FirewallMark), + RoutingTable: StringConfigOptionToDomain(src.RoutingTable), + PreUp: StringConfigOptionToDomain(src.PreUp), + PostUp: StringConfigOptionToDomain(src.PostUp), + PreDown: StringConfigOptionToDomain(src.PreDown), + PostDown: StringConfigOptionToDomain(src.PostDown), + }, } if src.Disabled { diff --git a/internal/app/repos.go b/internal/app/repos.go index 6a25226..ae2110f 100644 --- a/internal/app/repos.go +++ b/internal/app/repos.go @@ -37,6 +37,7 @@ type WireGuardManager interface { CreateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error) UpdateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error) DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error + PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error) } type StatisticsCollector interface { diff --git a/internal/app/wireguard/wireguard.go b/internal/app/wireguard/wireguard.go index 311f5c6..4bbf7fa 100644 --- a/internal/app/wireguard/wireguard.go +++ b/internal/app/wireguard/wireguard.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/google/uuid" "github.com/h44z/wg-portal/internal/app" "time" @@ -605,3 +606,67 @@ func (m Manager) deleteInterfacePeers(ctx context.Context, id domain.InterfaceId return nil } + +func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error) { + currentUser := domain.GetUserInfo(ctx) + + iface, err := m.db.GetInterface(ctx, id) + if err != nil { + return nil, fmt.Errorf("unable to find interface %s: %w", id, err) + } + + kp, err := domain.NewFreshKeypair() + if err != nil { + return nil, fmt.Errorf("failed to generate keys: %w", err) + } + + pk, err := domain.NewPreSharedKey() + if err != nil { + return nil, fmt.Errorf("failed to generate preshared key: %w", err) + } + + peerMode := domain.InterfaceTypeClient + if iface.Type == domain.InterfaceTypeClient { + peerMode = domain.InterfaceTypeServer + } + + freshPeer := &domain.Peer{ + BaseModel: domain.BaseModel{ + CreatedBy: string(currentUser.Id), + UpdatedBy: string(currentUser.Id), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + Endpoint: domain.StringConfigOption{}, + EndpointPublicKey: "", + AllowedIPsStr: domain.StringConfigOption{}, + ExtraAllowedIPsStr: "", + PresharedKey: pk, + PersistentKeepalive: domain.IntConfigOption{}, + DisplayName: "", + Identifier: domain.PeerIdentifier(uuid.New().String()), + UserIdentifier: currentUser.Id, + InterfaceIdentifier: iface.Identifier, + Disabled: nil, + DisabledReason: "", + ExpiresAt: nil, + Notes: "", + Interface: domain.PeerInterfaceConfig{ + KeyPair: kp, + Type: peerMode, + Addresses: nil, + CheckAliveAddress: "", + DnsStr: domain.StringConfigOption{}, + DnsSearchStr: domain.StringConfigOption{}, + Mtu: domain.IntConfigOption{}, + FirewallMark: domain.Int32ConfigOption{}, + RoutingTable: domain.StringConfigOption{}, + PreUp: domain.StringConfigOption{}, + PostUp: domain.StringConfigOption{}, + PreDown: domain.StringConfigOption{}, + PostDown: domain.StringConfigOption{}, + }, + } + + return freshPeer, nil +} diff --git a/internal/domain/peer.go b/internal/domain/peer.go index 2a27c2b..52cd70c 100644 --- a/internal/domain/peer.go +++ b/internal/domain/peer.go @@ -40,7 +40,6 @@ type Peer struct { 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 - Temporary *time.Time `gorm:"-"` // is this a temporary peer (only prepared, but never saved to db) 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 @@ -159,7 +158,6 @@ func ConvertPhysicalPeer(pp *PhysicalPeer) *Peer { Identifier: pp.Identifier, UserIdentifier: "", InterfaceIdentifier: "", - Temporary: nil, Disabled: nil, Interface: PeerInterfaceConfig{ KeyPair: pp.KeyPair,