...

Text file src/github.com/datawire/ambassador/v2/python/tests/kat/t_extauth.py

Documentation: github.com/datawire/ambassador/v2/python/tests/kat

     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