1// Copyright 2020 Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package validation
16
17import (
18	"errors"
19	"fmt"
20
21	networking "istio.io/api/networking/v1alpha3"
22)
23
24func validateRootHTTPRoute(http *networking.HTTPRoute) (errs error) {
25	if http.Delegate == nil {
26		errs = appendErrors(errs, fmt.Errorf("root HTTP route %s must specify delegate", http.Name))
27	}
28	// only delegate can be specified
29	if http.Redirect != nil {
30		errs = appendErrors(errs, fmt.Errorf("root HTTP route %s must not specify rewrite", http.Name))
31	}
32	if http.Route != nil {
33		errs = appendErrors(errs, fmt.Errorf("root HTTP route %s must not specify route", http.Name))
34	}
35
36	errs = appendErrors(errs, validateChainingHTTPRoute(http))
37	return
38}
39
40func validateDelegateHTTPRoute(http *networking.HTTPRoute) (errs error) {
41	if http.Delegate != nil {
42		errs = appendErrors(errs, errors.New("delegate HTTP route cannot contain delegate"))
43	}
44	// check for conflicts
45	if http.Redirect != nil {
46		if len(http.Route) > 0 {
47			errs = appendErrors(errs, errors.New("HTTP route cannot contain both route and redirect"))
48		}
49	} else if len(http.Route) == 0 {
50		errs = appendErrors(errs, errors.New("HTTP route or redirect is required"))
51	}
52	errs = appendErrors(errs, validateChainingHTTPRoute(http))
53	return
54}
55
56// TODO(@hzxuzhonghu): deduplicate with validateHTTPRoute
57func validateChainingHTTPRoute(http *networking.HTTPRoute) (errs error) {
58	// check for conflicts
59	if http.Redirect != nil {
60		if http.Fault != nil {
61			errs = appendErrors(errs, errors.New("HTTP route cannot contain both fault and redirect"))
62		}
63
64		if http.Rewrite != nil {
65			errs = appendErrors(errs, errors.New("HTTP route rule cannot contain both rewrite and redirect"))
66		}
67	}
68	// header manipulation
69	for name := range http.Headers.GetRequest().GetAdd() {
70		errs = appendErrors(errs, ValidateHTTPHeaderName(name))
71	}
72	for name := range http.Headers.GetRequest().GetSet() {
73		errs = appendErrors(errs, ValidateHTTPHeaderName(name))
74	}
75	for _, name := range http.Headers.GetRequest().GetRemove() {
76		errs = appendErrors(errs, ValidateHTTPHeaderName(name))
77	}
78	for name := range http.Headers.GetResponse().GetAdd() {
79		errs = appendErrors(errs, ValidateHTTPHeaderName(name))
80	}
81	for name := range http.Headers.GetResponse().GetSet() {
82		errs = appendErrors(errs, ValidateHTTPHeaderName(name))
83	}
84	for _, name := range http.Headers.GetResponse().GetRemove() {
85		errs = appendErrors(errs, ValidateHTTPHeaderName(name))
86	}
87
88	errs = appendErrors(errs, validateCORSPolicy(http.CorsPolicy))
89	errs = appendErrors(errs, validateHTTPFaultInjection(http.Fault))
90
91	for _, match := range http.Match {
92		if match != nil {
93			if containRegexMatch(match.Uri) {
94				errs = appendErrors(errs, errors.New("url match does not support regex match for delegating"))
95			}
96			if containRegexMatch(match.Scheme) {
97				errs = appendErrors(errs, errors.New("scheme match does not support regex match for delegating"))
98			}
99			if containRegexMatch(match.Method) {
100				errs = appendErrors(errs, errors.New("method match does not support regex match for delegating"))
101			}
102			if containRegexMatch(match.Authority) {
103				errs = appendErrors(errs, errors.New("authority match does not support regex match for delegating"))
104			}
105
106			for name, header := range match.Headers {
107				if header == nil {
108					errs = appendErrors(errs, fmt.Errorf("header match %v cannot be null", name))
109				}
110				if containRegexMatch(header) {
111					errs = appendErrors(errs, fmt.Errorf("header match %v does not support regex match for delegating", name))
112				}
113				errs = appendErrors(errs, ValidateHTTPHeaderName(name))
114			}
115			for name, param := range match.QueryParams {
116				if param == nil {
117					errs = appendErrors(errs, fmt.Errorf("query param match %v cannot be null", name))
118				}
119				if containRegexMatch(param) {
120					errs = appendErrors(errs, fmt.Errorf("query param match %v does not support regex match for delegating", name))
121				}
122			}
123			for name, header := range match.WithoutHeaders {
124				if header == nil {
125					errs = appendErrors(errs, fmt.Errorf("withoutHeaders match %v cannot be null", name))
126				}
127				if containRegexMatch(header) {
128					errs = appendErrors(errs, fmt.Errorf("withoutHeaders match %v does not support regex match for delegating", name))
129				}
130				errs = appendErrors(errs, ValidateHTTPHeaderName(name))
131			}
132
133			if match.Port != 0 {
134				errs = appendErrors(errs, ValidatePort(int(match.Port)))
135			}
136
137			// delegate only applies to gateway, so sourceLabels or sourceNamespace does not apply.
138			if len(match.SourceLabels) != 0 {
139				errs = appendErrors(errs, fmt.Errorf("sourceLabels match can not specify on delegate"))
140			}
141			if match.SourceNamespace != "" {
142				errs = appendErrors(errs, fmt.Errorf("sourceNamespace match can not specify on delegate"))
143			}
144			if len(match.Gateways) != 0 {
145				errs = appendErrors(errs, fmt.Errorf("gateways match can not specify on delegate"))
146			}
147		}
148	}
149
150	if http.MirrorPercent != nil {
151		if value := http.MirrorPercent.GetValue(); value > 100 {
152			errs = appendErrors(errs, fmt.Errorf("mirror_percent must have a max value of 100 (it has %d)", value))
153		}
154	}
155
156	if http.MirrorPercentage != nil {
157		if value := http.MirrorPercentage.GetValue(); value > 100 {
158			errs = appendErrors(errs, fmt.Errorf("mirror_percentage must have a max value of 100 (it has %f)", value))
159		}
160	}
161
162	errs = appendErrors(errs, validateDestination(http.Mirror))
163	errs = appendErrors(errs, validateHTTPRedirect(http.Redirect))
164	errs = appendErrors(errs, validateHTTPRetry(http.Retries))
165	errs = appendErrors(errs, validateHTTPRewrite(http.Rewrite))
166	errs = appendErrors(errs, validateHTTPRouteDestinations(http.Route))
167	if http.Timeout != nil {
168		errs = appendErrors(errs, ValidateDurationGogo(http.Timeout))
169	}
170
171	return
172}
173
174func containRegexMatch(config *networking.StringMatch) bool {
175	if config == nil {
176		return false
177	}
178	if config.GetRegex() != "" {
179		return true
180	}
181	return false
182}
183