From 9d22c94b7171a9a6ce0d167f6cb25abce2079941 Mon Sep 17 00:00:00 2001
From: Rob Scott <robertjscott@google.com>
Date: Fri, 9 Apr 2021 15:24:17 -0700
Subject: [PATCH] Updating EndpointSlice validation to match Endpoints
 validation

(cherry picked from commit dd95bba6cd1dfec0985d3e1068c12713597cbe4a)
---
 pkg/apis/core/validation/validation.go           | 18 +++++----
 pkg/apis/core/validation/validation_test.go      | 40 +++++++++++++++++++
 pkg/apis/discovery/validation/validation.go      |  2 +
 pkg/apis/discovery/validation/validation_test.go | 51 ++++++++++++++++++++++--
 4 files changed, 101 insertions(+), 10 deletions(-)

diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go
index af58e0e..d5e9037 100644
--- a/pkg/apis/core/validation/validation.go
+++ b/pkg/apis/core/validation/validation.go
@@ -4238,7 +4238,7 @@ func ValidateService(service *core.Service) field.ErrorList {
 				allErrs = append(allErrs, field.Invalid(idxPath, ip, msgs[i]))
 			}
 		} else {
-			allErrs = append(allErrs, validateNonSpecialIP(ip, idxPath)...)
+			allErrs = append(allErrs, ValidateNonSpecialIP(ip, idxPath)...)
 		}
 	}
 
@@ -5673,15 +5673,19 @@ func validateEndpointAddress(address *core.EndpointAddress, fldPath *field.Path)
 			allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), *address.NodeName, msg))
 		}
 	}
-	allErrs = append(allErrs, validateNonSpecialIP(address.IP, fldPath.Child("ip"))...)
+	allErrs = append(allErrs, ValidateNonSpecialIP(address.IP, fldPath.Child("ip"))...)
 	return allErrs
 }
 
-func validateNonSpecialIP(ipAddress string, fldPath *field.Path) field.ErrorList {
-	// We disallow some IPs as endpoints or external-ips.  Specifically,
-	// unspecified and loopback addresses are nonsensical and link-local
-	// addresses tend to be used for node-centric purposes (e.g. metadata
-	// service).
+// ValidateNonSpecialIP is used to validate Endpoints, EndpointSlices, and
+// external IPs. Specifically, this disallows unspecified and loopback addresses
+// are nonsensical and link-local addresses tend to be used for node-centric
+// purposes (e.g. metadata service).
+//
+// IPv6 references
+// - https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
+// - https://www.iana.org/assignments/ipv6-multicast-addresses/ipv6-multicast-addresses.xhtml
+func ValidateNonSpecialIP(ipAddress string, fldPath *field.Path) field.ErrorList {
 	allErrs := field.ErrorList{}
 	ip := net.ParseIP(ipAddress)
 	if ip == nil {
diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go
index bfdb523..f379cd4 100644
--- a/pkg/apis/core/validation/validation_test.go
+++ b/pkg/apis/core/validation/validation_test.go
@@ -16915,3 +16915,43 @@ func TestValidatePodTemplateSpecSeccomp(t *testing.T) {
 		asserttestify.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
 	}
 }
+
+func TestValidateNonSpecialIP(t *testing.T) {
+	fp := field.NewPath("ip")
+
+	// Valid values.
+	for _, tc := range []struct {
+		desc string
+		ip   string
+	}{
+		{"ipv4", "10.1.2.3"},
+		{"ipv6", "2000::1"},
+	} {
+		t.Run(tc.desc, func(t *testing.T) {
+			errs := ValidateNonSpecialIP(tc.ip, fp)
+			if len(errs) != 0 {
+				t.Errorf("ValidateNonSpecialIP(%q, ...) = %v; want nil", tc.ip, errs)
+			}
+		})
+	}
+	// Invalid cases
+	for _, tc := range []struct {
+		desc string
+		ip   string
+	}{
+		{"ipv4 unspecified", "0.0.0.0"},
+		{"ipv6 unspecified", "::0"},
+		{"ipv4 localhost", "127.0.0.0"},
+		{"ipv4 localhost", "127.255.255.255"},
+		{"ipv6 localhost", "::1"},
+		{"ipv6 link local", "fe80::"},
+		{"ipv6 local multicast", "ff02::"},
+	} {
+		t.Run(tc.desc, func(t *testing.T) {
+			errs := ValidateNonSpecialIP(tc.ip, fp)
+			if len(errs) == 0 {
+				t.Errorf("ValidateNonSpecialIP(%q, ...) = nil; want non-nil (errors)", tc.ip)
+			}
+		})
+	}
+}
diff --git a/pkg/apis/discovery/validation/validation.go b/pkg/apis/discovery/validation/validation.go
index 8499e7a..d1fa4c8 100644
--- a/pkg/apis/discovery/validation/validation.go
+++ b/pkg/apis/discovery/validation/validation.go
@@ -96,8 +96,10 @@ func validateEndpoints(endpoints []discovery.Endpoint, addrType discovery.Addres
 			switch addrType {
 			case discovery.AddressTypeIPv4:
 				allErrs = append(allErrs, validation.IsValidIPv4Address(addressPath.Index(i), address)...)
+				allErrs = append(allErrs, apivalidation.ValidateNonSpecialIP(address, addressPath.Index(i))...)
 			case discovery.AddressTypeIPv6:
 				allErrs = append(allErrs, validation.IsValidIPv6Address(addressPath.Index(i), address)...)
+				allErrs = append(allErrs, apivalidation.ValidateNonSpecialIP(address, addressPath.Index(i))...)
 			case discovery.AddressTypeFQDN:
 				allErrs = append(allErrs, validation.IsFullyQualifiedDomainName(addressPath.Index(i), address)...)
 			}
diff --git a/pkg/apis/discovery/validation/validation_test.go b/pkg/apis/discovery/validation/validation_test.go
index 5c7d478..0d944b5 100644
--- a/pkg/apis/discovery/validation/validation_test.go
+++ b/pkg/apis/discovery/validation/validation_test.go
@@ -52,6 +52,21 @@ func TestValidateEndpointSlice(t *testing.T) {
 				}},
 			},
 		},
