1import json
2from typing import Generator, Literal, Tuple, Union
3
4from abstract_tests import AGRPC, AHTTP, HTTP, AmbassadorTest, Node, ServiceType, WebsocketEcho
5from ambassador import Config
6from kat.harness import EDGE_STACK, Query
7from tests.selfsigned import TLSCerts
8
9
10class AuthenticationGRPCTest(AmbassadorTest):
11
12 target: ServiceType
13 auth: ServiceType
14
15 def init(self):
16 if EDGE_STACK:
17 self.xfail = "XFailing for now, custom AuthServices not supported in Edge Stack"
18 self.target = HTTP()
19 self.auth = AGRPC(name="auth")
20
21 def manifests(self) -> str:
22 return (
23 self.format(
24 """
25---
26apiVersion: getambassador.io/v3alpha1
27kind: Mapping
28metadata:
29 name: auth-context-mapping
30spec:
31 ambassador_id: [{self.ambassador_id}]
32 service: {self.target.path.fqdn}
33 hostname: "*"
34 prefix: /context-extensions-crd/
35 auth_context_extensions:
36 context: "auth-context-name"
37 data: "auth-data"
38"""
39 )
40 + super().manifests()
41 )
42
43 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
44 yield self, self.format(
45 """
46---
47apiVersion: getambassador.io/v3alpha1
48kind: AuthService
49name: {self.auth.path.k8s}
50auth_service: "{self.auth.path.fqdn}"
51timeout_ms: 5000
52proto: grpc
53"""
54 )
55 yield self, self.format(
56 """
57---
58apiVersion: getambassador.io/v3alpha1
59kind: Mapping
60name: {self.target.path.k8s}
61hostname: "*"
62prefix: /target/
63service: {self.target.path.fqdn}
64---
65apiVersion: getambassador.io/v3alpha1
66kind: Mapping
67name: {self.target.path.k8s}-context-extensions
68hostname: "*"
69prefix: /context-extensions/
70service: {self.target.path.fqdn}
71auth_context_extensions:
72 first: "first element"
73 second: "second element"
74"""
75 )
76
77 def queries(self):
78 # [0]
79 yield Query(
80 self.url("target/"),
81 headers={"requested-status": "401", "baz": "baz", "request-header": "baz"},
82 expected=401,
83 )
84 # [1]
85 yield Query(
86 self.url("target/"),
87 headers={"requested-status": "302", "requested-location": "foo"},
88 expected=302,
89 )
90
91 # [2]
92 yield Query(
93 self.url("target/"),
94 headers={"requested-status": "401", "x-foo": "foo", "requested-header": "x-foo"},
95 expected=401,
96 )
97 # [3]
98 yield Query(
99 self.url("target/"),
100 headers={
101 "requested-status": "200",
102 "authorization": "foo-11111",
103 "foo": "foo",
104 "x-grpc-auth-append": "foo=bar;baz=bar",
105 "requested-header": "Authorization",
106 },
107 expected=200,
108 )
109 # [4]
110 yield Query(
111 self.url("context-extensions/"),
112 headers={
113 "request-status": "200",
114 "authorization": "foo-22222",
115 "requested-header": "Authorization",
116 },
117 expected=200,
118 )
119 # [5]
120 yield Query(
121 self.url("context-extensions-crd/"),
122 headers={
123 "request-status": "200",
124 "authorization": "foo-33333",
125 "requested-header": "Authorization",
126 },
127 expected=200,
128 )
129
130 def check(self):
131 # [0] Verifies all request headers sent to the authorization server.
132 assert self.results[0].backend.name == self.auth.path.k8s
133 assert self.results[0].backend.request.url.path == "/target/"
134 assert self.results[0].backend.request.headers["x-envoy-internal"] == ["true"]
135 assert self.results[0].backend.request.headers["x-forwarded-proto"] == ["http"]
136 assert "user-agent" in self.results[0].backend.request.headers
137 assert "baz" in self.results[0].backend.request.headers
138 assert self.results[0].status == 401
139 assert self.results[0].headers["Server"] == ["envoy"]
140 assert self.results[0].headers["X-Grpc-Service-Protocol-Version"] == ["v2"]
141
142 # [1] Verifies that Location header is returned from Envoy.
143 assert self.results[1].backend.name == self.auth.path.k8s
144 assert self.results[1].backend.request.headers["requested-status"] == ["302"]
145 assert self.results[1].backend.request.headers["requested-location"] == ["foo"]
146 assert self.results[1].status == 302
147 assert self.results[1].headers["Location"] == ["foo"]
148 assert self.results[1].headers["X-Grpc-Service-Protocol-Version"] == ["v2"]
149
150 # [2] Verifies Envoy returns whitelisted headers input by the user.
151 assert self.results[2].backend.name == self.auth.path.k8s
152 assert self.results[2].backend.request.headers["requested-status"] == ["401"]
153 assert self.results[2].backend.request.headers["requested-header"] == ["x-foo"]
154 assert self.results[2].backend.request.headers["x-foo"] == ["foo"]
155 assert self.results[2].status == 401
156 assert self.results[2].headers["Server"] == ["envoy"]
157 assert self.results[2].headers["X-Foo"] == ["foo"]
158 assert self.results[2].headers["X-Grpc-Service-Protocol-Version"] == ["v2"]
159
160 # [3] Verifies default whitelisted Authorization request header.
161 assert self.results[3].backend.request.headers["requested-status"] == ["200"]
162 assert self.results[3].backend.request.headers["requested-header"] == ["Authorization"]
163 assert self.results[3].backend.request.headers["authorization"] == ["foo-11111"]
164 assert self.results[3].backend.request.headers["foo"] == ["foo,bar"]
165 assert self.results[3].backend.request.headers["baz"] == ["bar"]
166 assert self.results[3].status == 200
167 assert self.results[3].headers["Server"] == ["envoy"]
168 assert self.results[3].headers["Authorization"] == ["foo-11111"]
169 assert self.results[3].backend.request.headers["x-grpc-service-protocol-version"] == ["v2"]
170
171 # [4] Verifies that auth_context_extension is passed along by Envoy.
172 assert self.results[4].status == 200
173 assert self.results[4].headers["Server"] == ["envoy"]
174 assert self.results[4].headers["Authorization"] == ["foo-22222"]
175 context_ext = json.loads(
176 self.results[4].backend.request.headers["x-request-context-extensions"][0]
177 )
178 assert context_ext["first"] == "first element"
179 assert context_ext["second"] == "second element"
180
181 # [5] Verifies that auth_context_extension is passed along by Envoy when using a crd Mapping
182 assert self.results[5].status == 200
183 assert self.results[5].headers["Server"] == ["envoy"]
184 assert self.results[5].headers["Authorization"] == ["foo-33333"]
185 context_ext = json.loads(
186 self.results[5].backend.request.headers["x-request-context-extensions"][0]
187 )
188 assert context_ext["context"] == "auth-context-name"
189 assert context_ext["data"] == "auth-data"
190
191
192class AuthenticationHTTPPartialBufferTest(AmbassadorTest):
193
194 target: ServiceType
195 auth: ServiceType
196
197 def init(self):
198 if EDGE_STACK:
199 self.xfail = "XFailing for now, custom AuthServices not supported in Edge Stack"
200 self.target = HTTP()
201 self.auth = HTTP(name="auth")
202
203 def manifests(self) -> str:
204 return (
205 f"""
206---
207apiVersion: v1
208data:
209 tls.crt: {TLSCerts["tls-context-host-1"].k8s_crt}
210 tls.key: {TLSCerts["tls-context-host-1"].k8s_key}
211kind: Secret
212metadata:
213 name: auth-partial-secret
214type: kubernetes.io/tls
215"""
216 + super().manifests()
217 )
218
219 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
220 yield self, self.format(
221 """
222---
223apiVersion: getambassador.io/v3alpha1
224kind: TLSContext
225name: {self.name}-same-context-1
226secret: auth-partial-secret
227
228---
229apiVersion: getambassador.io/v3alpha1
230kind: AuthService
231name: {self.auth.path.k8s}
232auth_service: "{self.auth.path.fqdn}"
233path_prefix: "/extauth"
234timeout_ms: 5000
235tls: {self.name}-same-context-1
236
237allowed_request_headers:
238- Requested-Status
239- Requested-Header
240
241allowed_authorization_headers:
242- Auth-Request-Body
243
244add_auth_headers:
245 X-Added-Auth: auth-added
246
247include_body:
248 max_bytes: 7
249 allow_partial: true
250"""
251 )
252 yield self, self.format(
253 """
254---
255apiVersion: getambassador.io/v3alpha1
256kind: Mapping
257name: {self.target.path.k8s}
258hostname: "*"
259prefix: /target/
260service: {self.target.path.fqdn}
261"""
262 )
263
264 def queries(self):
265 # [0]
266 yield Query(
267 self.url("target/"),
268 headers={"Requested-Status": "200"},
269 body="message_body",
270 expected=200,
271 )
272
273 # [1]
274 yield Query(
275 self.url("target/"), headers={"Requested-Status": "200"}, body="body", expected=200
276 )
277
278 # [2]
279 yield Query(
280 self.url("target/"), headers={"Requested-Status": "401"}, body="body", expected=401
281 )
282
283 def check(self):
284 # [0] Verifies that the authorization server received the partial message body.
285 extauth_res1 = json.loads(self.results[0].headers["Extauth"][0])
286 assert self.results[0].backend.request.headers["requested-status"] == ["200"]
287 assert self.results[0].status == 200
288 assert self.results[0].headers["Server"] == ["envoy"]
289 assert extauth_res1["request"]["headers"]["auth-request-body"] == ["message"]
290
291 # [1] Verifies that the authorization server received the full message body.
292 extauth_res2 = json.loads(self.results[1].headers["Extauth"][0])
293 assert self.results[1].backend.request.headers["requested-status"] == ["200"]
294 assert self.results[1].status == 200
295 assert self.results[1].headers["Server"] == ["envoy"]
296 assert extauth_res2["request"]["headers"]["auth-request-body"] == ["body"]
297
298 # [2] Verifies that the authorization server received added headers
299 assert self.results[2].backend.request.headers["requested-status"] == ["401"]
300 assert self.results[2].backend.request.headers["x-added-auth"] == ["auth-added"]
301 assert self.results[2].status == 401
302 assert self.results[2].headers["Server"] == ["envoy"]
303 assert extauth_res2["request"]["headers"]["auth-request-body"] == ["body"]
304
305
306class AuthenticationHTTPBufferedTest(AmbassadorTest):
307
308 target: ServiceType
309 auth: ServiceType
310
311 def init(self):
312 if EDGE_STACK:
313 self.xfail = "XFailing for now, custom AuthServices not supported in Edge Stack"
314 self.target = HTTP()
315 self.auth = HTTP(name="auth")
316
317 def manifests(self) -> str:
318 return (
319 f"""
320---
321apiVersion: v1
322data:
323 tls.crt: {TLSCerts["tls-context-host-1"].k8s_crt}
324 tls.key: {TLSCerts["tls-context-host-1"].k8s_key}
325kind: Secret
326metadata:
327 name: auth-buffered-secret
328type: kubernetes.io/tls
329"""
330 + super().manifests()
331 )
332
333 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
334 yield self, self.format(
335 """
336---
337apiVersion: getambassador.io/v3alpha1
338kind: Module
339name: ambassador
340config:
341 add_linkerd_headers: true
342 buffer:
343 max_request_bytes: 16384
344---
345apiVersion: getambassador.io/v3alpha1
346kind: TLSContext
347name: {self.name}-same-context-1
348secret: auth-buffered-secret
349---
350apiVersion: getambassador.io/v3alpha1
351kind: AuthService
352name: {self.auth.path.k8s}
353auth_service: "{self.auth.path.fqdn}"
354path_prefix: "/extauth"
355timeout_ms: 5000
356tls: {self.name}-same-context-1
357
358allowed_request_headers:
359- X-Foo
360- X-Bar
361- Requested-Status
362- Requested-Header
363- Requested-Cookie
364- Location
365
366allowed_authorization_headers:
367- X-Foo
368- Set-Cookie
369
370include_body:
371 max_bytes: 4096
372 allow_partial: true
373"""
374 )
375 yield self, self.format(
376 """
377---
378apiVersion: getambassador.io/v3alpha1
379kind: Mapping
380name: {self.target.path.k8s}
381hostname: "*"
382prefix: /target/
383service: {self.target.path.fqdn}
384"""
385 )
386
387 def queries(self):
388 # [0]
389 yield Query(
390 self.url("target/"),
391 headers={"Requested-Status": "401", "Baz": "baz", "Request-Header": "Baz"},
392 expected=401,
393 )
394 # [1]
395 yield Query(
396 self.url("target/"),
397 headers={
398 "requested-status": "302",
399 "location": "foo",
400 "requested-cookie": "foo, bar, baz",
401 "requested-header": "location",
402 },
403 expected=302,
404 )
405 # [2]
406 yield Query(
407 self.url("target/"),
408 headers={"Requested-Status": "401", "X-Foo": "foo", "Requested-Header": "X-Foo"},
409 expected=401,
410 )
411 # [3]
412 yield Query(
413 self.url("target/"),
414 headers={"Requested-Status": "401", "X-Bar": "bar", "Requested-Header": "X-Bar"},
415 expected=401,
416 )
417 # [4]
418 yield Query(
419 self.url("target/"),
420 headers={
421 "Requested-Status": "200",
422 "Authorization": "foo-11111",
423 "Requested-Header": "Authorization",
424 },
425 expected=200,
426 )
427
428 def check(self):
429 # [0] Verifies all request headers sent to the authorization server.
430 assert self.results[0].backend.name == self.auth.path.k8s
431 assert self.results[0].backend.request.url.path == "/extauth/target/"
432 assert self.results[0].backend.request.headers["x-forwarded-proto"] == ["http"]
433 assert self.results[0].backend.request.headers["content-length"] == ["0"]
434 assert "x-forwarded-for" in self.results[0].backend.request.headers
435 assert "user-agent" in self.results[0].backend.request.headers
436 assert "baz" not in self.results[0].backend.request.headers
437 assert self.results[0].status == 401
438 assert self.results[0].headers["Server"] == ["envoy"]
439
440 # [1] Verifies that Location header is returned from Envoy.
441 assert self.results[1].backend.name == self.auth.path.k8s
442 assert self.results[1].backend.request.headers["requested-status"] == ["302"]
443 assert self.results[1].backend.request.headers["requested-header"] == ["location"]
444 assert self.results[1].backend.request.headers["location"] == ["foo"]
445 assert self.results[1].status == 302
446 assert self.results[1].headers["Server"] == ["envoy"]
447 assert self.results[1].headers["Location"] == ["foo"]
448 assert self.results[1].headers["Set-Cookie"] == ["foo=foo", "bar=bar", "baz=baz"]
449
450 # [2] Verifies Envoy returns whitelisted headers input by the user.
451 assert self.results[2].backend.name == self.auth.path.k8s
452 assert self.results[2].backend.request.headers["requested-status"] == ["401"]
453 assert self.results[2].backend.request.headers["requested-header"] == ["X-Foo"]
454 assert self.results[2].backend.request.headers["x-foo"] == ["foo"]
455 assert self.results[2].status == 401
456 assert self.results[2].headers["Server"] == ["envoy"]
457 assert self.results[2].headers["X-Foo"] == ["foo"]
458
459 # [3] Verifies that envoy does not return not whitelisted headers.
460 assert self.results[3].backend.name == self.auth.path.k8s
461 assert self.results[3].backend.request.headers["requested-status"] == ["401"]
462 assert self.results[3].backend.request.headers["requested-header"] == ["X-Bar"]
463 assert self.results[3].backend.request.headers["x-bar"] == ["bar"]
464 assert self.results[3].status == 401
465 assert self.results[3].headers["Server"] == ["envoy"]
466 assert "X-Bar" not in self.results[3].headers
467
468 # [4] Verifies default whitelisted Authorization request header.
469 assert self.results[4].backend.request.headers["requested-status"] == ["200"]
470 assert self.results[4].backend.request.headers["requested-header"] == ["Authorization"]
471 assert self.results[4].backend.request.headers["authorization"] == ["foo-11111"]
472 assert self.results[4].backend.request.headers["l5d-dst-override"] == [
473 f"{self.target.path.fqdn}:80"
474 ]
475 assert self.results[4].status == 200
476 assert self.results[4].headers["Server"] == ["envoy"]
477 assert self.results[4].headers["Authorization"] == ["foo-11111"]
478
479
480class AuthenticationHTTPFailureModeAllowTest(AmbassadorTest):
481 target: ServiceType
482 auth: ServiceType
483
484 def init(self):
485 if EDGE_STACK:
486 self.xfail = "XFailing for now, custom AuthServices not supported in Edge Stack"
487 self.target = HTTP()
488 self.auth = HTTP(name="auth")
489
490 def manifests(self) -> str:
491 return (
492 f"""
493---
494apiVersion: v1
495data:
496 tls.crt: {TLSCerts["tls-context-host-1"].k8s_crt}
497 tls.key: {TLSCerts["tls-context-host-1"].k8s_key}
498kind: Secret
499metadata:
500 name: auth-failure-secret
501type: kubernetes.io/tls
502"""
503 + super().manifests()
504 )
505
506 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
507 yield self, self.format(
508 """
509---
510apiVersion: getambassador.io/v3alpha1
511kind: TLSContext
512name: {self.name}-failure-context
513secret: auth-failure-secret
514
515---
516apiVersion: getambassador.io/v3alpha1
517kind: AuthService
518name: {self.auth.path.k8s}
519auth_service: "{self.auth.path.fqdn}"
520path_prefix: "/extauth"
521timeout_ms: 5000
522tls: {self.name}-failure-context
523
524allowed_request_headers:
525- Requested-Status
526- Requested-Header
527
528failure_mode_allow: true
529"""
530 )
531 yield self, self.format(
532 """
533---
534apiVersion: getambassador.io/v3alpha1
535kind: Mapping
536name: {self.target.path.k8s}
537hostname: "*"
538prefix: /target/
539service: {self.target.path.fqdn}
540"""
541 )
542
543 def queries(self):
544 # [0]
545 yield Query(self.url("target/"), headers={"Requested-Status": "200"}, expected=200)
546
547 # [1]
548 yield Query(self.url("target/"), headers={"Requested-Status": "503"}, expected=503)
549
550 def check(self):
551 # [0] Verifies that the authorization server received the partial message body.
552 extauth_res1 = json.loads(self.results[0].headers["Extauth"][0])
553 assert self.results[0].backend.request.headers["requested-status"] == ["200"]
554 assert self.results[0].status == 200
555 assert self.results[0].headers["Server"] == ["envoy"]
556
557 # [1] Verifies that the authorization server received the full message body.
558 extauth_res2 = json.loads(self.results[1].headers["Extauth"][0])
559 assert self.results[1].backend.request.headers["requested-status"] == ["503"]
560 assert self.results[1].headers["Server"] == ["envoy"]
561
562
563class AuthenticationTestV1(AmbassadorTest):
564
565 target: ServiceType
566 auth: ServiceType
567
568 def init(self):
569 if EDGE_STACK:
570 self.xfail = "XFailing for now, custom AuthServices not supported in Edge Stack"
571 self.target = HTTP()
572 self.auth1 = AHTTP(name="auth1")
573 self.auth2 = AHTTP(name="auth2")
574 self.backend_counts = {}
575
576 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
577 yield self, self.format(
578 """
579---
580apiVersion: getambassador.io/v3alpha1
581kind: AuthService
582name: {self.auth1.path.k8s}
583auth_service: "{self.auth1.path.fqdn}"
584proto: http
585path_prefix: "/extauth"
586timeout_ms: 5000
587
588allowed_request_headers:
589- X-Foo
590- X-Bar
591- Requested-Status
592- Requested-Header
593- Location
594
595allowed_authorization_headers:
596- X-Foo
597- Extauth
598
599status_on_error:
600 code: 503
601
602---
603apiVersion: getambassador.io/v3alpha1
604kind: AuthService
605name: {self.auth2.path.k8s}
606auth_service: "{self.auth2.path.fqdn}"
607proto: http
608path_prefix: "/extauth"
609timeout_ms: 5000
610add_linkerd_headers: true
611
612allowed_request_headers:
613- X-Foo
614- X-Bar
615- Requested-Status
616- Requested-Header
617- Location
618
619allowed_authorization_headers:
620- X-Foo
621- Extauth
622
623status_on_error:
624 code: 503
625
626"""
627 )
628 yield self, self.format(
629 """
630---
631apiVersion: getambassador.io/v3alpha1
632kind: Mapping
633name: {self.target.path.k8s}
634hostname: "*"
635prefix: /target/
636service: {self.target.path.fqdn}
637---
638apiVersion: getambassador.io/v3alpha1
639kind: Mapping
640name: {self.target.path.fqdn}-unauthed
641hostname: "*"
642prefix: /target/unauthed/
643service: {self.target.path.fqdn}
644bypass_auth: true
645"""
646 )
647
648 def queries(self):
649 # [0]
650 yield Query(
651 self.url("target/0"),
652 headers={"Requested-Status": "401", "Baz": "baz", "Request-Header": "Baz"},
653 expected=401,
654 )
655 # [1]
656 yield Query(
657 self.url("target/1"),
658 headers={"requested-status": "302", "location": "foo", "requested-header": "location"},
659 expected=302,
660 )
661 # [2]
662 yield Query(
663 self.url("target/2"),
664 headers={"Requested-Status": "401", "X-Foo": "foo", "Requested-Header": "X-Foo"},
665 expected=401,
666 )
667 # [3]
668 yield Query(
669 self.url("target/3"),
670 headers={"Requested-Status": "401", "X-Bar": "bar", "Requested-Header": "X-Bar"},
671 expected=401,
672 )
673 # [4]
674 yield Query(
675 self.url("target/4"),
676 headers={
677 "Requested-Status": "200",
678 "Authorization": "foo-11111",
679 "Requested-Header": "Authorization",
680 },
681 expected=200,
682 )
683
684 # [5]
685 yield Query(self.url("target/5"), headers={"X-Forwarded-Proto": "https"}, expected=200)
686
687 # [6]
688 yield Query(
689 self.url("target/unauthed/6"), headers={"Requested-Status": "200"}, expected=200
690 )
691
692 # [7]
693 yield Query(self.url("target/7"), headers={"Requested-Status": "500"}, expected=503)
694
695 # Create some traffic to make it more likely that both auth services get at least one
696 # request
697 for i in range(20):
698 yield Query(
699 self.url("target/" + str(8 + i)), headers={"Requested-Status": "403"}, expected=403
700 )
701
702 def check_backend_name(self, result) -> bool:
703 backend_name = result.backend.name
704
705 self.backend_counts.setdefault(backend_name, 0)
706 self.backend_counts[backend_name] += 1
707
708 return (backend_name == self.auth1.path.k8s) or (backend_name == self.auth2.path.k8s)
709
710 def check(self):
711
712 # [0] Verifies all request headers sent to the authorization server.
713 assert self.check_backend_name(self.results[0])
714 assert self.results[0].backend.request.url.path == "/extauth/target/0"
715 assert self.results[0].backend.request.headers["x-forwarded-proto"] == ["http"]
716 assert self.results[0].backend.request.headers["content-length"] == ["0"]
717 assert "x-forwarded-for" in self.results[0].backend.request.headers
718 assert "user-agent" in self.results[0].backend.request.headers
719 assert "baz" not in self.results[0].backend.request.headers
720 assert self.results[0].status == 401
721 assert self.results[0].headers["Server"] == ["envoy"]
722
723 # [1] Verifies that Location header is returned from Envoy.
724 assert self.check_backend_name(self.results[1])
725 assert self.results[1].backend.request.headers["requested-status"] == ["302"]
726 assert self.results[1].backend.request.headers["requested-header"] == ["location"]
727 assert self.results[1].backend.request.headers["location"] == ["foo"]
728 assert self.results[1].status == 302
729 assert self.results[1].headers["Server"] == ["envoy"]
730 assert self.results[1].headers["Location"] == ["foo"]
731
732 # [2] Verifies Envoy returns whitelisted headers input by the user.
733 assert self.check_backend_name(self.results[2])
734 assert self.results[2].backend.request.headers["requested-status"] == ["401"]
735 assert self.results[2].backend.request.headers["requested-header"] == ["X-Foo"]
736 assert self.results[2].backend.request.headers["x-foo"] == ["foo"]
737 assert self.results[2].status == 401
738 assert self.results[2].headers["Server"] == ["envoy"]
739 assert self.results[2].headers["X-Foo"] == ["foo"]
740
741 # [3] Verifies that envoy does not return not whitelisted headers.
742 assert self.check_backend_name(self.results[3])
743 assert self.results[3].backend.request.headers["requested-status"] == ["401"]
744 assert self.results[3].backend.request.headers["requested-header"] == ["X-Bar"]
745 assert self.results[3].backend.request.headers["x-bar"] == ["bar"]
746 assert self.results[3].status == 401
747 assert self.results[3].headers["Server"] == ["envoy"]
748 assert "X-Bar" not in self.results[3].headers
749
750 # [4] Verifies default whitelisted Authorization request header.
751 assert (
752 self.results[4].backend.name == self.target.path.k8s
753 ) # this response is from an auth success
754 assert self.results[4].backend.request.headers["requested-status"] == ["200"]
755 assert self.results[4].backend.request.headers["requested-header"] == ["Authorization"]
756 assert self.results[4].backend.request.headers["authorization"] == ["foo-11111"]
757 assert self.results[4].status == 200
758 assert self.results[4].headers["Server"] == ["envoy"]
759 assert self.results[4].headers["Authorization"] == ["foo-11111"]
760
761 extauth_req = json.loads(self.results[4].backend.request.headers["extauth"][0])
762 assert extauth_req["request"]["headers"]["l5d-dst-override"] == ["extauth:80"]
763
764 # [5] Verify that X-Forwarded-Proto makes it to the auth service.
765 #
766 # We use the 'extauth' header returned from the test extauth service for this, since
767 # the extauth service (on success) won't actually alter other things going upstream.
768 r5 = self.results[5]
769 assert r5
770 assert r5.backend.name == self.target.path.k8s # this response is from an auth success
771
772 assert r5.status == 200
773 assert r5.headers["Server"] == ["envoy"]
774
775 eahdr = r5.backend.request.headers["extauth"]
776 assert eahdr, "no extauth header was returned?"
777 assert eahdr[0], "an empty extauth header element was returned?"
778
779 # [6] Verifies that Envoy bypasses external auth when disabled for a mapping.
780 assert (
781 self.results[6].backend.name == self.target.path.k8s
782 ) # ensure the request made it to the backend
783 assert not self.check_backend_name(
784 self.results[6]
785 ) # ensure the request did not go to the auth service
786 assert self.results[6].backend.request.headers["requested-status"] == ["200"]
787 assert self.results[6].status == 200
788 assert self.results[6].headers["Server"] == ["envoy"]
789
790 try:
791 eainfo = json.loads(eahdr[0])
792
793 if eainfo:
794 # Envoy should force this to HTTP, not HTTPS.
795 assert eainfo["request"]["headers"]["x-forwarded-proto"] == ["http"]
796 except ValueError as e:
797 assert False, "could not parse Extauth header '%s': %s" % (eahdr, e)
798
799 # [7] Verifies that envoy returns customized status_on_error code.
800 assert self.results[7].status == 503
801
802 # TODO(gsagula): Write tests for all UCs which request header headers
803 # are overridden, e.g. Authorization.
804
805 for i in range(20):
806 assert self.check_backend_name(self.results[8 + i])
807
808 print("auth1 service got %d requests" % self.backend_counts.get(self.auth1.path.k8s, -1))
809 print("auth2 service got %d requests" % self.backend_counts.get(self.auth2.path.k8s, -1))
810 assert self.backend_counts.get(self.auth1.path.k8s, 0) > 0, "auth1 got no requests"
811 assert self.backend_counts.get(self.auth2.path.k8s, 0) > 0, "auth2 got no requests"
812
813
814class AuthenticationTest(AmbassadorTest):
815 target: ServiceType
816 auth: ServiceType
817
818 def init(self):
819 if EDGE_STACK:
820 self.xfail = "XFailing for now, custom AuthServices not supported in Edge Stack"
821 self.target = HTTP()
822 self.auth = AHTTP(name="auth")
823
824 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
825 yield self, self.format(
826 """
827---
828apiVersion: getambassador.io/v3alpha1
829kind: AuthService
830name: {self.auth.path.k8s}
831auth_service: "{self.auth.path.fqdn}"
832path_prefix: "/extauth"
833
834allowed_request_headers:
835- X-Foo
836- X-Bar
837- Requested-Location
838- Requested-Status
839- Requested-Header
840
841allowed_authorization_headers:
842- X-Foo
843- X-Bar
844- Extauth
845
846"""
847 )
848 yield self, self.format(
849 """
850---
851apiVersion: getambassador.io/v3alpha1
852kind: Mapping
853name: {self.target.path.k8s}
854hostname: "*"
855prefix: /target/
856service: {self.target.path.fqdn}
857"""
858 )
859
860 def queries(self):
861 # [0]
862 yield Query(
863 self.url("target/"),
864 headers={"Requested-Status": "401", "Baz": "baz", "Request-Header": "Baz"},
865 expected=401,
866 )
867 # [1]
868 yield Query(
869 self.url("target/"),
870 headers={
871 "requested-status": "302",
872 "requested-location": "foo",
873 "requested-header": "location",
874 },
875 expected=302,
876 )
877 # [2]
878 yield Query(
879 self.url("target/"),
880 headers={"Requested-Status": "401", "X-Foo": "foo", "Requested-Header": "X-Foo"},
881 expected=401,
882 )
883 # [3]
884 yield Query(
885 self.url("target/"),
886 headers={"Requested-Status": "401", "X-Bar": "bar", "Requested-Header": "X-Bar"},
887 expected=401,
888 )
889 # [4]
890 yield Query(
891 self.url("target/"),
892 headers={
893 "Requested-Status": "200",
894 "Authorization": "foo-11111",
895 "Requested-Header": "Authorization",
896 },
897 expected=200,
898 )
899 # [5]
900 yield Query(self.url("target/"), headers={"X-Forwarded-Proto": "https"}, expected=200)
901
902 def check(self):
903 # [0] Verifies all request headers sent to the authorization server.
904 assert (
905 self.results[0].backend.name == self.auth.path.k8s
906 ), f"wanted backend {self.auth.path.k8s}, got {self.results[0].backend.name}"
907 assert self.results[0].backend.request.url.path == "/extauth/target/"
908 assert self.results[0].backend.request.headers["content-length"] == ["0"]
909 assert "x-forwarded-for" in self.results[0].backend.request.headers
910 assert "user-agent" in self.results[0].backend.request.headers
911 assert "baz" not in self.results[0].backend.request.headers
912 assert self.results[0].status == 401
913 assert self.results[0].headers["Server"] == ["envoy"]
914
915 # [1] Verifies that Location header is returned from Envoy.
916 assert self.results[1].backend.name == self.auth.path.k8s
917 assert self.results[1].backend.request.headers["requested-status"] == ["302"]
918 assert self.results[1].backend.request.headers["requested-header"] == ["location"]
919 assert self.results[1].backend.request.headers["requested-location"] == ["foo"]
920 assert self.results[1].status == 302
921 assert self.results[1].headers["Server"] == ["envoy"]
922 assert self.results[1].headers["Location"] == ["foo"]
923
924 # [2] Verifies Envoy returns whitelisted headers input by the user.
925 assert self.results[2].backend.name == self.auth.path.k8s
926 assert self.results[2].backend.request.headers["requested-status"] == ["401"]
927 assert self.results[2].backend.request.headers["requested-header"] == ["X-Foo"]
928 assert self.results[2].backend.request.headers["x-foo"] == ["foo"]
929 assert self.results[2].status == 401
930 assert self.results[2].headers["Server"] == ["envoy"]
931 assert self.results[2].headers["X-Foo"] == ["foo"]
932
933 # [3] Verifies that envoy does not return not whitelisted headers.
934 assert self.results[3].backend.name == self.auth.path.k8s
935 assert self.results[3].backend.request.headers["requested-status"] == ["401"]
936 assert self.results[3].backend.request.headers["requested-header"] == ["X-Bar"]
937 assert self.results[3].backend.request.headers["x-bar"] == ["bar"]
938 assert self.results[3].status == 401
939 assert self.results[3].headers["Server"] == ["envoy"]
940 assert "X-Bar" in self.results[3].headers
941
942 # [4] Verifies default whitelisted Authorization request header.
943 assert self.results[4].backend.request.headers["requested-status"] == ["200"]
944 assert self.results[4].backend.request.headers["requested-header"] == ["Authorization"]
945 assert self.results[4].backend.request.headers["authorization"] == ["foo-11111"]
946 assert self.results[4].status == 200
947 assert self.results[4].headers["Server"] == ["envoy"]
948 assert self.results[4].headers["Authorization"] == ["foo-11111"]
949
950 # [5] Verify that X-Forwarded-Proto makes it to the auth service.
951 #
952 # We use the 'extauth' header returned from the test extauth service for this, since
953 # the extauth service (on success) won't actually alter other things going upstream.
954 r5 = self.results[5]
955 assert r5
956
957 assert r5.status == 200
958 assert r5.headers["Server"] == ["envoy"]
959
960 eahdr = r5.backend.request.headers["extauth"]
961 assert eahdr, "no extauth header was returned?"
962 assert eahdr[0], "an empty extauth header element was returned?"
963
964 try:
965 eainfo = json.loads(eahdr[0])
966
967 if eainfo:
968 # Envoy should force this to HTTP, not HTTPS.
969 assert eainfo["request"]["headers"]["x-forwarded-proto"] == ["http"]
970 except ValueError as e:
971 assert False, "could not parse Extauth header '%s': %s" % (eahdr, e)
972
973 # TODO(gsagula): Write tests for all UCs which request header headers
974 # are overridden, e.g. Authorization.
975
976
977class AuthenticationWebsocketTest(AmbassadorTest):
978
979 auth: ServiceType
980 backend: ServiceType
981
982 def init(self):
983 if EDGE_STACK:
984 self.xfail = "XFailing for now, custom AuthServices not supported in Edge Stack"
985 self.auth = HTTP(name="auth")
986 self.backend = WebsocketEcho()
987
988 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
989 yield self, self.format(
990 """
991---
992apiVersion: getambassador.io/v3alpha1
993kind: AuthService
994name: {self.auth.path.k8s}
995auth_service: "{self.auth.path.fqdn}"
996path_prefix: "/extauth"
997timeout_ms: 10000
998allowed_request_headers:
999- Requested-Status
1000allow_request_body: true
1001---
1002apiVersion: getambassador.io/v3alpha1
1003kind: Mapping
1004name: {self.name}
1005hostname: "*"
1006prefix: /{self.name}/
1007service: {self.backend.path.fqdn}
1008use_websocket: true
1009"""
1010 )
1011
1012 def queries(self):
1013 yield Query(self.url(self.name + "/"), expected=404)
1014
1015 yield Query(self.url(self.name + "/", scheme="ws"), messages=["one", "two", "three"])
1016
1017 def check(self):
1018 assert self.results[-1].messages == ["one", "two", "three"]
1019
1020
1021class AuthenticationGRPCVerTest(AmbassadorTest):
1022
1023 target: ServiceType
1024 specified_protocol_version: Literal["v2", "v3", "default"]
1025 expected_protocol_version: Literal["v2", "v3"]
1026 auth: ServiceType
1027
1028 @classmethod
1029 def variants(cls) -> Generator[Node, None, None]:
1030 for protocol_version in ["v2", "v3", "default"]:
1031 yield cls(protocol_version, name="{self.specified_protocol_version}")
1032
1033 def init(self, protocol_version: Literal["v2", "v3", "default"]):
1034 self.target = HTTP()
1035 self.specified_protocol_version = protocol_version
1036 self.expected_protocol_version = "v2" if protocol_version == "default" else protocol_version
1037 if Config.envoy_api_version == "V2" and self.expected_protocol_version == "v3":
1038 self.skip_node = True
1039 self.auth = AGRPC(name="auth", protocol_version=self.expected_protocol_version)
1040
1041 def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
1042 yield self, self.format(
1043 """
1044---
1045apiVersion: getambassador.io/v3alpha1
1046kind: AuthService
1047name: {self.auth.path.k8s}
1048auth_service: "{self.auth.path.fqdn}"
1049timeout_ms: 5000
1050proto: grpc
1051"""
1052 ) + (
1053 ""
1054 if self.specified_protocol_version == "default"
1055 else f"protocol_version: '{self.specified_protocol_version}'"
1056 )
1057
1058 yield self, self.format(
1059 """
1060---
1061apiVersion: getambassador.io/v3alpha1
1062kind: Mapping
1063name: {self.target.path.k8s}
1064hostname: "*"
1065prefix: /target/
1066service: {self.target.path.fqdn}
1067"""
1068 )
1069
1070 def queries(self):
1071 # TODO add more
1072 # [0]
1073 yield Query(
1074 self.url("target/"),
1075 headers={"requested-status": "401", "baz": "baz", "request-header": "baz"},
1076 expected=401,
1077 )
1078
1079 # [1]
1080 yield Query(
1081 self.url("target/"),
1082 headers={"requested-status": "302", "requested-location": "foo"},
1083 expected=302,
1084 )
1085
1086 # [2]
1087 yield Query(
1088 self.url("target/"),
1089 headers={"requested-status": "401", "x-foo": "foo", "requested-header": "x-foo"},
1090 expected=401,
1091 )
1092 # [3]
1093 yield Query(
1094 self.url("target/"),
1095 headers={
1096 "requested-status": "200",
1097 "authorization": "foo-11111",
1098 "foo": "foo",
1099 "x-grpc-auth-append": "foo=bar;baz=bar",
1100 "requested-header": "Authorization",
1101 },
1102 expected=200,
1103 )
1104
1105 def check(self):
1106 # [0] Verifies all request headers sent to the authorization server.
1107 assert self.results[0].backend.name == self.auth.path.k8s
1108 assert self.results[0].backend.request.url.path == "/target/"
1109 assert self.results[0].backend.request.headers["x-forwarded-proto"] == ["http"]
1110 assert "user-agent" in self.results[0].backend.request.headers
1111 assert "baz" in self.results[0].backend.request.headers
1112 assert self.results[0].status == 401
1113 assert self.results[0].headers["Server"] == ["envoy"]
1114 assert self.results[0].headers["X-Grpc-Service-Protocol-Version"] == [
1115 self.expected_protocol_version
1116 ]
1117
1118 # [1] Verifies that Location header is returned from Envoy.
1119 assert self.results[1].backend.name == self.auth.path.k8s
1120 assert self.results[1].backend.request.headers["requested-status"] == ["302"]
1121 assert self.results[1].backend.request.headers["requested-location"] == ["foo"]
1122 assert self.results[1].status == 302
1123 assert self.results[1].headers["Location"] == ["foo"]
1124 assert self.results[1].headers["X-Grpc-Service-Protocol-Version"] == [
1125 self.expected_protocol_version
1126 ]
1127
1128 # [2] Verifies Envoy returns whitelisted headers input by the user.
1129 assert self.results[2].backend.name == self.auth.path.k8s
1130 assert self.results[2].backend.request.headers["requested-status"] == ["401"]
1131 assert self.results[2].backend.request.headers["requested-header"] == ["x-foo"]
1132 assert self.results[2].backend.request.headers["x-foo"] == ["foo"]
1133 assert self.results[2].status == 401
1134 assert self.results[2].headers["Server"] == ["envoy"]
1135 assert self.results[2].headers["X-Foo"] == ["foo"]
1136 assert self.results[2].headers["X-Grpc-Service-Protocol-Version"] == [
1137 self.expected_protocol_version
1138 ]
1139
1140 # [3] Verifies default whitelisted Authorization request header.
1141 assert self.results[3].backend.request.headers["requested-status"] == ["200"]
1142 assert self.results[3].backend.request.headers["requested-header"] == ["Authorization"]
1143 assert self.results[3].backend.request.headers["authorization"] == ["foo-11111"]
1144 assert self.results[3].backend.request.headers["foo"] == ["foo,bar"]
1145 assert self.results[3].backend.request.headers["baz"] == ["bar"]
1146 assert self.results[3].status == 200
1147 assert self.results[3].headers["Server"] == ["envoy"]
1148 assert self.results[3].headers["Authorization"] == ["foo-11111"]
1149 assert self.results[3].backend.request.headers["x-grpc-service-protocol-version"] == [
1150 self.expected_protocol_version
1151 ]
View as plain text