Skip to content

Commit

Permalink
fix(ServiceExport): Reject Conflicting Alias Names for ServiceExport …
Browse files Browse the repository at this point in the history
…object (#315)
  • Loading branch information
narmidm authored May 17, 2024
2 parents 4d3fd69 + 5664b02 commit f59b1fc
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 0 deletions.
26 changes: 26 additions & 0 deletions config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,29 @@ webhooks:
- statefulsets
- daemonsets
sideEffects: NoneOnDryRun
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /validate-webhook
failurePolicy: Fail
name: webhook.kubeslice.io
rules:
- apiGroups:
- networking.kubeslice.io
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- serviceexports
sideEffects: NoneOnDryRun
1 change: 1 addition & 0 deletions controllers/slice/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type SliceReconciler struct {
//+kubebuilder:rbac:groups=networking.istio.io,resources=serviceentries,verbs=get;list;create;update;watch;delete
//+kubebuilder:rbac:groups=networking.istio.io,resources=virtualservices,verbs=get;list;create;update;watch;delete
//+kubebuilder:webhook:path=/mutate-webhook,mutating=true,failurePolicy=fail,groups="";apps,resources=pods;deployments;statefulsets;daemonsets,verbs=create;update,versions=v1,name=webhook.kubeslice.io,admissionReviewVersions=v1,sideEffects=NoneOnDryRun
//+kubebuilder:webhook:path=/validate-webhook,mutating=false,failurePolicy=fail,groups="networking.kubeslice.io",resources=serviceexports,verbs=create;update,versions=v1beta1,name=webhook.kubeslice.io,admissionReviewVersions=v1,sideEffects=NoneOnDryRun
//+kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch
//+kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=networking.k8s.io,resources=networkpolicies,verbs=get;list;watch;create;update;patch;delete
Expand Down
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ func main() {
Decoder: admission.NewDecoder(mgr.GetScheme()),
},
})
mgr.GetWebhookServer().Register("/validate-webhook", &webhook.Admission{
Handler: &podwh.WebhookServer{
Client: mgr.GetClient(),
SliceInfoClient: podwh.NewWebhookClient(),
Decoder: admission.NewDecoder(mgr.GetScheme()),
},
})
}
if err != nil {
setupLog.With("error", err).Error("unable to start manager")
Expand Down
47 changes: 47 additions & 0 deletions pkg/webhook/pod/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"net/http"

"github.com/kubeslice/apis/pkg/controller/v1alpha1"
"github.com/kubeslice/worker-operator/api/v1beta1"
"github.com/kubeslice/worker-operator/controllers"
"github.com/kubeslice/worker-operator/pkg/logger"
v1 "k8s.io/api/admission/v1"
Expand Down Expand Up @@ -53,6 +54,7 @@ type SliceInfoProvider interface {
SliceAppNamespaceConfigured(ctx context.Context, slice string, namespace string) (bool, error)
GetNamespaceLabels(ctx context.Context, client client.Client, namespace string) (map[string]string, error)
GetSliceOverlayNetworkType(ctx context.Context, client client.Client, sliceName string) (v1alpha1.NetworkType, error)
GetAllServiceExports(ctx context.Context, client client.Client, slice string) (*v1beta1.ServiceExportList, error)
}

type WebhookServer struct {
Expand Down Expand Up @@ -151,6 +153,24 @@ func (wh *WebhookServer) Handle(ctx context.Context, req admission.Request) admi
return admission.Errored(http.StatusInternalServerError, err)
}
return admission.PatchResponseFromRaw(req.Object.Raw, marshaled)
} else if req.Kind.Kind == "ServiceExport" {
serviceexport := &v1beta1.ServiceExport{}
err := wh.Decoder.Decode(req, serviceexport)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
log := logger.FromContext(ctx)

log.Info("validating serviceexport", "serviceexport spec", serviceexport.Spec)
validation, conflictingAlias, err := wh.ValidateServiceExport(serviceexport, ctx)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
if !validation {
log.Info("serviceexport validation failed: alias already exist", "serviceexport-name", serviceexport.ObjectMeta.Name)
return admission.Denied(fmt.Sprintf("Alias %s already exist", conflictingAlias))
}
return admission.Allowed("")
}