+		"good-ipv6": {
+			expectedErrors: 0,
+			endpointSlice: &discovery.EndpointSlice{
+				ObjectMeta:  standardMeta,
+				AddressType: discovery.AddressTypeIPv6,
+				Ports: []discovery.EndpointPort{{
+					Name:     utilpointer.StringPtr("http"),
+					Protocol: protocolPtr(api.ProtocolTCP),
+				}},
+				Endpoints: []discovery.Endpoint{{
+					Addresses: []string{"a00:100::4"},
+					Hostname:  utilpointer.StringPtr("valid-123"),
+				}},
+			},
+		},
 		"good-fqdns": {
 			expectedErrors: 0,
 			endpointSlice: &discovery.EndpointSlice{
@@ -375,7 +390,7 @@ func TestValidateEndpointSlice(t *testing.T) {
 			},
 		},
 		"bad-ip": {
-			expectedErrors: 1,
+			expectedErrors: 2,
 			endpointSlice: &discovery.EndpointSlice{
 				ObjectMeta:  standardMeta,
 				AddressType: discovery.AddressTypeIPv4,
@@ -390,7 +405,7 @@ func TestValidateEndpointSlice(t *testing.T) {
 			},
 		},
 		"bad-ipv4": {
-			expectedErrors: 2,
+			expectedErrors: 3,
 			endpointSlice: &discovery.EndpointSlice{
 				ObjectMeta:  standardMeta,
 				AddressType: discovery.AddressTypeIPv4,
@@ -405,7 +420,7 @@ func TestValidateEndpointSlice(t *testing.T) {
 			},
 		},
 		"bad-ipv6": {
-			expectedErrors: 2,
+			expectedErrors: 4,
 			endpointSlice: &discovery.EndpointSlice{
 				ObjectMeta:  standardMeta,
 				AddressType: discovery.AddressTypeIPv6,
@@ -454,6 +469,36 @@ func TestValidateEndpointSlice(t *testing.T) {
 			expectedErrors: 3,
 			endpointSlice:  &discovery.EndpointSlice{},
 		},
+		"special-ipv4": {
+			expectedErrors: 1,
+			endpointSlice: &discovery.EndpointSlice{
+				ObjectMeta:  standardMeta,
+				AddressType: discovery.AddressTypeIPv4,
+				Ports: []discovery.EndpointPort{{
+					Name:     utilpointer.StringPtr("http"),
+					Protocol: protocolPtr(api.ProtocolTCP),
+				}},
+				Endpoints: []discovery.Endpoint{{
+					Addresses: []string{"127.0.0.1"},
+					Hostname:  utilpointer.StringPtr("valid-123"),
+				}},
+			},
+		},
+		"special-ipv6": {
+			expectedErrors: 1,
+			endpointSlice: &discovery.EndpointSlice{
+				ObjectMeta:  standardMeta,
+				AddressType: discovery.AddressTypeIPv6,
+				Ports: []discovery.EndpointPort{{
+					Name:     utilpointer.StringPtr("http"),
+					Protocol: protocolPtr(api.ProtocolTCP),
+				}},
+				Endpoints: []discovery.Endpoint{{
+					Addresses: []string{"fe80::9656:d028:8652:66b6"},
+					Hostname:  utilpointer.StringPtr("valid-123"),
+				}},
+			},
+		},
 	}
 
 	for name, testCase := range testCases {
-- 
1.8.3.1

