Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apis/authentication/v1beta1/tenant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ type TenantSpec struct {
// +kubebuilder:validation:Enum=Active;Cordoned;Drained
// +kubebuilder:default=Active
TenantCondition TenantCondition `json:"tenantCondition,omitempty"`
// ConsumerAPIServerURL is the URL of the consumer cluster's API server.
// This is used by the provider cluster to fetch the consumer's Liqo version.
// +kubebuilder:validation:Optional
ConsumerAPIServerURL string `json:"consumerAPIServerURL,omitempty"`
// ConsumerVersionReaderToken is the token used to authenticate when fetching the consumer's Liqo version.
// This should be the token from the liqo-version-reader ServiceAccount on the consumer cluster.
// +kubebuilder:validation:Optional
ConsumerVersionReaderToken string `json:"consumerVersionReaderToken,omitempty"`
}

// TenantCondition contains the conditions of the tenant.
Expand Down
4 changes: 4 additions & 0 deletions apis/core/v1beta1/foreigncluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ type ForeignClusterStatus struct {
// +kubebuilder:validation:Optional
TenantNamespace TenantNamespaceType `json:"tenantNamespace"`

// RemoteVersion is the Liqo version running on the remote cluster.
// +kubebuilder:validation:Optional
RemoteVersion string `json:"remoteVersion,omitempty"`

// Generic conditions related to the foreign cluster.
Conditions []Condition `json:"conditions,omitempty"`
}
Expand Down
11 changes: 11 additions & 0 deletions cmd/liqo-controller-manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import (
foreignclustercontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/core/foreigncluster-controller"
ipmapping "github.com/liqotech/liqo/pkg/liqo-controller-manager/ipmapping"
quotacreatorcontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/quotacreator-controller"
versionpkg "github.com/liqotech/liqo/pkg/liqo-controller-manager/version"
virtualnodecreatorcontroller "github.com/liqotech/liqo/pkg/liqo-controller-manager/virtualnodecreator-controller"
tenantnamespace "github.com/liqotech/liqo/pkg/tenantNamespace"
dynamicutils "github.com/liqotech/liqo/pkg/utils/dynamic"
Expand Down Expand Up @@ -163,6 +164,13 @@ func run(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("unable to setup the indexer for the Pod nodeName field: %w", err)
}

// Setup version resources (ConfigMap, Role, RoleBinding) for remote clusters to read the local Liqo version.
// Read the version from the liqo-controller-manager deployment's image tag
liqoVersion := versionpkg.GetVersionFromDeployment(cmd.Context(), clientset, opts.LiqoNamespace, "liqo-controller-manager")
if err := versionpkg.SetupVersionResources(cmd.Context(), clientset, opts.LiqoNamespace, liqoVersion); err != nil {
return fmt.Errorf("unable to setup version resources: %w", err)
}

namespaceManager := tenantnamespace.NewCachedManager(cmd.Context(), clientset, scheme)

// Setup operators for each module:
Expand Down Expand Up @@ -285,6 +293,9 @@ func run(cmd *cobra.Command, _ []string) error {
AuthenticationEnabled: opts.AuthenticationEnabled,
OffloadingEnabled: opts.OffloadingEnabled,

IdentityManager: idManager,
LiqoNamespace: opts.LiqoNamespace,

APIServerCheckers: foreignclustercontroller.NewAPIServerCheckers(idManager, opts.ForeignClusterPingInterval, opts.ForeignClusterPingTimeout),
}
if err = foreignClusterReconciler.SetupWithManager(mgr, opts.ForeignClusterWorkers); err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ spec:
x-kubernetes-validations:
- message: ClusterID is immutable
rule: self == oldSelf
consumerAPIServerURL:
description: |-
ConsumerAPIServerURL is the URL of the consumer cluster's API server.
This is used by the provider cluster to fetch the consumer's Liqo version.
type: string
consumerVersionReaderToken:
description: |-
ConsumerVersionReaderToken is the token used to authenticate when fetching the consumer's Liqo version.
This should be the token from the liqo-version-reader ServiceAccount on the consumer cluster.
type: string
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 @@ -322,6 +322,10 @@ spec:
- networking
- offloading
type: object
remoteVersion:
description: RemoteVersion is the Liqo version running on the remote
cluster.
type: string
role:
default: Unknown
description: Role of the ForeignCluster.
Expand Down
16 changes: 16 additions & 0 deletions deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- get
- update
- apiGroups:
- ""
resources:
Expand Down Expand Up @@ -334,6 +342,14 @@ rules:
- patch
- update
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- create
- get
- update
- apiGroups:
- storage
- storage.k8s.io
Expand Down
42 changes: 42 additions & 0 deletions docs/usage/version-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Summary
Enables querying a remote Liqo cluster's version without establishing full peering, using only a minimal authentication token.

## Motivation
Before initiating peering, administrators need to check version compatibility between clusters. This feature allows version queries without full peering setup.

## Changes

### Version Query Infrastructure
- Added `QueryRemoteVersion()` function for standalone version queries
- Created `liqo-version` ConfigMap to expose local cluster version
- Set up `liqo-version-reader` ServiceAccount with minimal RBAC permissions
- Added token-based authentication for reading version ConfigMap

### Helper Functions
- `GetLocalVersion()`: Retrieve local cluster version from ConfigMap
- `GetVersionReaderToken()`: Extract token from secret
- `GetRemoteVersionWithToken()`: Query remote version with minimal auth

### Supporting Features
- Version resources auto-created at liqo-controller-manager startup
- ForeignCluster auto-creation for tenant consumers (enables bidirectional detection)
- Comprehensive unit tests (21 test specs)

## Testing
- ✅ All version package unit tests pass (17/17 specs)
- ✅ Tenant controller tests added
- ✅ `make generate` runs successfully
- ✅ RBAC auto-generated correctly

## Usage Example
```bash
# Extract token from consumer cluster
kubectl get secret -n liqo liqo-version-reader-token \
-o jsonpath='{.data.token}' | base64 -d > token

# Query version from any cluster
kubectl --server=https://consumer-cluster:6443 \
--token="$(cat token)" \
--insecure-skip-tls-verify \
get configmap liqo-version -n liqo \
-o jsonpath='{.data.version}'
18 changes: 10 additions & 8 deletions pkg/liqo-controller-manager/authentication/forge/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import (

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

return tenant
}
Expand All @@ -48,7 +48,7 @@ func Tenant(remoteClusterID liqov1beta1.ClusterID, namespace *string) *authv1bet

// MutateTenant mutates a Tenant resource.
func MutateTenant(tenant *authv1beta1.Tenant, remoteClusterID liqov1beta1.ClusterID,
publicKey, csr, signature []byte, proxyURL *string) {
publicKey, csr, signature []byte, proxyURL *string, apiServerURL, versionReaderToken string) {
if tenant.Labels == nil {
tenant.Labels = map[string]string{}
}
Expand All @@ -60,10 +60,12 @@ func MutateTenant(tenant *authv1beta1.Tenant, remoteClusterID liqov1beta1.Cluste
}

tenant.Spec = authv1beta1.TenantSpec{
ClusterID: remoteClusterID,
PublicKey: publicKey,
CSR: csr,
Signature: signature,
ProxyURL: proxyURLPtr,
ClusterID: remoteClusterID,
PublicKey: publicKey,
CSR: csr,
Signature: signature,
ProxyURL: proxyURLPtr,
ConsumerAPIServerURL: apiServerURL,
ConsumerVersionReaderToken: versionReaderToken,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/record"
Expand All @@ -30,6 +31,7 @@ import (
controllerutil "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"
identitymanager "github.com/liqotech/liqo/pkg/identityManager"
"github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication"
Expand Down Expand Up @@ -94,6 +96,7 @@ func NewTenantReconciler(cl client.Client, scheme *runtime.Scheme, config *rest.
// cluster-role
// +kubebuilder:rbac:groups=authentication.liqo.io,resources=tenants;tenants/status,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=authentication.liqo.io,resources=tenants;tenants/finalizers,verbs=update
// +kubebuilder:rbac:groups=core.liqo.io,resources=foreignclusters,verbs=get;list;watch;create;update;patch
// +kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=namespaces/finalizers,verbs=update
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;deletecollection;delete
Expand Down Expand Up @@ -265,6 +268,12 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res
}
}

// Ensure a ForeignCluster exists for the consumer cluster to enable bidirectional version detection
if err := r.ensureForeignCluster(ctx, tenant); err != nil {
klog.Errorf("Unable to ensure ForeignCluster for Tenant %q: %s", req.Name, err)
// Don't fail the reconciliation if ForeignCluster creation fails, just log it
}

return ctrl.Result{}, nil
}

Expand Down Expand Up @@ -375,6 +384,53 @@ func (r *TenantReconciler) handleTenantUncordoned(ctx context.Context, tenant *a
return nil
}

// ensureForeignCluster ensures a ForeignCluster exists for the consumer cluster.
// This enables bidirectional version detection even in unidirectional peering scenarios.
func (r *TenantReconciler) ensureForeignCluster(ctx context.Context, tenant *authv1beta1.Tenant) error {
clusterID := tenant.Spec.ClusterID

// Check if a ForeignCluster already exists
var existingFC liqov1beta1.ForeignCluster
err := r.Get(ctx, client.ObjectKey{Name: string(clusterID)}, &existingFC)
if err == nil {
// ForeignCluster already exists, nothing to do
klog.V(6).Infof("ForeignCluster for consumer cluster %q already exists", clusterID)
return nil
}

if !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to check for existing ForeignCluster: %w", err)
}

// ForeignCluster doesn't exist, create it
foreignCluster := &liqov1beta1.ForeignCluster{
ObjectMeta: metav1.ObjectMeta{
Name: string(clusterID),
Labels: map[string]string{
consts.RemoteClusterID: string(clusterID),
},
},
Spec: liqov1beta1.ForeignClusterSpec{
ClusterID: clusterID,
},
}

if err := r.Create(ctx, foreignCluster); err != nil {
if apierrors.IsAlreadyExists(err) {
// Race condition - another controller created it
klog.V(6).Infof("ForeignCluster for consumer cluster %q was created concurrently", clusterID)
return nil
}
return fmt.Errorf("failed to create ForeignCluster: %w", err)
}

klog.Infof("Created ForeignCluster for consumer cluster %q", clusterID)
r.EventRecorder.Event(tenant, corev1.EventTypeNormal, "ForeignClusterCreated",
fmt.Sprintf("ForeignCluster created for consumer cluster %s", clusterID))

return nil
}

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,
Expand Down
Loading