From 0382bd85f7af622322b55dabb3b3bc22766cf3ca Mon Sep 17 00:00:00 2001 From: Tamal Saha Date: Mon, 13 Jan 2025 14:53:19 +0600 Subject: [PATCH] Add license restrictions Signed-off-by: Tamal Saha --- .../v1alpha1/license_constraints_types.go | 25 ++++++ apis/config/v1alpha1/openapi_generated.go | 35 ++++++++ apis/config/v1alpha1/zz_generated.deepcopy.go | 43 ++++++++++ go.mod | 6 +- go.sum | 12 +-- pkg/controller/controller.go | 2 + pkg/license/lib.go | 83 +++++++++++++++++++ .../apis/licenses/v1alpha1/helper.go | 2 +- .../apis/licenses/v1alpha1/types.go | 65 +++++++++++---- .../v1alpha1/zz_generated.deepcopy.go | 24 +++++- .../license-verifier/lib.go | 4 +- vendor/modules.txt | 6 +- 12 files changed, 277 insertions(+), 30 deletions(-) create mode 100644 apis/config/v1alpha1/license_constraints_types.go create mode 100644 pkg/license/lib.go diff --git a/apis/config/v1alpha1/license_constraints_types.go b/apis/config/v1alpha1/license_constraints_types.go new file mode 100644 index 0000000000..822f692a95 --- /dev/null +++ b/apis/config/v1alpha1/license_constraints_types.go @@ -0,0 +1,25 @@ +/* +Copyright AppsCode Inc. and Contributors + +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 v1alpha1 + +type LicenseRestrictions map[string]Restriction + +type Restriction struct { + VersionConstraint string `json:"versionConstraint"` + // +optional + Distributions []string `json:"distributions,omitempty"` +} diff --git a/apis/config/v1alpha1/openapi_generated.go b/apis/config/v1alpha1/openapi_generated.go index ccffd76862..067acce1e2 100644 --- a/apis/config/v1alpha1/openapi_generated.go +++ b/apis/config/v1alpha1/openapi_generated.go @@ -497,6 +497,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "kubedb.dev/apimachinery/apis/config/v1alpha1.GaleraArbitratorConfiguration": schema_apimachinery_apis_config_v1alpha1_GaleraArbitratorConfiguration(ref), "kubedb.dev/apimachinery/apis/config/v1alpha1.MongoDBConfiguration": schema_apimachinery_apis_config_v1alpha1_MongoDBConfiguration(ref), "kubedb.dev/apimachinery/apis/config/v1alpha1.RedisConfiguration": schema_apimachinery_apis_config_v1alpha1_RedisConfiguration(ref), + "kubedb.dev/apimachinery/apis/config/v1alpha1.Restriction": schema_apimachinery_apis_config_v1alpha1_Restriction(ref), "kubedb.dev/apimachinery/apis/config/v1alpha1.SinglestoreConfiguration": schema_apimachinery_apis_config_v1alpha1_SinglestoreConfiguration(ref), } } @@ -25573,6 +25574,40 @@ func schema_apimachinery_apis_config_v1alpha1_RedisConfiguration(ref common.Refe } } +func schema_apimachinery_apis_config_v1alpha1_Restriction(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "versionConstraint": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "distributions": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"versionConstraint"}, + }, + }, + } +} + func schema_apimachinery_apis_config_v1alpha1_SinglestoreConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/apis/config/v1alpha1/zz_generated.deepcopy.go b/apis/config/v1alpha1/zz_generated.deepcopy.go index 880bee29a2..a9ab84da25 100644 --- a/apis/config/v1alpha1/zz_generated.deepcopy.go +++ b/apis/config/v1alpha1/zz_generated.deepcopy.go @@ -52,6 +52,28 @@ func (in *GaleraArbitratorConfiguration) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in LicenseRestrictions) DeepCopyInto(out *LicenseRestrictions) { + { + in := &in + *out = make(LicenseRestrictions, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LicenseRestrictions. +func (in LicenseRestrictions) DeepCopy() LicenseRestrictions { + if in == nil { + return nil + } + out := new(LicenseRestrictions) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MongoDBConfiguration) DeepCopyInto(out *MongoDBConfiguration) { *out = *in @@ -116,6 +138,27 @@ func (in *RedisConfiguration) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Restriction) DeepCopyInto(out *Restriction) { + *out = *in + if in.Distributions != nil { + in, out := &in.Distributions, &out.Distributions + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Restriction. +func (in *Restriction) DeepCopy() *Restriction { + if in == nil { + return nil + } + out := new(Restriction) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SinglestoreConfiguration) DeepCopyInto(out *SinglestoreConfiguration) { *out = *in diff --git a/go.mod b/go.mod index 725009305c..12bd2a60f8 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.75.2 github.com/prometheus-operator/prometheus-operator/pkg/client v0.75.2 go.bytebuilders.dev/audit v0.0.39 - go.bytebuilders.dev/license-verifier/kubernetes v0.14.4 + go.bytebuilders.dev/license-verifier/kubernetes v0.14.5 gomodules.xyz/encoding v0.0.8 gomodules.xyz/pointer v0.1.0 gomodules.xyz/runtime v0.3.0 @@ -151,8 +151,8 @@ require ( github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.bytebuilders.dev/license-proxyserver v0.0.19 // indirect - go.bytebuilders.dev/license-verifier v0.14.4 // indirect + go.bytebuilders.dev/license-proxyserver v0.0.20 // indirect + go.bytebuilders.dev/license-verifier v0.14.5 // indirect go.etcd.io/etcd/api/v3 v3.5.14 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect go.etcd.io/etcd/client/v3 v3.5.14 // indirect diff --git a/go.sum b/go.sum index 67b8e7997a..9f73a2d431 100644 --- a/go.sum +++ b/go.sum @@ -385,12 +385,12 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.bytebuilders.dev/audit v0.0.39 h1:JNwXIwqFZc+x5uvl0yyR2LdSVtfiLX85ypybKHTrNhA= go.bytebuilders.dev/audit v0.0.39/go.mod h1:XbqHg9V/MldBx6qEsvs/McufBYiBkAg5r/cCfEit94Y= -go.bytebuilders.dev/license-proxyserver v0.0.19 h1:mY7zPDN0JCw2a1UajOuQUQKQKjjm5KBx2CbkT/+a1N8= -go.bytebuilders.dev/license-proxyserver v0.0.19/go.mod h1:B3Ig2Fo1qUollSV9GgfyFK8tXBI0RmUSpP1KFMZ2N7Q= -go.bytebuilders.dev/license-verifier v0.14.4 h1:JwTGQFew4nudwv8Pk3BdfQRts8KfgUQ5xhu138w1wt4= -go.bytebuilders.dev/license-verifier v0.14.4/go.mod h1:LqWXJKee5ofDcCYM6T5WilYlUc4NlKeZz58tHwO8GEs= -go.bytebuilders.dev/license-verifier/kubernetes v0.14.4 h1:NeHq6SuVhRIVaMW2kSXdr8DcuUOg2jVL9rsODIQp9Fc= -go.bytebuilders.dev/license-verifier/kubernetes v0.14.4/go.mod h1:1C7SaOJShC60mIXP1hXBaDWGpb0hrHQ4p4nUEvI6YQY= +go.bytebuilders.dev/license-proxyserver v0.0.20 h1:gzRSwUmX/LSwPVE6T9oy5RLIutU1EeI7hmS+QGsYBY4= +go.bytebuilders.dev/license-proxyserver v0.0.20/go.mod h1:2PJmjMCXncVyeP3fIVQ+hwZnuhmWSTmbcuEMBrFKIac= +go.bytebuilders.dev/license-verifier v0.14.5 h1:4P+eoi7n8QaEUFpM2TtXASmIdg4mjtXbMsvexWr2GWU= +go.bytebuilders.dev/license-verifier v0.14.5/go.mod h1:LqWXJKee5ofDcCYM6T5WilYlUc4NlKeZz58tHwO8GEs= +go.bytebuilders.dev/license-verifier/kubernetes v0.14.5 h1:pB2OkG/rDK8m8IQzS5l8ibJhfZwd/A2JQsbZtTxT0+8= +go.bytebuilders.dev/license-verifier/kubernetes v0.14.5/go.mod h1:yhEnPHRAlQ8t3Ini2K4qFI4dn+zWFCi3cW8GXS0Fj1A= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index fb93270801..5b9fd89371 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -19,6 +19,7 @@ package controller import ( "time" + configapi "kubedb.dev/apimachinery/apis/config/v1alpha1" cs "kubedb.dev/apimachinery/client/clientset/versioned" kubedbinformers "kubedb.dev/apimachinery/client/informers/externalversions" @@ -88,6 +89,7 @@ type Config struct { // Only watch or reconcile objects in this namespace (usually for license reasons) RestrictToNamespace string + LicenseRestrictions configapi.LicenseRestrictions ResyncPeriod time.Duration ReadinessProbeInterval time.Duration MaxNumRequeues int diff --git a/pkg/license/lib.go b/pkg/license/lib.go new file mode 100644 index 0000000000..7cdc5891d4 --- /dev/null +++ b/pkg/license/lib.go @@ -0,0 +1,83 @@ +/* +Copyright AppsCode Inc. and Contributors + +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 license + +import ( + "context" + + catalogapi "kubedb.dev/apimachinery/apis/catalog/v1alpha1" + configapi "kubedb.dev/apimachinery/apis/config/v1alpha1" + + "github.com/Masterminds/semver/v3" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func MeetsLicenseRestrictions(kc client.Client, restrictions configapi.LicenseRestrictions, dbGK schema.GroupKind, dbVersion string) (bool, error) { + if len(restrictions) == 0 { + return true, nil + } + restriction, found := restrictions[dbGK.Kind] + if !found { + return false, nil + } + + var dbv unstructured.Unstructured + dbv.SetGroupVersionKind(catalogapi.SchemeGroupVersion.WithKind(dbGK.Kind + "Version")) + err := kc.Get(context.TODO(), client.ObjectKey{Name: dbVersion}, &dbv) + if err != nil { + return false, err + } + + strVer, ok, err := unstructured.NestedString(dbv.UnstructuredContent(), "spec", "version") + if err != nil || !ok { + return false, err + } + v, err := semver.NewVersion(strVer) + if err != nil { + return false, err + } + + c, err := semver.NewConstraint(restriction.VersionConstraint) + if err != nil { + return false, err + } + if !c.Check(v) { + // write reason ? + return false, nil + } + if len(restriction.Distributions) > 0 { + strDistro, ok, err := unstructured.NestedString(dbv.UnstructuredContent(), "spec", "distribution") + if err != nil || !ok { + return false, err + } + if !contains(restriction.Distributions, strDistro) { + return false, nil + } + } + return true, nil +} + +func contains(list []string, str string) bool { + for _, v := range list { + if v == str { + return true + } + } + return false +} diff --git a/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/helper.go b/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/helper.go index 0221b461ba..c80b2d4017 100644 --- a/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/helper.go +++ b/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/helper.go @@ -17,7 +17,7 @@ limitations under the License. package v1alpha1 func (l License) DisableAnalytics() bool { - return len(l.FeatureFlags) > 0 && l.FeatureFlags["DisableAnalytics"] == "true" + return len(l.FeatureFlags) > 0 && l.FeatureFlags[FeatureDisableAnalytics] == "true" } func (i *License) Less(j *License) bool { diff --git a/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/types.go b/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/types.go index eb8d562172..bb8adf5775 100644 --- a/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/types.go +++ b/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/types.go @@ -17,7 +17,11 @@ limitations under the License. package v1alpha1 import ( + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -26,20 +30,20 @@ import ( type License struct { metav1.TypeMeta `json:",inline,omitempty"` - Data []byte `json:"-"` - Issuer string `json:"issuer,omitempty"` // byte.builders - ProductLine string `json:"productLine,omitempty"` - TierName string `json:"tierName,omitempty"` - PlanName string `json:"planName,omitempty"` - Features []string `json:"features,omitempty"` - FeatureFlags map[string]string `json:"featureFlags,omitempty"` - Clusters []string `json:"clusters,omitempty"` // cluster_id ? - User *User `json:"user,omitempty"` - NotBefore *metav1.Time `json:"notBefore,omitempty"` // start of subscription start - NotAfter *metav1.Time `json:"notAfter,omitempty"` // if set, use this - ID string `json:"id,omitempty"` // license ID - Status LicenseStatus `json:"status"` - Reason string `json:"reason"` + Data []byte `json:"-"` + Issuer string `json:"issuer,omitempty"` // byte.builders + ProductLine string `json:"productLine,omitempty"` + TierName string `json:"tierName,omitempty"` + PlanName string `json:"planName,omitempty"` + Features []string `json:"features,omitempty"` + FeatureFlags FeatureFlags `json:"featureFlags,omitempty"` + Clusters []string `json:"clusters,omitempty"` // cluster_id ? + User *User `json:"user,omitempty"` + NotBefore *metav1.Time `json:"notBefore,omitempty"` // start of subscription start + NotAfter *metav1.Time `json:"notAfter,omitempty"` // if set, use this + ID string `json:"id,omitempty"` // license ID + Status LicenseStatus `json:"status"` + Reason string `json:"reason"` } type User struct { @@ -62,3 +66,36 @@ type Contract struct { StartTimestamp metav1.Time `json:"startTimestamp"` ExpiryTimestamp metav1.Time `json:"expiryTimestamp"` } + +type FeatureFlag string + +const ( + FeatureDisableAnalytics FeatureFlag = "DisableAnalytics" + FeatureRestrictions FeatureFlag = "Restrictions" + FeatureEnableClientBilling FeatureFlag = "EnableClientBilling" +) + +var knownFlags = sets.New[FeatureFlag](FeatureDisableAnalytics, FeatureRestrictions, FeatureEnableClientBilling) + +type FeatureFlags map[FeatureFlag]string + +func (f FeatureFlags) IsValid() error { + var errs []error + for k := range f { + if !knownFlags.Has(k) { + errs = append(errs, fmt.Errorf("unknown feature flag %q", k)) + } + } + return errors.NewAggregate(errs) +} + +func (f FeatureFlags) ToSlice() []string { + if len(f) == 0 { + return nil + } + result := make([]string, 0, len(f)) + for k, v := range f { + result = append(result, fmt.Sprintf("%s=%s", k, v)) + } + return result +} diff --git a/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/zz_generated.deepcopy.go b/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/zz_generated.deepcopy.go index a191107c0f..9ba4b716c1 100644 --- a/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/zz_generated.deepcopy.go +++ b/vendor/go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1/zz_generated.deepcopy.go @@ -43,6 +43,28 @@ func (in *Contract) DeepCopy() *Contract { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in FeatureFlags) DeepCopyInto(out *FeatureFlags) { + { + in := &in + *out = make(FeatureFlags, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureFlags. +func (in FeatureFlags) DeepCopy() FeatureFlags { + if in == nil { + return nil + } + out := new(FeatureFlags) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *License) DeepCopyInto(out *License) { *out = *in @@ -59,7 +81,7 @@ func (in *License) DeepCopyInto(out *License) { } if in.FeatureFlags != nil { in, out := &in.FeatureFlags, &out.FeatureFlags - *out = make(map[string]string, len(*in)) + *out = make(FeatureFlags, len(*in)) for key, val := range *in { (*out)[key] = val } diff --git a/vendor/go.bytebuilders.dev/license-verifier/lib.go b/vendor/go.bytebuilders.dev/license-verifier/lib.go index 5f8c107ce8..34c62fd3de 100644 --- a/vendor/go.bytebuilders.dev/license-verifier/lib.go +++ b/vendor/go.bytebuilders.dev/license-verifier/lib.go @@ -115,11 +115,11 @@ func ParseLicense(opts ParserOptions) (v1alpha1.License, error) { license.TierName = parts[1] } } - license.FeatureFlags = map[string]string{} + license.FeatureFlags = v1alpha1.FeatureFlags{} for _, ff := range cert.Subject.Locality { parts := strings.SplitN(ff, "=", 2) if len(parts) == 2 { - license.FeatureFlags[parts[0]] = parts[1] + license.FeatureFlags[v1alpha1.FeatureFlag(parts[0])] = parts[1] } } diff --git a/vendor/modules.txt b/vendor/modules.txt index b16039779f..8c3c51d130 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -572,20 +572,20 @@ github.com/zeebo/xxh3 ## explicit; go 1.22.1 go.bytebuilders.dev/audit/api/v1 go.bytebuilders.dev/audit/lib -# go.bytebuilders.dev/license-proxyserver v0.0.19 +# go.bytebuilders.dev/license-proxyserver v0.0.20 ## explicit; go 1.22.0 go.bytebuilders.dev/license-proxyserver/apis/proxyserver go.bytebuilders.dev/license-proxyserver/apis/proxyserver/v1alpha1 go.bytebuilders.dev/license-proxyserver/client/clientset/versioned go.bytebuilders.dev/license-proxyserver/client/clientset/versioned/scheme go.bytebuilders.dev/license-proxyserver/client/clientset/versioned/typed/proxyserver/v1alpha1 -# go.bytebuilders.dev/license-verifier v0.14.4 +# go.bytebuilders.dev/license-verifier v0.14.5 ## explicit; go 1.21 go.bytebuilders.dev/license-verifier go.bytebuilders.dev/license-verifier/apis/licenses go.bytebuilders.dev/license-verifier/apis/licenses/v1alpha1 go.bytebuilders.dev/license-verifier/info -# go.bytebuilders.dev/license-verifier/kubernetes v0.14.4 +# go.bytebuilders.dev/license-verifier/kubernetes v0.14.5 ## explicit; go 1.22.0 go.bytebuilders.dev/license-verifier/kubernetes # go.etcd.io/etcd/api/v3 v3.5.14