Skip to content

Commit

Permalink
add cluster id for license controller. (#4237)
Browse files Browse the repository at this point in the history
* add cluster id for license controller.

Signed-off-by: yy <[email protected]>

* fix typo error.

Signed-off-by: yy <[email protected]>

* add cluster id and db for it.

Signed-off-by: yy <[email protected]>

* use kube-system ns uid as cluster id.

Signed-off-by: yy <[email protected]>

* delete cluster id env

Signed-off-by: yy <[email protected]>

* fix license check func.

Signed-off-by: yy <[email protected]>

* chore: use id first 8 char as is

Signed-off-by: yy <[email protected]>

* ci: fix make license.

Signed-off-by: yy <[email protected]>

* ci: fix typos.

Signed-off-by: yy <[email protected]>

---------

Signed-off-by: yy <[email protected]>
  • Loading branch information
lingdie authored Dec 13, 2023
1 parent 4bea4ea commit 40e33e0
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 24 deletions.
10 changes: 9 additions & 1 deletion controllers/license/cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
accountv1 "github.com/labring/sealos/controllers/account/api/v1"
licensev1 "github.com/labring/sealos/controllers/license/api/v1"
"github.com/labring/sealos/controllers/license/internal/controller"
utilid "github.com/labring/sealos/controllers/license/internal/util/clusterid"
"github.com/labring/sealos/controllers/license/internal/util/database"
//+kubebuilder:scaffold:imports
)
Expand Down Expand Up @@ -101,7 +102,14 @@ func main() {
}
defer db.Disconnect(context.Background())

if err = (&controller.LicenseReconciler{}).SetupWithManager(mgr, db); err != nil {
clusterID, err := utilid.GetClusterID(context.Background(), mgr.GetClient())
if err != nil {
setupLog.Error(err, "unable to get cluster id")
os.Exit(1)
}
setupLog.Info("cluster id", "id", clusterID)

if err = (&controller.LicenseReconciler{ClusterID: clusterID}).SetupWithManager(mgr, db); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "License")
os.Exit(1)
}
Expand Down
41 changes: 41 additions & 0 deletions controllers/license/internal/controller/license_activator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright © 2023 sealos.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package controller

import (
"context"

licensev1 "github.com/labring/sealos/controllers/license/api/v1"
accountutil "github.com/labring/sealos/controllers/license/internal/util/account"

"sigs.k8s.io/controller-runtime/pkg/client"
)

type LicenseActivator struct {
client.Client
}

func (a *LicenseActivator) Active(ctx context.Context, license *licensev1.License) error {
// TODO mv to active function
switch license.Spec.Type {
case licensev1.AccountLicenseType:
if err := accountutil.Recharge(ctx, a.Client, license); err != nil {
return err
}
case licensev1.ClusterLicenseType:
// TODO implement cluster license
}
return nil
}
28 changes: 16 additions & 12 deletions controllers/license/internal/controller/license_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controller

import (
"context"
"errors"
"time"

"github.com/go-logr/logr"
Expand All @@ -30,8 +31,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/predicate"

licensev1 "github.com/labring/sealos/controllers/license/api/v1"
accountutil "github.com/labring/sealos/controllers/license/internal/util/account"
"github.com/labring/sealos/controllers/license/internal/util/database"
utilerrors "github.com/labring/sealos/controllers/license/internal/util/errors"
)

// LicenseReconciler reconciles a License object
Expand All @@ -41,8 +42,11 @@ type LicenseReconciler struct {
Logger logr.Logger
//finalizer *ctrlsdk.Finalizer

ClusterID string

validator *LicenseValidator
recorder *LicenseRecorder
activator *LicenseActivator
}

// +kubebuilder:rbac:groups=license.sealos.io,resources=licenses,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -73,6 +77,12 @@ func (r *LicenseReconciler) reconcile(ctx context.Context, license *licensev1.Li

