Skip to content

Commit c6db5a7

Browse files
committed
controllers: webhook for restricting storageclaims to auto created ones
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]>
1 parent 4b75856 commit c6db5a7

File tree

4 files changed

+103
-3
lines changed

4 files changed

+103
-3
lines changed

cmd/main.go

+9
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,15 @@ func main() {
172172
}},
173173
)
174174

175+
setupLog.Info("registering StorageClaim validating webhook endpoint")
176+
hookServer.Register("/validate-storageclim", &webhook.Admission{
177+
Handler: &admwebhook.StorageClaimAdmission{
178+
Client: mgr.GetClient(),
179+
Decoder: admission.NewDecoder(mgr.GetScheme()),
180+
Log: mgr.GetLogger().WithName("webhook.storageclaim"),
181+
}},
182+
)
183+
175184
if err = (&controller.StorageClientReconciler{
176185
Client: mgr.GetClient(),
177186
Scheme: mgr.GetScheme(),

internal/controller/operatorconfigmap_controller.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,6 @@ func (c *OperatorConfigMapReconciler) reconcileSubscriptionValidatingWebhook() e
545545
whConfig := &admrv1.ValidatingWebhookConfiguration{}
546546
whConfig.Name = templates.SubscriptionWebhookName
547547

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

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

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

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

585+
wh = &whConfig.Webhooks[1]
586+
templates.StorageClaimValidatingWebhook.DeepCopyInto(wh)
587+
588+
wh.Name = templates.StorageClaimWebhookName
589+
// preserve the existing (injected) CA bundle if any
590+
// reusing same caBundle as the certs are being generated by same signer
591+
wh.ClientConfig.CABundle = caBundle
592+
// send request to the service running in own namespace
593+
wh.ClientConfig.Service.Namespace = c.OperatorNamespace
586594
return nil
587595
})
588596

pkg/templates/validatingwebhook.go

+27
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
const (
99
SubscriptionWebhookName = "subscription.ocs.openshift.io"
10+
StorageClaimWebhookName = "storageclaim.ocs.openshift.io"
1011
)
1112

1213
var SubscriptionValidatingWebhook = admrv1.ValidatingWebhook{
@@ -34,3 +35,29 @@ var SubscriptionValidatingWebhook = admrv1.ValidatingWebhook{
3435
// fail the validation if webhook can't be reached
3536
FailurePolicy: ptr.To(admrv1.Fail),
3637
}
38+
39+
var StorageClaimValidatingWebhook = admrv1.ValidatingWebhook{
40+
ClientConfig: admrv1.WebhookClientConfig{
41+
Service: &admrv1.ServiceReference{
42+
Name: "ocs-client-operator-webhook-server",
43+
Path: ptr.To("/validate-storageclaim"),
44+
Port: ptr.To(int32(443)),
45+
},
46+
},
47+
Rules: []admrv1.RuleWithOperations{
48+
{
49+
Rule: admrv1.Rule{
50+
APIGroups: []string{"ocs.openshift.io"},
51+
APIVersions: []string{"v1alpha1"},
52+
Resources: []string{"storageclaims"},
53+
Scope: ptr.To(admrv1.ClusterScope),
54+
},
55+
Operations: []admrv1.OperationType{admrv1.Create},
56+
},
57+
},
58+
SideEffects: ptr.To(admrv1.SideEffectClassNone),
59+
TimeoutSeconds: ptr.To(int32(30)),
60+
AdmissionReviewVersions: []string{"v1"},
61+
// fail the validation if webhook can't be reached
62+
FailurePolicy: ptr.To(admrv1.Fail),
63+
}

pkg/webhook/storageclaim.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package webhook
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/red-hat-storage/ocs-client-operator/api/v1alpha1"
9+
10+
"github.com/go-logr/logr"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
13+
)
14+
15+
var (
16+
rbdFormatWithSuffix = "%s-ceph-rbd"
17+
cephFsFormatWithSuffix = "%s-cephfs"
18+
)
19+
20+
type StorageClaimAdmission struct {
21+
Client client.Client
22+
Decoder admission.Decoder
23+
Log logr.Logger
24+
}
25+
26+
func (s *StorageClaimAdmission) Handle(ctx context.Context, req admission.Request) admission.Response {
27+
s.Log.Info("Request received for admission review")
28+
29+
// review should be for a storageClaim
30+
storageClaim := &v1alpha1.StorageClaim{}
31+
if err := s.Decoder.Decode(req, storageClaim); err != nil {
32+
s.Log.Error(err, "failed to decode admission review as storageclaim")
33+
return admission.Errored(http.StatusBadRequest, fmt.Errorf("only storageclaims admission reviews are supported: %v", err))
34+
}
35+
36+
storageClients := &v1alpha1.StorageClientList{}
37+
if err := s.Client.List(ctx, storageClients); err != nil {
38+
s.Log.Error(err, "failed to list storageclients")
39+
return admission.Errored(http.StatusInternalServerError, fmt.Errorf("failed to list storageclients for validating subscription request: %v", err))
40+
}
41+
42+
for idx := range storageClients.Items {
43+
if storageClients.Items[idx].Status.Phase != v1alpha1.StorageClientFailed {
44+
clientName := storageClients.Items[idx].Name
45+
supportedRbdClaim := fmt.Sprintf(rbdFormatWithSuffix, clientName)
46+
supportedCephFsClaim := fmt.Sprintf(cephFsFormatWithSuffix, clientName)
47+
if storageClaim.Name == supportedRbdClaim || storageClaim.Name == supportedCephFsClaim {
48+
s.Log.Info(fmt.Sprintf("Allowing review request as the storageclaim has one of the supported names"))
49+
return admission.Allowed("valid storageclaim")
50+
}
51+
}
52+
}
53+
54+
s.Log.Info(fmt.Sprintf("Rejecting review request as the storageclaim name doesn't match any of the supported names"))
55+
return admission.Denied(fmt.Sprintf("unsupported storageclaim with name %s", storageClaim.Name))
56+
}

0 commit comments

Comments
 (0)