1/*
2Copyright 2019 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package validation
18
19import (
20	"fmt"
21
22	corev1 "k8s.io/api/core/v1"
23	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
24	metavalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
25	"k8s.io/apimachinery/pkg/util/sets"
26	"k8s.io/apimachinery/pkg/util/validation"
27	"k8s.io/apimachinery/pkg/util/validation/field"
28	api "k8s.io/kubernetes/pkg/apis/core"
29	apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
30	"k8s.io/kubernetes/pkg/apis/discovery"
31)
32
33var (
34	supportedAddressTypes = sets.NewString(
35		string(discovery.AddressTypeIPv4),
36		string(discovery.AddressTypeIPv6),
37		string(discovery.AddressTypeFQDN),
38	)
39	supportedPortProtocols = sets.NewString(
40		string(api.ProtocolTCP),
41		string(api.ProtocolUDP),
42		string(api.ProtocolSCTP),
43	)
44	maxTopologyLabels = 16
45	maxAddresses      = 100
46	maxPorts          = 20000
47	maxEndpoints      = 1000
48	maxZoneHints      = 8
49)
50
51// ValidateEndpointSliceName can be used to check whether the given endpoint
52// slice name is valid. Prefix indicates this name will be used as part of
53// generation, in which case trailing dashes are allowed.
54var ValidateEndpointSliceName = apimachineryvalidation.NameIsDNSSubdomain
55
56// ValidateEndpointSlice validates an EndpointSlice.
57func ValidateEndpointSlice(endpointSlice *discovery.EndpointSlice) field.ErrorList {
58	allErrs := apivalidation.ValidateObjectMeta(&endpointSlice.ObjectMeta, true, ValidateEndpointSliceName, field.NewPath("metadata"))
59	allErrs = append(allErrs, validateAddressType(endpointSlice.AddressType)...)
60	allErrs = append(allErrs, validateEndpoints(endpointSlice.Endpoints, endpointSlice.AddressType, field.NewPath("endpoints"))...)
61	allErrs = append(allErrs, validatePorts(endpointSlice.Ports, field.NewPath("ports"))...)
62
63	return allErrs
64}
65
66// ValidateEndpointSliceCreate validates an EndpointSlice when it is created.
67func ValidateEndpointSliceCreate(endpointSlice *discovery.EndpointSlice) field.ErrorList {
68	return ValidateEndpointSlice(endpointSlice)
69}
70
71// ValidateEndpointSliceUpdate validates an EndpointSlice when it is updated.
72func ValidateEndpointSliceUpdate(newEndpointSlice, oldEndpointSlice *discovery.EndpointSlice) field.ErrorList {
73	allErrs := ValidateEndpointSlice(newEndpointSlice)
74	allErrs = append(allErrs, apivalidation.ValidateImmutableField(newEndpointSlice.AddressType, oldEndpointSlice.AddressType, field.NewPath("addressType"))...)
75
76	return allErrs
77}
78
79func validateEndpoints(endpoints []discovery.Endpoint, addrType discovery.AddressType, fldPath *field.Path) field.ErrorList {
80	allErrs := field.ErrorList{}
81
82	if len(endpoints) > maxEndpoints {
83		allErrs = append(allErrs, field.TooMany(fldPath, len(endpoints), maxEndpoints))
84		return allErrs
85	}
86
87	for i, endpoint := range endpoints {
88		idxPath := fldPath.Index(i)
89		addressPath := idxPath.Child("addresses")
90
91		if len(endpoint.Addresses) == 0 {
92			allErrs = append(allErrs, field.Required(addressPath, "must contain at least 1 address"))
93		} else if len(endpoint.Addresses) > maxAddresses {
94			allErrs = append(allErrs, field.TooMany(addressPath, len(endpoint.Addresses), maxAddresses))
95		}
96
97		for i, address := range endpoint.Addresses {
98			// This validates known address types, unknown types fall through
99			// and do not get validated.
100			switch addrType {
101			case discovery.AddressTypeIPv4:
102				allErrs = append(allErrs, validation.IsValidIPv4Address(addressPath.Index(i), address)...)
103				allErrs = append(allErrs, apivalidation.ValidateNonSpecialIP(address, addressPath.Index(i))...)
104			case discovery.AddressTypeIPv6:
105				allErrs = append(allErrs, validation.IsValidIPv6Address(addressPath.Index(i), address)...)
106				allErrs = append(allErrs, apivalidation.ValidateNonSpecialIP(address, addressPath.Index(i))...)
107			case discovery.AddressTypeFQDN:
108				allErrs = append(allErrs, validation.IsFullyQualifiedDomainName(addressPath.Index(i), address)...)
109			}
110		}
111
112		if endpoint.NodeName != nil {
113			nnPath := idxPath.Child("nodeName")
114			for _, msg := range apivalidation.ValidateNodeName(*endpoint.NodeName, false) {
115				allErrs = append(allErrs, field.Invalid(nnPath, *endpoint.NodeName, msg))
116			}
117		}
118
119		topologyPath := idxPath.Child("deprecatedTopology")
120		if len(endpoint.DeprecatedTopology) > maxTopologyLabels {
121			allErrs = append(allErrs, field.TooMany(topologyPath, len(endpoint.DeprecatedTopology), maxTopologyLabels))
122		}
123		allErrs = append(allErrs, metavalidation.ValidateLabels(endpoint.DeprecatedTopology, topologyPath)...)
124		if _, found := endpoint.DeprecatedTopology[corev1.LabelTopologyZone]; found {
125			allErrs = append(allErrs, field.InternalError(topologyPath.Key(corev1.LabelTopologyZone), fmt.Errorf("reserved key was not removed in conversion")))
126		}
127
128		if endpoint.Hostname != nil {
129			allErrs = append(allErrs, apivalidation.ValidateDNS1123Label(*endpoint.Hostname, idxPath.Child("hostname"))...)
130		}
131
132		if endpoint.Hints != nil {
133			allErrs = append(allErrs, validateHints(endpoint.Hints, idxPath.Child("hints"))...)
134		}
135	}
136
137	return allErrs
138}
139
140func validatePorts(endpointPorts []discovery.EndpointPort, fldPath *field.Path) field.ErrorList {
141	allErrs := field.ErrorList{}
142
143	if len(endpointPorts) > maxPorts {
144		allErrs = append(allErrs, field.TooMany(fldPath, len(endpointPorts), maxPorts))
145		return allErrs
146	}
147
148	portNames := sets.String{}
149	for i, endpointPort := range endpointPorts {
150		idxPath := fldPath.Index(i)
151
152		if len(*endpointPort.Name) > 0 {
153			allErrs = append(allErrs, apivalidation.ValidateDNS1123Label(*endpointPort.Name, idxPath.Child("name"))...)
154		}
155
156		if portNames.Has(*endpointPort.Name) {
157			allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), endpointPort.Name))
158		} else {
159			portNames.Insert(*endpointPort.Name)
160		}
161
162		if endpointPort.Protocol == nil {
163			allErrs = append(allErrs, field.Required(idxPath.Child("protocol"), ""))
164		} else if !supportedPortProtocols.Has(string(*endpointPort.Protocol)) {
165			allErrs = append(allErrs, field.NotSupported(idxPath.Child("protocol"), *endpointPort.Protocol, supportedPortProtocols.List()))
166		}
167
168		if endpointPort.AppProtocol != nil {
169			allErrs = append(allErrs, apivalidation.ValidateQualifiedName(*endpointPort.AppProtocol, idxPath.Child("appProtocol"))...)
170		}
171	}
172
173	return allErrs
174}
175
176func validateAddressType(addressType discovery.AddressType) field.ErrorList {
177	allErrs := field.ErrorList{}
178
179	if addressType == "" {
180		allErrs = append(allErrs, field.Required(field.NewPath("addressType"), ""))
181	} else if !supportedAddressTypes.Has(string(addressType)) {
182		allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), addressType, supportedAddressTypes.List()))
183	}
184
185	return allErrs
186}
187
188func validateHints(endpointHints *discovery.EndpointHints, fldPath *field.Path) field.ErrorList {
189	allErrs := field.ErrorList{}
190
191	fzPath := fldPath.Child("forZones")
192	if len(endpointHints.ForZones) > maxZoneHints {
193		allErrs = append(allErrs, field.TooMany(fzPath, len(endpointHints.ForZones), maxZoneHints))
194		return allErrs
195	}
196
197	zoneNames := sets.String{}
198	for i, forZone := range endpointHints.ForZones {
199		zonePath := fzPath.Index(i).Child("name")
200		if zoneNames.Has(forZone.Name) {
201			allErrs = append(allErrs, field.Duplicate(zonePath, forZone.Name))
202		} else {
203			zoneNames.Insert(forZone.Name)
204		}
205
206		for _, msg := range validation.IsValidLabelValue(forZone.Name) {
207			allErrs = append(allErrs, field.Invalid(zonePath, forZone.Name, msg))
208		}
209	}
210
211	return allErrs
212}
213