Skip to content

Commit

Permalink
controllers: webhook for restricting storageclaims to auto created ones
Browse files Browse the repository at this point in the history
we only support a single storageclaim of a specific type created
corresponding to a storageclient and this webhook ensures that support.

this helps in avoiding surprises during storageclaim controller removal.

Signed-off-by: Leela Venkaiah G <[email protected]>
  • Loading branch information
leelavg committed Dec 9, 2024
1 parent 4b75856 commit 5becb81
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 3 deletions.
9 changes: 9 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ func main() {
}},
)

setupLog.Info("registering StorageClaim validating webhook endpoint")
hookServer.Register("/validate-storageclim", &webhook.Admission{
Handler: &admwebhook.StorageClaimAdmission{
Client: mgr.GetClient(),
Decoder: admission.NewDecoder(mgr.GetScheme()),
Log: mgr.GetLogger().WithName("webhook.storageclaim"),
}},
)

if err = (&controller.StorageClientReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Expand Down
14 changes: 11 additions & 3 deletions internal/controller/operatorconfigmap_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,6 @@ func (c *OperatorConfigMapReconciler) reconcileSubscriptionValidatingWebhook() e
whConfig := &admrv1.ValidatingWebhookConfiguration{}
whConfig.Name = templates.SubscriptionWebhookName

// TODO (lgangava): after change to configmap controller, need to remove webhook during deletion
err := c.createOrUpdate(whConfig, func() error {

// openshift fills in the ca on finding this annotation
Expand All @@ -555,7 +554,7 @@ func (c *OperatorConfigMapReconciler) reconcileSubscriptionValidatingWebhook() e

var caBundle []byte
if len(whConfig.Webhooks) == 0 {
whConfig.Webhooks = make([]admrv1.ValidatingWebhook, 1)
whConfig.Webhooks = make([]admrv1.ValidatingWebhook, 2)
} else {
// do not mutate CA bundle that was injected by openshift
caBundle = whConfig.Webhooks[0].ClientConfig.CABundle
Expand All @@ -565,7 +564,7 @@ func (c *OperatorConfigMapReconciler) reconcileSubscriptionValidatingWebhook() e
var wh *admrv1.ValidatingWebhook = &whConfig.Webhooks[0]
templates.SubscriptionValidatingWebhook.DeepCopyInto(wh)

wh.Name = whConfig.Name
wh.Name = templates.SubscriptionWebhookName
// only send requests received from own namespace
wh.NamespaceSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
Expand All @@ -583,6 +582,15 @@ func (c *OperatorConfigMapReconciler) reconcileSubscriptionValidatingWebhook() e
// send request to the service running in own namespace
wh.ClientConfig.Service.Namespace = c.OperatorNamespace

wh = &whConfig.Webhooks[1]
templates.StorageClaimValidatingWebhook.DeepCopyInto(wh)

wh.Name = templates.StorageClaimWebhookName
// preserve the existing (injected) CA bundle if any
// reusing same caBundle as the certs are being generated by same signer
wh.ClientConfig.CABundle = caBundle
// send request to the service running in own namespace
wh.ClientConfig.Service.Namespace = c.OperatorNamespace
return nil
})

Expand Down
27 changes: 27 additions & 0 deletions pkg/templates/validatingwebhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

const (
SubscriptionWebhookName = "subscription.ocs.openshift.io"
StorageClaimWebhookName = "storageclaim.ocs.openshift.io"
)

var SubscriptionValidatingWebhook = admrv1.ValidatingWebhook{
Expand Down Expand Up @@ -34,3 +35,29 @@ var SubscriptionValidatingWebhook = admrv1.ValidatingWebhook{
// fail the validation if webhook can't be reached
FailurePolicy: ptr.To(admrv1.Fail),
}

var StorageClaimValidatingWebhook = admrv1.ValidatingWebhook{
ClientConfig: admrv1.WebhookClientConfig{
Service: &admrv1.ServiceReference{
Name: "ocs-client-operator-webhook-server",
Path: ptr.To("/validate-storageclaim"),
Port: ptr.To(int32(443)),
},
},
Rules: []admrv1.RuleWithOperations{
{
Rule: admrv1.Rule{
APIGroups: []string{"ocs.openshift.io"},
APIVersions: []string{"v1alpha1"},
Resources: []string{"storageclaims"},
Scope: ptr.To(admrv1.ClusterScope),
},
Operations: []admrv1.OperationType{admrv1.Create},
},
},
SideEffects: ptr.To(admrv1.SideEffectClassNone),
TimeoutSeconds: ptr.To(int32(30)),
AdmissionReviewVersions: []string{"v1"},
// fail the validation if webhook can't be reached
FailurePolicy: ptr.To(admrv1.Fail),
}
56 changes: 56 additions & 0 deletions pkg/webhook/storageclaim.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package webhook

import (
"context"
"fmt"
"net/http"

"github.com/red-hat-storage/ocs-client-operator/api/v1alpha1"

"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

var (
rbdFormatWithSuffix = "%s-ceph-rbd"
cephFsFormatWithSuffix = "%s-cephfs"
)

type StorageClaimAdmission struct {
Client client.Client
Decoder admission.Decoder
Log logr.Logger
}

func (s *StorageClaimAdmission) Handle(ctx context.Context, req admission.Request) admission.Response {
s.Log.Info("Request received for admission review")

// review should be for a storageClaim
storageClaim := &v1alpha1.StorageClaim{}
if err := s.Decoder.Decode(req, storageClaim); err != nil {
s.Log.Error(err, "failed to decode admission review as storageclaim")
return admission.Errored(http.StatusBadRequest, fmt.Errorf("only storageclaims admission reviews are supported: %v", err))
}

storageClients := &v1alpha1.StorageClientList{}
if err := s.Client.List(ctx, storageClients); err != nil {
s.Log.Error(err, "failed to list storageclients")
return admission.Errored(http.StatusInternalServerError, fmt.Errorf("failed to list storageclients for validating subscription request: %v", err))
}

for idx := range storageClients.Items {
if storageClients.Items[idx].Status.Phase != v1alpha1.StorageClientFailed {
clientName := storageClients.Items[idx].Name
supportedRbdClaim := fmt.Sprintf(rbdFormatWithSuffix, clientName)
supportedCephFsClaim := fmt.Sprintf(cephFsFormatWithSuffix, clientName)
if storageClaim.Name == supportedRbdClaim || storageClaim.Name == supportedCephFsClaim {
s.Log.Info("Allowing review request as the storageclaim has one of the supported names")
return admission.Allowed("valid storageclaim")
}
}
}

s.Log.Info("Rejecting review request as the storageclaim name doesn't match any of the supported names")
return admission.Denied(fmt.Sprintf("unsupported storageclaim with name %s", storageClaim.Name))
}

0 comments on commit 5becb81

Please sign in to comment.