kubernetes/pkg/api/service/warnings_test.go
Antonio Ojea 1b804fc87c Services API: warnings
The Services API should warn users about some IP addresses
representations, mainly because some of them are not allowed
by the golang std parsers since go 1.17

Specifically:

- IPv4 addresses with leading zeros, that may cause security risks
- IPv6 addresses in non canonical format, that may cause problems
with controllers hotlooping or cause security issues

Change-Id: Ife50a651d1b22dc4c318e42bd3e5f2e5f88ecbcd
2022-12-16 11:54:05 +00:00

245 lines
9.1 KiB
Go

/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package service
import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
)
func TestGetWarningsForServiceClusterIPs(t *testing.T) {
service := func(clusterIPs []string) *api.Service {
svc := api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-test",
Namespace: "ns",
},
Spec: api.ServiceSpec{
Type: api.ServiceTypeClusterIP,
},
}
if len(clusterIPs) > 0 {
svc.Spec.ClusterIP = clusterIPs[0]
svc.Spec.ClusterIPs = clusterIPs
}
return &svc
}
tests := []struct {
name string
service *api.Service
oldService *api.Service
want []string
}{
{
name: "IPv4 No failures",
service: service([]string{"192.12.2.2"}),
},
{
name: "IPv6 No failures",
service: service([]string{"2001:db8::2"}),
},
{
name: "IPv4 with leading zeros",
service: service([]string{"192.012.2.2"}),
want: []string{
`spec.clusterIPs[0]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
},
},
{
name: "Dual Stack IPv4-IPv6 and IPv4 with leading zeros",
service: service([]string{"192.012.2.2", "2001:db8::2"}),
want: []string{
`spec.clusterIPs[0]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
},
},
{
name: "Dual Stack IPv6-IPv4 and IPv4 with leading zeros",
service: service([]string{"2001:db8::2", "192.012.2.2"}),
want: []string{
`spec.clusterIPs[1]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
},
},
{
name: "IPv6 non canonical format",
service: service([]string{"2001:db8:0:0::2"}),
want: []string{
`spec.clusterIPs[0]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
},
},
{
name: "Dual Stack IPv4-IPv6 and IPv6 non-canonical format",
service: service([]string{"192.12.2.2", "2001:db8:0:0::2"}),
want: []string{
`spec.clusterIPs[1]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
},
},
{
name: "Dual Stack IPv6-IPv4 and IPv6 non-canonical formats",
service: service([]string{"2001:db8:0:0::2", "192.12.2.2"}),
want: []string{
`spec.clusterIPs[0]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
},
},
{
name: "Dual Stack IPv4-IPv6 and IPv4 with leading zeros and IPv6 non-canonical format",
service: service([]string{"192.012.2.2", "2001:db8:0:0::2"}),
want: []string{
`spec.clusterIPs[0]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
`spec.clusterIPs[1]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
},
},
{
name: "Dual Stack IPv6-IPv4 and IPv4 with leading zeros and IPv6 non-canonical format",
service: service([]string{"2001:db8:0:0::2", "192.012.2.2"}),
want: []string{
`spec.clusterIPs[0]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
`spec.clusterIPs[1]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
},
},
{
name: "Service with all IPs fields with errors",
service: &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "svc-test",
Namespace: "ns",
},
Spec: api.ServiceSpec{
ClusterIP: "2001:db8:0:0::2",
ClusterIPs: []string{"2001:db8:0:0::2", "192.012.2.2"},
ExternalIPs: []string{"2001:db8:1:0::2", "10.012.2.2"},
LoadBalancerIP: "10.001.1.1",
LoadBalancerSourceRanges: []string{"2001:db8:1:0::/64", "10.012.2.0/24"},
Type: api.ServiceTypeClusterIP,
},
},
want: []string{
`spec.clusterIPs[0]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
`spec.clusterIPs[1]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
`spec.externalIPs[0]: IPv6 address "2001:db8:1:0::2" is not in RFC 5952 canonical format ("2001:db8:1::2"), which may cause controller apply-loops`,
`spec.externalIPs[1]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("10.012.2.2"): IPv4 field has octet with leading zero`,
`spec.loadBalancerIP: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("10.001.1.1"): IPv4 field has octet with leading zero`,
`spec.loadBalancerSourceRanges[0]: IPv6 prefix "2001:db8:1:0::/64" is not in RFC 5952 canonical format ("2001:db8:1::/64"), which may cause controller apply-loops`,
`spec.loadBalancerSourceRanges[1]: IP prefix was accepted, but will be invalid in a future Kubernetes release: netip.ParsePrefix("10.012.2.0/24"): ParseAddr("10.012.2.0"): IPv4 field has octet with leading zero`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetWarningsForService(tt.service, tt.oldService); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetWarningsForService() = %v", cmp.Diff(got, tt.want))
}
})
}
}
func Test_getWarningsForIP(t *testing.T) {
tests := []struct {
name string
fieldPath *field.Path
address string
want []string
}{
{
name: "IPv4 No failures",
address: "192.12.2.2",
fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0),
want: []string{},
},
{
name: "IPv6 No failures",
address: "2001:db8::2",
fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0),
want: []string{},
},
{
name: "IPv4 with leading zeros",
address: "192.012.2.2",
fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0),
want: []string{
`spec.clusterIPs[0]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
},
},
{
name: "IPv6 non-canonical format",
address: "2001:db8:0:0::2",
fieldPath: field.NewPath("spec").Child("loadBalancerIP"),
want: []string{
`spec.loadBalancerIP: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getWarningsForIP(tt.fieldPath, tt.address); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getWarningsForIP() = %v, want %v", got, tt.want)
}
})
}
}
func Test_getWarningsForCIDR(t *testing.T) {
tests := []struct {
name string
fieldPath *field.Path
cidr string
want []string
}{
{
name: "IPv4 No failures",
cidr: "192.12.2.0/24",
fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0),
want: []string{},
},
{
name: "IPv6 No failures",
cidr: "2001:db8::/64",
fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0),
want: []string{},
},
{
name: "IPv4 with leading zeros",
cidr: "192.012.2.0/24",
fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0),
want: []string{
`spec.loadBalancerSourceRanges[0]: IP prefix was accepted, but will be invalid in a future Kubernetes release: netip.ParsePrefix("192.012.2.0/24"): ParseAddr("192.012.2.0"): IPv4 field has octet with leading zero`,
},
},
{
name: "IPv6 non-canonical format",
cidr: "2001:db8:0:0::/64",
fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0),
want: []string{
`spec.loadBalancerSourceRanges[0]: IPv6 prefix "2001:db8:0:0::/64" is not in RFC 5952 canonical format ("2001:db8::/64"), which may cause controller apply-loops`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getWarningsForCIDR(tt.fieldPath, tt.cidr); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getWarningsForCIDR() = %v, want %v", got, tt.want)
}
})
}
}