mirror of
https://github.com/clastix/kamaji.git
synced 2026-03-19 01:50:36 +00:00
Compare commits
3 Commits
26.3.2-edg
...
26.3.3-edg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ed71b1e3e | ||
|
|
a5bfbaaf72 | ||
|
|
adaaef0857 |
@@ -39,7 +39,6 @@ kos:
|
||||
- linux/arm
|
||||
|
||||
release:
|
||||
prerelease: auto
|
||||
footer: |
|
||||
**Container Images**
|
||||
```
|
||||
|
||||
260
api/v1alpha1/networkprofile_types_test.go
Normal file
260
api/v1alpha1/networkprofile_types_test.go
Normal file
@@ -0,0 +1,260 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var _ = Describe("NetworkProfile validation", func() {
|
||||
var (
|
||||
ctx context.Context
|
||||
tcp *TenantControlPlane
|
||||
)
|
||||
|
||||
const (
|
||||
ipv6CIDRBlock = "fd00::/108"
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = context.Background()
|
||||
tcp = &TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "tcp-network-",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: TenantControlPlaneSpec{
|
||||
ControlPlane: ControlPlane{
|
||||
Service: ServiceSpec{
|
||||
ServiceType: ServiceTypeClusterIP,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// When creation is denied by validation, GenerateName is never resolved
|
||||
// and tcp.Name remains empty, so there is nothing to delete.
|
||||
if tcp.Name == "" {
|
||||
return
|
||||
}
|
||||
if err := k8sClient.Delete(ctx, tcp); err != nil && !apierrors.IsNotFound(err) {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
})
|
||||
|
||||
Context("serviceCidr", func() {
|
||||
It("allows creation with the default IPv4 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = "10.96.0.0/16"
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with a non-default valid IPv4 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = "172.16.0.0/12"
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with a valid IPv6 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = ipv6CIDRBlock
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation when serviceCidr is empty", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = ""
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("denies creation with a plain IP address instead of a CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = "10.96.0.1"
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("serviceCidr must be empty or a valid CIDR"))
|
||||
})
|
||||
|
||||
It("denies creation with an arbitrary non-CIDR string", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = "not-a-cidr"
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("serviceCidr must be empty or a valid CIDR"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("podCidr", func() {
|
||||
It("allows creation with the default IPv4 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = "10.244.0.0/16"
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with a non-default valid IPv4 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = "192.168.128.0/17"
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with a valid IPv6 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = "2001:db8::/48"
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation when podCidr is empty", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = ""
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("denies creation with a plain IP address instead of a CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = "10.244.0.1"
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("podCidr must be empty or a valid CIDR"))
|
||||
})
|
||||
|
||||
It("denies creation with an arbitrary non-CIDR string", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = "not-a-cidr"
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("podCidr must be empty or a valid CIDR"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("loadBalancerSourceRanges CIDR format", func() {
|
||||
BeforeEach(func() {
|
||||
tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeLoadBalancer
|
||||
})
|
||||
|
||||
It("allows creation with a single valid CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with multiple valid CIDRs", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{
|
||||
"10.0.0.0/8",
|
||||
"192.168.0.0/24",
|
||||
"172.16.0.0/12",
|
||||
}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with valid IPv6 CIDRs", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{
|
||||
"2001:db8::/32",
|
||||
"fd00::/8",
|
||||
}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("denies creation when an entry is a plain IP address", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.1.1"}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all LoadBalancer source range entries must be valid CIDR"))
|
||||
})
|
||||
|
||||
It("denies creation when an entry is an arbitrary string", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"not-a-cidr"}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all LoadBalancer source range entries must be valid CIDR"))
|
||||
})
|
||||
|
||||
It("denies creation when at least one entry in a mixed list is invalid", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{
|
||||
"10.0.0.0/8",
|
||||
"not-a-cidr",
|
||||
}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all LoadBalancer source range entries must be valid CIDR"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("dnsServiceIPs", func() {
|
||||
BeforeEach(func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = "10.96.0.0/16"
|
||||
})
|
||||
|
||||
It("allows creation when dnsServiceIPs is not set", func() {
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with an explicitly empty dnsServiceIPs list", func() {
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation when all IPs are within the service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{"10.96.0.10"}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with multiple IPs all within the service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{
|
||||
"10.96.0.10",
|
||||
"10.96.0.11",
|
||||
}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("denies creation when a DNS service IP is outside the service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{"192.168.1.10"}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all DNS service IPs must be part of the Service CIDR"))
|
||||
})
|
||||
|
||||
It("denies creation when at least one IP in a mixed list is outside the service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{
|
||||
"10.96.0.10",
|
||||
"192.168.1.10",
|
||||
}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all DNS service IPs must be part of the Service CIDR"))
|
||||
})
|
||||
|
||||
It("allows creation with an IPv6 DNS service IP within an IPv6 service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = ipv6CIDRBlock
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{"fd00::10"}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("denies creation when an IPv6 DNS service IP is outside the IPv6 service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = ipv6CIDRBlock
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{"2001:db8::10"}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all DNS service IPs must be part of the Service CIDR"))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// NetworkProfileSpec defines the desired state of NetworkProfile.
|
||||
// +kubebuilder:validation:XValidation:rule="!has(self.dnsServiceIPs) || self.dnsServiceIPs.all(r, cidr(self.serviceCidr).containsIP(r))",message="all DNS service IPs must be part of the Service CIDR"
|
||||
type NetworkProfileSpec struct {
|
||||
// LoadBalancerSourceRanges restricts the IP ranges that can access
|
||||
// the LoadBalancer type Service. This field defines a list of IP
|
||||
@@ -20,14 +21,16 @@ type NetworkProfileSpec struct {
|
||||
// This feature is useful for restricting access to API servers or services
|
||||
// to specific networks for security purposes.
|
||||
// Example: {"192.168.1.0/24", "10.0.0.0/8"}
|
||||
//+kubebuilder:validation:MaxItems=16
|
||||
//+kubebuilder:validation:XValidation:rule="self.all(r, isCIDR(r))",message="all LoadBalancer source range entries must be valid CIDR"
|
||||
LoadBalancerSourceRanges []string `json:"loadBalancerSourceRanges,omitempty"`
|
||||
// Specify the LoadBalancer class in case of multiple load balancer implementations.
|
||||
// Field supported only for Tenant Control Plane instances exposed using a LoadBalancer Service.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="LoadBalancerClass is immutable"
|
||||
LoadBalancerClass *string `json:"loadBalancerClass,omitempty"`
|
||||
// Address where API server of will be exposed.
|
||||
// In case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
// Address where API server will be exposed.
|
||||
// In the case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
Address string `json:"address,omitempty"`
|
||||
// The default domain name used for DNS resolution within the cluster.
|
||||
//+kubebuilder:default="cluster.local"
|
||||
@@ -37,7 +40,7 @@ type NetworkProfileSpec struct {
|
||||
// AllowAddressAsExternalIP will include tenantControlPlane.Spec.NetworkProfile.Address in the section of
|
||||
// ExternalIPs of the Kubernetes Service (only ClusterIP or NodePort)
|
||||
AllowAddressAsExternalIP bool `json:"allowAddressAsExternalIP,omitempty"`
|
||||
// Port where API server of will be exposed
|
||||
// Port where API server will be exposed
|
||||
//+kubebuilder:default=6443
|
||||
Port int32 `json:"port,omitempty"`
|
||||
// CertSANs sets extra Subject Alternative Names (SANs) for the API Server signing certificate.
|
||||
@@ -45,14 +48,20 @@ type NetworkProfileSpec struct {
|
||||
CertSANs []string `json:"certSANs,omitempty"`
|
||||
// CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.
|
||||
//+kubebuilder:default="10.96.0.0/16"
|
||||
//+kubebuilder:validation:Optional
|
||||
//+kubebuilder:validation:XValidation:rule="self == '' || isCIDR(self)",message="serviceCidr must be empty or a valid CIDR"
|
||||
ServiceCIDR string `json:"serviceCidr,omitempty"`
|
||||
// CIDR for Kubernetes Pods: if empty, defaulted to 10.244.0.0/16.
|
||||
//+kubebuilder:default="10.244.0.0/16"
|
||||
//+kubebuilder:validation:Optional
|
||||
//+kubebuilder:validation:XValidation:rule="self == '' || isCIDR(self)",message="podCidr must be empty or a valid CIDR"
|
||||
PodCIDR string `json:"podCidr,omitempty"`
|
||||
// The DNS Service for internal resolution, it must match the Service CIDR.
|
||||
// In case of an empty value, it is automatically computed according to the Service CIDR, e.g.:
|
||||
// Service CIDR 10.96.0.0/16, the resulting DNS Service IP will be 10.96.0.10 for IPv4,
|
||||
// for IPv6 from the CIDR 2001:db8:abcd::/64 the resulting DNS Service IP will be 2001:db8:abcd::10.
|
||||
//+kubebuilder:validation:MaxItems=8
|
||||
//+kubebuilder:validation:Optional
|
||||
DNSServiceIPs []string `json:"dnsServiceIPs,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
apisv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
@@ -653,6 +654,13 @@ func (in *DataStoreStatus) DeepCopyInto(out *DataStoreStatus) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataStoreStatus.
|
||||
@@ -957,7 +965,7 @@ func (in *JSONPatch) DeepCopyInto(out *JSONPatch) {
|
||||
*out = *in
|
||||
if in.Value != nil {
|
||||
in, out := &in.Value, &out.Value
|
||||
*out = new(v1.JSON)
|
||||
*out = new(apiextensionsv1.JSON)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7576,8 +7576,8 @@ versions:
|
||||
properties:
|
||||
address:
|
||||
description: |-
|
||||
Address where API server of will be exposed.
|
||||
In case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
Address where API server will be exposed.
|
||||
In the case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
type: string
|
||||
allowAddressAsExternalIP:
|
||||
description: |-
|
||||
@@ -7607,6 +7607,7 @@ versions:
|
||||
for IPv6 from the CIDR 2001:db8:abcd::/64 the resulting DNS Service IP will be 2001:db8:abcd::10.
|
||||
items:
|
||||
type: string
|
||||
maxItems: 8
|
||||
type: array
|
||||
loadBalancerClass:
|
||||
description: |-
|
||||
@@ -7628,21 +7629,34 @@ versions:
|
||||
Example: {"192.168.1.0/24", "10.0.0.0/8"}
|
||||
items:
|
||||
type: string
|
||||
maxItems: 16
|
||||
type: array
|
||||
x-kubernetes-validations:
|
||||
- message: all LoadBalancer source range entries must be valid CIDR
|
||||
rule: self.all(r, isCIDR(r))
|
||||
podCidr:
|
||||
default: 10.244.0.0/16
|
||||
description: 'CIDR for Kubernetes Pods: if empty, defaulted to 10.244.0.0/16.'
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: podCidr must be empty or a valid CIDR
|
||||
rule: self == '' || isCIDR(self)
|
||||
port:
|
||||
default: 6443
|
||||
description: Port where API server of will be exposed
|
||||
description: Port where API server will be exposed
|
||||
format: int32
|
||||
type: integer
|
||||
serviceCidr:
|
||||
default: 10.96.0.0/16
|
||||
description: 'CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.'
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: serviceCidr must be empty or a valid CIDR
|
||||
rule: self == '' || isCIDR(self)
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: all DNS service IPs must be part of the Service CIDR
|
||||
rule: '!has(self.dnsServiceIPs) || self.dnsServiceIPs.all(r, cidr(self.serviceCidr).containsIP(r))'
|
||||
writePermissions:
|
||||
description: |-
|
||||
WritePermissions allows to select which operations (create, delete, update) must be blocked:
|
||||
|
||||
@@ -7584,8 +7584,8 @@ spec:
|
||||
properties:
|
||||
address:
|
||||
description: |-
|
||||
Address where API server of will be exposed.
|
||||
In case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
Address where API server will be exposed.
|
||||
In the case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
type: string
|
||||
allowAddressAsExternalIP:
|
||||
description: |-
|
||||
@@ -7615,6 +7615,7 @@ spec:
|
||||
for IPv6 from the CIDR 2001:db8:abcd::/64 the resulting DNS Service IP will be 2001:db8:abcd::10.
|
||||
items:
|
||||
type: string
|
||||
maxItems: 8
|
||||
type: array
|
||||
loadBalancerClass:
|
||||
description: |-
|
||||
@@ -7636,21 +7637,34 @@ spec:
|
||||
Example: {"192.168.1.0/24", "10.0.0.0/8"}
|
||||
items:
|
||||
type: string
|
||||
maxItems: 16
|
||||
type: array
|
||||
x-kubernetes-validations:
|
||||
- message: all LoadBalancer source range entries must be valid CIDR
|
||||
rule: self.all(r, isCIDR(r))
|
||||
podCidr:
|
||||
default: 10.244.0.0/16
|
||||
description: 'CIDR for Kubernetes Pods: if empty, defaulted to 10.244.0.0/16.'
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: podCidr must be empty or a valid CIDR
|
||||
rule: self == '' || isCIDR(self)
|
||||
port:
|
||||
default: 6443
|
||||
description: Port where API server of will be exposed
|
||||
description: Port where API server will be exposed
|
||||
format: int32
|
||||
type: integer
|
||||
serviceCidr:
|
||||
default: 10.96.0.0/16
|
||||
description: 'CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.'
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: serviceCidr must be empty or a valid CIDR
|
||||
rule: self == '' || isCIDR(self)
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: all DNS service IPs must be part of the Service CIDR
|
||||
rule: '!has(self.dnsServiceIPs) || self.dnsServiceIPs.all(r, cidr(self.serviceCidr).containsIP(r))'
|
||||
writePermissions:
|
||||
description: |-
|
||||
WritePermissions allows to select which operations (create, delete, update) must be blocked:
|
||||
|
||||
@@ -255,8 +255,6 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
Scheme: *mgr.GetScheme(),
|
||||
},
|
||||
},
|
||||
handlers.TenantControlPlaneServiceCIDR{},
|
||||
handlers.TenantControlPlaneLoadBalancerSourceRanges{},
|
||||
handlers.TenantControlPlaneGatewayValidation{
|
||||
Client: mgr.GetClient(),
|
||||
DiscoveryClient: discoveryClient,
|
||||
|
||||
@@ -40,7 +40,7 @@ Throughout the following instructions, shell variables are used to indicate valu
|
||||
source kamaji.env
|
||||
```
|
||||
|
||||
Any regular and conformant Kubernetes v1.22+ cluster can be turned into a Kamaji setup. To work properly, the Management Cluster should provide:
|
||||
Any regular and conformant Kubernetes v1.33+ cluster can be turned into a Kamaji setup. To work properly, the Management Cluster should provide:
|
||||
|
||||
- CNI module installed, eg. [Calico](https://github.com/projectcalico/calico), [Cilium](https://github.com/cilium/cilium).
|
||||
- CSI module installed with a Storage Class for the Tenant datastores. The [Local Path Provisioner](https://github.com/rancher/local-path-provisioner) is a suggested choice, even for production environments.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,7 @@ Edge Releases are generally considered production ready and the project will mar
|
||||
|
||||
| Kamaji | Management Cluster | Tenant Cluster |
|
||||
|-------------|--------------------|----------------------|
|
||||
| edge-25.4.1 | v1.22+ | [v1.30.0 .. v1.33.0] |
|
||||
| 26.3.2-edge | v1.33+ | [v1.30.0 .. v1.35.0] |
|
||||
|
||||
|
||||
Using Edge Release artifacts and reporting bugs helps us ensure a rapid pace of development and is a great way to help maintainers.
|
||||
|
||||
2
go.mod
2
go.mod
@@ -35,7 +35,7 @@ require (
|
||||
k8s.io/cluster-bootstrap v0.0.0
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kubelet v0.0.0
|
||||
k8s.io/kubernetes v1.35.1
|
||||
k8s.io/kubernetes v1.35.2
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
|
||||
sigs.k8s.io/controller-runtime v0.22.4
|
||||
sigs.k8s.io/gateway-api v1.4.1
|
||||
|
||||
4
go.sum
4
go.sum
@@ -549,8 +549,8 @@ k8s.io/kube-proxy v0.35.0 h1:erv2wYmGZ6nyu/FtmaIb+ORD3q2rfZ4Fhn7VXs/8cPQ=
|
||||
k8s.io/kube-proxy v0.35.0/go.mod h1:bd9lpN3uLLOOWc/CFZbkPEi9DTkzQQymbE8FqSU4bWk=
|
||||
k8s.io/kubelet v0.35.0 h1:8cgJHCBCKLYuuQ7/Pxb/qWbJfX1LXIw7790ce9xHq7c=
|
||||
k8s.io/kubelet v0.35.0/go.mod h1:ciRzAXn7C4z5iB7FhG1L2CGPPXLTVCABDlbXt/Zz8YA=
|
||||
k8s.io/kubernetes v1.35.1 h1:qmjXSCDPnOuXPuJb5pv+eLzpXhhlD09Jid1pG/OvFU8=
|
||||
k8s.io/kubernetes v1.35.1/go.mod h1:AaPpCpiS8oAqRbEwpY5r3RitLpwpVp5lVXKFkJril58=
|
||||
k8s.io/kubernetes v1.35.2 h1:2HthVDfK3YJYv624imuKXPzUJ17xQop9OT5dgT+IMKE=
|
||||
k8s.io/kubernetes v1.35.2/go.mod h1:AaPpCpiS8oAqRbEwpY5r3RitLpwpVp5lVXKFkJril58=
|
||||
k8s.io/system-validators v1.12.1 h1:AY1+COTLJN/Sj0w9QzH1H0yvyF3Kl6CguMnh32WlcUU=
|
||||
k8s.io/system-validators v1.12.1/go.mod h1:awfSS706v9R12VC7u7K89FKfqVy44G+E0L1A0FX9Wmw=
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// 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(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
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// 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"))
|
||||
})
|
||||
})
|
||||
@@ -1,71 +0,0 @@
|
||||
// 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 TenantControlPlaneServiceCIDR struct{}
|
||||
|
||||
func (t TenantControlPlaneServiceCIDR) handle(tcp *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if tcp.Spec.Addons.CoreDNS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, cidr, err := net.ParseCIDR(tcp.Spec.NetworkProfile.ServiceCIDR)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse Service CIDR, %s", err.Error())
|
||||
}
|
||||
|
||||
for _, serviceIP := range tcp.Spec.NetworkProfile.DNSServiceIPs {
|
||||
ip := net.ParseIP(serviceIP)
|
||||
if ip == nil {
|
||||
return fmt.Errorf("unable to parse IP address %s", serviceIP)
|
||||
}
|
||||
|
||||
if !cidr.Contains(ip) {
|
||||
return fmt.Errorf("the Service CIDR does not contain the DNS Service IP %s", serviceIP)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t TenantControlPlaneServiceCIDR) 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 TenantControlPlaneServiceCIDR) OnDelete(runtime.Object) AdmissionResponse {
|
||||
return utils.NilOp()
|
||||
}
|
||||
|
||||
func (t TenantControlPlaneServiceCIDR) OnUpdate(object runtime.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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user