From cec0ef17e07be4fa3187aedef6f790aaae8a7f52 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Wed, 4 Nov 2020 03:07:45 +0200 Subject: [PATCH] feat(licenses): sync between portainer and license-server (#41) * feat(license): add sync service * feat(licenses): check license server * chore(deps): update liblicense * feat(license): revoke license if invalid * feat(license): log revokation * - removed retry logics - removed license sync logging * revert liblicense version * - remove not used field Co-authored-by: yi-portainer --- api/bolt/license/license.go | 6 ++++ api/cmd/portainer/main.go | 5 +++ api/license/license.go | 43 ++++++++++++++++++++++--- api/license/sync.go | 64 +++++++++++++++++++++++++++++++++++++ api/portainer.go | 1 + 5 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 api/license/sync.go diff --git a/api/bolt/license/license.go b/api/bolt/license/license.go index bd72bbc73..249f00b52 100644 --- a/api/bolt/license/license.go +++ b/api/bolt/license/license.go @@ -79,6 +79,12 @@ func (service *Service) AddLicense(licenseKey string, license *liblicense.Portai }) } +// UpdateLicense updates a license. +func (service *Service) UpdateLicense(licenseKey string, license *liblicense.PortainerLicense) error { + identifier := []byte(licenseKey) + return internal.UpdateObject(service.db, BucketName, identifier, license) +} + // DeleteLicense deletes a License. func (service *Service) DeleteLicense(licenseKey string) error { identifier := []byte(licenseKey) diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index e0ccfbd78..a5191c86e 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -463,6 +463,11 @@ func main() { log.Fatal(err) } + err = licenseService.Start() + if err != nil { + log.Fatalf("failed starting license service: %w", err) + } + var server portainer.Server = &http.Server{ ReverseTunnelService: reverseTunnelService, Status: applicationStatus, diff --git a/api/license/license.go b/api/license/license.go index c3207981b..09227e76c 100644 --- a/api/license/license.go +++ b/api/license/license.go @@ -2,22 +2,33 @@ package license import ( "errors" - "github.com/portainer/liblicense" - "github.com/portainer/liblicense/master" - "github.com/portainer/portainer/api" "log" "time" + + "github.com/portainer/liblicense" + "github.com/portainer/liblicense/master" + portainer "github.com/portainer/portainer/api" ) // Service represents a service for managing portainer licenses type Service struct { repository portainer.LicenseRepository info *portainer.LicenseInfo + stopSignal chan struct{} } // NewService creates a new instance of Service func NewService(repository portainer.LicenseRepository) *Service { - return &Service{repository: repository, info: nil} + return &Service{ + repository: repository, + info: nil, + stopSignal: nil, + } +} + +// Start starts the service +func (service *Service) Start() error { + return service.startSyncLoop() } // Info returns aggregation of the information about the existing licenses @@ -120,6 +131,30 @@ func (service *Service) DeleteLicense(licenseKey string) error { return nil } +// revokeLicense revokes the license +func (service *Service) revokeLicense(licenseKey string) error { + license, err := service.repository.License(licenseKey) + if err != nil { + return err + } + + license.Revoked = true + + err = service.repository.UpdateLicense(licenseKey, license) + if err != nil { + return err + } + + licenses, err := service.Licenses() + if err != nil { + return err + } + + service.info = aggregate(licenses) + + return nil +} + func aggregate(licenses []liblicense.PortainerLicense) *portainer.LicenseInfo { nodes := 0 var expiresAt time.Time diff --git a/api/license/sync.go b/api/license/sync.go new file mode 100644 index 000000000..001ea7b32 --- /dev/null +++ b/api/license/sync.go @@ -0,0 +1,64 @@ +package license + +import ( + "time" + + "github.com/portainer/liblicense/master" +) + +const ( + syncInterval = 5 * time.Minute +) + +func (service *Service) startSyncLoop() error { + if service.stopSignal != nil { + return nil + } + + err := service.syncLicenses() + if err != nil { + return err + } + + service.stopSignal = make(chan struct{}) + ticker := time.NewTicker(syncInterval) + + go (func() { + for { + select { + case <-service.stopSignal: + ticker.Stop() + return + case <-ticker.C: + service.syncLicenses() + } + } + })() + + return nil +} + +func (service *Service) syncLicenses() error { + licenses, err := service.Licenses() + if err != nil { + return err + } + + licensesToRemove := []string{} + + for _, license := range licenses { + valid, err := master.ValidateLicense(&license) + if err != nil || !valid { + licensesToRemove = append(licensesToRemove, license.LicenseKey) + } + } + + for _, licenseKey := range licensesToRemove { + err := service.revokeLicense(licenseKey) + if err != nil { + return err + } + } + + return nil +} diff --git a/api/portainer.go b/api/portainer.go index 5afc661f7..e2eca0d1a 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1058,6 +1058,7 @@ type ( Licenses() ([]liblicense.PortainerLicense, error) License(licenseKey string) (*liblicense.PortainerLicense, error) AddLicense(licenseKey string, license *liblicense.PortainerLicense) error + UpdateLicense(licenseKey string, license *liblicense.PortainerLicense) error DeleteLicense(licenseKey string) error }