From 90cf2d11c3d562ff47545aa036f105c5053e3af5 Mon Sep 17 00:00:00 2001 From: gciezkowski-acc Date: Tue, 29 Oct 2024 10:22:16 +0100 Subject: [PATCH] feat(cluster) configurable max token validity (#518) (#634) * feat(cluster) configurable max token validity (#518) * feat(cluster) Unit tests for MaxTokenVelidity (#518) * feat(cluster) Make deal with refresh token (#518) * feat(cluster) Unit tests and avoid impact to existing clusters (#518) * feat(cluster) Rebase side effecte (#518) * feat(cluster) Add missing things to the chart * feat(cluster) Fix errors in tests (#518) * feat(cluster) Add maxTokenValidity to documentation * Automatic generation of CRD API Docs * Automatic application of license header * feat(cluster) KubeConfig struct (#518) * Automatic application of license header * Automatic generation of CRD API Docs * Automatic generation of CRD API Docs * Revert "feat(clusters) Nodes created in namespace (#518)" This reverts commit 585f2dc60483dabeffbd53daf70b4278ccb47ab0. * feat(clusters) Minimum value of TokenValidity (#518) * Automatic generation of CRD API Docs * feat(clusters) RenewRemoteClusterBearerTokenAfter to current value in reconciler * feat(clusters) Move validation to kubebuilder (#518) * Automatic generation of CRD API Docs --------- Co-authored-by: CRD API Docs Bot Co-authored-by: License Bot --- .../manager/crds/greenhouse.sap_clusters.yaml | 13 +++++ docs/reference/api/index.html | 58 +++++++++++++++++++ docs/reference/api/openapi.yaml | 13 ++++- docs/user-guides/cluster/onboarding.md | 1 + pkg/admission/cluster_webhook.go | 2 + pkg/admission/cluster_webhook_test.go | 15 +++++ pkg/apis/greenhouse/v1alpha1/cluster_types.go | 26 +++++++++ .../v1alpha1/zz_generated.deepcopy.go | 18 +++++- pkg/controllers/cluster/cluster_controller.go | 3 +- pkg/controllers/cluster/status_test.go | 6 +- 10 files changed, 149 insertions(+), 6 deletions(-) diff --git a/charts/manager/crds/greenhouse.sap_clusters.yaml b/charts/manager/crds/greenhouse.sap_clusters.yaml index c25b8c50e..d5297cce6 100644 --- a/charts/manager/crds/greenhouse.sap_clusters.yaml +++ b/charts/manager/crds/greenhouse.sap_clusters.yaml @@ -58,6 +58,19 @@ spec: enum: - direct type: string + kubeConfig: + description: KubeConfig contains specific values for `KubeConfig` + for the cluster. + properties: + maxTokenValidity: + default: 72 + description: MaxTokenValidity specifies the maximum duration for + which a token remains valid in hours. + format: int32 + maximum: 72 + minimum: 24 + type: integer + type: object required: - accessMode type: object diff --git a/docs/reference/api/index.html b/docs/reference/api/index.html index 2c5937289..4a4c95329 100644 --- a/docs/reference/api/index.html +++ b/docs/reference/api/index.html @@ -112,6 +112,19 @@

Cluster

AccessMode configures how the cluster is accessed from the Greenhouse operator.

+ + +kubeConfig
+ + +ClusterKubeConfig + + + + +

KubeConfig contains specific values for KubeConfig for the cluster.

+ + @@ -141,6 +154,38 @@

ClusterAccessMode

ClusterConditionType (string alias)

ClusterConditionType is a valid condition of a cluster.

+

ClusterKubeConfig +

+

+(Appears on: +ClusterSpec) +

+

ClusterKubeConfig configures kube config values.

+
+
+ + + + + + + + + + + + + +
FieldDescription
+maxTokenValidity
+ +int32 + +
+

MaxTokenValidity specifies the maximum duration for which a token remains valid in hours.

+
+
+

ClusterKubeconfig

ClusterKubeconfig is the Schema for the clusterkubeconfigs API @@ -727,6 +772,19 @@

ClusterSpec

AccessMode configures how the cluster is accessed from the Greenhouse operator.

+ + +kubeConfig
+ + +ClusterKubeConfig + + + + +

KubeConfig contains specific values for KubeConfig for the cluster.

+ + diff --git a/docs/reference/api/openapi.yaml b/docs/reference/api/openapi.yaml index 834f02d8b..f70f4f0bb 100755 --- a/docs/reference/api/openapi.yaml +++ b/docs/reference/api/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: Greenhouse - version: 84e81b8 + version: aad9425 description: PlusOne operations platform paths: /TeamMembership: @@ -1097,6 +1097,17 @@ components: enum: - direct type: string + kubeConfig: + description: KubeConfig contains specific values for `KubeConfig` for the cluster. + properties: + maxTokenValidity: + default: 72 + description: MaxTokenValidity specifies the maximum duration for which a token remains valid in hours. + format: int32 + maximum: 72 + minimum: 24 + type: integer + type: object required: - accessMode type: object diff --git a/docs/user-guides/cluster/onboarding.md b/docs/user-guides/cluster/onboarding.md index 39b3c1967..929bf22d3 100644 --- a/docs/user-guides/cluster/onboarding.md +++ b/docs/user-guides/cluster/onboarding.md @@ -74,6 +74,7 @@ metadata: uid: 0db6e464-ec36-459e-8a05-4ad668b57f42 spec: accessMode: direct + maxTokenValidity: 72h status: bearerTokenExpirationTimestamp: "2024-02-09T06:28:57Z" kubernetesVersion: v1.27.8 diff --git a/pkg/admission/cluster_webhook.go b/pkg/admission/cluster_webhook.go index 11db3eecf..b3002dbc1 100644 --- a/pkg/admission/cluster_webhook.go +++ b/pkg/admission/cluster_webhook.go @@ -47,6 +47,7 @@ func DefaultCluster(ctx context.Context, _ client.Client, obj runtime.Object) er if !ok { return nil } + annotations := cluster.GetAnnotations() deletionVal, deletionMarked := annotations[apis.MarkClusterDeletionAnnotation] _, scheduleExists := annotations[apis.ScheduleClusterDeletionAnnotation] @@ -95,6 +96,7 @@ func ValidateCreateCluster(ctx context.Context, _ client.Client, obj runtime.Obj logger.Error(err, "found deletion annotation on cluster creation, admission will be denied") return admission.Warnings{"you cannot create a cluster with deletion annotation"}, err } + return nil, nil } diff --git a/pkg/admission/cluster_webhook_test.go b/pkg/admission/cluster_webhook_test.go index 0863d6ef0..9076dc342 100644 --- a/pkg/admission/cluster_webhook_test.go +++ b/pkg/admission/cluster_webhook_test.go @@ -171,6 +171,21 @@ var _ = Describe("Cluster Webhook", func() { }, true, ), + Entry("it should allow creation of cluster with not too long token validity", + &greenhousev1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "test-namespace", + }, + Spec: greenhousev1alpha1.ClusterSpec{ + AccessMode: greenhousev1alpha1.ClusterAccessModeDirect, + KubeConfig: greenhousev1alpha1.ClusterKubeConfig{ + MaxTokenValidity: 72, + }, + }, + }, + false, + ), ) DescribeTable("Validate Update Cluster", diff --git a/pkg/apis/greenhouse/v1alpha1/cluster_types.go b/pkg/apis/greenhouse/v1alpha1/cluster_types.go index 9dd0c578d..b313b2b3b 100644 --- a/pkg/apis/greenhouse/v1alpha1/cluster_types.go +++ b/pkg/apis/greenhouse/v1alpha1/cluster_types.go @@ -11,12 +11,24 @@ import ( type ClusterSpec struct { // AccessMode configures how the cluster is accessed from the Greenhouse operator. AccessMode ClusterAccessMode `json:"accessMode"` + + // KubeConfig contains specific values for `KubeConfig` for the cluster. + KubeConfig ClusterKubeConfig `json:"kubeConfig,omitempty"` } // ClusterAccessMode configures the access mode to the customer cluster. // +kubebuilder:validation:Enum=direct type ClusterAccessMode string +// ClusterKubeConfig configures kube config values. +type ClusterKubeConfig struct { + // MaxTokenValidity specifies the maximum duration for which a token remains valid in hours. + // +kubebuilder:default:=72 + // +kubebuilder:validation:Minimum=24 + // +kubebuilder:validation:Maximum=72 + MaxTokenValidity int32 `json:"maxTokenValidity,omitempty"` +} + const ( // ClusterAccessModeDirect configures direct access to the cluster. ClusterAccessModeDirect ClusterAccessMode = "direct" @@ -26,6 +38,12 @@ const ( // KubeConfigValid reflects the validity of the kubeconfig of a cluster. KubeConfigValid ConditionType = "KubeConfigValid" + + // MaxTokenValidity contains maximum bearer token validity duration. It is also default value. + MaxTokenValidity = 72 + + // MinTokenValidity contains maximum bearer token validity duration. + MinTokenValidity = 24 ) // ClusterStatus defines the observed state of Cluster @@ -91,3 +109,11 @@ type ClusterList struct { func init() { SchemeBuilder.Register(&Cluster{}, &ClusterList{}) } + +func (c *Cluster) SetDefaultTokenValidityIfNeeded() { + if c.Spec.KubeConfig.MaxTokenValidity != 0 { + return + } + + c.Spec.KubeConfig.MaxTokenValidity = MaxTokenValidity +} diff --git a/pkg/apis/greenhouse/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/greenhouse/v1alpha1/zz_generated.deepcopy.go index bddeef083..4566f257f 100644 --- a/pkg/apis/greenhouse/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/greenhouse/v1alpha1/zz_generated.deepcopy.go @@ -9,7 +9,7 @@ package v1alpha1 import ( rbacv1 "k8s.io/api/rbac/v1" - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -65,6 +65,21 @@ func (in *Cluster) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterKubeConfig) DeepCopyInto(out *ClusterKubeConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterKubeConfig. +func (in *ClusterKubeConfig) DeepCopy() *ClusterKubeConfig { + if in == nil { + return nil + } + out := new(ClusterKubeConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterKubeconfig) DeepCopyInto(out *ClusterKubeconfig) { *out = *in @@ -372,6 +387,7 @@ func (in *ClusterOptionOverride) DeepCopy() *ClusterOptionOverride { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) { *out = *in + out.KubeConfig = in.KubeConfig } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec. diff --git a/pkg/controllers/cluster/cluster_controller.go b/pkg/controllers/cluster/cluster_controller.go index c7a6f33c1..4af3d3bd8 100644 --- a/pkg/controllers/cluster/cluster_controller.go +++ b/pkg/controllers/cluster/cluster_controller.go @@ -110,9 +110,10 @@ func (r *RemoteClusterReconciler) EnsureCreated(ctx context.Context, resource li return ctrl.Result{}, lifecycle.Failed, err } + cluster.SetDefaultTokenValidityIfNeeded() var tokenRequest = &tokenHelper{ Client: r.Client, - RemoteClusterBearerTokenValidity: r.RemoteClusterBearerTokenValidity, + RemoteClusterBearerTokenValidity: time.Duration(cluster.Spec.KubeConfig.MaxTokenValidity) * time.Hour, RenewRemoteClusterBearerTokenAfter: r.RenewRemoteClusterBearerTokenAfter, } if err := tokenRequest.ReconcileServiceAccountToken(ctx, restClientGetter, cluster); err != nil { diff --git a/pkg/controllers/cluster/status_test.go b/pkg/controllers/cluster/status_test.go index 3da268513..75ee7aebd 100644 --- a/pkg/controllers/cluster/status_test.go +++ b/pkg/controllers/cluster/status_test.go @@ -51,7 +51,7 @@ var _ = Describe("Cluster status", Ordered, func() { }, ObjectMeta: metav1.ObjectMeta{ Name: "test-node", - Namespace: setup.Namespace(), + Namespace: "", }, Status: corev1.NodeStatus{ Conditions: []corev1.NodeCondition{ @@ -72,7 +72,7 @@ var _ = Describe("Cluster status", Ordered, func() { }, ObjectMeta: metav1.ObjectMeta{ Name: "test-node-2", - Namespace: setup.Namespace(), + Namespace: "", }, Status: corev1.NodeStatus{ Conditions: []corev1.NodeCondition{ @@ -93,7 +93,7 @@ var _ = Describe("Cluster status", Ordered, func() { }, ObjectMeta: metav1.ObjectMeta{ Name: "test-node-3", - Namespace: setup.Namespace(), + Namespace: "", }, Status: corev1.NodeStatus{ Conditions: []corev1.NodeCondition{