1from typing import Generator, Tuple, Union
2
3from abstract_tests import HTTP, AmbassadorTest, HTTPBin, Node, ServiceType
4from ambassador.constants import Constants
5from kat.harness import Query
6from tests.integration.manifests import namespace_manifest
7
8# This is the place to add new MappingTests that run in the default namespace
9
10
11# This has to be an `AmbassadorTest` because we're going to set up a Module that
12# needs to apply to just this test. If this were a MappingTest, then the Module
13# would apply to all other MappingTest's and we don't want that.
14class HostHeaderMappingStripMatchingHostPort(AmbassadorTest):
15 target: ServiceType
16
17 def init(self):
18 self.target = HTTP()
19
20 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
21 yield self, self.format(
22 """
23---
24apiVersion: getambassador.io/v3alpha1
25kind: Module
26name: ambassador
27config:
28 strip_matching_host_port: true
29---
30apiVersion: getambassador.io/v3alpha1
31kind: Mapping
32name: {self.name}
33prefix: /{self.name}/
34service: http://{self.target.path.fqdn}
35host: myhostname.com
36"""
37 )
38
39 def queries(self):
40 # Sanity test that a missing or incorrect hostname does not route, and it does route with a correct hostname.
41 yield Query(self.url(self.name + "/"), expected=404)
42 yield Query(self.url(self.name + "/"), headers={"Host": "yourhostname.com"}, expected=404)
43 yield Query(self.url(self.name + "/"), headers={"Host": "myhostname.com"})
44 # Test that a host header with a port value that does match the listener's configured port is correctly
45 # stripped for the purpose of routing, and matches the mapping.
46 yield Query(
47 self.url(self.name + "/"),
48 headers={"Host": "myhostname.com:" + str(Constants.SERVICE_PORT_HTTP)},
49 )
50 # Test that a host header with a port value that does _not_ match the listener's configured does not have its
51 # port value stripped for the purpose of routing, so it does not match the mapping.
52 yield Query(
53 self.url(self.name + "/"), headers={"Host": "myhostname.com:11875"}, expected=404
54 )
55
56
57# This has to be an `AmbassadorTest` because we're going to set up a Module that
58# needs to apply to just this test. If this were a MappingTest, then the Module
59# would apply to all other MappingTest's and we don't want that.
60class MergeSlashesDisabled(AmbassadorTest):
61 target: ServiceType
62
63 def init(self):
64 self.target = HTTPBin()
65
66 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
67 yield self, self.format(
68 """
69---
70apiVersion: getambassador.io/v3alpha1
71kind: Mapping
72name: {self.name}
73hostname: "*"
74prefix: /{self.name}/status/
75rewrite: /status/
76service: {self.target.path.fqdn}
77"""
78 )
79
80 def queries(self):
81 yield Query(self.url(self.name + "/status/200"))
82 # Sanity test that an extra slash in the front of the request URL does not match the mapping,
83 # since we did not set merge_slashes on the Ambassador module.
84 yield Query(self.url("/" + self.name + "/status/200"), expected=404)
85 yield Query(self.url("/" + self.name + "//status/200"), expected=404)
86 yield Query(self.url(self.name + "//status/200"), expected=404)
87
88
89# This has to be an `AmbassadorTest` because we're going to set up a Module that
90# needs to apply to just this test. If this were a MappingTest, then the Module
91# would apply to all other MappingTest's and we don't want that.
92class MergeSlashesEnabled(AmbassadorTest):
93 target: ServiceType
94
95 def init(self):
96 self.target = HTTPBin()
97
98 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
99 yield self, self.format(
100 """
101---
102apiVersion: getambassador.io/v3alpha1
103kind: Module
104name: ambassador
105config:
106 merge_slashes: true
107---
108apiVersion: getambassador.io/v3alpha1
109kind: Mapping
110name: {self.name}
111hostname: "*"
112prefix: /{self.name}/status/
113rewrite: /status/
114service: {self.target.path.fqdn}
115"""
116 )
117
118 def queries(self):
119 yield Query(self.url(self.name + "/status/200"))
120 # Since merge_slashes is on the Ambassador module, extra slashes in the URL should not prevent the request
121 # from matching.
122 yield Query(self.url("/" + self.name + "/status/200"))
123 yield Query(self.url("/" + self.name + "//status/200"))
124 yield Query(self.url(self.name + "//status/200"))
125
126
127# This has to be an `AmbassadorTest` because we're going to set up a Module that
128# needs to apply to just this test. If this were a MappingTest, then the Module
129# would apply to all other MappingTest's and we don't want that.
130class RejectRequestsWithEscapedSlashesDisabled(AmbassadorTest):
131 target: ServiceType
132
133 def init(self):
134 self.target = HTTPBin()
135
136 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
137 yield self, self.format(
138 """
139---
140apiVersion: getambassador.io/v3alpha1
141kind: Mapping
142name: {self.name}
143hostname: "*"
144prefix: /{self.name}/status/
145rewrite: /status/
146service: {self.target.path.fqdn}
147"""
148 )
149
150 def queries(self):
151 # Sanity test that escaped slashes are not rejected by default. The upstream
152 # httpbin server doesn't know what to do with this request, though, so expect
153 # a 404. In another test, we'll expect HTTP 400 with reject_requests_with_escaped_slashes
154 yield Query(self.url(self.name + "/status/%2F200"), expected=404)
155
156 def check(self):
157 # We should have observed this 404 upstream from httpbin. The presence of this header verifies that.
158 print("headers=%s", repr(self.results[0].headers))
159 assert "X-Envoy-Upstream-Service-Time" in self.results[0].headers
160
161
162# This has to be an `AmbassadorTest` because we're going to set up a Module that
163# needs to apply to just this test. If this were a MappingTest, then the Module
164# would apply to all other MappingTest's and we don't want that.
165class RejectRequestsWithEscapedSlashesEnabled(AmbassadorTest):
166 target: ServiceType
167
168 def init(self):
169 self.target = HTTPBin()
170
171 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
172 yield self, self.format(
173 """
174---
175apiVersion: getambassador.io/v3alpha1
176kind: Module
177name: ambassador
178config:
179 reject_requests_with_escaped_slashes: true
180---
181apiVersion: getambassador.io/v3alpha1
182kind: Mapping
183name: {self.name}
184hostname: "*"
185prefix: /{self.name}/status/
186rewrite: /status/
187service: {self.target.path.fqdn}
188"""
189 )
190
191 def queries(self):
192 # Expect that requests with escaped slashes are rejected by Envoy. We know this is rejected
193 # by envoy because in a previous test, without the reject_requests_with_escaped_slashes,
194 # this same request got status 404.
195 yield Query(self.url(self.name + "/status/%2F200"), expected=400)
196
197 def check(self):
198 # We should have not have observed this 400 upstream from httpbin. The absence of this header
199 # suggests that (though does not prove, in theory).
200 assert "X-Envoy-Upstream-Service-Time" not in self.results[0].headers
201
202
203class LinkerdHeaderMapping(AmbassadorTest):
204 target: ServiceType
205
206 def init(self):
207 self.target = HTTP()
208 self.target_no_header = HTTP(name="noheader")
209 self.target_add_linkerd_header_only = HTTP(name="addlinkerdonly")
210
211 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
212 yield self, self.format(
213 """
214---
215apiVersion: getambassador.io/v3alpha1
216kind: Module
217name: ambassador
218config:
219 add_linkerd_headers: true
220 defaults:
221 httpmapping:
222 add_request_headers:
223 fruit:
224 append: False
225 value: orange
226 remove_request_headers:
227 - x-evil-header
228---
229apiVersion: getambassador.io/v3alpha1
230kind: Mapping
231name: {self.target_add_linkerd_header_only.path.k8s}
232hostname: "*"
233prefix: /target_add_linkerd_header_only/
234service: {self.target_add_linkerd_header_only.path.fqdn}
235add_request_headers: {{}}
236remove_request_headers: []
237---
238apiVersion: getambassador.io/v3alpha1
239kind: Mapping
240name: {self.target_no_header.path.k8s}
241hostname: "*"
242prefix: /target_no_header/
243service: {self.target_no_header.path.fqdn}
244add_linkerd_headers: false
245---
246apiVersion: getambassador.io/v3alpha1
247kind: Mapping
248name: {self.target.path.k8s}
249hostname: "*"
250prefix: /target/
251service: {self.target.path.fqdn}
252add_request_headers:
253 fruit:
254 append: False
255 value: banana
256remove_request_headers:
257- x-evilness
258"""
259 )
260
261 def queries(self):
262 # [0] expect Linkerd headers set through mapping
263 yield Query(
264 self.url("target/"),
265 headers={"x-evil-header": "evilness", "x-evilness": "more evilness"},
266 expected=200,
267 )
268
269 # [1] expect no Linkerd headers
270 yield Query(
271 self.url("target_no_header/"),
272 headers={"x-evil-header": "evilness", "x-evilness": "more evilness"},
273 expected=200,
274 )
275
276 # [2] expect Linkerd headers only
277 yield Query(
278 self.url("target_add_linkerd_header_only/"),
279 headers={"x-evil-header": "evilness", "x-evilness": "more evilness"},
280 expected=200,
281 )
282
283 def check(self):
284 # [0]
285 assert self.results[0].backend
286 assert self.results[0].backend.request
287 assert len(self.results[0].backend.request.headers["l5d-dst-override"]) > 0
288 assert self.results[0].backend.request.headers["l5d-dst-override"] == [
289 "{}:80".format(self.target.path.fqdn)
290 ]
291 assert len(self.results[0].backend.request.headers["fruit"]) > 0
292 assert self.results[0].backend.request.headers["fruit"] == ["banana"]
293 assert len(self.results[0].backend.request.headers["x-evil-header"]) > 0
294 assert self.results[0].backend.request.headers["x-evil-header"] == ["evilness"]
295 assert "x-evilness" not in self.results[0].backend.request.headers
296
297 # [1]
298 assert self.results[1].backend
299 assert self.results[1].backend.request
300 assert "l5d-dst-override" not in self.results[1].backend.request.headers
301 assert len(self.results[1].backend.request.headers["fruit"]) > 0
302 assert self.results[1].backend.request.headers["fruit"] == ["orange"]
303 assert "x-evil-header" not in self.results[1].backend.request.headers
304 assert len(self.results[1].backend.request.headers["x-evilness"]) > 0
305 assert self.results[1].backend.request.headers["x-evilness"] == ["more evilness"]
306
307 # [2]
308 assert self.results[2].backend
309 assert self.results[2].backend.request
310 assert len(self.results[2].backend.request.headers["l5d-dst-override"]) > 0
311 assert self.results[2].backend.request.headers["l5d-dst-override"] == [
312 "{}:80".format(self.target_add_linkerd_header_only.path.fqdn)
313 ]
314 assert len(self.results[2].backend.request.headers["x-evil-header"]) > 0
315 assert self.results[2].backend.request.headers["x-evil-header"] == ["evilness"]
316 assert len(self.results[2].backend.request.headers["x-evilness"]) > 0
317 assert self.results[2].backend.request.headers["x-evilness"] == ["more evilness"]
318
319
320class SameMappingDifferentNamespaces(AmbassadorTest):
321 target: ServiceType
322
323 def init(self):
324 self.target = HTTP()
325
326 def manifests(self) -> str:
327 return (
328 namespace_manifest("same-mapping-1")
329 + namespace_manifest("same-mapping-2")
330 + self.format(
331 """
332---
333apiVersion: getambassador.io/v3alpha1
334kind: Mapping
335metadata:
336 name: {self.target.path.k8s}
337 namespace: same-mapping-1
338spec:
339 ambassador_id: [{self.ambassador_id}]
340 hostname: "*"
341 prefix: /{self.name}-1/
342 service: {self.target.path.fqdn}
343---
344apiVersion: getambassador.io/v3alpha1
345kind: Mapping
346metadata:
347 name: {self.target.path.k8s}
348 namespace: same-mapping-2
349spec:
350 ambassador_id: [{self.ambassador_id}]
351 hostname: "*"
352 prefix: /{self.name}-2/
353 service: {self.target.path.fqdn}
354"""
355 )
356 + super().manifests()
357 )
358
359 def queries(self):
360 yield Query(self.url(self.name + "-1/"))
361 yield Query(self.url(self.name + "-2/"))
362
363
364class LongClusterNameMapping(AmbassadorTest):
365 target: ServiceType
366
367 def init(self):
368 self.target = HTTPBin()
369
370 def manifests(self) -> str:
371 return (
372 self.format(
373 """
374---
375apiVersion: v1
376kind: Service
377metadata:
378 name: thisisaverylongservicenameoverwithsixythreecharacters123456789
379spec:
380 type: ExternalName
381 externalName: {self.target.path.fqdn}
382---
383apiVersion: getambassador.io/v3alpha1
384kind: Mapping
385metadata:
386 name: {self.target.path.k8s}
387spec:
388 ambassador_id: [{self.ambassador_id}]
389 hostname: "*"
390 prefix: /{self.name}-1/
391 service: thisisaverylongservicenameoverwithsixythreecharacters123456789
392"""
393 )
394 + super().manifests()
395 )
396
397 def queries(self):
398 yield Query(self.url(self.name + "-1/"))
View as plain text