forked from kubernetes/kubernetes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
validation.go
172 lines (154 loc) · 7.15 KB
/
validation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*
Copyright 2015 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 validation
import (
"net"
"regexp"
"strings"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/apis/extensions"
)
// ValidateIngress tests if required fields in the Ingress are set.
func ValidateIngress(ingress *extensions.Ingress) field.ErrorList {
allErrs := apivalidation.ValidateObjectMeta(&ingress.ObjectMeta, true, ValidateIngressName, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, field.NewPath("spec"))...)
return allErrs
}
// ValidateIngressName validates that the given name can be used as an Ingress name.
var ValidateIngressName = apimachineryvalidation.NameIsDNSSubdomain
func validateIngressTLS(spec *extensions.IngressSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// TODO: Perform a more thorough validation of spec.TLS.Hosts that takes
// the wildcard spec from RFC 6125 into account.
for _, itls := range spec.TLS {
for i, host := range itls.Hosts {
if strings.Contains(host, "*") {
for _, msg := range validation.IsWildcardDNS1123Subdomain(host) {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("hosts"), host, msg))
}
continue
}
for _, msg := range validation.IsDNS1123Subdomain(host) {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("hosts"), host, msg))
}
}
}
return allErrs
}
// ValidateIngressSpec tests if required fields in the IngressSpec are set.
func ValidateIngressSpec(spec *extensions.IngressSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// TODO: Is a default backend mandatory?
if spec.Backend != nil {
allErrs = append(allErrs, validateIngressBackend(spec.Backend, fldPath.Child("backend"))...)
} else if len(spec.Rules) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, spec.Rules, "either `backend` or `rules` must be specified"))
}
if len(spec.Rules) > 0 {
allErrs = append(allErrs, validateIngressRules(spec.Rules, fldPath.Child("rules"))...)
}
if len(spec.TLS) > 0 {
allErrs = append(allErrs, validateIngressTLS(spec, fldPath.Child("tls"))...)
}
return allErrs
}
// ValidateIngressUpdate tests if required fields in the Ingress are set.
func ValidateIngressUpdate(ingress, oldIngress *extensions.Ingress) field.ErrorList {
allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, field.NewPath("spec"))...)
return allErrs
}
// ValidateIngressStatusUpdate tests if required fields in the Ingress are set when updating status.
func ValidateIngressStatusUpdate(ingress, oldIngress *extensions.Ingress) field.ErrorList {
allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, apivalidation.ValidateLoadBalancerStatus(&ingress.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...)
return allErrs
}
func validateIngressRules(ingressRules []extensions.IngressRule, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(ingressRules) == 0 {
return append(allErrs, field.Required(fldPath, ""))
}
for i, ih := range ingressRules {
if len(ih.Host) > 0 {
if isIP := (net.ParseIP(ih.Host) != nil); isIP {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, "must be a DNS name, not an IP address"))
}
// TODO: Ports and ips are allowed in the host part of a url
// according to RFC 3986, consider allowing them.
if strings.Contains(ih.Host, "*") {
for _, msg := range validation.IsWildcardDNS1123Subdomain(ih.Host) {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg))
}
continue
}
for _, msg := range validation.IsDNS1123Subdomain(ih.Host) {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg))
}
}
allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(0))...)
}
return allErrs
}
func validateIngressRuleValue(ingressRule *extensions.IngressRuleValue, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if ingressRule.HTTP != nil {
allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP, fldPath.Child("http"))...)
}
return allErrs
}
func validateHTTPIngressRuleValue(httpIngressRuleValue *extensions.HTTPIngressRuleValue, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(httpIngressRuleValue.Paths) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("paths"), ""))
}
for i, rule := range httpIngressRuleValue.Paths {
if len(rule.Path) > 0 {
if !strings.HasPrefix(rule.Path, "/") {
allErrs = append(allErrs, field.Invalid(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be an absolute path"))
}
// TODO: More draconian path regex validation.
// Path must be a valid regex. This is the basic requirement.
// In addition to this any characters not allowed in a path per
// RFC 3986 section-3.3 cannot appear as a literal in the regex.
// Consider the example: http://host/valid?#bar, everything after
// the last '/' is a valid regex that matches valid#bar, which
// isn't a valid path, because the path terminates at the first ?
// or #. A more sophisticated form of validation would detect that
// the user is confusing url regexes with path regexes.
_, err := regexp.CompilePOSIX(rule.Path)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be a valid regex"))
}
}
allErrs = append(allErrs, validateIngressBackend(&rule.Backend, fldPath.Child("backend"))...)
}
return allErrs
}
// validateIngressBackend tests if a given backend is valid.
func validateIngressBackend(backend *extensions.IngressBackend, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// All backends must reference a single local service by name, and a single service port by name or number.
if len(backend.ServiceName) == 0 {
return append(allErrs, field.Required(fldPath.Child("serviceName"), ""))
} else {
for _, msg := range apivalidation.ValidateServiceName(backend.ServiceName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceName"), backend.ServiceName, msg))
}
}
allErrs = append(allErrs, apivalidation.ValidatePortNumOrName(backend.ServicePort, fldPath.Child("servicePort"))...)
return allErrs
}