diff --git a/apis/authentication/v1beta1/renew_types.go b/apis/authentication/v1beta1/renew_types.go new file mode 100644 index 0000000000..d2afe3b657 --- /dev/null +++ b/apis/authentication/v1beta1/renew_types.go @@ -0,0 +1,82 @@ +// Copyright 2019-2025 The Liqo Authors +// +// 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 v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + liqov1beta1 "github.com/liqotech/liqo/apis/core/v1beta1" +) + +// RenewResource is the name of the renew resources. +var RenewResource = "renews" + +// RenewKind specifies the kind of the renew. +var RenewKind = "Renew" + +// RenewGroupResource is group resource used to register these objects. +var RenewGroupResource = schema.GroupResource{Group: GroupVersion.Group, Resource: RenewResource} + +// RenewGroupVersionResource is groupResourceVersion used to register these objects. +var RenewGroupVersionResource = GroupVersion.WithResource(RenewResource) + +// RenewSpec defines the desired state of Renew. +type RenewSpec struct { + // ConsumerClusterID is the id of the consumer cluster. + ConsumerClusterID liqov1beta1.ClusterID `json:"consumerClusterID,omitempty"` + // PublicKey is the public key of the tenant cluster. + PublicKey []byte `json:"publicKey,omitempty"` + // CSR is the Certificate Signing Request of the tenant cluster. + CSR []byte `json:"csr,omitempty"` + // IdentityType is the type of the identity. + IdentityType IdentityType `json:"identityType,omitempty"` + // ResoruceSliceRef is the reference to the resource slice. + ResourceSliceRef *corev1.LocalObjectReference `json:"resourceSliceRef,omitempty"` +} + +// RenewStatus defines the observed state of Renew. +type RenewStatus struct { + // AuthParams contains the authentication parameters for the consumer cluster. + AuthParams *AuthParams `json:"authParams,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:categories=liqo +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` + +// Renew represents a slice of resources given by the provider cluster to the consumer cluster. +type Renew struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RenewSpec `json:"spec,omitempty"` + Status RenewStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RenewList contains a list of Renews. +type RenewList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Renew `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Renew{}, &RenewList{}) +} diff --git a/apis/authentication/v1beta1/resourceslice_types.go b/apis/authentication/v1beta1/resourceslice_types.go index aec3cbf349..42969be3aa 100644 --- a/apis/authentication/v1beta1/resourceslice_types.go +++ b/apis/authentication/v1beta1/resourceslice_types.go @@ -133,7 +133,7 @@ type ResourceSlice struct { // +kubebuilder:object:root=true -// ResourceSliceList contains a list of Identities. +// ResourceSliceList contains a list of ResourceSlices. type ResourceSliceList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/apis/authentication/v1beta1/zz_generated.deepcopy.go b/apis/authentication/v1beta1/zz_generated.deepcopy.go index 18410455fa..e20e767cbd 100644 --- a/apis/authentication/v1beta1/zz_generated.deepcopy.go +++ b/apis/authentication/v1beta1/zz_generated.deepcopy.go @@ -174,6 +174,115 @@ func (in *IdentityStatus) DeepCopy() *IdentityStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Renew) DeepCopyInto(out *Renew) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Renew. +func (in *Renew) DeepCopy() *Renew { + if in == nil { + return nil + } + out := new(Renew) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Renew) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RenewList) DeepCopyInto(out *RenewList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Renew, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RenewList. +func (in *RenewList) DeepCopy() *RenewList { + if in == nil { + return nil + } + out := new(RenewList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RenewList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RenewSpec) DeepCopyInto(out *RenewSpec) { + *out = *in + if in.PublicKey != nil { + in, out := &in.PublicKey, &out.PublicKey + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.CSR != nil { + in, out := &in.CSR, &out.CSR + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.ResourceSliceRef != nil { + in, out := &in.ResourceSliceRef, &out.ResourceSliceRef + *out = new(v1.LocalObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RenewSpec. +func (in *RenewSpec) DeepCopy() *RenewSpec { + if in == nil { + return nil + } + out := new(RenewSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RenewStatus) DeepCopyInto(out *RenewStatus) { + *out = *in + if in.AuthParams != nil { + in, out := &in.AuthParams, &out.AuthParams + *out = new(AuthParams) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RenewStatus. +func (in *RenewStatus) DeepCopy() *RenewStatus { + if in == nil { + return nil + } + out := new(RenewStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceSlice) DeepCopyInto(out *ResourceSlice) { *out = *in diff --git a/cmd/liqo-controller-manager/modules/authentication.go b/cmd/liqo-controller-manager/modules/authentication.go index dcf8f21913..ac76bf7a19 100644 --- a/cmd/liqo-controller-manager/modules/authentication.go +++ b/cmd/liqo-controller-manager/modules/authentication.go @@ -27,9 +27,11 @@ import ( "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication" identitycontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication/identity-controller" identitycreatorcontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication/identitycreator-controller" + localrenwercontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication/localrenwer-controller" localresourceslicecontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication/localresourceslice-controller" noncecreatorcontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication/noncecreator-controller" noncesigner "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication/noncesigner-controller" + remoterenwercontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication/remoterenwer-controller" remoteresourceslicecontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication/remoteresourceslice-controller" tenantcontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication/tenant-controller" tenantnamespace "github.com/liqotech/liqo/pkg/tenantNamespace" @@ -115,7 +117,8 @@ func SetupAuthenticationModule(ctx context.Context, mgr manager.Manager, uncache // Configure controller that fills the remote resource slice status. remoteResourceSliceReconciler := remoteresourceslicecontroller.NewRemoteResourceSliceReconciler(mgr.GetClient(), mgr.GetScheme(), mgr.GetConfig(), mgr.GetEventRecorderFor("remoteresourceslice-controller"), - opts.IdentityProvider, opts.APIServerAddressOverride, caOverride, opts.TrustedCA, + opts.IdentityProvider, opts.NamespaceManager, + opts.APIServerAddressOverride, caOverride, opts.TrustedCA, opts.SliceStatusOptions) if err := remoteResourceSliceReconciler.SetupWithManager(mgr); err != nil { klog.Errorf("Unable to setup the remote resource slice reconciler: %v", err) @@ -131,6 +134,24 @@ func SetupAuthenticationModule(ctx context.Context, mgr manager.Manager, uncache return err } + // Configure controllers that handle the certificate rotation. + localRenewerReconciler := localrenwercontroller.NewLocalRenewerReconciler(mgr.GetClient(), mgr.GetScheme(), + opts.LiqoNamespace, opts.LocalClusterID, + mgr.GetEventRecorderFor("local-renewer-controller")) + if err := localRenewerReconciler.SetupWithManager(mgr); err != nil { + klog.Errorf("Unable to setup the local renewer reconciler: %v", err) + return err + } + + remoteRenewerReconciler := remoterenwercontroller.NewRemoteRenewerReconciler(mgr.GetClient(), mgr.GetScheme(), + opts.IdentityProvider, opts.NamespaceManager, + opts.APIServerAddressOverride, caOverride, opts.TrustedCA, + mgr.GetEventRecorderFor("remote-renewer-controller")) + if err := remoteRenewerReconciler.SetupWithManager(mgr); err != nil { + klog.Errorf("Unable to setup the remote renewer reconciler: %v", err) + return err + } + return nil } diff --git a/deployments/liqo/charts/liqo-crds/crds/authentication.liqo.io_renews.yaml b/deployments/liqo/charts/liqo-crds/crds/authentication.liqo.io_renews.yaml new file mode 100644 index 0000000000..74608200bd --- /dev/null +++ b/deployments/liqo/charts/liqo-crds/crds/authentication.liqo.io_renews.yaml @@ -0,0 +1,124 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.3 + name: renews.authentication.liqo.io +spec: + group: authentication.liqo.io + names: + categories: + - liqo + kind: Renew + listKind: RenewList + plural: renews + singular: renew + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Renew represents a slice of resources given by the provider cluster + to the consumer cluster. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: RenewSpec defines the desired state of Renew. + properties: + consumerClusterID: + description: ConsumerClusterID is the id of the consumer cluster. + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + csr: + description: CSR is the Certificate Signing Request of the tenant + cluster. + format: byte + type: string + identityType: + description: IdentityType is the type of the identity. + type: string + publicKey: + description: PublicKey is the public key of the tenant cluster. + format: byte + type: string + resourceSliceRef: + description: ResoruceSliceRef is the reference to the resource slice. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: object + status: + description: RenewStatus defines the observed state of Renew. + properties: + authParams: + description: AuthParams contains the authentication parameters for + the consumer cluster. + properties: + apiServer: + type: string + awsConfig: + description: AwsConfig contains the AWS configuration and access + key for the Liqo user and the current EKS cluster. + properties: + awsAccessKeyID: + type: string + awsClusterName: + type: string + awsRegion: + type: string + awsSecretAccessKey: + type: string + awsUserArn: + type: string + required: + - awsAccessKeyID + - awsClusterName + - awsRegion + - awsSecretAccessKey + - awsUserArn + type: object + ca: + format: byte + type: string + proxyURL: + type: string + signedCRT: + format: byte + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml b/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml index bc40cf2a63..751ebb0167 100644 --- a/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml +++ b/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml @@ -43,6 +43,7 @@ rules: resources: - identities - identities/status + - renews - resourceslices - resourceslices/status - tenants @@ -55,6 +56,21 @@ rules: - patch - update - watch +- apiGroups: + - authentication.liqo.io + resources: + - identities/finalizers + - renews/finalizers + verbs: + - update +- apiGroups: + - authentication.liqo.io + resources: + - renews/status + verbs: + - get + - patch + - update - apiGroups: - certificates.k8s.io resources: diff --git a/deployments/liqo/files/liqo-remote-controlplane-ClusterRole.yaml b/deployments/liqo/files/liqo-remote-controlplane-ClusterRole.yaml index 75e7b6e501..cb52e88f7b 100644 --- a/deployments/liqo/files/liqo-remote-controlplane-ClusterRole.yaml +++ b/deployments/liqo/files/liqo-remote-controlplane-ClusterRole.yaml @@ -2,6 +2,8 @@ rules: - apiGroups: - authentication.liqo.io resources: + - renews + - renews/status - resourceslices - resourceslices/status verbs: diff --git a/internal/crdReplicator/resources/resources.go b/internal/crdReplicator/resources/resources.go index dcc2acc21b..b56346c147 100644 --- a/internal/crdReplicator/resources/resources.go +++ b/internal/crdReplicator/resources/resources.go @@ -42,5 +42,9 @@ func GetResourcesToReplicate() []Resource { GroupVersionResource: authv1beta1.ResourceSliceGroupVersionResource, Ownership: consts.OwnershipShared, }, + { + GroupVersionResource: authv1beta1.RenewGroupVersionResource, + Ownership: consts.OwnershipShared, + }, } } diff --git a/pkg/consts/authentication.go b/pkg/consts/authentication.go index 2b752f991c..cb7deda195 100644 --- a/pkg/consts/authentication.go +++ b/pkg/consts/authentication.go @@ -51,4 +51,7 @@ const ( // CordonTenantAnnotation is the value of the annotation that enables the cordon of a tenant. CordonTenantAnnotation = "liqo.io/cordon-tenant" + + // RenewAnnotation is the value of the annotation that enables the renewal of a resource. + RenewAnnotation = "liqo.io/renew" ) diff --git a/pkg/consts/controllers.go b/pkg/consts/controllers.go index d8c30c6710..0cc0213cb2 100644 --- a/pkg/consts/controllers.go +++ b/pkg/consts/controllers.go @@ -58,6 +58,8 @@ const ( // Authentication. CtrlIdentity = "identity" CtrlIdentityCreator = "identity_creator" + CtrlRenewLocal = "renew_local" + CtrlRenewRemote = "renew_remote" CtrlSecretNonceCreator = "secret_noncecreator" CtrlSecretNonceSigner = "secret_noncesigner" CtrlResourceSliceLocal = "resourceslice_local" diff --git a/pkg/identityManager/certificateIdentityProvider.go b/pkg/identityManager/certificateIdentityProvider.go index 58b4af5423..ae0d8623db 100644 --- a/pkg/identityManager/certificateIdentityProvider.go +++ b/pkg/identityManager/certificateIdentityProvider.go @@ -84,7 +84,7 @@ func (identityProvider *certificateIdentityProvider) GetRemoteCertificate(ctx co } // check that this certificate is related to this signing request - if !bytes.Equal(signingRequestSecret, options.SigningRequest) { + if !bytes.Equal(signingRequestSecret, options.SigningRequest) && !options.IsUpdate { err = kerrors.NewBadRequest(fmt.Sprintf("the stored and the provided CSR for cluster %s does not match", options.Cluster)) klog.Error(err) return response, err diff --git a/pkg/identityManager/interface.go b/pkg/identityManager/interface.go index 1e2aa2efdb..12917b1dc9 100644 --- a/pkg/identityManager/interface.go +++ b/pkg/identityManager/interface.go @@ -57,6 +57,7 @@ type SigningRequestOptions struct { TrustedCA bool ResourceSlice *authv1beta1.ResourceSlice ProxyURL *string + IsUpdate bool } // IdentityProvider provides the interface to retrieve and approve remote cluster identities. diff --git a/pkg/liqo-controller-manager/authentication/localrenwer-controller/doc.go b/pkg/liqo-controller-manager/authentication/localrenwer-controller/doc.go new file mode 100644 index 0000000000..8f14b86edb --- /dev/null +++ b/pkg/liqo-controller-manager/authentication/localrenwer-controller/doc.go @@ -0,0 +1,31 @@ +// Copyright 2019-2025 The Liqo Authors +// +// 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 localrenwercontroller implements the controller for managing certificate renewals +// for local Identity resources. It monitors Identity objects and creates Renew objects +// when certificates need to be renewed. +// +// The controller is responsible for: +// * Monitoring Identity objects and their certificates +// * Determining when certificates need renewal based on their lifetime +// * Creating and managing Renew objects for certificate renewal +// * Handling manual renewal requests via the "liqo.io/renew" annotation +// +// Certificate renewal is triggered in two ways: +// 1. Automatically when a certificate reaches 2/3 of its lifetime +// 2. Manually when an Identity is annotated with "liqo.io/renew: true" +// +// The controller implements an adaptive requeue mechanism that adjusts the check frequency +// based on how close the certificate is to requiring renewal. +package localrenwercontroller diff --git a/pkg/liqo-controller-manager/authentication/localrenwer-controller/localrenewer_controller.go b/pkg/liqo-controller-manager/authentication/localrenwer-controller/localrenewer_controller.go new file mode 100644 index 0000000000..9d620995c2 --- /dev/null +++ b/pkg/liqo-controller-manager/authentication/localrenwer-controller/localrenewer_controller.go @@ -0,0 +1,310 @@ +// Copyright 2019-2025 The Liqo Authors +// +// 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 localrenwercontroller + +import ( + "context" + "crypto/x509" + "encoding/pem" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + authv1beta1 "github.com/liqotech/liqo/apis/authentication/v1beta1" + liqov1beta1 "github.com/liqotech/liqo/apis/core/v1beta1" + "github.com/liqotech/liqo/pkg/consts" + "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication" + "github.com/liqotech/liqo/pkg/utils/events" +) + +// LocalRenewerReconciler reconciles an Identity object. +type LocalRenewerReconciler struct { + client.Client + Scheme *runtime.Scheme + + LiqoNamespace string + LocalClusterID liqov1beta1.ClusterID + recorder record.EventRecorder +} + +// NewLocalRenewerReconciler returns a new LocalRenewerReconciler. +func NewLocalRenewerReconciler(cl client.Client, s *runtime.Scheme, + liqoNamespace string, + localClusterID liqov1beta1.ClusterID, + recorder record.EventRecorder) *LocalRenewerReconciler { + return &LocalRenewerReconciler{ + Client: cl, + Scheme: s, + LiqoNamespace: liqoNamespace, + LocalClusterID: localClusterID, + recorder: recorder, + } +} + +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=identities,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=identities/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=identities/finalizers,verbs=update +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=renews,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=renews/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=renews/finalizers,verbs=update +//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch + +// Reconcile implements the logic to determine if an Identity should be renewed, +// and enforces the creation of a Renew object if needed. It also removes the +// current Renew object if the Identity does not need renewal anymore. +// +// The function first retrieves the Identity object and checks if it should be +// renewed using the shouldRenew function. Renewal can be triggered either by +// the presence of a "liqo.io/renew" annotation set to true, or by the certificate +// approaching its expiration time (2/3 of its lifetime). +// +// If the Identity does not need renewal, it removes the current Renew object +// if present and returns a requeue time calculated by the shouldRenew function. +// +// If the Identity needs renewal, the function creates a Renew object and returns +// a nil error. If an error occurs during the process, the function logs the +// error and returns it. +func (r *LocalRenewerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Get the Identity + var identity authv1beta1.Identity + if err := r.Get(ctx, req.NamespacedName, &identity); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + shouldRenew, requeueAfter, err := r.shouldRenew(ctx, &identity) + if err != nil { + klog.Errorf("Unable to check if Identity %q should renew: %s", req.NamespacedName, err) + return ctrl.Result{}, err + } + + if !shouldRenew { + klog.V(4).Infof("Identity %q does not need renewal", req.NamespacedName) + + // Remove current Renew if present + if err := r.removeCurrentRenew(ctx, &identity); err != nil { + klog.Errorf("Unable to remove current Renew for Identity %q: %s", req.NamespacedName, err) + return ctrl.Result{}, err + } + + return ctrl.Result{ + RequeueAfter: requeueAfter, + }, nil + } + + if err := r.enforceRenew(ctx, &identity); err != nil { + klog.Errorf("Unable to create Renew for Identity %q: %s", req.NamespacedName, err) + events.EventWithOptions(r.recorder, &identity, fmt.Sprintf("Failed to create Renew: %s", err), + &events.Option{EventType: events.Error, Reason: "RenewCreationFailed"}) + return ctrl.Result{}, err + } + + klog.V(4).Infof("Created Renew for Identity %q", req.NamespacedName) + events.Event(r.recorder, &identity, "Created Renew object for certificate renewal") + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *LocalRenewerReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr).Named(consts.CtrlRenewLocal). + Owns(&authv1beta1.Renew{}). + For(&authv1beta1.Identity{}). + Complete(r) +} + +// shouldRenew determines whether an Identity object needs certificate renewal. +// +// The function first checks if the Identity has the renewal annotation set to true. +// If the annotation is present, it immediately triggers renewal regardless of certificate status. +// +// Otherwise, it retrieves the kubeconfig secret referenced by the Identity and checks the +// signed certificate within. The function calculates the certificate's lifetime +// and determines if a renewal is required based on the 2/3 life rule. +// If the certificate is not near expiration, it calculates the next check time +// as the remaining time until the 2/3 point plus a 10% buffer. +// If the certificate is near expiration, it checks if a Renew object already exists. +// +// Args: +// - ctx: the context of the request +// - identity: the Identity object to be checked +// +// Returns: +// - renew: true if renewal is needed (either by annotation or certificate expiration) +// - requeueIn: duration after which to requeue the reconciliation +// - err: any error encountered during the process +func (r *LocalRenewerReconciler) shouldRenew(ctx context.Context, identity *authv1beta1.Identity) (renew bool, requeueIn time.Duration, err error) { + // Check if the Identity has the renewal annotation + if identity.Annotations != nil && (identity.Annotations[consts.RenewAnnotation] == "true" || + identity.Annotations[consts.RenewAnnotation] == "True") { + return true, requeueIn, nil + } + + // Get the kubeconfig secret referenced by the Identity + if identity.Status.KubeconfigSecretRef == nil { + return false, requeueIn, fmt.Errorf("identity %s/%s has no kubeconfig secret reference", identity.Namespace, identity.Name) + } + + // Get the signed certificate from the kubeconfig + signedCrt := identity.Spec.AuthParams.SignedCRT + if len(signedCrt) == 0 { + return false, requeueIn, fmt.Errorf("identity %s/%s has no signed certificate", identity.Namespace, identity.Name) + } + + // Parse the certificate to get its expiration time + block, _ := pem.Decode(signedCrt) + if block == nil { + return false, requeueIn, fmt.Errorf("failed to decode PEM block containing certificate") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return false, requeueIn, fmt.Errorf("failed to parse certificate: %w", err) + } + + // Calculate if we need to renew based on 2/3 life rule + lifetime := cert.NotAfter.Sub(cert.NotBefore) + twoThirdsPoint := cert.NotAfter.Add(-lifetime / 3) + + if time.Now().Before(twoThirdsPoint) { + // Calculate requeue time as the remaining time until the 2/3 point of the certificate expiration time + 10% + timeUntilTwoThirds := time.Until(twoThirdsPoint) + requeueIn = timeUntilTwoThirds * 11 / 10 + + klog.V(4).Infof("Certificate not ready for renewal, will check again in %v", requeueIn) + return false, requeueIn, nil + } + + // If certificate is not near expiration, check if Renew already exists + var existingRenew authv1beta1.Renew + err = r.Get(ctx, client.ObjectKey{ + Namespace: identity.Namespace, + Name: identity.Name, + }, &existingRenew) + if err == nil { + // Renew already exists, skip + return false, requeueIn, nil + } + + return true, requeueIn, nil // No existing Renew, proceed with creation +} + +// enforceRenew enforces the creation of a Renew object for the given Identity. +// +// The function creates a Renew object with the same name and namespace as the given Identity. +// The Renew object is filled with the public key of the local cluster and a CSR for the remote cluster. +// The CSR is generated based on the IdentityType of the given Identity. +// If the IdentityType is ControlPlaneIdentityType, the CSR is generated using the GenerateCSRForControlPlane function. +// If the IdentityType is ResourceSliceIdentityType, the CSR is generated using the GenerateCSRForResourceSlice function. +// The function sets the owner reference of the Renew object to the given Identity. +// The function returns an error if it fails to get the cluster keys or generate the CSR. +func (r *LocalRenewerReconciler) enforceRenew(ctx context.Context, identity *authv1beta1.Identity) error { + // Create or update the Renew object + renew := &authv1beta1.Renew{ + ObjectMeta: metav1.ObjectMeta{ + Name: identity.Name, + Namespace: identity.Namespace, + }, + } + + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, renew, func() error { + // Set replication labels + if renew.Labels == nil { + renew.Labels = make(map[string]string) + } + renew.Labels[consts.ReplicationRequestedLabel] = consts.ReplicationRequestedLabelValue + renew.Labels[consts.ReplicationDestinationLabel] = string(identity.Spec.ClusterID) + renew.Labels[consts.RemoteClusterID] = string(identity.Spec.ClusterID) + + // Set owner reference + if err := controllerutil.SetControllerReference(identity, renew, r.Scheme); err != nil { + return err + } + + // Copy fields from Identity to Renew + renew.Spec.ConsumerClusterID = r.LocalClusterID + renew.Spec.IdentityType = identity.Spec.Type + + // If this is a ResourceSlice identity, set the reference + if identity.Spec.Type == authv1beta1.ResourceSliceIdentityType { + // Find the ResourceSlice owner reference + for _, ownerRef := range identity.GetOwnerReferences() { + if ownerRef.Kind == authv1beta1.ResourceSliceKind { + renew.Spec.ResourceSliceRef = &corev1.LocalObjectReference{ + Name: ownerRef.Name, + } + break + } + } + } + + // Get public and private keys of the local cluster. + privateKey, publicKey, err := authentication.GetClusterKeys(ctx, r.Client, r.LiqoNamespace) + if err != nil { + return fmt.Errorf("unable to get cluster keys: %w", err) + } + + renew.Spec.PublicKey = publicKey + + switch identity.Spec.Type { + case authv1beta1.ControlPlaneIdentityType: + // Generate a CSR for the remote cluster. + CSR, err := authentication.GenerateCSRForControlPlane(privateKey, identity.Spec.ClusterID) + if err != nil { + return fmt.Errorf("unable to generate CSR: %w", err) + } + renew.Spec.CSR = CSR + case authv1beta1.ResourceSliceIdentityType: + var resourceSlice authv1beta1.ResourceSlice + if err := r.Get(ctx, client.ObjectKey{ + Namespace: identity.Namespace, + Name: renew.Spec.ResourceSliceRef.Name, + }, &resourceSlice); err != nil { + return fmt.Errorf("unable to get ResourceSlice: %w", err) + } + + // Generate a CSR for the remote cluster. + CSR, err := authentication.GenerateCSRForResourceSlice(privateKey, &resourceSlice) + if err != nil { + return fmt.Errorf("unable to generate CSR: %w", err) + } + renew.Spec.CSR = CSR + } + + return nil + }) + + return err +} + +// removeCurrentRenew removes the current Renew object for the given Identity. +// +// The function deletes the Renew object with the same name and namespace as the given Identity. +// If the deletion fails, it returns an error. +func (r *LocalRenewerReconciler) removeCurrentRenew(ctx context.Context, identity *authv1beta1.Identity) error { + return client.IgnoreNotFound(r.Delete(ctx, &authv1beta1.Renew{ + ObjectMeta: metav1.ObjectMeta{ + Name: identity.Name, + Namespace: identity.Namespace, + }, + })) +} diff --git a/pkg/liqo-controller-manager/authentication/remoterenwer-controller/doc.go b/pkg/liqo-controller-manager/authentication/remoterenwer-controller/doc.go new file mode 100644 index 0000000000..9965fddb48 --- /dev/null +++ b/pkg/liqo-controller-manager/authentication/remoterenwer-controller/doc.go @@ -0,0 +1,28 @@ +// Copyright 2019-2025 The Liqo Authors +// +// 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 remoterenwercontroller implements the controller for handling certificate renewal requests +// from remote clusters. It processes Renew objects created by remote clusters and generates +// new certificates based on the provided CSR. +// +// The controller is responsible for: +// * Processing Renew objects created by remote clusters +// * Validating the renewal request against the tenant namespace +// * Generating new certificates using the provided CSR +// * Updating the status of related resources (Tenant or ResourceSlice) +// * Managing the lifecycle of Renew objects +// +// The renewal process is triggered either by certificate expiration (2/3 lifetime rule) +// or manually through the "liqo.io/renew" annotation. +package remoterenwercontroller diff --git a/pkg/liqo-controller-manager/authentication/remoterenwer-controller/remoterenewer_controller.go b/pkg/liqo-controller-manager/authentication/remoterenwer-controller/remoterenewer_controller.go new file mode 100644 index 0000000000..c82b84099f --- /dev/null +++ b/pkg/liqo-controller-manager/authentication/remoterenwer-controller/remoterenewer_controller.go @@ -0,0 +1,259 @@ +// Copyright 2019-2025 The Liqo Authors +// +// 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 remoterenwercontroller + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + authv1beta1 "github.com/liqotech/liqo/apis/authentication/v1beta1" + "github.com/liqotech/liqo/internal/crdReplicator/reflection" + "github.com/liqotech/liqo/pkg/consts" + identitymanager "github.com/liqotech/liqo/pkg/identityManager" + tenantnamespace "github.com/liqotech/liqo/pkg/tenantNamespace" + "github.com/liqotech/liqo/pkg/utils/events" + "github.com/liqotech/liqo/pkg/utils/getters" +) + +// RemoteRenewerReconciler reconciles an Renew object. +type RemoteRenewerReconciler struct { + client.Client + Scheme *runtime.Scheme + + NamespaceManager tenantnamespace.Manager + IdentityProvider identitymanager.IdentityProvider + APIServerAddressOverride string + CAOverride []byte + TrustedCA bool + recorder record.EventRecorder +} + +// NewRemoteRenewerReconciler returns a new RemoteRenewerReconciler. +func NewRemoteRenewerReconciler(cl client.Client, s *runtime.Scheme, + identityProvider identitymanager.IdentityProvider, + namespaceManager tenantnamespace.Manager, + apiServerAddressOverride string, caOverride []byte, trustedCA bool, + recorder record.EventRecorder) *RemoteRenewerReconciler { + return &RemoteRenewerReconciler{ + Client: cl, + Scheme: s, + + NamespaceManager: namespaceManager, + IdentityProvider: identityProvider, + APIServerAddressOverride: apiServerAddressOverride, + CAOverride: caOverride, + TrustedCA: trustedCA, + recorder: recorder, + } +} + +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=renews,verbs=get;list;watch;update;patch;delete;create +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=renews/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=tenants,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=tenants/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=resourceslices,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=authentication.liqo.io,resources=resourceslices/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=,resources=secrets,verbs=get;list;watch;update;patch + +// Reconcile implements the logic to reconcile an Renew object. +// +// The function first retrieves the Renew object and the related tenant and namespace. +// If the namespace of the Renew object doesn't match with the tenant namespace, it skips the reconciliation. +// Then, it calls the handleRenew function to generate the certificate for the renew and update the Renew object. +// Finally, if the renew is for a control plane or a resource slice, it calls the updateTenantStatusOnRenew or +// updateResourceSliceStatusOnRenew function to update the tenant or resource slice status. +// +// If an error occurs during the process, the function logs the error and returns it. +func (r *RemoteRenewerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var renew authv1beta1.Renew + if err := r.Get(ctx, req.NamespacedName, &renew); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + tenantNamespace, err := r.NamespaceManager.GetNamespace(ctx, renew.Spec.ConsumerClusterID) + if err != nil { + klog.Errorf("Unable to get tenant namespace for Renew %q: %s", req.NamespacedName, err) + events.EventWithOptions(r.recorder, &renew, fmt.Sprintf("Failed to get tenant namespace: %s", err), + &events.Option{EventType: events.Error, Reason: "TenantNamespaceNotFound"}) + return ctrl.Result{}, err + } + + if tenantNamespace.Name != renew.Namespace { + klog.V(4).Infof("Skipping Renew %q as it's not in the tenant namespace %q", req.NamespacedName, tenantNamespace.Name) + events.EventWithOptions(r.recorder, &renew, fmt.Sprintf("Skipping renewal as it's not in tenant namespace %s", tenantNamespace.Name), + &events.Option{EventType: events.Warning, Reason: "WrongNamespace"}) + return ctrl.Result{}, nil + } + + tenant, err := getters.GetTenantByClusterID(ctx, r.Client, renew.Spec.ConsumerClusterID) + if err != nil { + return ctrl.Result{}, err + } + + var resourceSlice *authv1beta1.ResourceSlice + if renew.Spec.ResourceSliceRef != nil { + resourceSlice = &authv1beta1.ResourceSlice{} + if err := r.Get(ctx, client.ObjectKey{ + Namespace: tenantNamespace.Name, + Name: renew.Spec.ResourceSliceRef.Name, + }, resourceSlice); err != nil { + return ctrl.Result{}, err + } + } + + if err := r.handleRenew(ctx, &renew, tenant, resourceSlice); err != nil { + klog.Errorf("Unable to handle Renew %q: %s", req.NamespacedName, err) + events.EventWithOptions(r.recorder, &renew, fmt.Sprintf("Failed to handle renewal: %s", err), + &events.Option{EventType: events.Error, Reason: "RenewalFailed"}) + return ctrl.Result{}, err + } + + klog.V(4).Infof("Successfully handled Renew %q", req.NamespacedName) + events.Event(r.recorder, &renew, "Successfully renewed certificate") + + switch { + case renew.Spec.IdentityType == authv1beta1.ControlPlaneIdentityType: + err = r.updateTenantStatusOnRenew(ctx, &renew, tenant) + if err != nil { + return ctrl.Result{}, err + } + case renew.Spec.IdentityType == authv1beta1.ResourceSliceIdentityType && resourceSlice != nil: + err = r.updateResourceSliceStatusOnRenew(ctx, &renew, resourceSlice) + if err != nil { + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *RemoteRenewerReconciler) SetupWithManager(mgr ctrl.Manager) error { + p, err := predicate.LabelSelectorPredicate(reflection.ReplicatedResourcesLabelSelector()) + if err != nil { + return err + } + + return ctrl.NewControllerManagedBy(mgr).Named(consts.CtrlRenewRemote). + For(&authv1beta1.Renew{}, builder.WithPredicates(p)). + Complete(r) +} + +// updateResourceSliceStatusOnRenew updates the status of the given ResourceSlice with the +// authParams obtained by the given Renew. +// +// The function updates the ResourceSlice object in the cluster with the obtained authParams. +// If the update fails, the function logs an error and returns the error. +// +// Args: +// - ctx: the context of the request +// - renew: the Renew object containing the authParams +// - resourceSlice: the ResourceSlice object to be updated +// +// Returns: +// - error: the error occurred during the update, if any +func (r *RemoteRenewerReconciler) updateResourceSliceStatusOnRenew(ctx context.Context, + renew *authv1beta1.Renew, + resourceSlice *authv1beta1.ResourceSlice) error { + resourceSlice.Status.AuthParams = renew.Status.AuthParams + if err := r.Status().Update(ctx, resourceSlice); err != nil { + klog.Errorf("Failed to update ResourceSlice status for %q: %s", resourceSlice.Name, err) + return err + } + + return nil +} + +// updateTenantStatusOnRenew updates the status of the given Tenant with the +// authParams obtained by the given Renew. +// +// The function updates the Tenant object in the cluster with the obtained authParams. +// If the update fails, the function logs an error and returns the error. +// +// Args: +// - ctx: the context of the request +// - renew: the Renew object containing the authParams +// - tenant: the Tenant object to be updated +// +// Returns: +// - error: the error occurred during the update, if any +func (r *RemoteRenewerReconciler) updateTenantStatusOnRenew(ctx context.Context, + renew *authv1beta1.Renew, + tenant *authv1beta1.Tenant) error { + tenant.Status.AuthParams = renew.Status.AuthParams + if err := r.Status().Update(ctx, tenant); err != nil { + klog.Errorf("Failed to update Tenant status for %q: %s", tenant.Name, err) + return err + } + + return nil +} + +// handleRenew handles a Renew object, creating the corresponding AuthParams and updating the Renew status. +// +// Args: +// - ctx: the context of the request +// - renew: the Renew object to be handled +// - tenant: the Tenant object associated with the Renew +// - resourceSlice: the ResourceSlice object associated with the Renew, if any +// +// Returns: +// - error: the error occurred during the handling, if any +func (r *RemoteRenewerReconciler) handleRenew(ctx context.Context, + renew *authv1beta1.Renew, + tenant *authv1beta1.Tenant, + resourceSlice *authv1beta1.ResourceSlice) error { + var name string + if resourceSlice != nil { + name = resourceSlice.Name + } else { + name = tenant.Name + } + + authParams, err := r.IdentityProvider.ForgeAuthParams(ctx, &identitymanager.SigningRequestOptions{ + Cluster: renew.Spec.ConsumerClusterID, + TenantNamespace: tenant.Status.TenantNamespace, + IdentityType: renew.Spec.IdentityType, + Name: name, + SigningRequest: renew.Spec.CSR, + + APIServerAddressOverride: r.APIServerAddressOverride, + CAOverride: r.CAOverride, + TrustedCA: r.TrustedCA, + ResourceSlice: resourceSlice, + ProxyURL: tenant.Spec.ProxyURL, + IsUpdate: true, + }) + if err != nil { + klog.Errorf("Unable to forge the AuthParams for the Renew %q: %s", renew.Name, err) + return err + } + + renew.Status.AuthParams = authParams + if err := r.Status().Update(ctx, renew); err != nil { + klog.Errorf("Failed to update Renew status for %q: %s", renew.Name, err) + return err + } + + return nil +} diff --git a/pkg/liqo-controller-manager/authentication/remoteresourceslice-controller/remoteresourceslice_controller.go b/pkg/liqo-controller-manager/authentication/remoteresourceslice-controller/remoteresourceslice_controller.go index de119039c2..8ded2229db 100644 --- a/pkg/liqo-controller-manager/authentication/remoteresourceslice-controller/remoteresourceslice_controller.go +++ b/pkg/liqo-controller-manager/authentication/remoteresourceslice-controller/remoteresourceslice_controller.go @@ -40,6 +40,7 @@ import ( "github.com/liqotech/liqo/pkg/consts" identitymanager "github.com/liqotech/liqo/pkg/identityManager" "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication" + tenantnamespace "github.com/liqotech/liqo/pkg/tenantNamespace" "github.com/liqotech/liqo/pkg/utils/getters" liqolabels "github.com/liqotech/liqo/pkg/utils/labels" ) @@ -48,6 +49,7 @@ import ( func NewRemoteResourceSliceReconciler(cl client.Client, s *runtime.Scheme, config *rest.Config, recorder record.EventRecorder, identityProvider identitymanager.IdentityProvider, + namespaceManager tenantnamespace.Manager, apiServerAddressOverride string, caOverride []byte, trustedCA bool, sliceStatusOptions *SliceStatusOptions) *RemoteResourceSliceReconciler { return &RemoteResourceSliceReconciler{ @@ -57,6 +59,7 @@ func NewRemoteResourceSliceReconciler(cl client.Client, s *runtime.Scheme, confi eventRecorder: recorder, identityProvider: identityProvider, + namespaceManager: namespaceManager, apiServerAddressOverride: apiServerAddressOverride, caOverride: caOverride, @@ -79,6 +82,7 @@ type RemoteResourceSliceReconciler struct { eventRecorder record.EventRecorder identityProvider identitymanager.IdentityProvider + namespaceManager tenantnamespace.Manager apiServerAddressOverride string caOverride []byte @@ -114,6 +118,17 @@ func (r *RemoteResourceSliceReconciler) Reconcile(ctx context.Context, req ctrl. return ctrl.Result{}, nil } + tenantNamespace, err := r.namespaceManager.GetNamespace(ctx, *resourceSlice.Spec.ConsumerClusterID) + if err != nil { + return ctrl.Result{}, err + } + + if tenantNamespace.Name != resourceSlice.Namespace { + klog.Errorf("The namespace %q of the ResourceSlice %q doesn't match with the tenant namespace %q, skipping", + resourceSlice.Namespace, resourceSlice.Name, tenantNamespace.Name) + return ctrl.Result{}, nil + } + // Get Tenant associated with the ResourceSlice. tenant, err := getters.GetTenantByClusterID(ctx, r.Client, *resourceSlice.Spec.ConsumerClusterID) if err != nil { diff --git a/pkg/peering-roles/controlplane/controlplane.go b/pkg/peering-roles/controlplane/controlplane.go index 65e94827e7..36349abf46 100644 --- a/pkg/peering-roles/controlplane/controlplane.go +++ b/pkg/peering-roles/controlplane/controlplane.go @@ -19,5 +19,8 @@ package controlplane // +kubebuilder:rbac:groups=authentication.liqo.io,resources=resourceslices,verbs=get;update;patch;list;watch;delete;create;deletecollection // +kubebuilder:rbac:groups=authentication.liqo.io,resources=resourceslices/status,verbs=get;update;patch;list;watch;delete;create;deletecollection +// +kubebuilder:rbac:groups=authentication.liqo.io,resources=renews,verbs=get;update;patch;list;watch;delete;create;deletecollection +// +kubebuilder:rbac:groups=authentication.liqo.io,resources=renews/status,verbs=get;update;patch;list;watch;delete;create;deletecollection + // +kubebuilder:rbac:groups=offloading.liqo.io,resources=namespacemaps,verbs=get;update;patch;list;watch;delete;create;deletecollection // +kubebuilder:rbac:groups=offloading.liqo.io,resources=namespacemaps/status,verbs=get;update;patch;list;watch;delete;create;deletecollection