forked from projectcapsule/capsule
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: pv labelling and preventing cross-tenant mount
- Loading branch information
1 parent
b58cb19
commit ea88b10
Showing
5 changed files
with
277 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// Copyright 2020-2021 Clastix Labs | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package pv | ||
|
||
import ( | ||
"context" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/api/errors" | ||
"k8s.io/client-go/util/retry" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/builder" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
log2 "sigs.k8s.io/controller-runtime/pkg/log" | ||
"sigs.k8s.io/controller-runtime/pkg/predicate" | ||
"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
|
||
capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" | ||
capsuleutils "github.com/clastix/capsule/pkg/utils" | ||
webhookutils "github.com/clastix/capsule/pkg/webhook/utils" | ||
) | ||
|
||
type Controller struct { | ||
client client.Client | ||
label string | ||
} | ||
|
||
func (c *Controller) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { | ||
log := log2.FromContext(ctx) | ||
|
||
persistentVolume := corev1.PersistentVolume{} | ||
if err := c.client.Get(ctx, request.NamespacedName, &persistentVolume); err != nil { | ||
if errors.IsNotFound(err) { | ||
log.Info("skipping reconciliation, resource may have been deleted") | ||
|
||
return reconcile.Result{}, nil | ||
} | ||
|
||
log.Error(err, "cannot retrieve corev1.PersistentVolume") | ||
|
||
return reconcile.Result{}, err | ||
} | ||
|
||
if persistentVolume.Spec.ClaimRef == nil { | ||
log.Info("skipping reconciliation, missing claimRef") | ||
|
||
return reconcile.Result{}, nil | ||
} | ||
|
||
tnt, err := webhookutils.TenantByStatusNamespace(ctx, c.client, persistentVolume.Spec.ClaimRef.Namespace) | ||
if err != nil { | ||
log.Error(err, "unable to retrieve Tenant from the claimRef") | ||
|
||
return reconcile.Result{}, err | ||
} | ||
|
||
if tnt == nil { | ||
log.Info("skipping reconciliation, PV is claimed by a PVC not managed in a Tenant") | ||
|
||
return reconcile.Result{}, nil | ||
} | ||
|
||
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { | ||
pv := persistentVolume | ||
|
||
if err = c.client.Get(ctx, request.NamespacedName, &pv); err != nil { | ||
return err | ||
} | ||
|
||
labels := pv.GetLabels() | ||
if labels == nil { | ||
labels = map[string]string{} | ||
} | ||
|
||
labels[c.label] = tnt.GetName() | ||
|
||
pv.SetLabels(labels) | ||
|
||
return c.client.Update(ctx, &pv) | ||
}) | ||
if retryErr != nil { | ||
log.Error(retryErr, "unable to update PersistentVolume with Capsule label") | ||
|
||
return reconcile.Result{}, retryErr | ||
} | ||
|
||
return reconcile.Result{}, nil | ||
} | ||
|
||
func (c *Controller) SetupWithManager(mgr ctrl.Manager) error { | ||
label, err := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
c.client = mgr.GetClient() | ||
c.label = label | ||
|
||
return ctrl.NewControllerManagedBy(mgr). | ||
For(&corev1.PersistentVolume{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { | ||
pv, ok := object.(*corev1.PersistentVolume) | ||
if !ok { | ||
return false | ||
} | ||
|
||
if pv.Spec.ClaimRef == nil { | ||
return false | ||
} | ||
|
||
labels := object.GetLabels() | ||
_, ok = labels[c.label] | ||
|
||
return !ok | ||
}))). | ||
Complete(c) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Copyright 2020-2021 Clastix Labs | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package pvc | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/api/errors" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/client-go/tools/record" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||
|
||
capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" | ||
capsulewebhook "github.com/clastix/capsule/pkg/webhook" | ||
"github.com/clastix/capsule/pkg/webhook/utils" | ||
) | ||
|
||
type PV struct { | ||
capsuleLabel string | ||
} | ||
|
||
func PersistentVolumeReuse() capsulewebhook.Handler { | ||
value, err := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{}) | ||
if err != nil { | ||
panic(fmt.Sprintf("this shouldn't happen: %s", err.Error())) | ||
} | ||
|
||
return &PV{ | ||
capsuleLabel: value, | ||
} | ||
} | ||
|
||
func (p PV) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { | ||
return func(ctx context.Context, req admission.Request) *admission.Response { | ||
pvc := corev1.PersistentVolumeClaim{} | ||
if err := decoder.Decode(req, &pvc); err != nil { | ||
return utils.ErroredResponse(err) | ||
} | ||
|
||
tnt, err := utils.TenantByStatusNamespace(ctx, client, pvc.GetNamespace()) | ||
if err != nil { | ||
return utils.ErroredResponse(err) | ||
} | ||
// PVC is not in a Tenant Namespace, skipping | ||
if tnt == nil { | ||
return nil | ||
} | ||
// A PersistentVolume selector cannot help in preventing a cross-tenant mount: | ||
// thus, disallowing that in first place. | ||
if pvc.Spec.Selector != nil { | ||
return utils.ErroredResponse(NewPVSelectorError()) | ||
} | ||
// The PVC hasn't any volumeName pre-claimed, it can be skipped | ||
if len(pvc.Spec.VolumeName) == 0 { | ||
return nil | ||
} | ||
// Checking if the PV is labelled with the Tenant name | ||
pv := corev1.PersistentVolume{} | ||
if err = client.Get(ctx, types.NamespacedName{Name: pvc.Spec.VolumeName}, &pv); err != nil { | ||
if errors.IsNotFound(err) { | ||
err = fmt.Errorf("cannot create a PVC referring to a not yet existing PV") | ||
} | ||
|
||
return utils.ErroredResponse(err) | ||
} | ||
|
||
if pv.GetLabels() == nil { | ||
return utils.ErroredResponse(NewMissingPVLabelsError(pv.GetName())) | ||
} | ||
|
||
value, ok := pv.GetLabels()[p.capsuleLabel] | ||
if !ok { | ||
return utils.ErroredResponse(NewMissingTenantPVLabelsError(pv.GetName())) | ||
} | ||
|
||
if value != p.capsuleLabel { | ||
return utils.ErroredResponse(NewCrossTenantPVMountError(pv.GetName())) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
func (p PV) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { | ||
return func(context.Context, admission.Request) *admission.Response { | ||
return nil | ||
} | ||
} | ||
|
||
func (p PV) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { | ||
return func(context.Context, admission.Request) *admission.Response { | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters