pkg/controller/common/license/check.go (120 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package license
import (
"context"
"fmt"
"sort"
"time"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/elastic/cloud-on-k8s/v3/pkg/utils/k8s"
ulog "github.com/elastic/cloud-on-k8s/v3/pkg/utils/log"
)
const (
// EventInvalidLicense describes an event fired when a license is not valid.
EventInvalidLicense = "InvalidLicense"
)
type Checker interface {
CurrentEnterpriseLicense(context.Context) (*EnterpriseLicense, error)
EnterpriseFeaturesEnabled(ctx context.Context) (bool, error)
Valid(context.Context, EnterpriseLicense) (bool, error)
ValidOperatorLicenseKeyType(context.Context) (OperatorLicenseType, error)
}
// checker contains parameters for license checks.
type checker struct {
k8sClient k8s.Client
operatorNamespace string
publicKey []byte
}
// NewLicenseChecker creates a new license checker.
func NewLicenseChecker(client k8s.Client, operatorNamespace string) Checker {
return &checker{
k8sClient: client,
operatorNamespace: operatorNamespace,
publicKey: publicKeyBytes,
}
}
func (lc *checker) publicKeyFor(l EnterpriseLicense) ([]byte, error) {
if !l.IsECKManagedTrial() {
return lc.publicKey, nil
}
var signatureSec corev1.Secret
err := lc.k8sClient.Get(context.Background(), types.NamespacedName{
Namespace: lc.operatorNamespace,
Name: TrialStatusSecretKey,
}, &signatureSec)
// read secret data only after retrieving the secret
return signatureSec.Data[TrialPubkeyKey], err
}
// CurrentEnterpriseLicense returns the currently valid Enterprise license if installed.
func (lc *checker) CurrentEnterpriseLicense(ctx context.Context) (*EnterpriseLicense, error) {
licenses, err := EnterpriseLicenses(lc.k8sClient)
if err != nil {
return nil, errors.Wrap(err, "failed to list enterprise licenses")
}
sort.Slice(licenses, func(i, j int) bool {
t1, t2 := OperatorLicenseTypeOrder[licenses[i].License.Type], OperatorLicenseTypeOrder[licenses[j].License.Type]
if t1 != t2 { // sort by type (first the most features)
return t1 > t2
}
// and by expiry date (first which expires last)
return licenses[i].License.ExpiryDateInMillis > licenses[j].License.ExpiryDateInMillis
})
// pick the first valid Enterprise license in the sorted slice
for _, l := range licenses {
valid, err := lc.Valid(ctx, l)
if err != nil {
return nil, err
}
if valid {
return &l, nil
}
}
return nil, nil
}
// EnterpriseFeaturesEnabled returns true if a valid enterprise license is installed.
func (lc *checker) EnterpriseFeaturesEnabled(ctx context.Context) (bool, error) {
license, err := lc.CurrentEnterpriseLicense(ctx)
if err != nil {
return false, err
}
return license != nil, nil
}
// Valid returns true if the given Enterprise license is valid or an error if any.
func (lc *checker) Valid(ctx context.Context, l EnterpriseLicense) (bool, error) {
pk, err := lc.publicKeyFor(l)
if err != nil {
return false, errors.Wrap(err, "while loading signature secret")
}
if len(pk) == 0 {
ulog.FromContext(ctx).Info("This is an unlicensed development build of ECK. License management and Enterprise features are disabled")
return false, nil
}
verifier, err := NewVerifier(pk)
if err != nil {
return false, err
}
status := verifier.Valid(ctx, l, time.Now())
if status == LicenseStatusValid {
return true, nil
}
return false, nil
}
// ValidOperatorLicenseKeyType returns true if the current operator license key is valid
func (lc checker) ValidOperatorLicenseKeyType(ctx context.Context) (OperatorLicenseType, error) {
lic, err := lc.CurrentEnterpriseLicense(ctx)
if err != nil {
ulog.FromContext(ctx).V(-1).Info("Invalid Enterprise license, fallback to Basic: " + err.Error())
}
licType := lic.GetOperatorLicenseType()
if _, valid := OperatorLicenseTypeOrder[licType]; !valid {
return licType, fmt.Errorf("invalid license key: %s", licType)
}
return licType, nil
}
type MockLicenseChecker struct {
EnterpriseEnabled bool
}
func (m MockLicenseChecker) CurrentEnterpriseLicense(context.Context) (*EnterpriseLicense, error) {
return &EnterpriseLicense{}, nil
}
func (m MockLicenseChecker) EnterpriseFeaturesEnabled(context.Context) (bool, error) {
return m.EnterpriseEnabled, nil
}
func (m MockLicenseChecker) Valid(_ context.Context, _ EnterpriseLicense) (bool, error) {
return m.EnterpriseEnabled, nil
}
func (m MockLicenseChecker) ValidOperatorLicenseKeyType(_ context.Context) (OperatorLicenseType, error) {
return LicenseTypeEnterprise, nil
}
var _ Checker = &MockLicenseChecker{}