...

Text file src/github.com/emissary-ingress/emissary/v3/python/tests/kat/t_ip_allow_deny.py

Documentation: github.com/emissary-ingress/emissary/v3/python/tests/kat

     1from typing import Generator, Tuple, Union
     2
     3from abstract_tests import HTTP, AmbassadorTest, Node, ServiceType
     4from kat.harness import Query
     5
     6################
     7# NOTE: The IPAllow and IPDeny tests are not entirely straightforward. In
     8# particular:
     9#
    10# 1. They currently use an annotation for their Ambassador modules to keep
    11#    them distinct between the two tests. If you don't like annotations for
    12#    this, you'll have to set up separate namespaces.
    13#
    14# 2. Our Listener _must_ set l7Depth == 1 in order for the tests to work:
    15#
    16#    - When we hit /target/ with XFF "99.99.0.1", Envoy receives exactly that.
    17#      Since l7Depth is 1, Envoy accepts that as the valid address
    18#      of the remote end of the connection, RBAC accepts that as matching the
    19#      99.99.0.0/16 CIDR block, and the request is allowed or denied as
    20#      appropriate. Great. But when it's accepted, the rules for XFF are that
    21#      Envoy must append the peer address to the XFF list before forwarding, so
    22#      the upstream sees XFF "99.99.0.1,$katIP". In the /target/ case, the
    23#      upstream is a KAT backend HTTP service -- it doesn't care about XFF, and
    24#      just responds OK.
    25#
    26#    - When we hit /localhost/ with XFF "99.99.0.1", though, _Ambassador is the
    27#      upstream_. So everything up to rewriting XFF as "99.99.0.1,$katIP" is the
    28#      same, but Envoy hands that upstream to... itself. Since l7Depth is still 1,
    29#      Envoy throws away the 99.99.0.1 part and believes that the connection is
    30#      coming from $katIP, which does _not_ match the 99.99.0.0/16 CIDR block --
    31#      but the raw peer address _is_ in fact 127.0.0.1, so _that_ matches the
    32#      peer: 127.0.0.1 principal.
    33
    34
    35class IPAllow(AmbassadorTest):
    36    target: ServiceType
    37
    38    def init(self):
    39        self.target = HTTP()
    40        self.add_default_http_listener = False
    41        self.add_default_https_listener = False
    42
    43    def manifests(self) -> str:
    44        return (
    45            self.format(
    46                """
    47---
    48apiVersion: getambassador.io/v3alpha1
    49kind: Listener
    50metadata:
    51  name: {self.path.k8s}-listener
    52  labels:
    53    kat-ambassador-id: {self.ambassador_id}
    54spec:
    55  ambassador_id: [ {self.ambassador_id} ]
    56  port: 8080
    57  protocol: HTTP
    58  securityModel: XFP
    59  hostBinding:
    60    namespace:
    61      from: ALL
    62  # Allow one trusted hop, so that KAT can fake addresses with XFF (see NOTE above).
    63  l7Depth: 1
    64---
    65apiVersion: getambassador.io/v3alpha1
    66kind: Mapping
    67metadata:
    68  name: {self.path.k8s}-target-mapping
    69spec:
    70  ambassador_id: [{self.ambassador_id}]
    71  hostname: "*"
    72  prefix: /target/
    73  service: {self.target.path.fqdn}
    74---
    75apiVersion: getambassador.io/v3alpha1
    76kind: Mapping
    77metadata:
    78  name: {self.path.k8s}-localhost-mapping
    79spec:
    80  ambassador_id: [{self.ambassador_id}]
    81  hostname: "*"
    82  prefix: /localhost/
    83  rewrite: /target/             # See NOTE above
    84  service: 127.0.0.1:8080       # See NOTE above
    85"""
    86            )
    87            + super().manifests()
    88        )
    89
    90    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
    91        yield self, self.format(
    92            """
    93---
    94apiVersion: getambassador.io/v3alpha1
    95kind: Module
    96name: ambassador
    97ambassador_id: [{self.ambassador_id}]
    98config:
    99  ip_allow:
   100    - peer:   127.0.0.1      # peer address must be localhost
   101    - remote: 99.99.0.0/16   # honors PROXY and XFF
   102"""
   103        )
   104
   105    def queries(self):
   106        # 0. Straightforward: hit /target/ and /localhost/ with nothing special, get 403s.
   107        yield Query(self.url("target/00"), expected=403)
   108        yield Query(self.url("localhost/01"), expected=403)
   109
   110        # 1. Hit /target/ and /localhost/ with X-Forwarded-For specifying something good, get 200s.
   111        yield Query(self.url("target/10"), headers={"X-Forwarded-For": "99.99.0.1"})
   112        yield Query(self.url("localhost/11"), headers={"X-Forwarded-For": "99.99.0.1"})
   113
   114        # 2. Hit /target/ and /localhost/ with X-Forwarded-For specifying something bad, get a 403.
   115        yield Query(self.url("target/20"), headers={"X-Forwarded-For": "99.98.0.1"}, expected=403)
   116        yield Query(
   117            self.url("localhost/21"), headers={"X-Forwarded-For": "99.98.0.1"}, expected=403
   118        )
   119
   120        # Done. Note that the /localhost/ endpoint is wrapping around to make a localhost call back
   121        # to Ambassador to check the peer: principal -- see the NOTE above.
   122
   123    def requirements(self):
   124        # We're replacing super()'s requirements deliberately here. Without X-Forwarded-For they can't work.
   125        yield (
   126            "url",
   127            Query(self.url("ambassador/v0/check_ready"), headers={"X-Forwarded-For": "99.99.0.1"}),
   128        )
   129        yield (
   130            "url",
   131            Query(self.url("ambassador/v0/check_alive"), headers={"X-Forwarded-For": "99.99.0.1"}),
   132        )
   133
   134
   135class IPDeny(AmbassadorTest):
   136    target: ServiceType
   137
   138    def init(self):
   139        self.target = HTTP()
   140        self.add_default_http_listener = False
   141        self.add_default_https_listener = False
   142
   143    def manifests(self) -> str:
   144        return (
   145            self.format(
   146                """
   147---
   148apiVersion: getambassador.io/v3alpha1
   149kind: Listener
   150metadata:
   151  name: {self.path.k8s}-listener
   152  labels:
   153    kat-ambassador-id: {self.ambassador_id}
   154spec:
   155  ambassador_id: [ {self.ambassador_id} ]
   156  port: 8080
   157  protocol: HTTP
   158  securityModel: XFP
   159  hostBinding:
   160    namespace:
   161      from: ALL
   162  # Allow one trusted hop, so that KAT can fake addresses with XFF (see NOTE above).
   163  l7Depth: 1
   164---
   165apiVersion: getambassador.io/v3alpha1
   166kind: Mapping
   167metadata:
   168  name: {self.path.k8s}-target-mapping
   169spec:
   170  ambassador_id: [{self.ambassador_id}]
   171  hostname: "*"
   172  prefix: /target/
   173  service: {self.target.path.fqdn}
   174---
   175apiVersion: getambassador.io/v3alpha1
   176kind: Mapping
   177metadata:
   178  name: {self.path.k8s}-localhost-mapping
   179spec:
   180  ambassador_id: [{self.ambassador_id}]
   181  hostname: "*"
   182  prefix: /localhost/
   183  rewrite: /target/             # See NOTE above
   184  service: 127.0.0.1:8080       # See NOTE above
   185"""
   186            )
   187            + super().manifests()
   188        )
   189
   190    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   191        yield self, self.format(
   192            """
   193---
   194apiVersion: getambassador.io/v3alpha1
   195kind: Module
   196name: ambassador
   197ambassador_id: [{self.ambassador_id}]
   198config:
   199  ip_deny:
   200    - peer:   127.0.0.1      # peer address cannot be localhost (weird, huh?)
   201    - remote: 99.98.0.0/16   # honors PROXY and XFF
   202"""
   203        )
   204
   205    def queries(self):
   206        # 0. Straightforward: hit /target/ and /localhost/ with nothing special, get 403s.
   207        yield Query(self.url("target/00"), expected=200)
   208        yield Query(self.url("localhost/01"), expected=403)  # This should _never_ work.
   209
   210        # 1. Hit /target/ and /localhost/ with X-Forwarded-For specifying something bad, get 403s.
   211        yield Query(self.url("target/10"), headers={"X-Forwarded-For": "99.98.0.1"}, expected=403)
   212        yield Query(
   213            self.url("localhost/11"), headers={"X-Forwarded-For": "99.98.0.1"}, expected=403
   214        )
   215
   216        # 2. Hit /target/ with X-Forwarded-For specifying something not so bad, get a 200. /localhost/
   217        #    will _still_ get a 403 though.
   218        yield Query(self.url("target/20"), headers={"X-Forwarded-For": "99.99.0.1"}, expected=200)
   219        yield Query(
   220            self.url("localhost/21"), headers={"X-Forwarded-For": "99.99.0.1"}, expected=403
   221        )
   222
   223        # Done. Note that the /localhost/ endpoint is wrapping around to make a localhost call back
   224        # to Ambassador to check the peer: principal -- see the NOTE above.
   225
   226    def requirements(self):
   227        # We're replacing super()'s requirements deliberately here. Without X-Forwarded-For they can't work.
   228        yield (
   229            "url",
   230            Query(self.url("ambassador/v0/check_ready"), headers={"X-Forwarded-For": "99.99.0.1"}),
   231        )
   232        yield (
   233            "url",
   234            Query(self.url("ambassador/v0/check_alive"), headers={"X-Forwarded-For": "99.99.0.1"}),
   235        )

View as plain text