// check if license is valid
valid, err := r.validator.Validate(license)
if errors.Is(err, utilerrors.ErrClusterIDNotMatch) {
r.Logger.V(1).Info("license clusterID not match", "license", license.Namespace+"/"+license.Name)
license.Status.Phase = licensev1.LicenseStatusPhaseFailed
_ = r.Status().Update(ctx, license)
return ctrl.Result{}, nil
}
if err != nil {
r.Logger.V(1).Error(err, "failed to validate license")
return ctrl.Result{}, err
Expand Down Expand Up @@ -100,15 +110,9 @@ func (r *LicenseReconciler) reconcile(ctx context.Context, license *licensev1.Li
return ctrl.Result{}, nil
}

// TODO mv to active function
switch license.Spec.Type {
case licensev1.AccountLicenseType:
if err = accountutil.Recharge(ctx, r.Client, license); err != nil {
r.Logger.V(1).Error(err, "failed to recharge account")
return ctrl.Result{}, err
}
case licensev1.ClusterLicenseType:
// TODO implement cluster license
if err := r.activator.Active(ctx, license); err != nil {
r.Logger.V(1).Error(err, "failed to active license")
return ctrl.Result{}, err
}

// update license status to active
Expand All @@ -126,11 +130,11 @@ func (r *LicenseReconciler) reconcile(ctx context.Context, license *licensev1.Li
// SetupWithManager sets up the controller with the Manager.
func (r *LicenseReconciler) SetupWithManager(mgr ctrl.Manager, db *database.DataBase) error {
r.Logger = mgr.GetLogger().WithName("controller").WithName("License")
//r.finalizer = ctrlsdk.NewFinalizer(r.Client, "license.sealos.io/finalizer")
r.Client = mgr.GetClient()

r.validator = &LicenseValidator{
Client: r.Client,
Client: r.Client,
ClusterID: r.ClusterID,
}

r.recorder = &LicenseRecorder{
Expand Down
3 changes: 2 additions & 1 deletion controllers/license/internal/controller/license_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ import (

type LicenseValidator struct {
client.Client
ClusterID string
}

func (v *LicenseValidator) Validate(license *licensev1.License) (bool, error) {
return licenseutil.IsLicenseValid(license)
return licenseutil.IsLicenseValid(license, v.ClusterID)
}
5 changes: 3 additions & 2 deletions controllers/license/internal/util/claims/claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import (
)

type Claims struct {
Type v1.LicenseType `json:"type"`
Data ClaimData `json:"data"`
Type v1.LicenseType `json:"type"`
ClusterID string `json:"clusterID"`
Data ClaimData `json:"data"`

jwt.RegisteredClaims
}
Expand Down
36 changes: 36 additions & 0 deletions controllers/license/internal/util/clusterid/cluster_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright © 2023 sealos.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clusterid

import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func GetClusterID(ctx context.Context, c client.Client) (string, error) {
ns := &corev1.Namespace{}
err := c.Get(ctx, client.ObjectKey{Name: "kube-system"}, ns)
if err != nil {
return "", err
}
res := string(ns.UID)
if res == "" || len(res) < 8 {
return "", fmt.Errorf("failed to get cluster id")
}
return res[0:8], nil
}
35 changes: 29 additions & 6 deletions controllers/license/internal/util/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ type DataBase struct {
URI string
Client *mongo.Client

licenseCollection *mongo.Collection
licenseCollection *mongo.Collection
clusterIDCollection *mongo.Collection
}

const (
DefaultLicenseDataBase = "sealos-license"
DefaultLicenseCollection = "license"
DefaultLicenseDataBase = "sealos-license"
DefaultLicenseCollection = "license"
DefaultClusterIDCollection = "cluster-id"
)

func New(ctx context.Context, uri string) (*DataBase, error) {
Expand All @@ -46,9 +48,10 @@ func New(ctx context.Context, uri string) (*DataBase, error) {
return nil, err
}
return &DataBase{
URI: uri,
Client: client,
licenseCollection: client.Database(DefaultLicenseDataBase).Collection(DefaultLicenseCollection),
URI: uri,
Client: client,
licenseCollection: client.Database(DefaultLicenseDataBase).Collection(DefaultLicenseCollection),
clusterIDCollection: client.Database(DefaultLicenseDataBase).Collection(DefaultClusterIDCollection),
}, nil
}

Expand All @@ -64,6 +67,26 @@ func (db *DataBase) GetLicenseMeta(ctx context.Context, token string) (*meta.Met
return lic, err
}

func (db *DataBase) GetClusterID(ctx context.Context) (string, error) {
var result struct {
ClusterID string `bson:"cluster-id"`
}
err := db.licenseCollection.FindOne(ctx, bson.M{}).Decode(&result)
if err != nil {
return "", err
}
return result.ClusterID, nil
}

func (db *DataBase) StoreClusterID(ctx context.Context, clusterID string) error {
document := bson.M{"cluster-id": clusterID}
_, err := db.licenseCollection.InsertOne(ctx, document)
if err != nil {
return err
}
return nil
}

func (db *DataBase) Disconnect(ctx context.Context) {
err := db.Client.Disconnect(ctx)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions controllers/license/internal/util/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ import (

var ErrLicenseInvalid = fmt.Errorf("the license provided appears to be invalid")
var ErrClaimsConvent = fmt.Errorf("the claims data provided appears to be invalid")
var ErrClusterIDNotMatch = fmt.Errorf("the cluster id provided appears to be invalid")
11 changes: 10 additions & 1 deletion controllers/license/internal/util/license/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,19 @@ func GetClaims(license *licensev1.License) (*utilclaims.Claims, error) {
return claims, nil
}

func IsLicenseValid(license *licensev1.License) (bool, error) {
func IsLicenseValid(license *licensev1.License, clusterID string) (bool, error) {
token, err := ParseLicenseToken(license)
if err != nil {
return false, err
}
claims, err := GetClaims(license)
if err != nil {
return false, err
}
// if clusterID is empty, it means this license is a super license.
if claims.ClusterID != "" && claims.ClusterID != clusterID {
return false, errors.ErrClusterIDNotMatch
}

return token.Valid, nil
}
2 changes: 1 addition & 1 deletion controllers/license/internal/util/license/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestIsLicenseValid(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := IsLicenseValid(tt.args.license)
got, err := IsLicenseValid(tt.args.license, "")
if (err != nil) != tt.wantErr {
t.Errorf("IsLicenseValid() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down

0 comments on commit 40e33e0

Please sign in to comment.