Skip to content

Commit

Permalink
refactor!: make tenant namespace-scoped
Browse files Browse the repository at this point in the history
  • Loading branch information
claudiolor authored and adamjensenbot committed Mar 7, 2025
1 parent e1c10a4 commit 4261ee3
Show file tree
Hide file tree
Showing 46 changed files with 967 additions and 198 deletions.
3 changes: 2 additions & 1 deletion apis/authentication/v1beta1/tenant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func GetAuthzPolicyValue(policy *AuthzPolicy) AuthzPolicy {
// TenantSpec defines the desired state of Tenant.
type TenantSpec struct {
// ClusterID is the id of the consumer cluster.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ClusterID is immutable"
ClusterID liqov1beta1.ClusterID `json:"clusterID,omitempty"`
// AuthzPolicy is the policy used by the cluster to authorize or reject an incoming ResourceSlice.
// Default is KeysExchange.
Expand Down Expand Up @@ -99,7 +100,7 @@ type TenantStatus struct {
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,categories=liqo,shortName=tn
// +kubebuilder:resource:categories=liqo,shortName=tn
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Condition",type=string,JSONPath=`.spec.tenantCondition`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
Expand Down
15 changes: 15 additions & 0 deletions cmd/webhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import (
routecfgwh "github.com/liqotech/liqo/pkg/webhooks/routeconfiguration"
"github.com/liqotech/liqo/pkg/webhooks/secretcontroller"
shadowpodswh "github.com/liqotech/liqo/pkg/webhooks/shadowpod"
tenantwh "github.com/liqotech/liqo/pkg/webhooks/tenant"
virtualnodewh "github.com/liqotech/liqo/pkg/webhooks/virtualnode"
)

Expand Down Expand Up @@ -199,6 +200,8 @@ func main() {
mgr.GetWebhookServer().Register("/validate/firewallconfigurations", fwcfgwh.NewValidator(mgr.GetClient()))
mgr.GetWebhookServer().Register("/mutate/firewallconfigurations", fwcfgwh.NewMutator())
mgr.GetWebhookServer().Register("/validate/routeconfigurations", routecfgwh.NewValidator(mgr.GetClient()))
mgr.GetWebhookServer().Register("/validate/tenants", tenantwh.NewValidator(mgr.GetClient()))
mgr.GetWebhookServer().Register("/mutate/tenants", tenantwh.NewMutator(mgr.GetClient()))

// Register the secret controller
secretReconciler := secretcontroller.NewSecretReconciler(mgr.GetClient(), mgr.GetScheme(),
Expand All @@ -208,6 +211,18 @@ func main() {
os.Exit(1)
}

// Configure an indexer for the Tenant resource names
// Add an index to the cache for a specific resource
if err := mgr.GetFieldIndexer().IndexField(
ctx,
&authv1beta1.Tenant{},
"metadata.name",
tenantwh.NameExtractor,
); err != nil {
klog.Errorf("Unable to set up Tenant cache indexes: %v", err)
os.Exit(1)
}

if leaderElection != nil && *leaderElection {
leaderelection.LabelerOnElection(ctx, mgr, &leaderelection.PodInfo{
PodName: os.Getenv("POD_NAME"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ spec:
shortNames:
- tn
singular: tenant
scope: Cluster
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.tenantCondition
Expand Down Expand Up @@ -63,6 +63,9 @@ spec:
description: ClusterID is the id of the consumer cluster.
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
x-kubernetes-validations:
- message: ClusterID is immutable
rule: self == oldSelf
csr:
description: CSR is the Certificate Signing Request of the tenant
cluster.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ rules:
- apiGroups:
- authentication.liqo.io
resources:
- tenants/status
- tenants
verbs:
- create
- delete
- get
- list
- update
- apiGroups:
- ipam.liqo.io
resources:
Expand Down
1 change: 1 addition & 0 deletions deployments/liqo/files/liqo-webhook-ClusterRole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ rules:
- authentication.liqo.io
resources:
- resourceslices
- tenants
verbs:
- get
- list
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{{- $ctrlManagerConfig := (merge (dict "name" "controller-manager" "module" "controller-manager" "version" .Values.controllerManager.image.version) .) -}}
{{- $webhookConfig := (merge (dict "name" "webhook" "module" "webhook") .) -}}
{{- $ipamConfig := (merge (dict "name" "ipam" "module" "ipam") .) -}}
{{- $awsConfig := (merge (dict "name" "aws-config" "module" "aws-config") .) -}}

Expand Down
17 changes: 17 additions & 0 deletions deployments/liqo/templates/webhooks/liqo-mutating-webhook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,20 @@ webhooks:
resources: ["firewallconfigurations"]
sideEffects: None
failurePolicy: {{ .Values.webhook.failurePolicy }}
- name: tenant.validate.liqo.io
admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: {{ include "liqo.prefixedName" $webhookConfig }}
namespace: {{ .Release.Namespace }}
path: "/mutate/tenants"
port: {{ .Values.webhook.port }}
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["authentication.liqo.io"]
apiVersions: ["v1beta1"]
resources: ["tenants"]
sideEffects: None
failurePolicy: {{ .Values.webhook.failurePolicy }}
17 changes: 17 additions & 0 deletions deployments/liqo/templates/webhooks/liqo-validating-webhook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,20 @@ webhooks:
resources: ["resourceslices"]
sideEffects: None
failurePolicy: {{ .Values.webhook.failurePolicy }}
- name: tenant.validate.liqo.io
admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: {{ include "liqo.prefixedName" $webhookConfig }}
namespace: {{ .Release.Namespace }}
path: "/validate/tenants"
port: {{ .Values.webhook.port }}
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["authentication.liqo.io"]
apiVersions: ["v1beta1"]
resources: ["tenants"]
sideEffects: None
failurePolicy: {{ .Values.webhook.failurePolicy }}
10 changes: 6 additions & 4 deletions pkg/liqo-controller-manager/authentication/forge/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package forge

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"

authv1beta1 "github.com/liqotech/liqo/apis/authentication/v1beta1"
liqov1beta1 "github.com/liqotech/liqo/apis/core/v1beta1"
Expand All @@ -24,22 +25,23 @@ import (

// TenantForRemoteCluster forges a Tenant resource to be applied on a remote cluster.
func TenantForRemoteCluster(localClusterID liqov1beta1.ClusterID,
publicKey, csr, signature []byte, proxyURL *string) *authv1beta1.Tenant {
tenant := Tenant(localClusterID)
publicKey, csr, signature []byte, namespace, proxyURL *string) *authv1beta1.Tenant {
tenant := Tenant(localClusterID, namespace)
MutateTenant(tenant, localClusterID, publicKey, csr, signature, proxyURL)

return tenant
}

// Tenant forges a Tenant resource.
func Tenant(remoteClusterID liqov1beta1.ClusterID) *authv1beta1.Tenant {
func Tenant(remoteClusterID liqov1beta1.ClusterID, namespace *string) *authv1beta1.Tenant {
return &authv1beta1.Tenant{
TypeMeta: metav1.TypeMeta{
APIVersion: authv1beta1.GroupVersion.String(),
Kind: authv1beta1.TenantKind,
},
ObjectMeta: metav1.ObjectMeta{
Name: string(remoteClusterID),
Name: string(remoteClusterID),
Namespace: ptr.Deref(namespace, ""),
},
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (r *RemoteRenewerReconciler) Reconcile(ctx context.Context, req ctrl.Reques
return ctrl.Result{}, nil
}

tenant, err := getters.GetTenantByClusterID(ctx, r.Client, renew.Spec.ConsumerClusterID)
tenant, err := getters.GetTenantByClusterID(ctx, r.Client, renew.Spec.ConsumerClusterID, tenantNamespace.Name)
if err != nil {
return ctrl.Result{}, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (r *RemoteResourceSliceReconciler) Reconcile(ctx context.Context, req ctrl.
}

// Get Tenant associated with the ResourceSlice.
tenant, err := getters.GetTenantByClusterID(ctx, r.Client, *resourceSlice.Spec.ConsumerClusterID)
tenant, err := getters.GetTenantByClusterID(ctx, r.Client, *resourceSlice.Spec.ConsumerClusterID, tenantNamespace.Name)
if err != nil {
klog.Errorf("Unable to get the Tenant for the ResourceSlice %q: %s", req.NamespacedName, err)
r.eventRecorder.Event(&resourceSlice, corev1.EventTypeWarning, "TenantNotFound", err.Error())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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 tenantcontroller

import (
"context"

ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

authv1beta1 "github.com/liqotech/liqo/apis/authentication/v1beta1"
)

const (
// tenantControllerFinalizer is the finalizer added to tenant to allow the controller to clean up.
tenantControllerFinalizer = "tenant.liqo.io/finalizer"
)

func (r *TenantReconciler) enforceTenantFinalizerPresence(ctx context.Context, tenant *authv1beta1.Tenant) error {
ctrlutil.AddFinalizer(tenant, tenantControllerFinalizer)
return r.Client.Update(ctx, tenant)
}

func (r *TenantReconciler) enforceTenantFinalizerAbsence(ctx context.Context, tenant *authv1beta1.Tenant) error {
ctrlutil.RemoveFinalizer(tenant, tenantControllerFinalizer)
return r.Client.Update(ctx, tenant)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
controllerutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

authv1beta1 "github.com/liqotech/liqo/apis/authentication/v1beta1"
"github.com/liqotech/liqo/pkg/consts"
Expand All @@ -40,12 +41,16 @@ import (
)

var (
tenantClusterRoles = []string{
"liqo-remote-controlplane",
// tenantClusterRolesLabelValues is the list "app.kubernetes.io/name" label values assigned to the
// ClusterRoles to be binded to the control-plane Identity.
tenantClusterRolesLabelValues = []string{
"remote-controlplane",
}

tenantClusterRolesClusterWide = []string{
"liqo-virtual-kubelet-remote-clusterwide",
// tenantClusterRolesClusterWideLabelValues is the list "app.kubernetes.io/name" label values assigned to the
// ClusterRoles to be binded to the control-plane Identity with cluster-wide scope.
tenantClusterRolesClusterWideLabelValues = []string{
"virtual-kubelet-remote-clusterwide",
}
)

Expand Down Expand Up @@ -116,6 +121,21 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res
return ctrl.Result{}, err
}

// If the Tenant is being deleted, we remove the finalizer and delete related resources.
if !tenant.DeletionTimestamp.IsZero() {
// To allow the deletion of the resource we should first remove the ClusterRoleBindings.
if err := r.NamespaceManager.UnbindClusterRolesClusterWide(ctx, tenant.Spec.ClusterID, r.tenantClusterRolesClusterWide...); err != nil {
klog.Errorf("Unable to unbind the ClusterRolesClusterWide for the Tenant %q before deletion: %s", req.Name, err)
return ctrl.Result{}, err
}
return ctrl.Result{}, r.enforceTenantFinalizerAbsence(ctx, tenant)
}

// Check if the tenant has a finalizer and if not, add it.
if !controllerutil.ContainsFinalizer(tenant, tenantControllerFinalizer) {
return ctrl.Result{}, r.enforceTenantFinalizerPresence(ctx, tenant)
}

// If the Tenant is drained we remove the binding of cluster roles used to replicate resources and
// delete all replicated resources.
switch tenant.Spec.TenantCondition {
Expand Down Expand Up @@ -226,16 +246,14 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res

// bind permissions

_, err = r.NamespaceManager.BindClusterRoles(ctx, tenant.Spec.ClusterID,
tenant, r.tenantClusterRoles...)
_, err = r.NamespaceManager.BindClusterRoles(ctx, tenant.Spec.ClusterID, tenant, r.tenantClusterRoles...)
if err != nil {
klog.Errorf("Unable to bind the ClusterRoles for the Tenant %q: %s", req.Name, err)
r.EventRecorder.Event(tenant, corev1.EventTypeWarning, "ClusterRolesBindingFailed", err.Error())
return ctrl.Result{}, err
}

_, err = r.NamespaceManager.BindClusterRolesClusterWide(ctx, tenant.Spec.ClusterID,
tenant, r.tenantClusterRolesClusterWide...)
_, err = r.NamespaceManager.BindClusterRolesClusterWide(ctx, tenant.Spec.ClusterID, nil, r.tenantClusterRolesClusterWide...)
if err != nil {
klog.Errorf("Unable to bind the ClusterRolesClusterWide for the Tenant %q: %s", req.Name, err)
r.EventRecorder.Event(tenant, corev1.EventTypeWarning, "ClusterRolesClusterWideBindingFailed", err.Error())
Expand All @@ -246,27 +264,45 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res
return ctrl.Result{}, nil
}

// getCusterRoles returns the ClusterRoles having the `app.kubernetes.io/name` equals to the provided strings.
func (r *TenantReconciler) getClusterRoles(ctx context.Context, rolesAppLabel []string) ([]*rbacv1.ClusterRole, error) {
res := []*rbacv1.ClusterRole{}
for _, roleName := range rolesAppLabel {
var clusterRoles rbacv1.ClusterRoleList
if err := r.List(ctx, &clusterRoles, client.MatchingLabels{consts.K8sAppNameKey: roleName}); err != nil {
return nil, err
}

if len(clusterRoles.Items) == 0 {
return nil, fmt.Errorf("required ClusterRole resources with %s=%q not found", consts.K8sAppNameKey, roleName)
}

for i := range clusterRoles.Items {
res = append(res, &clusterRoles.Items[i])
}
}
return res, nil
}

func (r *TenantReconciler) ensureSetup(ctx context.Context) error {
if r.tenantClusterRoles == nil || len(r.tenantClusterRoles) == 0 {
r.tenantClusterRoles = make([]*rbacv1.ClusterRole, len(tenantClusterRoles))
for i, roleName := range tenantClusterRoles {
role := &rbacv1.ClusterRole{}
if err := r.Get(ctx, client.ObjectKey{Name: roleName}, role); err != nil {
return err
}
r.tenantClusterRoles[i] = role
if len(r.tenantClusterRoles) == 0 {
cRoles, err := r.getClusterRoles(ctx, tenantClusterRolesLabelValues)

if err != nil {
return fmt.Errorf("unable to get ClusterRoles to bind on tenant namespace: %w", err)
}

r.tenantClusterRoles = cRoles
}

if r.tenantClusterRolesClusterWide == nil || len(r.tenantClusterRolesClusterWide) == 0 {
r.tenantClusterRolesClusterWide = make([]*rbacv1.ClusterRole, len(tenantClusterRolesClusterWide))
for i, roleName := range tenantClusterRolesClusterWide {
role := &rbacv1.ClusterRole{}
if err := r.Get(ctx, client.ObjectKey{Name: roleName}, role); err != nil {
return err
}
r.tenantClusterRolesClusterWide[i] = role
if len(r.tenantClusterRolesClusterWide) == 0 {
cRoles, err := r.getClusterRoles(ctx, tenantClusterRolesClusterWideLabelValues)

if err != nil {
return fmt.Errorf("unable to get ClusterRoles to bind cluster-wide: %w", err)
}

r.tenantClusterRolesClusterWide = cRoles
}

return nil
Expand Down Expand Up @@ -338,13 +374,13 @@ func (r *TenantReconciler) handleTenantUncordoned(ctx context.Context, tenant *a
func (r *TenantReconciler) handleTenantDrained(ctx context.Context, tenant *authv1beta1.Tenant) error {
// Delete binding of cluster roles cluster wide
if err := r.NamespaceManager.UnbindClusterRolesClusterWide(ctx, tenant.Spec.ClusterID,
tenantClusterRolesClusterWide...); err != nil {
r.tenantClusterRolesClusterWide...); err != nil {
r.EventRecorder.Event(tenant, corev1.EventTypeWarning, "ClusterRolesClusterWideUnbindingFailed", err.Error())
return err
}

// Delete binding of cluster roles
if err := r.NamespaceManager.UnbindClusterRoles(ctx, tenant.Spec.ClusterID, tenantClusterRoles...); err != nil {
if err := r.NamespaceManager.UnbindClusterRoles(ctx, tenant.Spec.ClusterID, r.tenantClusterRoles...); err != nil {
r.EventRecorder.Event(tenant, corev1.EventTypeWarning, "ClusterRolesUnbindingFailed", err.Error())
return err
}
Expand Down
Loading

0 comments on commit 4261ee3

Please sign in to comment.