Ctrl K
ComunidadGuías…

api/handler/license_v2.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handler

import (
	"context"
	"fmt"
	"time"

	"github.com/goodrain/rainbond/api/util/license"
	"github.com/goodrain/rainbond/pkg/component/k8s"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	licenseConfigMapNamespace = "rbd-system"
	licenseConfigMapName      = "rbd-license-info"
	licenseConfigMapKey       = "license"
)

// LicenseV2Handler handles license V2 operations.
type LicenseV2Handler interface {
	ActivateLicense(ctx context.Context, licenseCode string, enterpriseID string) (*license.LicenseStatus, error)
	GetLicenseStatus(ctx context.Context) (*license.LicenseStatus, error)
}

type licenseV2Action struct{}

var defaultLicenseV2Handler LicenseV2Handler

// CreateLicenseV2Handler creates a singleton LicenseV2Handler.
func CreateLicenseV2Handler() {
	defaultLicenseV2Handler = &licenseV2Action{}
}

// GetLicenseV2Handler returns the singleton LicenseV2Handler.
func GetLicenseV2Handler() LicenseV2Handler {
	return defaultLicenseV2Handler
}

func (l *licenseV2Action) ActivateLicense(ctx context.Context, licenseCode string, enterpriseID string) (*license.LicenseStatus, error) {
	// Decode
	token, err := license.DecodeLicense(licenseCode)
	if err != nil {
		return license.TokenToStatus(nil, false, fmt.Sprintf("decode license: %v", err)), nil
	}

	// Get embedded public key
	pubKey, err := license.GetEmbeddedPublicKey()
	if err != nil {
		return nil, fmt.Errorf("get embedded public key: %w", err)
	}

	// Verify signature
	if err := license.VerifySignature(token, pubKey); err != nil {
		return license.TokenToStatus(token, false, fmt.Sprintf("invalid signature: %v", err)), nil
	}

	// Verify enterprise_id (strong check)
	if token.EnterpriseID != enterpriseID {
		return license.TokenToStatus(token, false, fmt.Sprintf("enterprise ID mismatch: license=%s, actual=%s", token.EnterpriseID, enterpriseID)), nil
	}

	// Verify time window
	now := time.Now()
	if now.Unix() < token.StartAt {
		return license.TokenToStatus(token, false, "license not yet valid"), nil
	}
	if now.Unix() > token.ExpireAt {
		return license.TokenToStatus(token, false, "license expired"), nil
	}

	// Write JSON (not base64) to ConfigMap so plugins can directly json.Unmarshal
	tokenJSON, err := license.MarshalLicenseJSON(token)
	if err != nil {
		return nil, fmt.Errorf("marshal license JSON: %w", err)
	}
	if err := l.writeLicenseConfigMap(ctx, tokenJSON); err != nil {
		return nil, fmt.Errorf("write license configmap: %w", err)
	}

	// Invalidate middleware cache
	InvalidateLicenseCacheFunc()

	logrus.Infof("License activated: code=%s, company=%s, enterprise=%s", token.Code, token.Company, token.EnterpriseID)
	return license.TokenToStatus(token, true, ""), nil
}

func (l *licenseV2Action) GetLicenseStatus(ctx context.Context) (*license.LicenseStatus, error) {
	// Read from ConfigMap
	licenseData, err := l.readLicenseConfigMap(ctx)
	if err != nil {
		if k8serrors.IsNotFound(err) {
			return license.TokenToStatus(nil, false, "no license configured"), nil
		}
		return nil, fmt.Errorf("read license configmap: %w", err)
	}
	if licenseData == "" {
		return license.TokenToStatus(nil, false, "no license configured"), nil
	}

	// Parse JSON directly (ConfigMap stores plain JSON, not base64)
	token, err := license.ParseLicenseJSON(licenseData)
	if err != nil {
		return license.TokenToStatus(nil, false, fmt.Sprintf("decode license: %v", err)), nil
	}

	// Get public key
	pubKey, err := license.GetEmbeddedPublicKey()
	if err != nil {
		return nil, fmt.Errorf("get embedded public key: %w", err)
	}

	// Verify signature
	if err := license.VerifySignature(token, pubKey); err != nil {
		return license.TokenToStatus(token, false, fmt.Sprintf("invalid signature: %v", err)), nil
	}

	// Check time
	now := time.Now().Unix()
	if now < token.StartAt {
		return license.TokenToStatus(token, false, "license not yet valid"), nil
	}
	if now > token.ExpireAt {
		return license.TokenToStatus(token, false, "license expired"), nil
	}

	return license.TokenToStatus(token, true, ""), nil
}

func (l *licenseV2Action) writeLicenseConfigMap(ctx context.Context, licenseData string) error {
	client := k8s.Default().Clientset.CoreV1().ConfigMaps(licenseConfigMapNamespace)

	cm, err := client.Get(ctx, licenseConfigMapName, metav1.GetOptions{})
	if err != nil {
		if !k8serrors.IsNotFound(err) {
			return err
		}
		cm = &corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Name:      licenseConfigMapName,
				Namespace: licenseConfigMapNamespace,
			},
			Data: map[string]string{
				licenseConfigMapKey: licenseData,
			},
		}
		_, err = client.Create(ctx, cm, metav1.CreateOptions{})
		return err
	}

	if cm.Data == nil {
		cm.Data = make(map[string]string)
	}
	cm.Data[licenseConfigMapKey] = licenseData
	_, err = client.Update(ctx, cm, metav1.UpdateOptions{})
	return err
}

func (l *licenseV2Action) readLicenseConfigMap(ctx context.Context) (string, error) {
	cm, err := k8s.Default().Clientset.CoreV1().ConfigMaps(licenseConfigMapNamespace).Get(ctx, licenseConfigMapName, metav1.GetOptions{})
	if err != nil {
		return "", err
	}
	return cm.Data[licenseConfigMapKey], nil
}

// InvalidateLicenseCacheFunc is set by the middleware package to allow cache invalidation.
// This avoids an import cycle between handler and middleware.
var InvalidateLicenseCacheFunc = func() {}