From 987ef11e3d0d68e1c055e8edf36a857657deac81 Mon Sep 17 00:00:00 2001 From: panktishah26 Date: Mon, 3 Feb 2025 22:23:02 -0800 Subject: [PATCH] Validation around licenseKey field for extended kubernetes version support --- controllers/cluster_controller_test.go | 38 +++------- controllers/cluster_controller_test_test.go | 35 ++------- go.mod | 1 + go.sum | 3 + pkg/constants/constants.go | 2 + pkg/signature/manifest.go | 57 ++++++++++++--- pkg/signature/manifest_test.go | 32 +++++++- pkg/validations/extendedversion.go | 81 ++++++++++++++++++++- pkg/validations/extendedversion_test.go | 70 +++++++++++++----- 9 files changed, 232 insertions(+), 87 deletions(-) diff --git a/controllers/cluster_controller_test.go b/controllers/cluster_controller_test.go index 8fdf5333012ae..dae51dfc6fa68 100644 --- a/controllers/cluster_controller_test.go +++ b/controllers/cluster_controller_test.go @@ -151,7 +151,8 @@ func TestClusterReconcilerReconcileSelfManagedCluster(t *testing.T) { Name: "my-management-cluster", }, Spec: anywherev1.ClusterSpec{ - EksaVersion: &version, + KubernetesVersion: anywherev1.Kube132, + EksaVersion: &version, ClusterNetwork: anywherev1.ClusterNetwork{ CNIConfig: &anywherev1.CNIConfig{ Cilium: &anywherev1.CiliumConfig{}, @@ -1082,6 +1083,7 @@ func TestClusterReconcilerReconcileSelfManagedClusterRegAuthFailNoSecret(t *test Name: "my-management-cluster", }, Spec: anywherev1.ClusterSpec{ + KubernetesVersion: anywherev1.Kube132, ClusterNetwork: anywherev1.ClusterNetwork{ CNIConfig: &anywherev1.CNIConfig{ Cilium: &anywherev1.CiliumConfig{}, @@ -1252,7 +1254,7 @@ func TestClusterReconcilerSkipDontInstallPackagesOnSelfManaged(t *testing.T) { Namespace: "default", }, Spec: anywherev1.ClusterSpec{ - KubernetesVersion: "v1.25", + KubernetesVersion: anywherev1.Kube132, ClusterNetwork: anywherev1.ClusterNetwork{ CNIConfig: &anywherev1.CNIConfig{ Cilium: &anywherev1.CiliumConfig{}, @@ -1402,7 +1404,7 @@ func TestClusterReconcilerPackagesInstall(s *testing.T) { Namespace: "default", }, Spec: anywherev1.ClusterSpec{ - KubernetesVersion: "v1.25", + KubernetesVersion: anywherev1.Kube132, ClusterNetwork: anywherev1.ClusterNetwork{ CNIConfig: &anywherev1.CNIConfig{ Cilium: &anywherev1.CiliumConfig{}, @@ -1667,17 +1669,17 @@ func createBundle() *releasev1.Bundles { Name: "bundles-1", Namespace: "default", Annotations: map[string]string{ - constants.SignatureAnnotation: "MEUCIQDbVAB+yy+pdCOFet/vWMoHQA2FYiiQtq1zltBRRhRo2QIgGQopCHraD/HpvpSh4Q7rVdesXeVriJv2ucEnoidoZlg=", + constants.SignatureAnnotation: "MEQCIG8DZfnqQtx1fF5x2assfSUEvuJ9BqaCN8jaoBHxKU8SAiBwR2B/T2BC3nzmnT2uEvwyemOy+A7V/K+PkGuKGX0E1Q==", }, }, Spec: releasev1.BundlesSpec{ VersionsBundles: []releasev1.VersionsBundle{ { - KubeVersion: "1.30", + KubeVersion: "1.32", EksD: releasev1.EksDRelease{ Name: "test", EksDReleaseUrl: "testdata/release.yaml", - KubeVersion: "1.30", + KubeVersion: "1.32", }, CertManager: releasev1.CertManagerBundle{}, ClusterAPI: releasev1.CoreClusterAPI{}, @@ -1693,6 +1695,7 @@ func createBundle() *releasev1.Bundles { ExternalEtcdBootstrap: releasev1.EtcdadmBootstrapBundle{}, ExternalEtcdController: releasev1.EtcdadmControllerBundle{}, Tinkerbell: releasev1.TinkerbellBundle{}, + EndOfStandardSupport: "2030-06-30", }, }, }, @@ -1745,7 +1748,7 @@ func vsphereCluster() *anywherev1.Cluster { Kind: "VSphereDatacenterConfig", Name: "datacenter", }, - KubernetesVersion: "1.20", + KubernetesVersion: anywherev1.Kube132, ControlPlaneConfiguration: anywherev1.ControlPlaneConfiguration{ Count: 1, Endpoint: &anywherev1.Endpoint{ @@ -1864,26 +1867,7 @@ func baseTestVsphereCluster() (*cluster.Config, *releasev1.Bundles) { config.AWSIAMConfigs[awsIAM.Name] = awsIAM config.OIDCConfigs[oidc.Name] = oidc - bundles := &releasev1.Bundles{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-bundles-ref", - Namespace: config.Cluster.Namespace, - Annotations: map[string]string{ - constants.SignatureAnnotation: "MEYCIQDA40Bizd/0mdCwRCIKq10gjLdJMT0s0y57RPW/zOyWZwIhALPOFS+NZZ7QCwI7wiC1TiArMHMq4TbzIJcx85H/zjU4", - }, - }, - Spec: releasev1.BundlesSpec{ - VersionsBundles: []releasev1.VersionsBundle{ - { - KubeVersion: "v1.30", - PackageController: releasev1.PackageBundle{ - HelmChart: releasev1.Image{}, - }, - }, - }, - }, - } - + bundles := createBundle() config.Cluster.Spec.BundlesRef = &anywherev1.BundlesRef{ Name: bundles.Name, Namespace: bundles.Namespace, diff --git a/controllers/cluster_controller_test_test.go b/controllers/cluster_controller_test_test.go index 73c00e4cf6071..4f87a0d2ced6d 100644 --- a/controllers/cluster_controller_test_test.go +++ b/controllers/cluster_controller_test_test.go @@ -27,7 +27,6 @@ import ( "github.com/aws/eks-anywhere/pkg/controller" "github.com/aws/eks-anywhere/pkg/controller/clientutil" "github.com/aws/eks-anywhere/pkg/controller/clusters" - "github.com/aws/eks-anywhere/release/api/v1alpha1" ) func TestClusterReconcilerEnsureOwnerReferences(t *testing.T) { @@ -54,7 +53,7 @@ func TestClusterReconcilerEnsureOwnerReferences(t *testing.T) { Namespace: "default", }, Spec: anywherev1.ClusterSpec{ - KubernetesVersion: "v1.25", + KubernetesVersion: anywherev1.Kube132, EksaVersion: &version, }, Status: anywherev1.ClusterStatus{ @@ -244,8 +243,8 @@ func TestClusterReconcilerSetBundlesRef(t *testing.T) { }, Spec: anywherev1.ClusterSpec{ BundlesRef: &anywherev1.BundlesRef{ - Name: "my-bundles-ref", - Namespace: "my-namespace", + Name: "bundles-1", + Namespace: "default", }, }, Status: anywherev1.ClusterStatus{ @@ -258,10 +257,10 @@ func TestClusterReconcilerSetBundlesRef(t *testing.T) { Name: "my-cluster", }, Spec: anywherev1.ClusterSpec{ - KubernetesVersion: "v1.25", + KubernetesVersion: anywherev1.Kube132, BundlesRef: &anywherev1.BundlesRef{ - Name: "my-bundles-ref", - Namespace: "my-namespace", + Name: "bundles-1", + Namespace: "default", }, }, Status: anywherev1.ClusterStatus{ @@ -275,25 +274,7 @@ func TestClusterReconcilerSetBundlesRef(t *testing.T) { Namespace: constants.EksaSystemNamespace, }, } - bundles := &v1alpha1.Bundles{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-bundles-ref", - Namespace: cluster.Spec.BundlesRef.Namespace, - Annotations: map[string]string{ - constants.SignatureAnnotation: "MEYCIQDA40Bizd/0mdCwRCIKq10gjLdJMT0s0y57RPW/zOyWZwIhALPOFS+NZZ7QCwI7wiC1TiArMHMq4TbzIJcx85H/zjU4", - }, - }, - Spec: v1alpha1.BundlesSpec{ - VersionsBundles: []v1alpha1.VersionsBundle{ - { - KubeVersion: "v1.30", - PackageController: v1alpha1.PackageBundle{ - HelmChart: v1alpha1.Image{}, - }, - }, - }, - }, - } + bundles := createBundle() objs := []runtime.Object{cluster, managementCluster, secret, bundles} cb := fake.NewClientBuilder() @@ -347,7 +328,7 @@ func TestClusterReconcilerSetDefaultEksaVersion(t *testing.T) { Namespace: "default", }, Spec: anywherev1.ClusterSpec{ - KubernetesVersion: "v1.25", + KubernetesVersion: anywherev1.Kube132, }, Status: anywherev1.ClusterStatus{ ReconciledGeneration: 1, diff --git a/go.mod b/go.mod index b56dcd41c9173..3fbaf433365df 100644 --- a/go.mod +++ b/go.mod @@ -124,6 +124,7 @@ require ( github.com/go-openapi/swag v0.22.3 // indirect github.com/gobuffalo/flect v1.0.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect diff --git a/go.sum b/go.sum index 3ec736dc5680c..6b4355d8ac177 100644 --- a/go.sum +++ b/go.sum @@ -437,7 +437,10 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 9c184cc0d60da..d91c55d2a81b1 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -122,6 +122,8 @@ const ( Excludes = "LnNwZWMudmVyc2lvbnNCdW5kbGVzW10uYm9vdHN0cmFwCi5zcGVjLnZlcnNpb25zQnVuZGxlc1tdLmJvdHRsZXJvY2tldEhvc3RDb250YWluZXJzCi5zcGVjLnZlcnNpb25zQnVuZGxlc1tdLmNlcnRNYW5hZ2VyCi5zcGVjLnZlcnNpb25zQnVuZGxlc1tdLmNpbGl1bQouc3BlYy52ZXJzaW9uc0J1bmRsZXNbXS5jbG91ZFN0YWNrCi5zcGVjLnZlcnNpb25zQnVuZGxlc1tdLmNsdXN0ZXJBUEkKLnNwZWMudmVyc2lvbnNCdW5kbGVzW10uY29udHJvbFBsYW5lCi5zcGVjLnZlcnNpb25zQnVuZGxlc1tdLmRvY2tlcgouc3BlYy52ZXJzaW9uc0J1bmRsZXNbXS5la3NhCi5zcGVjLnZlcnNpb25zQnVuZGxlc1tdLmV0Y2RhZG1Cb290c3RyYXAKLnNwZWMudmVyc2lvbnNCdW5kbGVzW10uZXRjZGFkbUNvbnRyb2xsZXIKLnNwZWMudmVyc2lvbnNCdW5kbGVzW10uZmx1eAouc3BlYy52ZXJzaW9uc0J1bmRsZXNbXS5oYXByb3h5Ci5zcGVjLnZlcnNpb25zQnVuZGxlc1tdLmtpbmRuZXRkCi5zcGVjLnZlcnNpb25zQnVuZGxlc1tdLm51dGFuaXgKLnNwZWMudmVyc2lvbnNCdW5kbGVzW10ucGFja2FnZUNvbnRyb2xsZXIKLnNwZWMudmVyc2lvbnNCdW5kbGVzW10uc25vdwouc3BlYy52ZXJzaW9uc0J1bmRsZXNbXS50aW5rZXJiZWxsCi5zcGVjLnZlcnNpb25zQnVuZGxlc1tdLnVwZ3JhZGVyCi5zcGVjLnZlcnNpb25zQnVuZGxlc1tdLnZTcGhlcmU=" // KMSPublicKey to verify bundle signature. KMSPublicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFZU/Z6VVMU9HioT7rGkPdJg3frC2xyQZhWFIrz5HeZEfeQ2nAdnJMLrs2Qr3V9xVrJrHA54wnIHDoPGbEhojqg==" + // LincesePublicKey is to verify the licenseKey field. + LincesePublicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE96Xb67YUq+at8gFOioYlf1kxOIPio7i3Y8sFrG3a3sn/MzqQmTO9K82psqOuN+E4NdE8VajOtbyfcLo+Ojax/w==" ) type Operation int diff --git a/pkg/signature/manifest.go b/pkg/signature/manifest.go index e0f8434b294a5..bcae38316337e 100644 --- a/pkg/signature/manifest.go +++ b/pkg/signature/manifest.go @@ -26,6 +26,7 @@ import ( "strings" "text/template" + "github.com/golang-jwt/jwt/v5" "github.com/itchyny/gojq" "sigs.k8s.io/yaml" @@ -50,19 +51,9 @@ func ValidateSignature(bundle *anywherev1alpha1.Bundles, pubKey string) (valid b return false, fmt.Errorf("signature in metadata isn't base64 encoded: %w", err) } - pubdecoded, err := base64.StdEncoding.DecodeString(pubKey) + pubkey, err := parsePublicKey(pubKey) if err != nil { - return false, fmt.Errorf("decoding the public key as string: %w", err) - } - - pubparsed, err := x509.ParsePKIXPublicKey(pubdecoded) - if err != nil { - return false, fmt.Errorf("parsing the public key (not PKIX): %w", err) - } - - pubkey, ok := pubparsed.(*ecdsa.PublicKey) - if !ok { - return false, fmt.Errorf("parsing the public key (not ECDSA): %T", pubparsed) + return false, err } return ecdsa.VerifyASN1(pubkey, digest[:], sig), nil @@ -162,3 +153,45 @@ func filterExcludes(jsonBytes []byte) ([]byte, error) { } return filtered, nil } + +func parsePublicKey(key string) (*ecdsa.PublicKey, error) { + pubdecoded, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return nil, fmt.Errorf("decoding the public key as string: %w", err) + } + + pubparsed, err := x509.ParsePKIXPublicKey(pubdecoded) + if err != nil { + return nil, fmt.Errorf("parsing the public key (not PKIX): %w", err) + } + + pubkey, ok := pubparsed.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("parsing the public key (not ECDSA): %T", pubparsed) + } + return pubkey, nil +} + +// ParseLicense parses licenseKey jwt token using the public key and returns token fields. +func ParseLicense(licenseToken string, key string) (*jwt.Token, error) { + tokenKey, err := parsePublicKey(key) + if err != nil { + return nil, err + } + + token, err := jwt.Parse(licenseToken, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodECDSA); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) + } + return tokenKey, nil + }) + if err != nil { + return nil, fmt.Errorf("parsing license token: %w", err) + } + + if !token.Valid { + return nil, errors.New("licenseKey is not valid") + } + + return token, nil +} diff --git a/pkg/signature/manifest_test.go b/pkg/signature/manifest_test.go index 05016abea91c1..adafeaedb62a3 100644 --- a/pkg/signature/manifest_test.go +++ b/pkg/signature/manifest_test.go @@ -157,7 +157,6 @@ func TestValidateSignature(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(_ *testing.T) { valid, err := ValidateSignature(tc.bundle, tc.publicKey) - fmt.Println(err) if err != nil && !strings.Contains(err.Error(), tc.wantErr.Error()) { t.Errorf("%v got = %v, \nwant %v", tc.name, err, tc.wantErr) } @@ -336,3 +335,34 @@ func TestFilterExcludes(t *testing.T) { }) } } + +func TestParseLicense(t *testing.T) { + tests := []struct { + name string + licenseKey string + key string + wantErr error + }{ + { + name: "invalid signing method", + licenseKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbmRWYWxpZGl0eSI6IjIwMjUtMDItMDNUMTI6MDA6MDBaIn0.XdQhB8DBE2m5Ufw6R4DCadp8D6ZLNkdfbCwOWMgSxB4", + key: constants.LincesePublicKey, + wantErr: fmt.Errorf("unexpected signing method"), + }, + { + name: "malformed token", + licenseKey: "invalid.token.string", + key: constants.LincesePublicKey, + wantErr: fmt.Errorf("parsing license token"), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := ParseLicense(tc.licenseKey, tc.key) + if err != nil && !strings.Contains(err.Error(), tc.wantErr.Error()) { + t.Errorf("%v got = %v, \nwant %v", tc.name, err, tc.wantErr) + } + }) + } +} diff --git a/pkg/validations/extendedversion.go b/pkg/validations/extendedversion.go index f027033161e41..5ed4b97ef9a0e 100644 --- a/pkg/validations/extendedversion.go +++ b/pkg/validations/extendedversion.go @@ -2,21 +2,43 @@ package validations import ( "context" + "errors" "fmt" + "time" + + "github.com/golang-jwt/jwt/v5" anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/clients/kubernetes" + "github.com/aws/eks-anywhere/pkg/cluster" "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/signature" "github.com/aws/eks-anywhere/release/api/v1alpha1" ) // ValidateExtendedK8sVersionSupport validates all the validations needed for the support of extended kubernetes support. -func ValidateExtendedK8sVersionSupport(_ context.Context, _ *anywherev1.Cluster, bundle *v1alpha1.Bundles, _ kubernetes.Client) error { +func ValidateExtendedK8sVersionSupport(_ context.Context, clusterSpec *anywherev1.Cluster, bundle *v1alpha1.Bundles, _ kubernetes.Client) error { // Validate EKS-A bundle has not been modified by verifying the signature in the bundle annotation if err := validateBundleSignature(bundle); err != nil { return fmt.Errorf("validating bundle signature: %w", err) } + + // Check whether the kubernetes version for the cluster is currently under extended support by comparing the endOfStandardSupport date from the bundle + // with the current date. If the current date is after the endOfStandardSupport date, + // it implies that the kubernetes version is still under standard support so skip performing any of the subsequent license validations. Otherwise, run the subsequent license validations + isExtended, err := isExtendedSupport(clusterSpec, bundle) + if err != nil { + return err + } + if isExtended { + token, err := getLicense(clusterSpec) + if err != nil { + return fmt.Errorf("getting licenseKey: %w", err) + } + if err = validateLicense(token); err != nil { + return fmt.Errorf("validating license: %w", err) + } + } return nil } @@ -31,3 +53,60 @@ func validateBundleSignature(bundle *v1alpha1.Bundles) error { } return nil } + +func isExtendedSupport(clusterSpec *anywherev1.Cluster, bundle *v1alpha1.Bundles) (bool, error) { + versionsBundle, err := cluster.GetVersionsBundle(clusterSpec.Spec.KubernetesVersion, bundle) + if err != nil { + return false, fmt.Errorf("getting versions bundle for %s kubernetes version: %w", clusterSpec.Spec.KubernetesVersion, err) + } + + endOfStandardSupport, err := parseDate("2006-01-02", versionsBundle.EndOfStandardSupport) + if err != nil { + return false, fmt.Errorf("parsing EndOfStandardSupport field format: %w", err) + } + + return isPastDateThanToday(endOfStandardSupport), nil +} + +func getLicense(clusterSpec *anywherev1.Cluster) (*jwt.Token, error) { + if clusterSpec.Spec.LicenseToken == "" { + return nil, fmt.Errorf("license token is required for extended kubernetes support") + } + token, err := signature.ParseLicense(clusterSpec.Spec.LicenseToken, constants.LincesePublicKey) + if err != nil { + return nil, fmt.Errorf("parsing license: %w", err) + } + + return token, nil +} + +func validateLicense(token *jwt.Token) error { + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return errors.New("could not parse the licenseKey claims") + } + endValidity, ok := claims["endValidity"].(string) + if !ok { + return errors.New("license validity field missing, not a valid license") + } + + validity, err := parseDate(time.RFC3339, endValidity) + if err != nil { + return fmt.Errorf("parsing endValidity field from licenseKey: %w", err) + } + + if isPastDateThanToday(validity) { + return errors.New("license is expired, please renew the license") + } + + return nil +} + +func isPastDateThanToday(dateToCompare time.Time) bool { + today := time.Now().Truncate(24 * time.Hour) + return dateToCompare.Before(today) +} + +func parseDate(format string, date string) (time.Time, error) { + return time.Parse(format, date) +} diff --git a/pkg/validations/extendedversion_test.go b/pkg/validations/extendedversion_test.go index 4aa94e307423b..5791a3def92ac 100644 --- a/pkg/validations/extendedversion_test.go +++ b/pkg/validations/extendedversion_test.go @@ -19,6 +19,7 @@ import ( func TestValidateExtendedK8sVersionSupport(t *testing.T) { ctx := context.Background() client := test.NewFakeKubeClient() + tests := []struct { name string cluster *anywherev1.Cluster @@ -27,7 +28,7 @@ func TestValidateExtendedK8sVersionSupport(t *testing.T) { wantErr error }{ { - name: "No bundle signature", + name: "no bundle signature", cluster: &anywherev1.Cluster{}, bundle: &v1alpha1.Bundles{ ObjectMeta: v1.ObjectMeta{ @@ -39,28 +40,36 @@ func TestValidateExtendedK8sVersionSupport(t *testing.T) { wantErr: fmt.Errorf("missing signature annotation"), }, { - name: "bundle verification succeeded", - cluster: &anywherev1.Cluster{}, - bundle: &v1alpha1.Bundles{ - TypeMeta: v1.TypeMeta{ - Kind: "Bundles", - APIVersion: v1alpha1.GroupVersion.String(), + name: "kubernetes version not supported", + cluster: &anywherev1.Cluster{ + Spec: anywherev1.ClusterSpec{ + KubernetesVersion: "1.22", }, - ObjectMeta: v1.ObjectMeta{ - Annotations: map[string]string{ - constants.SignatureAnnotation: "MEYCIQCiWwxw/Nchkgtan47FzagXHgB45Op7YWxvSZjFzHau8wIhALG2kbm+H8HJEfN/rUQ0ldo298MnzyhukBptUm0jCtZZ", - }, + }, + bundle: validBundle(), + wantErr: fmt.Errorf("getting versions bundle for 1.22 kubernetes version"), + }, + { + name: "missing license token", + cluster: &anywherev1.Cluster{ + Spec: anywherev1.ClusterSpec{ + KubernetesVersion: "1.28", + LicenseToken: "", }, - Spec: v1alpha1.BundlesSpec{ - Number: 1, - VersionsBundles: []v1alpha1.VersionsBundle{ - { - KubeVersion: "1.31", - }, - }, + }, + bundle: validBundle(), + wantErr: fmt.Errorf("license token is required for extended kubernetes support"), + }, + { + name: "invalid licenseKey", + cluster: &anywherev1.Cluster{ + Spec: anywherev1.ClusterSpec{ + KubernetesVersion: "1.28", + LicenseToken: "invalid-token", }, }, - wantErr: fmt.Errorf("missing signature annotation"), + bundle: validBundle(), + wantErr: fmt.Errorf("getting licenseKey"), }, } @@ -73,3 +82,26 @@ func TestValidateExtendedK8sVersionSupport(t *testing.T) { }) } } + +func validBundle() *v1alpha1.Bundles { + return &v1alpha1.Bundles{ + TypeMeta: v1.TypeMeta{ + Kind: "Bundles", + APIVersion: v1alpha1.GroupVersion.String(), + }, + ObjectMeta: v1.ObjectMeta{ + Annotations: map[string]string{ + constants.SignatureAnnotation: "MEYCIQC8Fuo81dxibtkvrOFZpbFXZGmJnhLN6bkJjx4YB0fGIQIhAJIxIAl3s26eXqcmS6kAyjDd0NXDlBbM0d/GCHcL2Xoo", + }, + }, + Spec: v1alpha1.BundlesSpec{ + Number: 1, + VersionsBundles: []v1alpha1.VersionsBundle{ + { + KubeVersion: "1.28", + EndOfStandardSupport: "2024-12-31", + }, + }, + }, + } +}