2023-06-13 23:50:32 +02:00
|
|
|
package wireguard
|
2023-02-12 23:13:04 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-06-21 23:22:27 +02:00
|
|
|
"github.com/google/uuid"
|
2023-06-13 23:50:32 +02:00
|
|
|
"github.com/h44z/wg-portal/internal/app"
|
2023-02-12 23:13:04 +01:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/h44z/wg-portal/internal"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
|
|
|
|
evbus "github.com/vardius/message-bus"
|
|
|
|
|
|
|
|
"github.com/h44z/wg-portal/internal/config"
|
|
|
|
"github.com/h44z/wg-portal/internal/domain"
|
|
|
|
)
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
type Manager struct {
|
2023-02-12 23:13:04 +01:00
|
|
|
cfg *config.Config
|
|
|
|
bus evbus.MessageBus
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
db InterfaceAndPeerDatabaseRepo
|
|
|
|
wg InterfaceController
|
2023-02-12 23:13:04 +01:00
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func NewWireGuardManager(cfg *config.Config, bus evbus.MessageBus, wg InterfaceController, db InterfaceAndPeerDatabaseRepo) (*Manager, error) {
|
|
|
|
m := &Manager{
|
2023-02-12 23:13:04 +01:00
|
|
|
cfg: cfg,
|
|
|
|
bus: bus,
|
2023-06-13 23:50:32 +02:00
|
|
|
wg: wg,
|
2023-02-12 23:13:04 +01:00
|
|
|
db: db,
|
|
|
|
}
|
|
|
|
|
|
|
|
m.connectToMessageBus()
|
|
|
|
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) connectToMessageBus() {
|
|
|
|
_ = m.bus.Subscribe(app.TopicUserCreated, m.handleUserCreationEvent)
|
2023-02-12 23:13:04 +01:00
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) handleUserCreationEvent(user *domain.User) {
|
2023-02-12 23:13:04 +01:00
|
|
|
logrus.Errorf("Handling new user event for %s", user.Identifier)
|
|
|
|
|
|
|
|
err := m.CreateDefaultPeer(context.Background(), user)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Failed to create default peer")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) GetImportableInterfaces(ctx context.Context) ([]domain.PhysicalInterface, error) {
|
2023-02-12 23:13:04 +01:00
|
|
|
physicalInterfaces, err := m.wg.GetInterfaces(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return physicalInterfaces, nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) ImportNewInterfaces(ctx context.Context, filter ...domain.InterfaceIdentifier) error {
|
2023-02-12 23:13:04 +01:00
|
|
|
physicalInterfaces, err := m.wg.GetInterfaces(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// if no filter is given, exclude already existing interfaces
|
|
|
|
var excludedInterfaces []domain.InterfaceIdentifier
|
|
|
|
if len(filter) == 0 {
|
|
|
|
existingInterfaces, err := m.db.GetAllInterfaces(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, existingInterface := range existingInterfaces {
|
|
|
|
excludedInterfaces = append(excludedInterfaces, existingInterface.Identifier)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, physicalInterface := range physicalInterfaces {
|
|
|
|
if internal.SliceContains(excludedInterfaces, physicalInterface.Identifier) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(filter) != 0 && !internal.SliceContains(filter, physicalInterface.Identifier) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Infof("importing new interface %s...", physicalInterface.Identifier)
|
|
|
|
|
|
|
|
physicalPeers, err := m.wg.GetPeers(ctx, physicalInterface.Identifier)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.importInterface(ctx, &physicalInterface, physicalPeers)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("import of %s failed: %w", physicalInterface.Identifier, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Infof("imported new interface %s and %d peers", physicalInterface.Identifier, len(physicalPeers))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) importInterface(ctx context.Context, in *domain.PhysicalInterface, peers []domain.PhysicalPeer) error {
|
2023-02-12 23:13:04 +01:00
|
|
|
now := time.Now()
|
|
|
|
iface := domain.ConvertPhysicalInterface(in)
|
|
|
|
iface.BaseModel = domain.BaseModel{
|
|
|
|
CreatedBy: "importer",
|
|
|
|
UpdatedBy: "importer",
|
|
|
|
CreatedAt: now,
|
|
|
|
UpdatedAt: now,
|
|
|
|
}
|
|
|
|
iface.PeerDefAllowedIPsStr = iface.AddressStr()
|
|
|
|
|
|
|
|
existingInterface, err := m.db.GetInterface(ctx, iface.Identifier)
|
|
|
|
if err != nil && !errors.Is(err, domain.ErrNotFound) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if existingInterface != nil {
|
|
|
|
return errors.New("interface already exists")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.db.SaveInterface(ctx, iface.Identifier, func(_ *domain.Interface) (*domain.Interface, error) {
|
|
|
|
return iface, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("database save failed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// import peers
|
|
|
|
for _, peer := range peers {
|
|
|
|
err = m.importPeer(ctx, iface, &peer)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("import of peer %s failed: %w", peer.Identifier, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) importPeer(ctx context.Context, in *domain.Interface, p *domain.PhysicalPeer) error {
|
2023-02-12 23:13:04 +01:00
|
|
|
now := time.Now()
|
|
|
|
peer := domain.ConvertPhysicalPeer(p)
|
|
|
|
peer.BaseModel = domain.BaseModel{
|
|
|
|
CreatedBy: "importer",
|
|
|
|
UpdatedBy: "importer",
|
|
|
|
CreatedAt: now,
|
|
|
|
UpdatedAt: now,
|
|
|
|
}
|
|
|
|
|
|
|
|
peer.InterfaceIdentifier = in.Identifier
|
|
|
|
peer.EndpointPublicKey = in.PublicKey
|
|
|
|
peer.AllowedIPsStr = domain.StringConfigOption{Value: in.PeerDefAllowedIPsStr, Overridable: true}
|
|
|
|
peer.Interface.Addresses = p.AllowedIPs // use allowed IP's as the peer IP's
|
|
|
|
peer.Interface.DnsStr = domain.StringConfigOption{Value: in.PeerDefDnsStr, Overridable: true}
|
|
|
|
peer.Interface.DnsSearchStr = domain.StringConfigOption{Value: in.PeerDefDnsSearchStr, Overridable: true}
|
|
|
|
peer.Interface.Mtu = domain.IntConfigOption{Value: in.PeerDefMtu, Overridable: true}
|
|
|
|
peer.Interface.FirewallMark = domain.Int32ConfigOption{Value: in.PeerDefFirewallMark, Overridable: true}
|
|
|
|
peer.Interface.RoutingTable = domain.StringConfigOption{Value: in.PeerDefRoutingTable, Overridable: true}
|
|
|
|
peer.Interface.PreUp = domain.StringConfigOption{Value: in.PeerDefPreUp, Overridable: true}
|
|
|
|
peer.Interface.PostUp = domain.StringConfigOption{Value: in.PeerDefPostUp, Overridable: true}
|
|
|
|
peer.Interface.PreDown = domain.StringConfigOption{Value: in.PeerDefPreDown, Overridable: true}
|
|
|
|
peer.Interface.PostDown = domain.StringConfigOption{Value: in.PeerDefPostDown, Overridable: true}
|
|
|
|
|
|
|
|
switch in.Type {
|
|
|
|
case domain.InterfaceTypeAny:
|
|
|
|
peer.Interface.Type = domain.InterfaceTypeAny
|
|
|
|
peer.DisplayName = "Autodetected Peer (" + peer.Interface.PublicKey[0:8] + ")"
|
|
|
|
case domain.InterfaceTypeClient:
|
|
|
|
peer.Interface.Type = domain.InterfaceTypeServer
|
|
|
|
peer.DisplayName = "Autodetected Endpoint (" + peer.Interface.PublicKey[0:8] + ")"
|
|
|
|
case domain.InterfaceTypeServer:
|
|
|
|
peer.Interface.Type = domain.InterfaceTypeClient
|
|
|
|
peer.DisplayName = "Autodetected Client (" + peer.Interface.PublicKey[0:8] + ")"
|
|
|
|
}
|
|
|
|
|
|
|
|
err := m.db.SavePeer(ctx, peer.Identifier, func(_ *domain.Peer) (*domain.Peer, error) {
|
|
|
|
return peer, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("database save failed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) RestoreInterfaceState(ctx context.Context, updateDbOnError bool, filter ...domain.InterfaceIdentifier) error {
|
2023-02-12 23:13:04 +01:00
|
|
|
interfaces, err := m.db.GetAllInterfaces(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, iface := range interfaces {
|
|
|
|
if len(filter) != 0 && !internal.SliceContains(filter, iface.Identifier) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
peers, err := m.db.GetInterfacePeers(ctx, iface.Identifier)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to load peers for %s: %w", iface.Identifier, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
physicalInterface, err := m.wg.GetInterface(ctx, iface.Identifier)
|
|
|
|
if err != nil {
|
|
|
|
// try to create a new interface
|
|
|
|
err := m.wg.SaveInterface(ctx, iface.Identifier, func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
|
|
|
|
domain.MergeToPhysicalInterface(pi, &iface)
|
|
|
|
|
|
|
|
return pi, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
if updateDbOnError {
|
|
|
|
// disable interface in database as no physical interface exists
|
|
|
|
_ = m.db.SaveInterface(ctx, iface.Identifier, func(in *domain.Interface) (*domain.Interface, error) {
|
|
|
|
now := time.Now()
|
|
|
|
in.Disabled = &now // set
|
|
|
|
in.DisabledReason = "no physical interface available"
|
|
|
|
return in, nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return fmt.Errorf("failed to create physical interface %s: %w", iface.Identifier, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// restore peers
|
|
|
|
for _, peer := range peers {
|
|
|
|
err := m.wg.SavePeer(ctx, iface.Identifier, peer.Identifier, func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error) {
|
|
|
|
domain.MergeToPhysicalPeer(pp, &peer)
|
|
|
|
return pp, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create physical peer %s: %w", peer.Identifier, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if physicalInterface.DeviceUp != !iface.IsDisabled() {
|
|
|
|
// try to move interface to stored state
|
|
|
|
err := m.wg.SaveInterface(ctx, iface.Identifier, func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
|
|
|
|
pi.DeviceUp = !iface.IsDisabled()
|
|
|
|
|
|
|
|
return pi, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
if updateDbOnError {
|
|
|
|
// disable interface in database as no physical interface is available
|
|
|
|
_ = m.db.SaveInterface(ctx, iface.Identifier, func(in *domain.Interface) (*domain.Interface, error) {
|
|
|
|
if iface.IsDisabled() {
|
|
|
|
now := time.Now()
|
|
|
|
in.Disabled = &now // set
|
|
|
|
in.DisabledReason = "no physical interface active"
|
|
|
|
} else {
|
|
|
|
in.Disabled = nil
|
|
|
|
in.DisabledReason = ""
|
|
|
|
}
|
|
|
|
return in, nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return fmt.Errorf("failed to change physical interface state for %s: %w", iface.Identifier, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) CreateDefaultPeer(ctx context.Context, user *domain.User) error {
|
2023-02-12 23:13:04 +01:00
|
|
|
// TODO: implement
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error) {
|
2023-02-12 23:13:04 +01:00
|
|
|
return m.db.GetInterfaceAndPeers(ctx, id)
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) GetAllInterfaces(ctx context.Context) ([]domain.Interface, error) {
|
2023-02-12 23:13:04 +01:00
|
|
|
return m.db.GetAllInterfaces(ctx)
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) {
|
2023-02-12 23:13:04 +01:00
|
|
|
return m.db.GetUserPeers(ctx, id)
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) PrepareInterface(ctx context.Context) (*domain.Interface, error) {
|
2023-02-12 23:13:04 +01:00
|
|
|
currentUser := domain.GetUserInfo(ctx)
|
|
|
|
|
|
|
|
kp, err := domain.NewFreshKeypair()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to generate keys: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
id, err := m.getNewInterfaceName(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to generate new identifier: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ipv4, ipv6, err := m.getFreshIpConfig(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to generate new ip config: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
port, err := m.getFreshListenPort(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to generate new listen port: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ips := []domain.Cidr{ipv4}
|
|
|
|
if m.cfg.Advanced.UseIpV6 {
|
|
|
|
ips = append(ips, ipv6)
|
|
|
|
}
|
2023-06-14 23:03:24 +02:00
|
|
|
networks := []domain.Cidr{ipv4.NetworkAddr()}
|
|
|
|
if m.cfg.Advanced.UseIpV6 {
|
|
|
|
networks = append(networks, ipv6.NetworkAddr())
|
|
|
|
}
|
2023-06-13 23:50:32 +02:00
|
|
|
|
2023-02-12 23:13:04 +01:00
|
|
|
freshInterface := &domain.Interface{
|
|
|
|
BaseModel: domain.BaseModel{
|
|
|
|
CreatedBy: string(currentUser.Id),
|
|
|
|
UpdatedBy: string(currentUser.Id),
|
|
|
|
CreatedAt: time.Now(),
|
|
|
|
UpdatedAt: time.Now(),
|
|
|
|
},
|
|
|
|
Identifier: id,
|
|
|
|
KeyPair: kp,
|
2023-06-13 23:50:32 +02:00
|
|
|
ListenPort: port,
|
|
|
|
Addresses: ips,
|
2023-02-12 23:13:04 +01:00
|
|
|
DnsStr: "",
|
|
|
|
DnsSearchStr: "",
|
|
|
|
Mtu: 1420,
|
|
|
|
FirewallMark: 0,
|
|
|
|
RoutingTable: "",
|
|
|
|
PreUp: "",
|
|
|
|
PostUp: "",
|
|
|
|
PreDown: "",
|
|
|
|
PostDown: "",
|
2023-06-14 23:03:24 +02:00
|
|
|
SaveConfig: m.cfg.Advanced.ConfigStoragePath != "",
|
2023-02-12 23:13:04 +01:00
|
|
|
DisplayName: string(id),
|
|
|
|
Type: domain.InterfaceTypeServer,
|
|
|
|
DriverType: "",
|
|
|
|
Disabled: nil,
|
|
|
|
DisabledReason: "",
|
2023-06-14 23:03:24 +02:00
|
|
|
PeerDefNetworkStr: domain.CidrsToString(networks),
|
|
|
|
PeerDefDnsStr: "",
|
2023-02-12 23:13:04 +01:00
|
|
|
PeerDefDnsSearchStr: "",
|
|
|
|
PeerDefEndpoint: "",
|
2023-06-14 23:03:24 +02:00
|
|
|
PeerDefAllowedIPsStr: domain.CidrsToString(networks),
|
2023-02-12 23:13:04 +01:00
|
|
|
PeerDefMtu: 1420,
|
|
|
|
PeerDefPersistentKeepalive: 16,
|
|
|
|
PeerDefFirewallMark: 0,
|
|
|
|
PeerDefRoutingTable: "",
|
|
|
|
PeerDefPreUp: "",
|
|
|
|
PeerDefPostUp: "",
|
|
|
|
PeerDefPreDown: "",
|
|
|
|
PeerDefPostDown: "",
|
|
|
|
}
|
|
|
|
|
|
|
|
return freshInterface, nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) getNewInterfaceName(ctx context.Context) (domain.InterfaceIdentifier, error) {
|
2023-02-12 23:13:04 +01:00
|
|
|
namePrefix := "wg"
|
2023-06-14 23:03:24 +02:00
|
|
|
nameSuffix := 0
|
2023-02-12 23:13:04 +01:00
|
|
|
|
|
|
|
existingInterfaces, err := m.db.GetAllInterfaces(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
var name domain.InterfaceIdentifier
|
|
|
|
for {
|
|
|
|
name = domain.InterfaceIdentifier(fmt.Sprintf("%s%d", namePrefix, nameSuffix))
|
|
|
|
|
|
|
|
conflict := false
|
|
|
|
for _, in := range existingInterfaces {
|
|
|
|
if in.Identifier == name {
|
|
|
|
conflict = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !conflict {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
nameSuffix++
|
|
|
|
}
|
|
|
|
|
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) getFreshIpConfig(ctx context.Context) (ipV4, ipV6 domain.Cidr, err error) {
|
2023-02-12 23:13:04 +01:00
|
|
|
ips, err := m.db.GetInterfaceIps(ctx)
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("failed to get existing IP addresses: %w", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
useV6 := m.cfg.Advanced.UseIpV6
|
|
|
|
ipV4, _ = domain.CidrFromString(m.cfg.Advanced.StartCidrV4)
|
|
|
|
ipV6, _ = domain.CidrFromString(m.cfg.Advanced.StartCidrV6)
|
2023-02-12 23:13:04 +01:00
|
|
|
|
|
|
|
for {
|
|
|
|
ipV4Conflict := false
|
|
|
|
ipV6Conflict := false
|
|
|
|
for _, usedIps := range ips {
|
|
|
|
for _, ip := range usedIps {
|
|
|
|
if ipV4 == ip {
|
|
|
|
ipV4Conflict = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if ipV6 == ip {
|
|
|
|
ipV6Conflict = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
if !ipV4Conflict && (!useV6 || !ipV6Conflict) {
|
2023-02-12 23:13:04 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if ipV4Conflict {
|
|
|
|
ipV4 = ipV4.NextSubnet()
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
if ipV6Conflict && useV6 {
|
2023-02-12 23:13:04 +01:00
|
|
|
ipV6 = ipV6.NextSubnet()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) getFreshListenPort(ctx context.Context) (port int, err error) {
|
|
|
|
existingInterfaces, err := m.db.GetAllInterfaces(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
|
|
|
|
port = m.cfg.Advanced.StartListenPort
|
|
|
|
|
|
|
|
for {
|
|
|
|
conflict := false
|
|
|
|
for _, in := range existingInterfaces {
|
|
|
|
if in.ListenPort == port {
|
|
|
|
conflict = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !conflict {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
port++
|
|
|
|
}
|
|
|
|
|
|
|
|
if port > 65535 { // maximum allowed port number (16 bit uint)
|
|
|
|
return -1, fmt.Errorf("port space exhausted")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m Manager) CreateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error) {
|
2023-02-12 23:13:04 +01:00
|
|
|
existingInterface, err := m.db.GetInterface(ctx, in.Identifier)
|
|
|
|
if err != nil && !errors.Is(err, domain.ErrNotFound) {
|
|
|
|
return nil, fmt.Errorf("unable to load existing interface %s: %w", in.Identifier, err)
|
|
|
|
}
|
|
|
|
if existingInterface != nil {
|
|
|
|
return nil, fmt.Errorf("interface %s already exists", in.Identifier)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := m.validateCreation(ctx, existingInterface, in); err != nil {
|
|
|
|
return nil, fmt.Errorf("creation not allowed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.db.SaveInterface(ctx, in.Identifier, func(i *domain.Interface) (*domain.Interface, error) {
|
|
|
|
in.CopyCalculatedAttributes(i)
|
|
|
|
|
|
|
|
err = m.wg.SaveInterface(ctx, in.Identifier, func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
|
|
|
|
domain.MergeToPhysicalInterface(pi, in)
|
|
|
|
return pi, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create physical interface %s: %w", in.Identifier, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return in, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("creation failure: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return in, nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) UpdateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error) {
|
2023-02-12 23:13:04 +01:00
|
|
|
existingInterface, err := m.db.GetInterface(ctx, in.Identifier)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to load existing interface %s: %w", in.Identifier, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := m.validateModifications(ctx, existingInterface, in); err != nil {
|
|
|
|
return nil, fmt.Errorf("update not allowed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.db.SaveInterface(ctx, in.Identifier, func(i *domain.Interface) (*domain.Interface, error) {
|
|
|
|
in.CopyCalculatedAttributes(i)
|
|
|
|
|
|
|
|
err = m.wg.SaveInterface(ctx, in.Identifier, func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error) {
|
|
|
|
domain.MergeToPhysicalInterface(pi, in)
|
|
|
|
return pi, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to update physical interface %s: %w", in.Identifier, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return in, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("update failure: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return in, nil
|
|
|
|
}
|
|
|
|
|
2023-06-20 21:27:34 +02:00
|
|
|
func (m Manager) DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error {
|
|
|
|
existingInterface, err := m.db.GetInterface(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to find interface %s: %w", id, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := m.validateDeletion(ctx, existingInterface); err != nil {
|
|
|
|
return fmt.Errorf("deletion not allowed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.deleteInterfacePeers(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("peer deletion failure: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.wg.DeleteInterface(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("wireguard deletion failure: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.db.DeleteInterface(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("deletion failure: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) validateModifications(ctx context.Context, old, new *domain.Interface) error {
|
2023-02-12 23:13:04 +01:00
|
|
|
currentUser := domain.GetUserInfo(ctx)
|
|
|
|
|
|
|
|
if !currentUser.IsAdmin {
|
|
|
|
return fmt.Errorf("insufficient permissions")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-13 23:50:32 +02:00
|
|
|
func (m Manager) validateCreation(ctx context.Context, old, new *domain.Interface) error {
|
2023-02-12 23:13:04 +01:00
|
|
|
currentUser := domain.GetUserInfo(ctx)
|
|
|
|
|
|
|
|
if new.Identifier == "" {
|
|
|
|
return fmt.Errorf("invalid interface identifier")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !currentUser.IsAdmin {
|
|
|
|
return fmt.Errorf("insufficient permissions")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-06-20 21:27:34 +02:00
|
|
|
|
|
|
|
func (m Manager) validateDeletion(ctx context.Context, del *domain.Interface) error {
|
|
|
|
currentUser := domain.GetUserInfo(ctx)
|
|
|
|
|
|
|
|
if !currentUser.IsAdmin {
|
|
|
|
return fmt.Errorf("insufficient permissions")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m Manager) deleteInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) error {
|
|
|
|
allPeers, err := m.db.GetInterfacePeers(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, peer := range allPeers {
|
|
|
|
err = m.wg.DeletePeer(ctx, id, peer.Identifier)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("wireguard peer deletion failure for %s: %w", peer.Identifier, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.db.DeletePeer(ctx, peer.Identifier)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("peer deletion failure for %s: %w", peer.Identifier, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-06-21 23:22:27 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-06-22 19:15:29 +02:00
|
|
|
peerId := domain.PeerIdentifier(uuid.New().String())
|
2023-06-21 23:22:27 +02:00
|
|
|
freshPeer := &domain.Peer{
|
|
|
|
BaseModel: domain.BaseModel{
|
|
|
|
CreatedBy: string(currentUser.Id),
|
|
|
|
UpdatedBy: string(currentUser.Id),
|
|
|
|
CreatedAt: time.Now(),
|
|
|
|
UpdatedAt: time.Now(),
|
|
|
|
},
|
2023-06-22 19:15:29 +02:00
|
|
|
Endpoint: domain.NewStringConfigOption(iface.PeerDefEndpoint, true),
|
|
|
|
EndpointPublicKey: iface.PublicKey,
|
|
|
|
AllowedIPsStr: domain.NewStringConfigOption(iface.PeerDefAllowedIPsStr, true),
|
2023-06-21 23:22:27 +02:00
|
|
|
ExtraAllowedIPsStr: "",
|
|
|
|
PresharedKey: pk,
|
2023-06-22 19:15:29 +02:00
|
|
|
PersistentKeepalive: domain.NewIntConfigOption(iface.PeerDefPersistentKeepalive, true),
|
|
|
|
DisplayName: fmt.Sprintf("Peer %s", peerId[0:8]),
|
|
|
|
Identifier: peerId,
|
2023-06-21 23:22:27 +02:00
|
|
|
UserIdentifier: currentUser.Id,
|
|
|
|
InterfaceIdentifier: iface.Identifier,
|
|
|
|
Disabled: nil,
|
|
|
|
DisabledReason: "",
|
|
|
|
ExpiresAt: nil,
|
|
|
|
Notes: "",
|
|
|
|
Interface: domain.PeerInterfaceConfig{
|
|
|
|
KeyPair: kp,
|
|
|
|
Type: peerMode,
|
2023-06-22 19:15:29 +02:00
|
|
|
Addresses: nil, // TODO
|
2023-06-21 23:22:27 +02:00
|
|
|
CheckAliveAddress: "",
|
2023-06-22 19:15:29 +02:00
|
|
|
DnsStr: domain.NewStringConfigOption(iface.PeerDefDnsStr, true),
|
|
|
|
DnsSearchStr: domain.NewStringConfigOption(iface.PeerDefDnsSearchStr, true),
|
|
|
|
Mtu: domain.NewIntConfigOption(iface.PeerDefMtu, true),
|
|
|
|
FirewallMark: domain.NewInt32ConfigOption(iface.PeerDefFirewallMark, true),
|
|
|
|
RoutingTable: domain.NewStringConfigOption(iface.PeerDefRoutingTable, true),
|
|
|
|
PreUp: domain.NewStringConfigOption(iface.PeerDefPreUp, true),
|
|
|
|
PostUp: domain.NewStringConfigOption(iface.PeerDefPostUp, true),
|
|
|
|
PreDown: domain.NewStringConfigOption(iface.PeerDefPreUp, true),
|
|
|
|
PostDown: domain.NewStringConfigOption(iface.PeerDefPostUp, true),
|
2023-06-21 23:22:27 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return freshPeer, nil
|
|
|
|
}
|
2023-06-21 23:58:57 +02:00
|
|
|
|
|
|
|
func (m Manager) DeletePeer(ctx context.Context, id domain.PeerIdentifier) error {
|
|
|
|
peer, err := m.db.GetPeer(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to find peer %s: %w", id, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.wg.DeletePeer(ctx, peer.InterfaceIdentifier, id)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("wireguard failed to delete peer %s: %w", id, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.db.DeletePeer(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to delete peer %s: %w", id, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m Manager) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) {
|
|
|
|
peer, err := m.db.GetPeer(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to find peer %s: %w", id, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return peer, nil
|
|
|
|
}
|