Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Support for loadBalancerSourceRanges on the envoy service #2878

Merged
merged 9 commits into from
Apr 19, 2024
9 changes: 9 additions & 0 deletions api/v1alpha1/shared_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ const (

// KubernetesServiceSpec defines the desired state of the Kubernetes service resource.
// +kubebuilder:validation:XValidation:message="allocateLoadBalancerNodePorts can only be set for LoadBalancer type",rule="!has(self.allocateLoadBalancerNodePorts) || self.type == 'LoadBalancer'"
// +kubebuilder:validation:XValidation:message="loadBalancerSourceRanges can only be set for LoadBalancer type",rule="!has(self.loadBalancerSourceRanges) || self.type == 'LoadBalancer'"
// +kubebuilder:validation:XValidation:message="loadBalancerIP can only be set for LoadBalancer type",rule="!has(self.loadBalancerIP) || self.type == 'LoadBalancer'"
type KubernetesServiceSpec struct {
// Annotations that should be appended to the service.
Expand Down Expand Up @@ -250,6 +251,14 @@ type KubernetesServiceSpec struct {
// +optional
AllocateLoadBalancerNodePorts *bool `json:"allocateLoadBalancerNodePorts,omitempty"`

// LoadBalancerSourceRanges defines a list of allowed IP addresses which will be configured as
// firewall rules on the platform providers load balancer. This is not guaranteed to be working as
// it happens outside of kubernetes and has to be supported and handled by the platform provider.
// This field may only be set for services with type LoadBalancer and will be cleared if the type
// is changed to any other type.
// +optional
LoadBalancerSourceRanges []string `json:"loadBalancerSourceRanges,omitempty"`

// LoadBalancerIP defines the IP Address of the underlying load balancer service. This field
// may be ignored if the load balancer provider does not support this feature.
// This field has been deprecated in Kubernetes, but it is still used for setting the IP Address in some cloud
Expand Down
13 changes: 13 additions & 0 deletions api/v1alpha1/validation/envoyproxy_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package validation
import (
"errors"
"fmt"
"net"
"net/netip"

bootstrapv3 "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3"
Expand Down Expand Up @@ -115,6 +116,18 @@ func validateService(spec *egv1a1.EnvoyProxySpec) []error {
errs = append(errs, fmt.Errorf("allocateLoadBalancerNodePorts can only be set for %v type", egv1a1.ServiceTypeLoadBalancer))
}
}
if serviceType, serviceLoadBalancerSourceRanges :=
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a IP Address check in here

spec.Provider.Kubernetes.EnvoyService.Type, spec.Provider.Kubernetes.EnvoyService.LoadBalancerSourceRanges; serviceType != nil && serviceLoadBalancerSourceRanges != nil {
if *serviceType != egv1a1.ServiceTypeLoadBalancer {
errs = append(errs, fmt.Errorf("loadBalancerSourceRanges can only be set for %v type", egv1a1.ServiceTypeLoadBalancer))
}

for _, serviceLoadBalancerSourceRange := range serviceLoadBalancerSourceRanges {
if ip, _, err := net.ParseCIDR(serviceLoadBalancerSourceRange); err != nil || ip.To4() == nil {
errs = append(errs, fmt.Errorf("loadBalancerSourceRange:%s is an invalid IPv4 subnet", serviceLoadBalancerSourceRange))
}
}
}
if serviceType, serviceLoadBalancerIP := spec.Provider.Kubernetes.EnvoyService.Type, spec.Provider.Kubernetes.EnvoyService.LoadBalancerIP; serviceType != nil && serviceLoadBalancerIP != nil {
if *serviceType != egv1a1.ServiceTypeLoadBalancer {
errs = append(errs, fmt.Errorf("loadBalancerIP can only be set for %v type", egv1a1.ServiceTypeLoadBalancer))
Expand Down
43 changes: 43 additions & 0 deletions api/v1alpha1/validation/envoyproxy_validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,49 @@ func TestValidateEnvoyProxy(t *testing.T) {
},
expected: false,
},

{
name: "envoy service type 'LoadBalancer' with loadBalancerSourceRanges",
proxy: &egv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.EnvoyProxySpec{
Provider: &egv1a1.EnvoyProxyProvider{
Type: egv1a1.ProviderTypeKubernetes,
Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
EnvoyService: &egv1a1.KubernetesServiceSpec{
Type: egv1a1.GetKubernetesServiceType(egv1a1.ServiceTypeLoadBalancer),
LoadBalancerSourceRanges: []string{"1.1.1.1/32"},
},
},
},
},
},
expected: true,
},
{
name: "non envoy service type 'LoadBalancer' with loadBalancerSourceRanges",
proxy: &egv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.EnvoyProxySpec{
Provider: &egv1a1.EnvoyProxyProvider{
Type: egv1a1.ProviderTypeKubernetes,
Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
EnvoyService: &egv1a1.KubernetesServiceSpec{
Type: egv1a1.GetKubernetesServiceType(egv1a1.ServiceTypeClusterIP),
LoadBalancerSourceRanges: []string{"1.1.1.1/32"},
},
},
},
},
},
expected: false,
},
{
name: "envoy service type 'LoadBalancer' with valid loadBalancerIP",
proxy: &egv1a1.EnvoyProxy{
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5761,6 +5761,16 @@ spec:
x-kubernetes-validations:
- message: loadBalancerIP must be a valid IPv4 address
rule: self.matches(r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")
loadBalancerSourceRanges:
description: |-
LoadBalancerSourceRanges defines a list of allowed IP addresses which will be configured as
firewall rules on the platform providers load balancer. This is not guaranteed to be working as
it happens outside of kubernetes and has to be supported and handled by the platform provider.
This field may only be set for services with type LoadBalancer and will be cleared if the type
is changed to any other type.
items:
type: string
type: array
patch:
description: Patch defines how to perform the patch operation
to the service
Expand Down Expand Up @@ -5798,6 +5808,10 @@ spec:
LoadBalancer type
rule: '!has(self.allocateLoadBalancerNodePorts) || self.type
== ''LoadBalancer'''
- message: loadBalancerSourceRanges can only be set for LoadBalancer
type
rule: '!has(self.loadBalancerSourceRanges) || self.type
== ''LoadBalancer'''
- message: loadBalancerIP can only be set for LoadBalancer
type
rule: '!has(self.loadBalancerIP) || self.type == ''LoadBalancer'''
Expand Down
3 changes: 3 additions & 0 deletions internal/infrastructure/kubernetes/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ func ExpectedServiceSpec(service *egv1a1.KubernetesServiceSpec) corev1.ServiceSp
if service.AllocateLoadBalancerNodePorts != nil {
serviceSpec.AllocateLoadBalancerNodePorts = service.AllocateLoadBalancerNodePorts
}
if service.LoadBalancerSourceRanges != nil && len(service.LoadBalancerSourceRanges) > 0 {
serviceSpec.LoadBalancerSourceRanges = service.LoadBalancerSourceRanges
}
if service.LoadBalancerIP != nil {
serviceSpec.LoadBalancerIP = *service.LoadBalancerIP
}
Expand Down
13 changes: 13 additions & 0 deletions internal/infrastructure/kubernetes/resource/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ func TestExpectedServiceSpec(t *testing.T) {
ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeLocal,
},
},
{
name: "LoadBalancerWithLoadBalancerSourceRanges",
args: args{service: &egv1a1.KubernetesServiceSpec{
Type: egv1a1.GetKubernetesServiceType(egv1a1.ServiceTypeLoadBalancer),
LoadBalancerSourceRanges: []string{"1.1.1.1/32"},
}},
want: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerSourceRanges: []string{"1.1.1.1/32"},
SessionAffinity: corev1.ServiceAffinityNone,
ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeLocal,
},
},
{
name: "LoadBalancerWithLoadBalancerIP",
args: args{service: &egv1a1.KubernetesServiceSpec{
Expand Down
1 change: 1 addition & 0 deletions site/content/en/latest/api/extension_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -1805,6 +1805,7 @@ _Appears in:_
| `type` | _[ServiceType](#servicetype)_ | false | Type determines how the Service is exposed. Defaults to LoadBalancer.<br />Valid options are ClusterIP, LoadBalancer and NodePort.<br />"LoadBalancer" means a service will be exposed via an external load balancer (if the cloud provider supports it).<br />"ClusterIP" means a service will only be accessible inside the cluster, via the cluster IP.<br />"NodePort" means a service will be exposed on a static Port on all Nodes of the cluster. |
| `loadBalancerClass` | _string_ | false | LoadBalancerClass, when specified, allows for choosing the LoadBalancer provider<br />implementation if more than one are available or is otherwise expected to be specified |
| `allocateLoadBalancerNodePorts` | _boolean_ | false | AllocateLoadBalancerNodePorts defines if NodePorts will be automatically allocated for<br />services with type LoadBalancer. Default is "true". It may be set to "false" if the cluster<br />load-balancer does not rely on NodePorts. If the caller requests specific NodePorts (by specifying a<br />value), those requests will be respected, regardless of this field. This field may only be set for<br />services with type LoadBalancer and will be cleared if the type is changed to any other type. |
| `loadBalancerSourceRanges` | _string array_ | false | LoadBalancerSourceRanges defines a list of allowed IP addresses which will be configured as<br />firewall rules on the platform providers load balancer. This is not guaranteed to be working as<br />it happens outside of kubernetes and has to be supported and handled by the platform provider.<br />This field may only be set for services with type LoadBalancer and will be cleared if the type<br />is changed to any other type. |
| `loadBalancerIP` | _string_ | false | LoadBalancerIP defines the IP Address of the underlying load balancer service. This field<br />may be ignored if the load balancer provider does not support this feature.<br />This field has been deprecated in Kubernetes, but it is still used for setting the IP Address in some cloud<br />providers such as GCP. |
| `externalTrafficPolicy` | _[ServiceExternalTrafficPolicy](#serviceexternaltrafficpolicy)_ | false | ExternalTrafficPolicy determines the externalTrafficPolicy for the Envoy Service. Valid options<br />are Local and Cluster. Default is "Local". "Local" means traffic will only go to pods on the node<br />receiving the traffic. "Cluster" means connections are loadbalanced to all pods in the cluster. |
| `patch` | _[KubernetesPatchSpec](#kubernetespatchspec)_ | false | Patch defines how to perform the patch operation to the service |
Expand Down
50 changes: 50 additions & 0 deletions test/cel-validation/envoyproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,56 @@ func TestEnvoyProxyProvider(t *testing.T) {
},
wantErrors: []string{"allocateLoadBalancerNodePorts can only be set for LoadBalancer type"},
},
{
desc: "loadBalancerSourceRanges-pass-case1",
mutate: func(envoy *egv1a1.EnvoyProxy) {
envoy.Spec = egv1a1.EnvoyProxySpec{
Provider: &egv1a1.EnvoyProxyProvider{
Type: egv1a1.ProviderTypeKubernetes,
Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
EnvoyService: &egv1a1.KubernetesServiceSpec{
Type: ptr.To(egv1a1.ServiceTypeLoadBalancer),
LoadBalancerSourceRanges: []string{"1.1.1.1"},
},
},
},
}
},
wantErrors: []string{},
},
{
desc: "loadBalancerSourceRanges-pass-case2",
mutate: func(envoy *egv1a1.EnvoyProxy) {
envoy.Spec = egv1a1.EnvoyProxySpec{
Provider: &egv1a1.EnvoyProxyProvider{
Type: egv1a1.ProviderTypeKubernetes,
Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
EnvoyService: &egv1a1.KubernetesServiceSpec{
Type: ptr.To(egv1a1.ServiceTypeClusterIP),
},
},
},
}
},
wantErrors: []string{},
},
{
desc: "loadBalancerSourceRanges-fail",
mutate: func(envoy *egv1a1.EnvoyProxy) {
envoy.Spec = egv1a1.EnvoyProxySpec{
Provider: &egv1a1.EnvoyProxyProvider{
Type: egv1a1.ProviderTypeKubernetes,
Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
EnvoyService: &egv1a1.KubernetesServiceSpec{
Type: ptr.To(egv1a1.ServiceTypeClusterIP),
LoadBalancerSourceRanges: []string{"1.1.1.1"},
},
},
},
}
},
wantErrors: []string{"loadBalancerSourceRanges can only be set for LoadBalancer type"},
},
{
desc: "ServiceTypeLoadBalancer-with-valid-IP",
mutate: func(envoy *egv1a1.EnvoyProxy) {
Expand Down
Loading