Skip to content

Commit

Permalink
Validate LicenseToken field is not used by other clusters (#9222)
Browse files Browse the repository at this point in the history
  • Loading branch information
panktishah26 authored Feb 6, 2025
1 parent d4928f0 commit 30fa933
Show file tree
Hide file tree
Showing 12 changed files with 273 additions and 23 deletions.
21 changes: 20 additions & 1 deletion pkg/clients/kubernetes/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,26 @@ type Reader interface {

// List retrieves list of objects. On a successful call, Items field
// in the list will be populated with the result returned from the server.
List(ctx context.Context, list ObjectList) error
List(ctx context.Context, list ObjectList, opts ...ListOption) error
}

// ListOption is some configuration that modifies options for an apply request.
type ListOption interface {
ApplyToList(*ListOptions)
}

// ListOptions contains options for listing object.
type ListOptions struct {
// Namespace represents the namespace to list for, or empty for
// non-namespaced objects, or to list across all namespaces.
Namespace string
}

// ApplyToList implements ApplyToList.
func (o ListOptions) ApplyToList(do *ListOptions) {
if o.Namespace != "" {
do.Namespace = o.Namespace
}
}

// Writer knows how to create, delete, and update Kubernetes objects.
Expand Down
24 changes: 24 additions & 0 deletions pkg/clients/kubernetes/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,30 @@ import (
"github.com/aws/eks-anywhere/pkg/clients/kubernetes"
)

func TestListOptionsApplyToList(t *testing.T) {
tests := []struct {
name string
option, want *kubernetes.ListOptions
}{
{
name: "with namespace",
option: &kubernetes.ListOptions{
Namespace: "test",
},
want: &kubernetes.ListOptions{
Namespace: "test",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
tt.option.ApplyToList(tt.option)
g.Expect(tt.option).To(BeComparableTo(tt.want))
})
}
}

func TestDeleteAllOfOptionsApplyToDeleteAllOf(t *testing.T) {
tests := []struct {
name string
Expand Down
2 changes: 1 addition & 1 deletion pkg/clients/kubernetes/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (c *KubeconfigClient) Get(ctx context.Context, name, namespace string, obj

// List retrieves list of objects. On a successful call, Items field
// in the list will be populated with the result returned from the server.
func (c *KubeconfigClient) List(ctx context.Context, list ObjectList) error {
func (c *KubeconfigClient) List(ctx context.Context, list ObjectList, _ ...ListOption) error {
return c.client.List(ctx, c.kubeconfig, list)
}

Expand Down
61 changes: 53 additions & 8 deletions pkg/clients/kubernetes/mocks/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/clients/kubernetes/unauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (c *UnAuthClient) ApplyServerSide(ctx context.Context, kubeconfig, fieldMan

// List retrieves list of objects. On a successful call, Items field
// in the list will be populated with the result returned from the server.
func (c *UnAuthClient) List(ctx context.Context, kubeconfig string, list ObjectList) error {
func (c *UnAuthClient) List(ctx context.Context, kubeconfig string, list ObjectList, _ ...ListOption) error {
resourceType, err := c.resourceTypeForObj(list)
if err != nil {
return fmt.Errorf("getting kubernetes resource: %v", err)
Expand Down
12 changes: 10 additions & 2 deletions pkg/controller/clientutil/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,16 @@ func (c *KubeClient) Get(ctx context.Context, name, namespace string, obj kubern

// List retrieves list of objects. On a successful call, Items field
// in the list will be populated with the result returned from the server.
func (c *KubeClient) List(ctx context.Context, list kubernetes.ObjectList) error {
return c.client.List(ctx, list)
func (c *KubeClient) List(ctx context.Context, list kubernetes.ObjectList, opts ...kubernetes.ListOption) error {
o := &kubernetes.ListOptions{}
for _, opt := range opts {
opt.ApplyToList(o)
}

clientOptions := &client.ListOptions{}
clientOptions.Namespace = o.Namespace

return c.client.List(ctx, list, clientOptions)
}

// Create saves the object obj in the Kubernetes cluster.
Expand Down
67 changes: 67 additions & 0 deletions pkg/controller/clientutil/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,73 @@ func TestKubeClientList(t *testing.T) {
g.Expect(receiveClusters.Items).To(ConsistOf(*cluster1, *cluster2))
}

func TestKubeClientListOpts(t *testing.T) {
g := NewWithT(t)
ctx := context.Background()
cluster1 := &anywherev1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster",
Namespace: "default",
},
TypeMeta: metav1.TypeMeta{
Kind: anywherev1.ClusterKind,
APIVersion: anywherev1.GroupVersion.String(),
},
}
cluster2 := &anywherev1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-2",
Namespace: "eksa-system",
},
TypeMeta: metav1.TypeMeta{
Kind: anywherev1.ClusterKind,
APIVersion: anywherev1.GroupVersion.String(),
},
}
cb := fake.NewClientBuilder()
cl := cb.WithRuntimeObjects(cluster1, cluster2).Build()

client := clientutil.NewKubeClient(cl)
receiveClusters := &anywherev1.ClusterList{}
opts := kubernetes.ListOptions{}
g.Expect(client.List(ctx, receiveClusters, opts)).To(Succeed())
g.Expect(receiveClusters.Items).To(ConsistOf(*cluster1, *cluster2))
}

func TestKubeClientListOptsWithNamespace(t *testing.T) {
g := NewWithT(t)
ctx := context.Background()
cluster1 := &anywherev1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster",
Namespace: "default",
},
TypeMeta: metav1.TypeMeta{
Kind: anywherev1.ClusterKind,
APIVersion: anywherev1.GroupVersion.String(),
},
}
cluster2 := &anywherev1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-2",
Namespace: "eksa-system",
},
TypeMeta: metav1.TypeMeta{
Kind: anywherev1.ClusterKind,
APIVersion: anywherev1.GroupVersion.String(),
},
}
cb := fake.NewClientBuilder()
cl := cb.WithRuntimeObjects(cluster1, cluster2).Build()

client := clientutil.NewKubeClient(cl)
receiveClusters := &anywherev1.ClusterList{}
opts := kubernetes.ListOptions{Namespace: "eksa-system"}
g.Expect(client.List(ctx, receiveClusters, opts)).To(Succeed())
g.Expect(receiveClusters.Items).To(ConsistOf(*cluster2))
g.Expect(receiveClusters.Items).NotTo(ConsistOf(*cluster1))
}

func TestKubeClientCreate(t *testing.T) {
g := NewWithT(t)
ctx := context.Background()
Expand Down
19 changes: 18 additions & 1 deletion pkg/validations/extendedversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
)

// ValidateExtendedK8sVersionSupport validates all the validations needed for the support of extended kubernetes support.
func ValidateExtendedK8sVersionSupport(_ context.Context, clusterSpec anywherev1.Cluster, bundle *v1alpha1.Bundles, _ kubernetes.Client) error {
func ValidateExtendedK8sVersionSupport(ctx context.Context, clusterSpec anywherev1.Cluster, bundle *v1alpha1.Bundles, k 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)
Expand All @@ -36,6 +36,9 @@ func ValidateExtendedK8sVersionSupport(_ context.Context, clusterSpec anywherev1
if err = validateLicense(token); err != nil {
return fmt.Errorf("validating licenseToken: %w", err)
}
if err := validateLicenseKeyIsUnique(ctx, clusterSpec.Name, clusterSpec.Spec.LicenseToken, k); err != nil {
return fmt.Errorf("validating licenseToken is unique for cluster %s: %w", clusterSpec.Name, err)
}
}
return nil
}
Expand Down Expand Up @@ -104,3 +107,17 @@ func isPastDateThanToday(dateToCompare time.Time) bool {
today := time.Now().Truncate(24 * time.Hour)
return dateToCompare.Before(today)
}

func validateLicenseKeyIsUnique(ctx context.Context, clusterName string, licenseToken string, k kubernetes.Client) error {
eksaClusters := &anywherev1.ClusterList{}
err := k.List(ctx, eksaClusters, kubernetes.ListOptions{})
if err != nil {
return fmt.Errorf("listing all EKS-A clusters: %w", err)
}
for _, eksaCluster := range eksaClusters.Items {
if eksaCluster.Name != clusterName && eksaCluster.Spec.LicenseToken == licenseToken {
return fmt.Errorf("license key %s is already in use by cluster %s", licenseToken, eksaCluster.Name)
}
}
return nil
}
71 changes: 68 additions & 3 deletions pkg/validations/extendedversion_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package validations_test
package validations

import (
"context"
Expand All @@ -7,12 +7,13 @@ import (
"testing"

v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

"github.com/aws/eks-anywhere/internal/test"
anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1"
"github.com/aws/eks-anywhere/pkg/clients/kubernetes"
"github.com/aws/eks-anywhere/pkg/constants"
"github.com/aws/eks-anywhere/pkg/validations"
"github.com/aws/eks-anywhere/pkg/controller/clientutil"
"github.com/aws/eks-anywhere/release/api/v1alpha1"
)

Expand Down Expand Up @@ -104,7 +105,71 @@ func TestValidateExtendedK8sVersionSupport(t *testing.T) {

for _, tc := range tests {
t.Run(tc.name, func(_ *testing.T) {
err := validations.ValidateExtendedK8sVersionSupport(ctx, tc.cluster, tc.bundle, client)
err := ValidateExtendedK8sVersionSupport(ctx, tc.cluster, tc.bundle, client)
if err != nil && !strings.Contains(err.Error(), tc.wantErr.Error()) {
t.Errorf("%v got = %v, \nwant %v", tc.name, err, tc.wantErr)
}
})
}
}

func TestValidateLicenseKeyIsUnique(t *testing.T) {
ctx := context.Background()

tests := []struct {
name string
cluster *anywherev1.Cluster
workloadCluster *anywherev1.Cluster
wantErr error
}{
{
name: "license key is unique",
cluster: &anywherev1.Cluster{
ObjectMeta: v1.ObjectMeta{
Name: "cluster1",
},
Spec: anywherev1.ClusterSpec{
LicenseToken: "valid-token",
},
},
workloadCluster: &anywherev1.Cluster{
ObjectMeta: v1.ObjectMeta{
Name: "cluster2",
},
Spec: anywherev1.ClusterSpec{
LicenseToken: "valid-token1",
},
},
wantErr: nil,
},
{
name: "license key is not unique",
cluster: &anywherev1.Cluster{
ObjectMeta: v1.ObjectMeta{
Name: "cluster1",
},
Spec: anywherev1.ClusterSpec{
LicenseToken: "valid-token",
},
},
workloadCluster: &anywherev1.Cluster{
ObjectMeta: v1.ObjectMeta{
Name: "cluster2",
},
Spec: anywherev1.ClusterSpec{
LicenseToken: "valid-token",
},
},
wantErr: fmt.Errorf("license key valid-token is already in use by cluster"),
},
}
for _, tc := range tests {
t.Run(tc.name, func(_ *testing.T) {
cb := fake.NewClientBuilder()
cl := cb.WithRuntimeObjects(tc.cluster, tc.workloadCluster).Build()
client := clientutil.NewKubeClient(cl)

err := validateLicenseKeyIsUnique(ctx, tc.cluster.Name, tc.cluster.Spec.LicenseToken, client)
if err != nil && !strings.Contains(err.Error(), tc.wantErr.Error()) {
t.Errorf("%v got = %v, \nwant %v", tc.name, err, tc.wantErr)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/validations/kubectl.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

type KubectlClient interface {
List(ctx context.Context, kubeconfig string, list kubernetes.ObjectList) error
List(ctx context.Context, kubeconfig string, list kubernetes.ObjectList, opts ...kubernetes.ListOption) error
ValidateControlPlaneNodes(ctx context.Context, cluster *types.Cluster, clusterName string) error
ValidateWorkerNodes(ctx context.Context, clusterName string, kubeconfig string) error
ValidateNodes(ctx context.Context, kubeconfig string) error
Expand Down
Loading

0 comments on commit 30fa933

Please sign in to comment.