return admission.Response{AdmissionResponse: v1.AdmissionResponse{
Expand Down Expand Up @@ -257,6 +277,33 @@ func MutateDaemonSet(ds *appsv1.DaemonSet, sliceName string) *appsv1.DaemonSet {
return ds
}

func (wh *WebhookServer) ValidateServiceExport(svcex *v1beta1.ServiceExport, ctx context.Context) (bool, string, error) {

log := logger.FromContext(ctx)
log.Info("fetching all serviceexport objects belonging to the slice", "slice", svcex.Spec.Slice)
serviceExportList, err := wh.SliceInfoClient.GetAllServiceExports(context.Background(), wh.Client, svcex.Spec.Slice)
if err != nil {
return false, "", err
}

newAliases := svcex.Spec.Aliases

for _, serviceExport := range serviceExportList.Items {
// In case we are updating an existing ServiceExport resource
if svcex.ObjectMeta.Name == serviceExport.ObjectMeta.Name &&
svcex.ObjectMeta.Namespace == serviceExport.ObjectMeta.Namespace {
continue
}
existingAliases := serviceExport.Spec.Aliases
for _, newAlias := range newAliases {
if aliasExist(existingAliases, newAlias) {
return false, newAlias, nil
}
}
}
return true, "", nil
}

func (wh *WebhookServer) MutationRequired(metadata metav1.ObjectMeta, ctx context.Context, kind string) (bool, string) {
log := logger.FromContext(ctx)
annotations := metadata.GetAnnotations()
Expand Down
136 changes: 136 additions & 0 deletions pkg/webhook/pod/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/kubeslice/apis/pkg/controller/v1alpha1"
"github.com/kubeslice/worker-operator/api/v1beta1"
"github.com/kubeslice/worker-operator/controllers"
"github.com/kubeslice/worker-operator/pkg/webhook/pod"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -45,6 +46,33 @@ func (f fakeWebhookClient) GetNamespaceLabels(ctx context.Context, client client
return map[string]string{controllers.ApplicationNamespaceSelectorLabelKey: "green"}, nil
}

func (f fakeWebhookClient) GetAllServiceExports(ctx context.Context, client client.Client, slice string) (*v1beta1.ServiceExportList, error) {
return &v1beta1.ServiceExportList{
Items: []v1beta1.ServiceExport{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svcex-1",
Namespace: "test-ns-1",
},
Spec: v1beta1.ServiceExportSpec{
Slice: "test-slice",
Aliases: []string{"server.com"},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svcex-2",
Namespace: "test-ns-2",
},
Spec: v1beta1.ServiceExportSpec{
Slice: "test-slice",
Aliases: []string{"traffic.com"},
},
},
},
}, nil
}

func (f fakeWebhookClient) GetSliceOverlayNetworkType(ctx context.Context, client client.Client, sliceName string) (v1alpha1.NetworkType, error) {
return "", nil
}
Expand Down Expand Up @@ -128,3 +156,111 @@ var _ = Describe("Deploy Webhook", func() {
})
})
})

