1import re
2from typing import Generator, Tuple, Union
3
4import yaml
5
6from abstract_tests import HTTP, AmbassadorTest, Node, ServiceType
7from kat.harness import EDGE_STACK, Query
8from tests.integration.manifests import namespace_manifest
9
10
11class Empty(AmbassadorTest):
12 single_namespace = True
13 namespace = "empty-namespace"
14 extra_ports = [8877]
15
16 def init(self):
17 if EDGE_STACK:
18 self.xfail = "XFailing for now"
19
20 @classmethod
21 def variants(cls) -> Generator[Node, None, None]:
22 yield cls()
23
24 def manifests(self) -> str:
25 return namespace_manifest("empty-namespace") + super().manifests()
26
27 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
28 yield from ()
29
30 def queries(self):
31 yield Query(self.url("ambassador/v0/diag/?json=true&filter=errors"), phase=2)
32 yield Query(self.url("_internal/v0/ping", scheme="http", port=8877), expected=403)
33
34 def check(self):
35 # XXX Ew. If self.results[0].json is empty, the harness won't convert it to a response.
36 errors = self.results[0].json or []
37
38 # We shouldn't have any missing-CRD-types errors any more.
39 for source, error in errors:
40 if ("could not find" in error) and ("CRD definitions" in error):
41 assert False, f"Missing CRDs: {error}"
42
43 if "Ingress resources" in error:
44 assert False, f"Ingress resource error: {error}"
45
46 # The default errors assume that we have missing CRDs, and that's not correct any more,
47 # so don't try to use assert_default_errors here.
48
49
50class AmbassadorIDTest(AmbassadorTest):
51
52 target: ServiceType
53
54 def init(self):
55 self.target = HTTP()
56
57 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
58 yield self, """
59---
60apiVersion: getambassador.io/v3alpha1
61kind: Module
62name: ambassador
63config:
64 use_ambassador_namespace_for_service_resolution: true
65"""
66 for prefix, amb_id in (
67 ("findme", "[{self.ambassador_id}]"),
68 ("findme-array", "[{self.ambassador_id}, missme]"),
69 ("findme-array2", "[missme, {self.ambassador_id}]"),
70 ("missme", "[missme]"),
71 ("missme-array", "[missme1, missme2]"),
72 ):
73 yield self.target, self.format(
74 """
75---
76apiVersion: getambassador.io/v3alpha1
77kind: Mapping
78name: {self.path.k8s}-{prefix}
79hostname: "*"
80prefix: /{prefix}/
81service: {self.target.path.fqdn}
82ambassador_id: {amb_id}
83 """,
84 prefix=self.format(prefix),
85 amb_id=self.format(amb_id),
86 )
87
88 def queries(self):
89 yield Query(self.url("findme/"))
90 yield Query(self.url("findme-array/"))
91 yield Query(self.url("findme-array2/"))
92 yield Query(self.url("missme/"), expected=404)
93 yield Query(self.url("missme-array/"), expected=404)
94
95
96class InvalidResources(AmbassadorTest):
97 target: ServiceType
98
99 def init(self):
100 self.target = HTTP()
101 self.resource_names = []
102
103 self.models = [
104 """
105apiVersion: getambassador.io/v3alpha1
106kind: AuthService
107metadata:
108 name: {self.path.k8s}-as-bad1-<<WHICH>>
109spec:
110 ambassador_id: ["{self.ambassador_id}"]
111 service_bad: {self.target.path.fqdn}
112""",
113 """
114apiVersion: getambassador.io/v3alpha1
115kind: Mapping
116metadata:
117 name: {self.path.k8s}-m-good-<<WHICH>>
118spec:
119 ambassador_id: ["{self.ambassador_id}"]
120 hostname: "*"
121 prefix: /good-<<WHICH>>/
122 service: {self.target.path.fqdn}
123""",
124 """
125apiVersion: getambassador.io/v3alpha1
126kind: Mapping
127metadata:
128 name: {self.path.k8s}-m-bad-<<WHICH>>
129spec:
130 ambassador_id: ["{self.ambassador_id}"]
131 hostname: "*"
132 prefix_bad: /bad-<<WHICH>>/
133 service: {self.target.path.fqdn}
134""",
135 """
136kind: Mapping
137metadata:
138 name: {self.path.k8s}-m-bad-no-apiversion-<<WHICH>>
139spec:
140 ambassador_id: ["{self.ambassador_id}"]
141 hostname: "*"
142 prefix_bad: /bad-<<WHICH>>/
143 service: {self.target.path.fqdn}
144""",
145 """
146apiVersion: getambassador.io/v3alpha1
147kind: Module
148metadata:
149 name: {self.path.k8s}-md-bad-<<WHICH>>
150spec:
151 ambassador_id: ["{self.ambassador_id}"]
152 config_bad: []
153""",
154 """
155apiVersion: getambassador.io/v3alpha1
156kind: RateLimitService
157metadata:
158 name: {self.path.k8s}-r-bad-<<WHICH>>
159spec:
160 ambassador_id: ["{self.ambassador_id}"]
161 service_bad: {self.target.path.fqdn}
162""",
163 """
164apiVersion: getambassador.io/v3alpha1
165kind: TCPMapping
166metadata:
167 name: {self.path.k8s}-tm-bad1-<<WHICH>>
168spec:
169 ambassador_id: ["{self.ambassador_id}"]
170 service: {self.target.path.fqdn}
171 port_bad: 8888
172""",
173 """
174apiVersion: getambassador.io/v3alpha1
175kind: TCPMapping
176metadata:
177 name: {self.path.k8s}-tm-bad2-<<WHICH>>
178spec:
179 ambassador_id: ["{self.ambassador_id}"]
180 service_bad: {self.target.path.fqdn}
181 port: 8888
182""",
183 """
184apiVersion: getambassador.io/v3alpha1
185kind: TracingService
186metadata:
187 name: {self.path.k8s}-ts-bad1-<<WHICH>>
188spec:
189 ambassador_id: ["{self.ambassador_id}"]
190 driver_bad: zipkin
191 service: {self.target.path.fqdn}
192""",
193 """
194apiVersion: getambassador.io/v3alpha1
195kind: TracingService
196metadata:
197 name: {self.path.k8s}-ts-bad2-<<WHICH>>
198spec:
199 ambassador_id: ["{self.ambassador_id}"]
200 driver: zipkin
201 service_bad: {self.target.path.fqdn}
202""",
203 ]
204
205 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
206 counter = 0
207
208 for m_yaml in self.models:
209 counter += 1
210 m = yaml.safe_load(self.format(m_yaml.replace("<<WHICH>>", "annotation")))
211
212 for k in m["metadata"].keys():
213 m[k] = m["metadata"][k]
214 del m["metadata"]
215
216 for k in m["spec"].keys():
217 if k == "ambassador_id":
218 continue
219
220 m[k] = m["spec"][k]
221 del m["spec"]
222
223 if "good" not in m["name"]:
224 # These all show up as "invalidresources.default.N" because they're
225 # annotations.
226 self.resource_names.append(f"invalidresources.default.{counter}")
227
228 yield self, yaml.dump(m)
229
230 def manifests(self):
231 manifests = []
232
233 for m in self.models:
234 m_yaml = self.format(m.replace("<<WHICH>>", "crd"))
235 m_obj = yaml.safe_load(m_yaml)
236 if "apiVersion" not in m_obj:
237 continue
238
239 manifests.append("---")
240 manifests.append(m_yaml)
241
242 if "good" not in m_obj["metadata"]["name"]:
243 self.resource_names.append(m_obj["metadata"]["name"] + ".default.1")
244
245 return super().manifests() + "\n".join(manifests)
246
247 def queries(self):
248 yield Query(self.url("ambassador/v0/diag/?json=true&filter=errors"))
249
250 yield Query(self.url("good-annotation/"), expected=200)
251 yield Query(self.url("bad-annotation/"), expected=404)
252 yield Query(self.url("good-crd/"), expected=200)
253 yield Query(self.url("bad-crd/"), expected=404)
254
255 def check(self):
256 # XXX Ew. If self.results[0].json is empty, the harness won't convert it to a response.
257 errors = self.results[0].json or []
258
259 assert errors, "Invalid resources must generate errors, but we didn't get any"
260
261 error_dict = {}
262
263 for resource, error in errors:
264 error_dict[resource] = error
265
266 for name in self.resource_names:
267 assert name in error_dict, f"no error found for {name}"
268
269 error = error_dict[name]
270
271 # Check that the error is one of the errors that we expect.
272 expected_errors = [
273 re.compile(r"^.* in body is required$"),
274 re.compile(r"^apiVersion None/ unsupported$"),
275 re.compile(r'^spec\.config in body must be of type object: "null"$'),
276 ]
277 assert any(
278 pat.match(error) for pat in expected_errors
279 ), f"error for {name} should match one of the expected errors: {repr(error)}"
280
281
282class ServerNameTest(AmbassadorTest):
283
284 target: ServiceType
285
286 def init(self):
287 self.target = HTTP()
288
289 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
290 yield self, self.format(
291 """
292---
293apiVersion: getambassador.io/v3alpha1
294kind: Module
295name: ambassador
296config:
297 server_name: "test-server"
298---
299apiVersion: getambassador.io/v3alpha1
300kind: Mapping
301name: {self.path.k8s}/server-name
302hostname: "*"
303prefix: /server-name
304service: {self.target.path.fqdn}
305"""
306 )
307
308 def queries(self):
309 yield Query(self.url("server-name/"), expected=301)
310
311 def check(self):
312 assert self.results[0].headers["Server"] == ["test-server"]
313
314
315class SafeRegexMapping(AmbassadorTest):
316
317 target: ServiceType
318
319 def init(self):
320 self.target = HTTP()
321
322 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
323 yield self, self.format(
324 """
325---
326apiVersion: getambassador.io/v3alpha1
327kind: Mapping
328name: {self.name}
329prefix: /{self.name}/
330prefix_regex: true
331host: "[a-zA-Z].*"
332host_regex: true
333regex_headers:
334 X-Foo: "^[a-z].*"
335service: http://{self.target.path.fqdn}
336"""
337 )
338
339 def queries(self):
340 yield Query(self.url(self.name + "/"), headers={"X-Foo": "hello"})
341 yield Query(self.url(f"need-normalization/../{self.name}/"), headers={"X-Foo": "hello"})
342 yield Query(self.url(self.name + "/"), expected=404)
343 yield Query(self.url(f"need-normalization/../{self.name}/"), expected=404)
344
345 def check(self):
346 for r in self.results:
347 if r.backend:
348 assert r.backend.name == self.target.path.k8s, (
349 r.backend.name,
350 self.target.path.k8s,
351 )
352 assert r.backend.request.headers["x-envoy-original-path"][0] == f"/{self.name}/"
353
354
355class UnsafeRegexMapping(AmbassadorTest):
356
357 target: ServiceType
358
359 def init(self):
360 self.target = HTTP()
361
362 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
363 yield self, self.format(
364 """
365---
366apiVersion: getambassador.io/v3alpha1
367kind: Mapping
368name: {self.name}
369prefix: /{self.name}/
370prefix_regex: true
371host: "[a-zA-Z].*"
372host_regex: true
373regex_headers:
374 X-Foo: "^[a-z].*"
375service: http://{self.target.path.fqdn}
376---
377apiVersion: getambassador.io/v3alpha1
378kind: Module
379name: ambassador
380config:
381 regex_type: unsafe
382"""
383 )
384
385 def queries(self):
386 yield Query(self.url(self.name + "/"), headers={"X-Foo": "hello"})
387 yield Query(self.url(f"need-normalization/../{self.name}/"), headers={"X-Foo": "hello"})
388 yield Query(self.url(self.name + "/"), expected=404)
389 yield Query(self.url(f"need-normalization/../{self.name}/"), expected=404)
390
391 def check(self):
392 for r in self.results:
393 if r.backend:
394 assert r.backend.name == self.target.path.k8s, (
395 r.backend.name,
396 self.target.path.k8s,
397 )
398 assert r.backend.request.headers["x-envoy-original-path"][0] == f"/{self.name}/"
View as plain text