diff --git a/internal/server/configuration.go b/internal/server/configuration.go index f904dbb..ec5a1cb 100644 --- a/internal/server/configuration.go +++ b/internal/server/configuration.go @@ -67,10 +67,11 @@ type Config struct { EditableKeys bool `yaml:"editableKeys" envconfig:"EDITABLE_KEYS"` CreateDefaultPeer bool `yaml:"createDefaultPeer" envconfig:"CREATE_DEFAULT_PEER"` SelfProvisioningAllowed bool `yaml:"selfProvisioning" envconfig:"SELF_PROVISIONING"` - WGExoprterFriendlyNames bool `yaml:"wgExporterFriendlyNames" envconfig:"WG_EXPORTER_FRIENDLY_NAMES"` + WGExporterFriendlyNames bool `yaml:"wgExporterFriendlyNames" envconfig:"WG_EXPORTER_FRIENDLY_NAMES"` LdapEnabled bool `yaml:"ldapEnabled" envconfig:"LDAP_ENABLED"` SessionSecret string `yaml:"sessionSecret" envconfig:"SESSION_SECRET"` LogoUrl string `yaml:"logoUrl" envconfig:"LOGO_URL"` + BackgroundTaskInterval int `yaml:"backgroundTaskInterval" envconfig:"BACKGROUND_TASK_INTERVAL"` // in seconds } `yaml:"core"` Database common.DatabaseConfig `yaml:"database"` Email common.MailConfig `yaml:"email"` @@ -92,8 +93,9 @@ func NewConfig() *Config { cfg.Core.AdminPassword = "wgportal" cfg.Core.LdapEnabled = false cfg.Core.EditableKeys = true - cfg.Core.WGExoprterFriendlyNames = false + cfg.Core.WGExporterFriendlyNames = false cfg.Core.SessionSecret = "secret" + cfg.Core.BackgroundTaskInterval = 15 * 60 // 15 minutes cfg.Database.Typ = "sqlite" cfg.Database.Database = "data/wg_portal.db" diff --git a/internal/server/handlers_interface.go b/internal/server/handlers_interface.go index 72f5a6f..ca6804d 100644 --- a/internal/server/handlers_interface.go +++ b/internal/server/handlers_interface.go @@ -112,7 +112,7 @@ func (s *Server) GetInterfaceConfig(c *gin.Context) { currentSession := GetSessionData(c) device := s.peers.GetDevice(currentSession.DeviceName) peers := s.peers.GetActivePeers(device.DeviceName) - cfg, err := device.GetConfigFile(peers, s.config.Core.WGExoprterFriendlyNames) + cfg, err := device.GetConfigFile(peers, s.config.Core.WGExporterFriendlyNames) if err != nil { s.GetHandleError(c, http.StatusInternalServerError, "ConfigFile error", err.Error()) return diff --git a/internal/server/server.go b/internal/server/server.go index 7968bd0..06cfeaa 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -216,6 +216,8 @@ func (s *Server) Run() { go s.SyncLdapWithUserDatabase() } + go s.RunBackgroundTasks(s.ctx) + // Run web service srv := &http.Server{ Addr: s.config.Core.ListeningAddress, diff --git a/internal/server/server_helper.go b/internal/server/server_helper.go index 71582d1..27914ab 100644 --- a/internal/server/server_helper.go +++ b/internal/server/server_helper.go @@ -1,6 +1,7 @@ package server import ( + "context" "crypto/md5" "fmt" "io/ioutil" @@ -205,7 +206,7 @@ func (s *Server) WriteWireGuardConfigFile(device string) error { } dev := s.peers.GetDevice(device) - cfg, err := dev.GetConfigFile(s.peers.GetActivePeers(device), s.config.Core.WGExoprterFriendlyNames) + cfg, err := dev.GetConfigFile(s.peers.GetActivePeers(device), s.config.Core.WGExporterFriendlyNames) if err != nil { return errors.WithMessage(err, "failed to get config file") } @@ -374,3 +375,60 @@ func (s *Server) GetDeviceNames() map[string]string { return devNames } + +func (s *Server) RunBackgroundTasks(ctx context.Context) { + running := true + for running { + select { + case <-ctx.Done(): + running = false + continue + case <-time.After(time.Duration(s.config.Core.BackgroundTaskInterval) * time.Second): + // sleep completed, select will stop blocking + } + + logrus.Debug("running periodic background tasks...") + + err := s.checkExpiredPeers() + if err != nil { + logrus.Errorf("failed to check expired peers: %v", err) + } + } +} + +func (s *Server) checkExpiredPeers() error { + now := time.Now() + + for _, devName := range s.wg.Cfg.DeviceNames { + changed := false + peers := s.peers.GetAllPeers(devName) + for _, peer := range peers { + if peer.IsExpired() && !peer.IsDeactivated() { + changed = true + + peer.UpdatedAt = now + peer.DeactivatedAt = &now + peer.DeactivatedReason = "expired" + + res := s.db.Save(&peer) + if res.Error != nil { + return fmt.Errorf("failed save expired peer %s: %w", peer.PublicKey, res.Error) + } + + err := s.wg.RemovePeer(peer.DeviceName, peer.PublicKey) + if err != nil { + return fmt.Errorf("failed to expire peer %s: %w", peer.PublicKey, err) + } + } + } + + if changed { + err := s.WriteWireGuardConfigFile(devName) + if err != nil { + return fmt.Errorf("failed to persist config for interface %s: %w", devName, err) + } + } + } + + return nil +} diff --git a/internal/wireguard/peermanager.go b/internal/wireguard/peermanager.go index 3b4e504..f2cd108 100644 --- a/internal/wireguard/peermanager.go +++ b/internal/wireguard/peermanager.go @@ -255,6 +255,20 @@ func (p Peer) WillExpire() bool { return false } +func (p Peer) IsExpired() bool { + if p.ExpiresAt == nil { + return false + } + if p.ExpiresAt.Before(time.Now()) { + return true + } + return false +} + +func (p Peer) IsDeactivated() bool { + return p.DeactivatedAt != nil +} + func (p Peer) GetConfigFileName() string { reg := regexp.MustCompile("[^a-zA-Z0-9_-]+") return reg.ReplaceAllString(strings.ReplaceAll(p.Identifier, " ", "-"), "") + ".conf"