var _ = Describe("Validating Webhook", func() {
fakeWhClient := new(fakeWebhookClient)
webhookServer := pod.WebhookServer{
SliceInfoClient: fakeWhClient,
}
Describe("ValidateServiceExport", func() {
Context("ServiceExport Object with no alias conflict", func() {
serviceExportList := &v1beta1.ServiceExportList{
Items: []v1beta1.ServiceExport{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svcex-3",
Namespace: "test-ns-1",
},
Spec: v1beta1.ServiceExportSpec{
Slice: "test-slice",
Aliases: []string{"hello.com"},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svcex-4",
Namespace: "test-ns-2",
},
Spec: v1beta1.ServiceExportSpec{
Slice: "test-slice",
Aliases: []string{"connect.com"},
},
},
},
}
It("should be created", func() {
for _, serviceExport := range serviceExportList.Items {
is, _, _ := webhookServer.ValidateServiceExport(&serviceExport, context.Background())
Expect(is).To(BeTrue())
}
})
})

Context("Alias already exist", func() {
serviceExportList := &v1beta1.ServiceExportList{
Items: []v1beta1.ServiceExport{
{
ObjectMeta: metav1.ObjectMeta{
Name: "svcex-3",
Namespace: "test-ns-1",
},
Spec: v1beta1.ServiceExportSpec{
Slice: "test-slice",
Aliases: []string{"Server.com"},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "svcex-4",
Namespace: "test-ns-2",
},
Spec: v1beta1.ServiceExportSpec{
Slice: "test-slice",
Aliases: []string{"traffic.com"},
},
},
},
}
It("should be rejected", func() {
for _, serviceExport := range serviceExportList.Items {
is, _, _ := webhookServer.ValidateServiceExport(&serviceExport, context.Background())
Expect(is).To(BeFalse())
}
})
})

Context("Update ServiceExport aliases", func() {
serviceExport := &v1beta1.ServiceExport{
ObjectMeta: metav1.ObjectMeta{
Name: "svcex-2",
Namespace: "test-ns-2",
},
Spec: v1beta1.ServiceExportSpec{
Slice: "test-slice",
Aliases: []string{"Traffic.com", "connect.com"},
},
}
It("should be updated", func() {
is, _, _ := webhookServer.ValidateServiceExport(serviceExport, context.Background())
Expect(is).To(BeTrue())
})
})

Context("Update ServiceExport with conflicting alias", func() {
serviceExport := &v1beta1.ServiceExport{
ObjectMeta: metav1.ObjectMeta{
Name: "svcex-1",
Namespace: "test-ns-1",
},
Spec: v1beta1.ServiceExportSpec{
Slice: "test-slice",
Aliases: []string{"traffic.com"},
},
}
It("should be rejected", func() {
is, _, _ := webhookServer.ValidateServiceExport(serviceExport, context.Background())
Expect(is).To(BeFalse())
})
})
})
})
29 changes: 29 additions & 0 deletions pkg/webhook/pod/webhook_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package pod

import (
"context"
"strings"

"github.com/kubeslice/apis/pkg/controller/v1alpha1"
"github.com/kubeslice/worker-operator/api/v1beta1"
"github.com/kubeslice/worker-operator/controllers"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -33,6 +35,33 @@ func (w *webhookClient) GetNamespaceLabels(ctx context.Context, client client.Cl
return nsLabels, nil
}

// Fetch all serviceexport objects belonging to the slice
func (w *webhookClient) GetAllServiceExports(ctx context.Context, c client.Client, slice string) (*v1beta1.ServiceExportList, error) {

listOpts := []client.ListOption{
client.MatchingLabels(map[string]string{
controllers.ApplicationNamespaceSelectorLabelKey: slice,
},
),
}

serviceExportList := &v1beta1.ServiceExportList{}
if err := c.List(ctx, serviceExportList, listOpts...); err != nil {
log.Info("Failed to get ServiceExportList", "slice", slice)
return nil, err
}
return serviceExportList, nil
}

func aliasExist(existingAliases []string, newAlias string) bool {
for _, alias := range existingAliases {
if strings.EqualFold(alias, newAlias) {
return true
}
}
return false
}

func (w *webhookClient) GetSliceOverlayNetworkType(ctx context.Context, client client.Client, sliceName string) (v1alpha1.NetworkType, error) {
return controllers.GetSliceOverlayNetworkType(ctx, client, sliceName)
}

0 comments on commit f59b1fc

Please sign in to comment.