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() {}