feat: loadbalancer source range (#611)

* feat(crd): add LoadBalancerSourceRanges field and integrate with service resource

* test(crd): add tests for CEL validation logic

* feat(webhook): implement LoadBalancerSourceRanges validation logic for CIDRs

* test(webhook): add tests for webhook validation logic

* test: modify Makefile for envtest setup

* docs: add LoadBalancerSourceRanges field to API reference
This commit is contained in:
daseul cho
2024-10-26 02:12:55 +09:00
committed by GitHub
parent 3b1020a8f3
commit 4e8c2b66c0
11 changed files with 325 additions and 6 deletions

View File

@@ -102,7 +102,9 @@ func (r *KubernetesServiceResource) mutate(ctx context.Context, tenantControlPla
switch tenantControlPlane.Spec.ControlPlane.Service.ServiceType {
case kamajiv1alpha1.ServiceTypeLoadBalancer:
r.resource.Spec.Type = corev1.ServiceTypeLoadBalancer
if len(tenantControlPlane.Spec.NetworkProfile.LoadBalancerSourceRanges) > 0 {
r.resource.Spec.LoadBalancerSourceRanges = tenantControlPlane.Spec.NetworkProfile.LoadBalancerSourceRanges
}
if len(address) > 0 {
r.resource.Spec.LoadBalancerIP = address
}

View File

@@ -0,0 +1,58 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"context"
"fmt"
"net"
"gomodules.xyz/jsonpatch/v2"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/webhook/utils"
)
type TenantControlPlaneLoadBalancerSourceRanges struct{}
func (t TenantControlPlaneLoadBalancerSourceRanges) handle(tcp *kamajiv1alpha1.TenantControlPlane) error {
for _, sourceCIDR := range tcp.Spec.NetworkProfile.LoadBalancerSourceRanges {
_, _, err := net.ParseCIDR(sourceCIDR)
if err != nil {
return fmt.Errorf("invalid LoadBalancer source CIDR %s, %s", sourceCIDR, err.Error())
}
}
return nil
}
func (t TenantControlPlaneLoadBalancerSourceRanges) OnCreate(object runtime.Object) AdmissionResponse {
return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
if err := t.handle(tcp); err != nil {
return nil, err
}
return nil, nil
}
}
func (t TenantControlPlaneLoadBalancerSourceRanges) OnDelete(runtime.Object) AdmissionResponse {
return utils.NilOp()
}
func (t TenantControlPlaneLoadBalancerSourceRanges) OnUpdate(object runtime.Object, _ runtime.Object) AdmissionResponse {
return func(ctx context.Context, req admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
if err := t.handle(tcp); err != nil {
return nil, err
}
return nil, nil
}
}

View File

@@ -0,0 +1,64 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package handlers_test
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/webhook/handlers"
)
var _ = Describe("TCP LoadBalancer Source Ranges Webhook", func() {
var (
ctx context.Context
t handlers.TenantControlPlaneLoadBalancerSourceRanges
tcp *kamajiv1alpha1.TenantControlPlane
)
BeforeEach(func() {
t = handlers.TenantControlPlaneLoadBalancerSourceRanges{}
tcp = &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "tcp",
Namespace: "default",
},
Spec: kamajiv1alpha1.TenantControlPlaneSpec{},
}
ctx = context.Background()
})
It("allows creation when valid CIDR ranges are provided", func() {
tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.0.0/24"}
_, err := t.OnCreate(tcp)(ctx, admission.Request{})
Expect(err).ToNot(HaveOccurred())
})
It("allows creation when LoadBalancer service has no CIDR field", func() {
tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer
_, err := t.OnCreate(tcp)(ctx, admission.Request{})
Expect(err).ToNot(HaveOccurred())
})
It("allows creation when LoadBalancer service has an empty CIDR list", func() {
tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{}
_, err := t.OnCreate(tcp)(ctx, admission.Request{})
Expect(err).ToNot(HaveOccurred())
})
It("denies creation when source ranges contain invalid CIDRs", func() {
tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.0.0/33"}
_, err := t.OnCreate(tcp)(ctx, admission.Request{})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("invalid LoadBalancer source CIDR 192.168.0.0/33"))
})
})