...

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

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

     1import hashlib
     2from base64 import b64decode
     3from typing import Generator, List, Tuple, Union
     4
     5from abstract_tests import HTTP, AmbassadorTest, Node, ServiceType
     6from kat.harness import EDGE_STACK, Query
     7from tests.integration.manifests import namespace_manifest
     8from tests.selfsigned import TLSCerts
     9from tests.utils import create_crl_pem_b64
    10
    11bug_404_routes = (
    12    True  # Do we erroneously send 404 responses directly instead of redirect-to-tls first?
    13)
    14
    15
    16class TLSContextsTest(AmbassadorTest):
    17    """
    18    This test makes sure that TLS is not turned on when it's not intended to. For example, when an 'upstream'
    19    TLS configuration is passed, the port is not supposed to switch to 443
    20    """
    21
    22    def init(self):
    23        self.target = HTTP()
    24
    25        if EDGE_STACK:
    26            self.xfail = "Not yet supported in Edge Stack"
    27
    28        self.xfail = "FIXME: IHA"
    29
    30    def manifests(self) -> str:
    31        return (
    32            f"""
    33---
    34apiVersion: v1
    35metadata:
    36  name: test-tlscontexts-secret
    37  labels:
    38    kat-ambassador-id: tlscontextstest
    39data:
    40  tls.crt: {TLSCerts["master.datawire.io"].k8s_crt}
    41kind: Secret
    42type: Opaque
    43"""
    44            + super().manifests()
    45        )
    46
    47    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
    48        yield self, self.format(
    49            """
    50---
    51apiVersion: getambassador.io/v3alpha1
    52kind: Module
    53name: tls
    54ambassador_id: [{self.ambassador_id}]
    55config:
    56  upstream:
    57    enabled: True
    58    secret: test-tlscontexts-secret
    59"""
    60        )
    61
    62        yield self, self.format(
    63            """
    64---
    65apiVersion: getambassador.io/v3alpha1
    66kind: Mapping
    67name:  {self.target.path.k8s}
    68prefix: /{self.name}/
    69service: {self.target.path.fqdn}
    70"""
    71        )
    72
    73    def scheme(self) -> str:
    74        return "https"
    75
    76    def queries(self):
    77        yield Query(
    78            self.url(self.name + "/"),
    79            error=["connection refused", "connection reset by peer", "EOF", "request canceled"],
    80        )
    81
    82    def requirements(self):
    83        yield from (
    84            r for r in super().requirements() if r[0] == "url" and r[1].url.startswith("http://")
    85        )
    86
    87
    88class ClientCertificateAuthentication(AmbassadorTest):
    89    def init(self):
    90        self.xfail = "FIXME: IHA"
    91        self.target = HTTP()
    92
    93    def manifests(self) -> str:
    94        return (
    95            f"""
    96---
    97apiVersion: v1
    98metadata:
    99  name: test-clientcert-client-secret
   100  labels:
   101    kat-ambassador-id: clientcertificateauthentication
   102data:
   103  tls.crt: {TLSCerts["master.datawire.io"].k8s_crt}
   104kind: Secret
   105type: Opaque
   106---
   107apiVersion: v1
   108kind: Secret
   109metadata:
   110  name: test-clientcert-server-secret
   111  labels:
   112    kat-ambassador-id: clientcertificateauthentication
   113type: kubernetes.io/tls
   114data:
   115  tls.crt: {TLSCerts["ambassador.example.com"].k8s_crt}
   116  tls.key: {TLSCerts["ambassador.example.com"].k8s_key}
   117"""
   118            + super().manifests()
   119        )
   120
   121    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   122        yield self, self.format(
   123            """
   124---
   125apiVersion: getambassador.io/v3alpha1
   126kind: Module
   127name: ambassador
   128config:
   129  forward_client_cert_details: SANITIZE_SET
   130  set_current_client_cert_details:
   131    subject: true
   132---
   133apiVersion: getambassador.io/v3alpha1
   134kind: Module
   135ambassador_id: [{self.ambassador_id}]
   136name: tls
   137config:
   138  server:
   139    enabled: True
   140    secret: test-clientcert-server-secret
   141  client:
   142    enabled: True
   143    secret: test-clientcert-client-secret
   144    cert_required: True
   145"""
   146        )
   147
   148        yield self, self.format(
   149            """
   150---
   151apiVersion: getambassador.io/v3alpha1
   152kind: Mapping
   153name:  {self.target.path.k8s}
   154prefix: /{self.name}/
   155service: {self.target.path.fqdn}
   156add_request_headers:
   157  x-cert-start: { value: "%DOWNSTREAM_PEER_CERT_V_START%" }
   158  x-cert-end: { value: "%DOWNSTREAM_PEER_CERT_V_END%" }
   159  x-cert-start-custom: { value: "%DOWNSTREAM_PEER_CERT_V_START(%b %e %H:%M:%S %Y %Z)%" }
   160  x-cert-end-custom: { value: "%DOWNSTREAM_PEER_CERT_V_END(%b %e %H:%M:%S %Y %Z)%" }
   161"""
   162        )
   163
   164    def scheme(self) -> str:
   165        return "https"
   166
   167    def queries(self):
   168        yield Query(
   169            self.url(self.name + "/"),
   170            insecure=True,
   171            client_crt=TLSCerts["presto.example.com"].pubcert,
   172            client_key=TLSCerts["presto.example.com"].privkey,
   173            client_cert_required=True,
   174            ca_cert=TLSCerts["master.datawire.io"].pubcert,
   175        )
   176
   177        # In TLS < 1.3, there's not a dedicated alert code for "the client forgot to include a certificate",
   178        # so we get a generic alert=40 ("handshake_failure"). We also include "write: connection reset by peer"
   179        # because we've seen cases where Envoy and the client library don't play nicely, so the error report doesn't
   180        # get back before the connection closes.
   181        yield Query(
   182            self.url(self.name + "/"),
   183            insecure=True,
   184            maxTLSv="v1.2",
   185            error=["tls: handshake failure", "write: connection reset by peer"],
   186        )
   187
   188        # TLS 1.3 added a dedicated alert=116 ("certificate_required") for that scenario. See above for why
   189        # "write: connection reset by peer " is also accepted.
   190        yield Query(
   191            self.url(self.name + "/"),
   192            insecure=True,
   193            minTLSv="v1.3",
   194            error=["tls: certificate required", "write: connection reset by peer"],
   195        )
   196
   197    def check(self):
   198        cert = TLSCerts["presto.example.com"].pubcert
   199        # base64-decode the cert data after removing the "---BEGIN CERTIFICATE---" / "---END CERTIFICATE---" lines.
   200        certraw = b64decode("\n".join(l for l in cert.split("\n") if not l.startswith("-")))
   201        # take the sha256 sum aof that.
   202        certhash = hashlib.sha256(certraw).hexdigest()
   203
   204        assert self.results[0].backend
   205        assert self.results[0].backend.request
   206        assert self.results[0].backend.request.headers["x-forwarded-client-cert"] == [
   207            f'Hash={certhash};Subject="CN=presto.example.com,OU=Engineering,O=Ambassador Labs,L=Boston,ST=MA,C=US"'
   208        ], (
   209            "unexpected x-forwarded-client-cert value: %s"
   210            % self.results[0].backend.request.headers["x-forwarded-client-cert"]
   211        )
   212        assert self.results[0].backend.request.headers["x-cert-start"] == [
   213            "2021-11-10T13:12:00.000Z"
   214        ], (
   215            "unexpected x-cert-start value: %s"
   216            % self.results[0].backend.request.headers["x-cert-start"]
   217        )
   218        assert self.results[0].backend.request.headers["x-cert-end"] == [
   219            "2099-11-10T13:12:00.000Z"
   220        ], (
   221            "unexpected x-cert-end value: %s"
   222            % self.results[0].backend.request.headers["x-cert-end"]
   223        )
   224        assert self.results[1].backend
   225        assert self.results[1].backend.request
   226        assert self.results[0].backend.request.headers["x-cert-start-custom"] == [
   227            "Nov 10 13:12:00 2021 UTC"
   228        ], (
   229            "unexpected x-cert-start-custom value: %s"
   230            % self.results[1].backend.request.headers["x-cert-start-custom"]
   231        )
   232        assert self.results[0].backend.request.headers["x-cert-end-custom"] == [
   233            "Nov 10 13:12:00 2099 UTC"
   234        ], (
   235            "unexpected x-cert-end-custom value: %s"
   236            % self.results[0].backend.request.headers["x-cert-end-custom"]
   237        )
   238
   239    def requirements(self):
   240        for r in super().requirements():
   241            query = r[1]
   242            query.insecure = True
   243            query.client_cert = TLSCerts["presto.example.com"].pubcert
   244            query.client_key = TLSCerts["presto.example.com"].privkey
   245            query.client_cert_required = True
   246            query.ca_cert = TLSCerts["master.datawire.io"].pubcert
   247            yield (r[0], query)
   248
   249
   250class ClientCertificateAuthenticationContext(AmbassadorTest):
   251    def init(self):
   252        self.xfail = "FIXME: IHA"
   253        self.target = HTTP()
   254
   255    def manifests(self) -> str:
   256        return (
   257            self.format(
   258                f"""
   259---
   260apiVersion: v1
   261metadata:
   262  name: ccauthctx-client-secret
   263  labels:
   264    kat-ambassador-id: {self.ambassador_id}
   265data:
   266  tls.crt: {TLSCerts["master.datawire.io"].k8s_crt}
   267kind: Secret
   268type: Opaque
   269---
   270apiVersion: v1
   271kind: Secret
   272metadata:
   273  name: ccauthctx-server-secret
   274  labels:
   275    kat-ambassador-id: {self.ambassador_id}
   276type: kubernetes.io/tls
   277data:
   278  tls.crt: {TLSCerts["ambassador.example.com"].k8s_crt}
   279  tls.key: {TLSCerts["ambassador.example.com"].k8s_key}
   280---
   281apiVersion: getambassador.io/v3alpha1
   282kind: TLSContext
   283metadata:
   284  name: ccauthctx-tls
   285  labels:
   286    kat-ambassador-id: {self.ambassador_id}
   287spec:
   288  ambassador_id: [{self.ambassador_id}]
   289  hosts: [ "*" ]
   290  secret: ccauthctx-server-secret
   291  ca_secret: ccauthctx-client-secret
   292  cert_required: True
   293"""
   294            )
   295            + super().manifests()
   296        )
   297
   298    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   299        yield self, self.format(
   300            """
   301---
   302apiVersion: getambassador.io/v3alpha1
   303kind: Mapping
   304name:  {self.target.path.k8s}
   305prefix: /{self.name}/
   306service: {self.target.path.fqdn}
   307"""
   308        )
   309
   310    def scheme(self) -> str:
   311        return "https"
   312
   313    def queries(self):
   314        yield Query(
   315            self.url(self.name + "/"),
   316            insecure=True,
   317            client_crt=TLSCerts["presto.example.com"].pubcert,
   318            client_key=TLSCerts["presto.example.com"].privkey,
   319            client_cert_required=True,
   320            ca_cert=TLSCerts["master.datawire.io"].pubcert,
   321        )
   322
   323        # In TLS < 1.3, there's not a dedicated alert code for "the client forgot to include a certificate",
   324        # so we get a generic alert=40 ("handshake_failure"). We also include "write: connection reset by peer"
   325        # because we've seen cases where Envoy and the client library don't play nicely, so the error report doesn't
   326        # get back before the connection closes.
   327        yield Query(
   328            self.url(self.name + "/"),
   329            insecure=True,
   330            maxTLSv="v1.2",
   331            error=["tls: handshake failure", "write: connection reset by peer"],
   332        )
   333
   334        # TLS 1.3 added a dedicated alert=116 ("certificate_required") for that scenario. See above for why
   335        # "write: connection reset by peer" is also accepted.
   336        yield Query(
   337            self.url(self.name + "/"),
   338            insecure=True,
   339            minTLSv="v1.3",
   340            error=["tls: certificate required", "write: connection reset by peer"],
   341        )
   342
   343    def requirements(self):
   344        for r in super().requirements():
   345            query = r[1]
   346            query.insecure = True
   347            query.client_cert = TLSCerts["presto.example.com"].pubcert
   348            query.client_key = TLSCerts["presto.example.com"].privkey
   349            query.client_cert_required = True
   350            query.ca_cert = TLSCerts["master.datawire.io"].pubcert
   351            yield (r[0], query)
   352
   353
   354class ClientCertificateAuthenticationContextCRL(AmbassadorTest):
   355    def init(self):
   356        self.xfail = "FIXME: IHA"  # This test should cover TLSContext with a crl_secret
   357        self.target = HTTP()
   358
   359    def manifests(self) -> str:
   360        return (
   361            self.format(
   362                f"""
   363---
   364apiVersion: v1
   365metadata:
   366  name: ccauthctxcrl-client-secret
   367  labels:
   368    kat-ambassador-id: {self.ambassador_id}
   369data:
   370  tls.crt: {TLSCerts["master.datawire.io"].k8s_crt}
   371kind: Secret
   372type: Opaque
   373---
   374apiVersion: v1
   375kind: Secret
   376metadata:
   377  name: ccauthctxcrl-server-secret
   378  labels:
   379    kat-ambassador-id: {self.ambassador_id}
   380type: kubernetes.io/tls
   381data:
   382  tls.crt: {TLSCerts["ambassador.example.com"].k8s_crt}
   383  tls.key: {TLSCerts["ambassador.example.com"].k8s_key}
   384---
   385apiVersion: v1
   386kind: Secret
   387metadata:
   388  name: ccauthctxcrl-crl-secret
   389  labels:
   390    kat-ambassador-id: {self.ambassador_id}
   391type: Opaque
   392data:
   393  crl.pem: {create_crl_pem_b64(TLSCerts["master.datawire.io"].pubcert, TLSCerts["master.datawire.io"].privkey, [TLSCerts["presto.example.com"].pubcert])}
   394---
   395apiVersion: getambassador.io/v3alpha1
   396kind: TLSContext
   397metadata:
   398  name: ccauthctxcrl-tls
   399  labels:
   400    kat-ambassador-id: {self.ambassador_id}
   401spec:
   402  ambassador_id: [{self.ambassador_id}]
   403  hosts: [ "*" ]
   404  secret: ccauthctxcrl-server-secret
   405  ca_secret: ccauthctxcrl-client-secret
   406  crl_secret: ccauthctxcrl-crl-secret
   407  cert_required: True
   408"""
   409            )
   410            + super().manifests()
   411        )
   412
   413    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   414        yield self, self.format(
   415            """
   416---
   417apiVersion: getambassador.io/v3alpha1
   418kind: Mapping
   419name:  {self.target.path.k8s}
   420prefix: /
   421service: {self.target.path.fqdn}
   422hostname: "*"
   423"""
   424        )
   425
   426    def scheme(self) -> str:
   427        return "https"
   428
   429    def queries(self):
   430        yield Query(
   431            self.url(self.name + "/"),
   432            insecure=True,
   433            client_crt=TLSCerts["presto.example.com"].pubcert,
   434            client_key=TLSCerts["presto.example.com"].privkey,
   435            client_cert_required=True,
   436            ca_cert=TLSCerts["master.datawire.io"].pubcert,
   437            error=["tls: revoked certificate"],
   438        )
   439
   440    def requirements(self):
   441        yield ("pod", self.path.k8s)
   442
   443
   444class TLSOriginationSecret(AmbassadorTest):
   445    def init(self):
   446        self.xfail = "FIXME: IHA"
   447        self.target = HTTP()
   448
   449    def manifests(self) -> str:
   450        return (
   451            f"""
   452---
   453apiVersion: v1
   454kind: Secret
   455metadata:
   456  name: test-origination-secret
   457  labels:
   458    kat-ambassador-id: tlsoriginationsecret
   459type: kubernetes.io/tls
   460data:
   461  tls.crt: {TLSCerts["localhost"].k8s_crt}
   462  tls.key: {TLSCerts["localhost"].k8s_key}
   463"""
   464            + super().manifests()
   465        )
   466
   467    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   468        fingerprint = (
   469            hashlib.sha1(
   470                (
   471                    TLSCerts["localhost"].pubcert + "\n" + TLSCerts["localhost"].privkey + "\n"
   472                ).encode("utf-8")
   473            )
   474            .hexdigest()
   475            .upper()
   476        )
   477
   478        yield self, f"""
   479---
   480apiVersion: getambassador.io/v3alpha1
   481kind:  Module
   482ambassador_id: [{self.ambassador_id}]
   483name: tls
   484config:
   485  upstream:
   486    secret: test-origination-secret
   487  upstream-files:
   488    cert_chain_file: /tmp/ambassador/snapshots/default/secrets-decoded/test-origination-secret/{fingerprint}.crt
   489    private_key_file: /tmp/ambassador/snapshots/default/secrets-decoded/test-origination-secret/{fingerprint}.key
   490"""
   491
   492        yield self, self.format(
   493            """
   494---
   495apiVersion: getambassador.io/v3alpha1
   496kind: Mapping
   497name:  {self.target.path.k8s}
   498prefix: /{self.name}/
   499service: {self.target.path.fqdn}
   500tls: upstream
   501"""
   502        )
   503
   504        yield self, self.format(
   505            """
   506---
   507apiVersion: getambassador.io/v3alpha1
   508kind: Mapping
   509name:  {self.target.path.k8s}-files
   510prefix: /{self.name}-files/
   511service: {self.target.path.fqdn}
   512tls: upstream-files
   513"""
   514        )
   515
   516    def queries(self):
   517        yield Query(self.url(self.name + "/"))
   518        yield Query(self.url(self.name + "-files/"))
   519
   520    def check(self):
   521        for r in self.results:
   522            assert r.backend
   523            assert r.backend.request
   524            assert r.backend.request.tls.enabled
   525
   526
   527class TLS(AmbassadorTest):
   528
   529    target: ServiceType
   530
   531    def init(self):
   532        self.xfail = "FIXME: IHA"
   533        self.target = HTTP()
   534
   535    def manifests(self) -> str:
   536        return (
   537            f"""
   538---
   539apiVersion: v1
   540kind: Secret
   541metadata:
   542  name: test-tls-secret
   543  labels:
   544    kat-ambassador-id: tls
   545type: kubernetes.io/tls
   546data:
   547  tls.crt: {TLSCerts["localhost"].k8s_crt}
   548  tls.key: {TLSCerts["localhost"].k8s_key}
   549---
   550apiVersion: v1
   551kind: Secret
   552metadata:
   553  name: ambassador-certs
   554  labels:
   555    kat-ambassador-id: tls
   556type: kubernetes.io/tls
   557data:
   558  tls.crt: {TLSCerts["localhost"].k8s_crt}
   559  tls.key: {TLSCerts["localhost"].k8s_key}
   560---
   561apiVersion: getambassador.io/v3alpha1
   562kind: Host
   563metadata:
   564  name: tls-host
   565  labels:
   566    kat-ambassador-id: tls
   567spec:
   568  ambassador_id: [tls]
   569  tlsSecret:
   570    name: test-tls-secret
   571  requestPolicy:
   572    insecure:
   573      action: Reject
   574"""
   575            + super().manifests()
   576        )
   577
   578    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   579        #         # Use self here, not self.target, because we want the TLS module to
   580        #         # be annotated on the Ambassador itself.
   581        #         yield self, self.format("""
   582        # ---
   583        # apiVersion: getambassador.io/v3alpha1
   584        # kind: Module
   585        # name: tls
   586        # ambassador_id: [{self.ambassador_id}]
   587        # config:
   588        #   server:
   589        #     enabled: True
   590        #     secret: test-tls-secret
   591        # """)
   592
   593        # Use self.target _here_, because we want the mapping to be annotated
   594        # on the service, not the Ambassador. Also, you don't need to include
   595        # the ambassador_id unless you need some special ambassador_id that
   596        # isn't something that kat already knows about.
   597        #
   598        # If the test were more complex, we'd probably need to do some sort
   599        # of mangling for the mapping name and prefix. For this simple test,
   600        # it's not necessary.
   601        yield self.target, self.format(
   602            """
   603---
   604apiVersion: getambassador.io/v3alpha1
   605kind: Mapping
   606name:  tls_target_mapping
   607prefix: /tls-target/
   608service: {self.target.path.fqdn}
   609"""
   610        )
   611
   612    def scheme(self) -> str:
   613        return "https"
   614
   615    def queries(self):
   616        yield Query(self.url("tls-target/"), insecure=True)
   617
   618
   619class TLSInvalidSecret(AmbassadorTest):
   620
   621    target: ServiceType
   622
   623    def init(self):
   624        self.xfail = "FIXME: IHA"
   625        self.target = HTTP()
   626
   627    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   628        yield self, self.format(
   629            """
   630---
   631apiVersion: getambassador.io/v3alpha1
   632kind: Module
   633name: tls
   634ambassador_id: [{self.ambassador_id}]
   635config:
   636  server:
   637    enabled: True
   638    secret: test-certs-secret-invalid
   639  missing-secret-key:
   640    cert_chain_file: /nonesuch
   641  bad-path-info:
   642    cert_chain_file: /nonesuch
   643    private_key_file: /nonesuch
   644  validation-without-termination:
   645    enabled: True
   646    secret: test-certs-secret-invalid
   647    ca_secret: ambassador-certs
   648"""
   649        )
   650
   651        yield self.target, self.format(
   652            """
   653---
   654apiVersion: getambassador.io/v3alpha1
   655kind: Mapping
   656name:  tls_target_mapping
   657prefix: /tls-target/
   658service: {self.target.path.fqdn}
   659"""
   660        )
   661
   662    def scheme(self) -> str:
   663        return "http"
   664
   665    def queries(self):
   666        yield Query(self.url("ambassador/v0/diag/?json=true&filter=errors"), phase=2)
   667
   668    def check(self):
   669        assert self.results[0].backend
   670        errors = self.results[0].backend.response
   671
   672        expected = set(
   673            {
   674                "TLSContext server found no certificate in secret test-certs-secret-invalid in namespace default, ignoring...",
   675                "TLSContext bad-path-info found no cert_chain_file '/nonesuch'",
   676                "TLSContext bad-path-info found no private_key_file '/nonesuch'",
   677                "TLSContext validation-without-termination found no certificate in secret test-certs-secret-invalid in namespace default, ignoring...",
   678                "TLSContext missing-secret-key: 'cert_chain_file' requires 'private_key_file' as well",
   679            }
   680        )
   681
   682        current = set({})
   683        for errsvc, errtext in errors:
   684            current.add(errtext)
   685
   686        diff = expected - current
   687
   688        assert len(diff) == 0, f"expected {len(expected)} errors, got {len(errors)}: Missing {diff}"
   689
   690
   691class TLSContextTest(AmbassadorTest):
   692    # debug = True
   693
   694    def init(self):
   695        self.xfail = "FIXME: IHA"
   696        self.target = HTTP()
   697
   698        if EDGE_STACK:
   699            self.xfail = "XFailing for now"
   700
   701    def manifests(self) -> str:
   702        return (
   703            namespace_manifest("secret-namespace")
   704            + f"""
   705---
   706apiVersion: v1
   707data:
   708  tls.crt: {TLSCerts["localhost"].k8s_crt}
   709  tls.key: {TLSCerts["localhost"].k8s_key}
   710kind: Secret
   711metadata:
   712  name: test-tlscontext-secret-0
   713  labels:
   714    kat-ambassador-id: tlscontexttest
   715type: kubernetes.io/tls
   716---
   717apiVersion: v1
   718data:
   719  tls.crt: {TLSCerts["tls-context-host-1"].k8s_crt}
   720  tls.key: {TLSCerts["tls-context-host-1"].k8s_key}
   721kind: Secret
   722metadata:
   723  name: test-tlscontext-secret-1
   724  namespace: secret-namespace
   725  labels:
   726    kat-ambassador-id: tlscontexttest
   727type: kubernetes.io/tls
   728---
   729apiVersion: v1
   730data:
   731  tls.crt: {TLSCerts["tls-context-host-2"].k8s_crt}
   732  tls.key: {TLSCerts["tls-context-host-2"].k8s_key}
   733kind: Secret
   734metadata:
   735  name: test-tlscontext-secret-2
   736  labels:
   737    kat-ambassador-id: tlscontexttest
   738type: kubernetes.io/tls
   739"""
   740            + super().manifests()
   741        )
   742
   743    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
   744        yield self, self.format(
   745            """
   746---
   747apiVersion: getambassador.io/v3alpha1
   748kind: Mapping
   749name:  {self.name}-same-prefix-1
   750prefix: /tls-context-same/
   751service: http://{self.target.path.fqdn}
   752host: tls-context-host-1
   753"""
   754        )
   755        yield self, self.format(
   756            """
   757---
   758apiVersion: getambassador.io/v3alpha1
   759kind: TLSContext
   760name: {self.name}-same-context-1
   761hosts:
   762- tls-context-host-1
   763secret: test-tlscontext-secret-1.secret-namespace
   764min_tls_version: v1.0
   765max_tls_version: v1.3
   766redirect_cleartext_from: 8080
   767"""
   768        )
   769        yield self, self.format(
   770            """
   771---
   772apiVersion: getambassador.io/v3alpha1
   773kind: Mapping
   774name:  {self.name}-same-prefix-2
   775prefix: /tls-context-same/
   776service: http://{self.target.path.fqdn}
   777host: tls-context-host-2
   778"""
   779        )
   780        yield self, self.format(
   781            """
   782---
   783apiVersion: getambassador.io/v3alpha1
   784kind: TLSContext
   785name: {self.name}-same-context-2
   786hosts:
   787- tls-context-host-2
   788secret: test-tlscontext-secret-2
   789alpn_protocols: h2,http/1.1
   790redirect_cleartext_from: 8080
   791"""
   792        )
   793        yield self, self.format(
   794            """
   795---
   796apiVersion: getambassador.io/v3alpha1
   797kind: Module
   798name: tls
   799config:
   800  server:
   801    enabled: True
   802    secret: test-tlscontext-secret-0
   803"""
   804        )
   805        yield self, self.format(
   806            """
   807---
   808apiVersion: getambassador.io/v3alpha1
   809kind: Mapping
   810name:  {self.name}-other-mapping
   811prefix: /{self.name}/
   812service: https://{self.target.path.fqdn}
   813"""
   814        )
   815        # Ambassador should not return an error when hostname is not present.
   816        yield self, self.format(
   817            """
   818---
   819apiVersion: getambassador.io/v3alpha1
   820kind: TLSContext
   821name: {self.name}-no-secret
   822min_tls_version: v1.0
   823max_tls_version: v1.3
   824redirect_cleartext_from: 8080
   825"""
   826        )
   827        # Ambassador should return an error for this configuration.
   828        yield self, self.format(
   829            """
   830---
   831apiVersion: getambassador.io/v3alpha1
   832kind: TLSContext
   833name: {self.name}-same-context-error
   834hosts:
   835- tls-context-host-1
   836redirect_cleartext_from: 8080
   837"""
   838        )
   839        # Ambassador should return an error for this configuration.
   840        yield self, self.format(
   841            """
   842---
   843apiVersion: getambassador.io/v3alpha1
   844kind: TLSContext
   845name: {self.name}-rcf-error
   846hosts:
   847- tls-context-host-1
   848redirect_cleartext_from: 8081
   849"""
   850        )
   851
   852    def scheme(self) -> str:
   853        return "https"
   854
   855    @staticmethod
   856    def _go_close_connection_error(url):
   857        """
   858        :param url: url passed to the query
   859        :return: error message string that Go's net/http package throws when server closes connection
   860        """
   861        return "Get {}: EOF".format(url)
   862
   863    def queries(self):
   864        # 0
   865        yield Query(
   866            self.url("ambassador/v0/diag/?json=true&filter=errors"),
   867            headers={"Host": "tls-context-host-2"},
   868            insecure=True,
   869            sni=True,
   870        )
   871
   872        # 1 - Correct host #1
   873        yield Query(
   874            self.url("tls-context-same/"),
   875            headers={"Host": "tls-context-host-1"},
   876            expected=200,
   877            insecure=True,
   878            sni=True,
   879        )
   880        # 2 - Correct host #2
   881        yield Query(
   882            self.url("tls-context-same/"),
   883            headers={"Host": "tls-context-host-2"},
   884            expected=200,
   885            insecure=True,
   886            sni=True,
   887        )
   888
   889        # 3 - Incorrect host
   890        yield Query(
   891            self.url("tls-context-same/"),
   892            headers={"Host": "tls-context-host-3"},
   893            # error=self._go_close_connection_error(self.url("tls-context-same/")),
   894            expected=404,
   895            insecure=True,
   896        )
   897
   898        # 4 - Incorrect path, correct host
   899        yield Query(
   900            self.url("tls-context-different/"),
   901            headers={"Host": "tls-context-host-1"},
   902            expected=404,
   903            insecure=True,
   904            sni=True,
   905        )
   906
   907        # Other mappings with no host will respond with the fallbock cert.
   908        # 5 - no Host header, fallback cert from the TLS module
   909        yield Query(
   910            self.url(self.name + "/"),
   911            # error=self._go_close_connection_error(self.url(self.name + "/")),
   912            insecure=True,
   913        )
   914
   915        # 6 - explicit Host header, fallback cert
   916        yield Query(
   917            self.url(self.name + "/"),
   918            # error=self._go_close_connection_error(self.url(self.name + "/")),
   919            # sni=True,
   920            headers={"Host": "tls-context-host-3"},
   921            insecure=True,
   922        )
   923
   924        # 7 - explicit Host header 1 wins, we'll get the SNI cert for this overlapping path
   925        yield Query(
   926            self.url(self.name + "/"),
   927            headers={"Host": "tls-context-host-1"},
   928            expected=200,
   929            insecure=True,
   930            sni=True,
   931        )
   932
   933        # 8 - explicit Host header 2 wins, we'll get the SNI cert for this overlapping path
   934        yield Query(
   935            self.url(self.name + "/"),
   936            headers={"Host": "tls-context-host-2"},
   937            expected=200,
   938            insecure=True,
   939            sni=True,
   940        )
   941
   942        # 9 - Redirect cleartext from actually redirects.
   943        yield Query(
   944            self.url("tls-context-same/", scheme="http"),
   945            headers={"Host": "tls-context-host-1"},
   946            expected=301,
   947            insecure=True,
   948            sni=True,
   949        )
   950
   951    def check(self):
   952        # XXX Ew. If self.results[0].json is empty, the harness won't convert it to a response.
   953        errors = self.results[0].json
   954        num_errors = len(errors)
   955        assert num_errors == 5, "expected 5 errors, got {} -\n{}".format(num_errors, errors)
   956
   957        errors_that_should_be_found = {
   958            "TLSContext TLSContextTest-no-secret has no certificate information at all?": False,
   959            "TLSContext TLSContextTest-same-context-error has no certificate information at all?": False,
   960            "TLSContext TLSContextTest-same-context-error is missing cert_chain_file": False,
   961            "TLSContext TLSContextTest-same-context-error is missing private_key_file": False,
   962            "TLSContext: TLSContextTest-rcf-error; configured conflicting redirect_from port: 8081": False,
   963        }
   964
   965        unknown_errors: List[str] = []
   966        for err in errors:
   967            text = err[1]
   968
   969            if text in errors_that_should_be_found:
   970                errors_that_should_be_found[text] = True
   971            else:
   972                unknown_errors.append(f"Unexpected error {text}")
   973
   974        for err, found in errors_that_should_be_found.items():
   975            if not found:
   976                unknown_errors.append(f"Missing error {err}")
   977
   978        assert not unknown_errors, f"Problems with errors: {unknown_errors}"
   979
   980        idx = 0
   981
   982        for result in self.results:
   983            if result.status == 200 and result.query.headers:
   984                host_header = result.query.headers["Host"]
   985                tls_common_name = result.tls[0]["Issuer"]["CommonName"]
   986
   987                # XXX Weirdness with the fallback cert here! You see, if we use host
   988                # tls-context-host-3 (or, really, anything except -1 or -2), then the
   989                # fallback cert actually has CN 'localhost'. We should replace this with
   990                # a real fallback cert, but for now, just hack the host_header.
   991                #
   992                # Ew.
   993
   994                if host_header == "tls-context-host-3":
   995                    host_header = "localhost"
   996
   997                assert host_header == tls_common_name, "test %d wanted CN %s, but got %s" % (
   998                    idx,
   999                    host_header,
  1000                    tls_common_name,
  1001                )
  1002
  1003            idx += 1
  1004
  1005    def requirements(self):
  1006        # We're replacing super()'s requirements deliberately here. Without a Host header they can't work.
  1007        yield (
  1008            "url",
  1009            Query(
  1010                self.url("ambassador/v0/check_ready"),
  1011                headers={"Host": "tls-context-host-1"},
  1012                insecure=True,
  1013                sni=True,
  1014            ),
  1015        )
  1016        yield (
  1017            "url",
  1018            Query(
  1019                self.url("ambassador/v0/check_alive"),
  1020                headers={"Host": "tls-context-host-1"},
  1021                insecure=True,
  1022                sni=True,
  1023            ),
  1024        )
  1025        yield (
  1026            "url",
  1027            Query(
  1028                self.url("ambassador/v0/check_ready"),
  1029                headers={"Host": "tls-context-host-2"},
  1030                insecure=True,
  1031                sni=True,
  1032            ),
  1033        )
  1034        yield (
  1035            "url",
  1036            Query(
  1037                self.url("ambassador/v0/check_alive"),
  1038                headers={"Host": "tls-context-host-2"},
  1039                insecure=True,
  1040                sni=True,
  1041            ),
  1042        )
  1043
  1044
  1045class TLSIngressTest(AmbassadorTest):
  1046    def init(self):
  1047        self.xfail = "FIXME: IHA"
  1048        self.target = HTTP()
  1049
  1050    def manifests(self) -> str:
  1051        self.manifest_envs = """
  1052    - name: AMBASSADOR_DEBUG
  1053      value: "diagd"
  1054"""
  1055
  1056        return (
  1057            namespace_manifest("secret-namespace-ingress")
  1058            + f"""
  1059---
  1060apiVersion: v1
  1061data:
  1062  tls.crt: {TLSCerts["localhost"].k8s_crt}
  1063  tls.key: {TLSCerts["localhost"].k8s_key}
  1064kind: Secret
  1065metadata:
  1066  name: test-tlscontext-secret-ingress-0
  1067  labels:
  1068    kat-ambassador-id: tlsingresstest
  1069type: kubernetes.io/tls
  1070---
  1071apiVersion: v1
  1072data:
  1073  tls.crt: {TLSCerts["tls-context-host-1"].k8s_crt}
  1074  tls.key: {TLSCerts["tls-context-host-1"].k8s_key}
  1075kind: Secret
  1076metadata:
  1077  name: test-tlscontext-secret-ingress-1
  1078  namespace: secret-namespace-ingress
  1079  labels:
  1080    kat-ambassador-id: tlsingresstest
  1081type: kubernetes.io/tls
  1082---
  1083apiVersion: v1
  1084data:
  1085  tls.crt: {TLSCerts["tls-context-host-2"].k8s_crt}
  1086  tls.key: {TLSCerts["tls-context-host-2"].k8s_key}
  1087kind: Secret
  1088metadata:
  1089  name: test-tlscontext-secret-ingress-2
  1090  labels:
  1091    kat-ambassador-id: tlsingresstest
  1092type: kubernetes.io/tls
  1093---
  1094apiVersion: networking.k8s.io/v1
  1095kind: Ingress
  1096metadata:
  1097  annotations:
  1098    kubernetes.io/ingress.class: ambassador
  1099    getambassador.io/ambassador-id: tlsingresstest
  1100  name: {self.name.lower()}-1
  1101spec:
  1102  tls:
  1103  - secretName: test-tlscontext-secret-ingress-1.secret-namespace-ingress
  1104    hosts:
  1105    - tls-context-host-1
  1106  rules:
  1107  - host: tls-context-host-1
  1108    http:
  1109      paths:
  1110      - backend:
  1111          service:
  1112            name: {self.target.path.k8s}
  1113            port:
  1114              number: 80
  1115        path: /tls-context-same/
  1116        pathType: Prefix
  1117---
  1118apiVersion: networking.k8s.io/v1
  1119kind: Ingress
  1120metadata:
  1121  annotations:
  1122    kubernetes.io/ingress.class: ambassador
  1123    getambassador.io/ambassador-id: tlsingresstest
  1124  name: {self.name.lower()}-2
  1125spec:
  1126  tls:
  1127  - secretName: test-tlscontext-secret-ingress-2
  1128    hosts:
  1129    - tls-context-host-2
  1130  rules:
  1131  - host: tls-context-host-2
  1132    http:
  1133      paths:
  1134      - backend:
  1135          service:
  1136            name: {self.target.path.k8s}
  1137            port:
  1138              number: 80
  1139        path: /tls-context-same/
  1140        pathType: Prefix
  1141"""
  1142            + super().manifests()
  1143        )
  1144
  1145    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
  1146        yield self, self.format(
  1147            """
  1148---
  1149apiVersion: getambassador.io/v3alpha1
  1150kind: Module
  1151name: tls
  1152config:
  1153  server:
  1154    enabled: True
  1155    secret: test-tlscontext-secret-ingress-0
  1156"""
  1157        )
  1158
  1159        yield self, self.format(
  1160            """
  1161---
  1162apiVersion: getambassador.io/v3alpha1
  1163kind: Mapping
  1164hostname: "*"
  1165name:  {self.name}-other-mapping
  1166prefix: /{self.name}/
  1167service: https://{self.target.path.fqdn}
  1168"""
  1169        )
  1170
  1171    def scheme(self) -> str:
  1172        return "https"
  1173
  1174    @staticmethod
  1175    def _go_close_connection_error(url):
  1176        """
  1177        :param url: url passed to the query
  1178        :return: error message string that Go's net/http package throws when server closes connection
  1179        """
  1180        return "Get {}: EOF".format(url)
  1181
  1182    def queries(self):
  1183        # 0
  1184        yield Query(
  1185            self.url("ambassador/v0/diag/?json=true&filter=errors"),
  1186            headers={"Host": "tls-context-host-2"},
  1187            insecure=True,
  1188            sni=True,
  1189        )
  1190
  1191        # 1 - Correct host #1
  1192        yield Query(
  1193            self.url("tls-context-same/"),
  1194            headers={"Host": "tls-context-host-1"},
  1195            expected=200,
  1196            insecure=True,
  1197            sni=True,
  1198        )
  1199        # 2 - Correct host #2
  1200        yield Query(
  1201            self.url("tls-context-same/"),
  1202            headers={"Host": "tls-context-host-2"},
  1203            expected=200,
  1204            insecure=True,
  1205            sni=True,
  1206        )
  1207
  1208        # 3 - Incorrect host
  1209        yield Query(
  1210            self.url("tls-context-same/"),
  1211            headers={"Host": "tls-context-host-3"},
  1212            # error=self._go_close_connection_error(self.url("tls-context-same/")),
  1213            expected=404,
  1214            insecure=True,
  1215        )
  1216
  1217        # 4 - Incorrect path, correct host
  1218        yield Query(
  1219            self.url("tls-context-different/"),
  1220            headers={"Host": "tls-context-host-1"},
  1221            expected=404,
  1222            insecure=True,
  1223            sni=True,
  1224        )
  1225
  1226        # Other mappings with no host will respond with the fallbock cert.
  1227        # 5 - no Host header, fallback cert from the TLS module
  1228        yield Query(
  1229            self.url(self.name + "/"),
  1230            # error=self._go_close_connection_error(self.url(self.name + "/")),
  1231            insecure=True,
  1232        )
  1233
  1234        # 6 - explicit Host header, fallback cert
  1235        yield Query(
  1236            self.url(self.name + "/"),
  1237            # error=self._go_close_connection_error(self.url(self.name + "/")),
  1238            # sni=True,
  1239            headers={"Host": "tls-context-host-3"},
  1240            insecure=True,
  1241        )
  1242
  1243        # 7 - explicit Host header 1 wins, we'll get the SNI cert for this overlapping path
  1244        yield Query(
  1245            self.url(self.name + "/"),
  1246            headers={"Host": "tls-context-host-1"},
  1247            expected=200,
  1248            insecure=True,
  1249            sni=True,
  1250        )
  1251
  1252        # 7 - explicit Host header 2 wins, we'll get the SNI cert for this overlapping path
  1253        yield Query(
  1254            self.url(self.name + "/"),
  1255            headers={"Host": "tls-context-host-2"},
  1256            expected=200,
  1257            insecure=True,
  1258            sni=True,
  1259        )
  1260
  1261    def check(self):
  1262        # XXX Ew. If self.results[0].json is empty, the harness won't convert it to a response.
  1263        errors = self.results[0].json
  1264        num_errors = len(errors)
  1265        assert num_errors == 0, "expected 0 errors, got {} -\n{}".format(num_errors, errors)
  1266
  1267        idx = 0
  1268
  1269        for result in self.results:
  1270            if result.status == 200 and result.query.headers:
  1271                host_header = result.query.headers["Host"]
  1272                tls_common_name = result.tls[0]["Issuer"]["CommonName"]
  1273
  1274                # XXX Weirdness with the fallback cert here! You see, if we use host
  1275                # tls-context-host-3 (or, really, anything except -1 or -2), then the
  1276                # fallback cert actually has CN 'localhost'. We should replace this with
  1277                # a real fallback cert, but for now, just hack the host_header.
  1278                #
  1279                # Ew.
  1280
  1281                if host_header == "tls-context-host-3":
  1282                    host_header = "localhost"
  1283
  1284                # Yep, that's expected. Since the TLS secret for 'tls-context-host-1' is
  1285                # not namespaced it should only resolve to the Ingress' own
  1286                # namespace, and can't use the 'secret.namespace' Ambassador syntax
  1287                if host_header == "tls-context-host-1":
  1288                    host_header = "localhost"
  1289
  1290                assert host_header == tls_common_name, "test %d wanted CN %s, but got %s" % (
  1291                    idx,
  1292                    host_header,
  1293                    tls_common_name,
  1294                )
  1295
  1296            idx += 1
  1297
  1298    def requirements(self):
  1299        yield (
  1300            "url",
  1301            Query(
  1302                self.url("ambassador/v0/check_ready"),
  1303                headers={"Host": "tls-context-host-1"},
  1304                insecure=True,
  1305                sni=True,
  1306            ),
  1307        )
  1308        yield (
  1309            "url",
  1310            Query(
  1311                self.url("ambassador/v0/check_alive"),
  1312                headers={"Host": "tls-context-host-1"},
  1313                insecure=True,
  1314                sni=True,
  1315            ),
  1316        )
  1317        yield (
  1318            "url",
  1319            Query(
  1320                self.url("ambassador/v0/check_ready"),
  1321                headers={"Host": "tls-context-host-2"},
  1322                insecure=True,
  1323                sni=True,
  1324            ),
  1325        )
  1326        yield (
  1327            "url",
  1328            Query(
  1329                self.url("ambassador/v0/check_alive"),
  1330                headers={"Host": "tls-context-host-2"},
  1331                insecure=True,
  1332                sni=True,
  1333            ),
  1334        )
  1335
  1336
  1337class TLSContextProtocolMaxVersion(AmbassadorTest):
  1338    # Here we're testing that the client can't exceed the maximum TLS version
  1339    # configured.
  1340    #
  1341    # XXX 2019-09-11: vet that the test client's support for TLS v1.3 is up-to-date.
  1342    # It appears not to be.
  1343
  1344    # debug = True
  1345
  1346    def init(self):
  1347        self.target = HTTP()
  1348
  1349        if EDGE_STACK:
  1350            self.xfail = "Not yet supported in Edge Stack"
  1351
  1352        self.xfail = "FIXME: IHA"
  1353
  1354    def manifests(self) -> str:
  1355        return (
  1356            f"""
  1357---
  1358apiVersion: v1
  1359data:
  1360  tls.crt: {TLSCerts["tls-context-host-1"].k8s_crt}
  1361  tls.key: {TLSCerts["tls-context-host-1"].k8s_key}
  1362kind: Secret
  1363metadata:
  1364  name: secret.max-version
  1365  labels:
  1366    kat-ambassador-id: tlscontextprotocolmaxversion
  1367type: kubernetes.io/tls
  1368"""
  1369            + super().manifests()
  1370        )
  1371
  1372    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
  1373        yield self, self.format(
  1374            """
  1375---
  1376apiVersion: getambassador.io/v3alpha1
  1377kind:  Module
  1378name:  ambassador
  1379config:
  1380  defaults:
  1381    tls_secret_namespacing: False
  1382---
  1383apiVersion: getambassador.io/v3alpha1
  1384kind: Mapping
  1385name:  {self.name}-same-prefix-1
  1386prefix: /tls-context-same/
  1387service: http://{self.target.path.fqdn}
  1388host: tls-context-host-1
  1389---
  1390apiVersion: getambassador.io/v3alpha1
  1391kind: TLSContext
  1392name: {self.name}-same-context-1
  1393hosts:
  1394- tls-context-host-1
  1395secret: secret.max-version
  1396min_tls_version: v1.1
  1397max_tls_version: v1.2
  1398"""
  1399        )
  1400
  1401    def scheme(self) -> str:
  1402        return "https"
  1403
  1404    @staticmethod
  1405    def _go_close_connection_error(url):
  1406        """
  1407        :param url: url passed to the query
  1408        :return: error message string that Go's net/http package throws when server closes connection
  1409        """
  1410        return "Get {}: EOF".format(url)
  1411
  1412    def queries(self):
  1413        # ----
  1414        # XXX 2019-09-11
  1415        # These aren't actually reporting the negotiated version, alhough correct
  1416        # behavior can be verified with a custom log format. What, does the silly thing just not
  1417        # report the negotiated version if it's the max you've requested??
  1418        #
  1419        # For now, we're checking for the None result, but, ew.
  1420        # ----
  1421
  1422        yield Query(
  1423            self.url("tls-context-same/"),
  1424            headers={"Host": "tls-context-host-1"},
  1425            expected=200,
  1426            insecure=True,
  1427            sni=True,
  1428            minTLSv="v1.2",
  1429            maxTLSv="v1.2",
  1430        )
  1431
  1432        # This should give us TLS v1.1
  1433        yield Query(
  1434            self.url("tls-context-same/"),
  1435            headers={"Host": "tls-context-host-1"},
  1436            expected=200,
  1437            insecure=True,
  1438            sni=True,
  1439            minTLSv="v1.0",
  1440            maxTLSv="v1.1",
  1441        )
  1442
  1443        # This should be an error.
  1444        yield Query(
  1445            self.url("tls-context-same/"),
  1446            headers={"Host": "tls-context-host-1"},
  1447            expected=200,
  1448            insecure=True,
  1449            sni=True,
  1450            minTLSv="v1.3",
  1451            maxTLSv="v1.3",
  1452            error=[
  1453                "tls: server selected unsupported protocol version 303",
  1454                "tls: no supported versions satisfy MinVersion and MaxVersion",
  1455                "tls: protocol version not supported",
  1456                "read: connection reset by peer",
  1457            ],
  1458        )  # The TLS inspector just closes the connection. Wow.
  1459
  1460    def check(self):
  1461        assert self.results[0].backend
  1462        assert self.results[0].backend.request
  1463        tls_0_version = self.results[0].backend.request.tls.negotiated_protocol_version
  1464        assert self.results[1].backend
  1465        assert self.results[1].backend.request
  1466        tls_1_version = self.results[1].backend.request.tls.negotiated_protocol_version
  1467
  1468        # See comment in queries for why these are None. They should be v1.2 and v1.1 respectively.
  1469        assert tls_0_version == None, f"requesting TLS v1.2 got TLS {tls_0_version}"
  1470        assert tls_1_version == None, f"requesting TLS v1.0-v1.1 got TLS {tls_1_version}"
  1471
  1472    def requirements(self):
  1473        # We're replacing super()'s requirements deliberately here. Without a Host header they can't work.
  1474        yield (
  1475            "url",
  1476            Query(
  1477                self.url("ambassador/v0/check_ready"),
  1478                headers={"Host": "tls-context-host-1"},
  1479                insecure=True,
  1480                sni=True,
  1481                minTLSv="v1.2",
  1482            ),
  1483        )
  1484        yield (
  1485            "url",
  1486            Query(
  1487                self.url("ambassador/v0/check_alive"),
  1488                headers={"Host": "tls-context-host-1"},
  1489                insecure=True,
  1490                sni=True,
  1491                minTLSv="v1.2",
  1492            ),
  1493        )
  1494
  1495
  1496class TLSContextProtocolMinVersion(AmbassadorTest):
  1497    # Here we're testing that the client can't drop below the minimum TLS version
  1498    # configured.
  1499    #
  1500    # XXX 2019-09-11: vet that the test client's support for TLS v1.3 is up-to-date.
  1501    # It appears not to be.
  1502
  1503    # debug = True
  1504
  1505    def init(self):
  1506        self.xfail = "FIXME: IHA"
  1507        self.target = HTTP()
  1508
  1509    def manifests(self) -> str:
  1510        return (
  1511            f"""
  1512---
  1513apiVersion: v1
  1514data:
  1515  tls.crt: {TLSCerts["tls-context-host-1"].k8s_crt}
  1516  tls.key: {TLSCerts["tls-context-host-1"].k8s_key}
  1517kind: Secret
  1518metadata:
  1519  name: secret.min-version
  1520  labels:
  1521    kat-ambassador-id: tlscontextprotocolminversion
  1522type: kubernetes.io/tls
  1523"""
  1524            + super().manifests()
  1525        )
  1526
  1527    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
  1528        yield self, self.format(
  1529            """
  1530---
  1531apiVersion: getambassador.io/v3alpha1
  1532kind: Mapping
  1533name:  {self.name}-same-prefix-1
  1534prefix: /tls-context-same/
  1535service: https://{self.target.path.fqdn}
  1536host: tls-context-host-1
  1537---
  1538apiVersion: getambassador.io/v3alpha1
  1539kind: TLSContext
  1540name: {self.name}-same-context-1
  1541hosts:
  1542- tls-context-host-1
  1543secret: secret.min-version
  1544secret_namespacing: False
  1545min_tls_version: v1.2
  1546max_tls_version: v1.3
  1547"""
  1548        )
  1549
  1550    def scheme(self) -> str:
  1551        return "https"
  1552
  1553    @staticmethod
  1554    def _go_close_connection_error(url):
  1555        """
  1556        :param url: url passed to the query
  1557        :return: error message string that Go's net/http package throws when server closes connection
  1558        """
  1559        return "Get {}: EOF".format(url)
  1560
  1561    def queries(self):
  1562        # This should give v1.3, but it currently seems to give 1.2.
  1563        yield Query(
  1564            self.url("tls-context-same/"),
  1565            headers={"Host": "tls-context-host-1"},
  1566            expected=200,
  1567            insecure=True,
  1568            sni=True,
  1569            minTLSv="v1.2",
  1570            maxTLSv="v1.3",
  1571        )
  1572
  1573        # This should give v1.2
  1574        yield Query(
  1575            self.url("tls-context-same/"),
  1576            headers={"Host": "tls-context-host-1"},
  1577            expected=200,
  1578            insecure=True,
  1579            sni=True,
  1580            minTLSv="v1.1",
  1581            maxTLSv="v1.2",
  1582        )
  1583
  1584        # This should be an error.
  1585        yield Query(
  1586            self.url("tls-context-same/"),
  1587            headers={"Host": "tls-context-host-1"},
  1588            expected=200,
  1589            insecure=True,
  1590            sni=True,
  1591            minTLSv="v1.0",
  1592            maxTLSv="v1.0",
  1593            error=[
  1594                "tls: server selected unsupported protocol version 303",
  1595                "tls: no supported versions satisfy MinVersion and MaxVersion",
  1596                "tls: protocol version not supported",
  1597            ],
  1598        )
  1599
  1600    def check(self):
  1601        assert self.results[0].backend
  1602        assert self.results[0].backend.request
  1603        tls_0_version = self.results[0].backend.request.tls.negotiated_protocol_version
  1604        assert self.results[1].backend
  1605        assert self.results[1].backend.request
  1606        tls_1_version = self.results[1].backend.request.tls.negotiated_protocol_version
  1607
  1608        # Hmmm. Why does Envoy prefer 1.2 to 1.3 here?? This may be a client thing -- have to
  1609        # rebuild with Go 1.13.
  1610        assert tls_0_version == "v1.2", f"requesting TLS v1.2-v1.3 got TLS {tls_0_version}"
  1611        assert tls_1_version == "v1.2", f"requesting TLS v1.1-v1.2 got TLS {tls_1_version}"
  1612
  1613    def requirements(self):
  1614        # We're replacing super()'s requirements deliberately here. Without a Host header they can't work.
  1615        yield (
  1616            "url",
  1617            Query(
  1618                self.url("ambassador/v0/check_ready"),
  1619                headers={"Host": "tls-context-host-1"},
  1620                insecure=True,
  1621                sni=True,
  1622            ),
  1623        )
  1624        yield (
  1625            "url",
  1626            Query(
  1627                self.url("ambassador/v0/check_alive"),
  1628                headers={"Host": "tls-context-host-1"},
  1629                insecure=True,
  1630                sni=True,
  1631            ),
  1632        )
  1633
  1634
  1635class TLSContextCipherSuites(AmbassadorTest):
  1636    # debug = True
  1637
  1638    def init(self):
  1639        self.xfail = "FIXME: IHA"
  1640        self.target = HTTP()
  1641
  1642    def manifests(self) -> str:
  1643        return (
  1644            f"""
  1645---
  1646apiVersion: v1
  1647data:
  1648  tls.crt: {TLSCerts["tls-context-host-1"].k8s_crt}
  1649  tls.key: {TLSCerts["tls-context-host-1"].k8s_key}
  1650kind: Secret
  1651metadata:
  1652  name: secret.cipher-suites
  1653  labels:
  1654    kat-ambassador-id: tlscontextciphersuites
  1655type: kubernetes.io/tls
  1656"""
  1657            + super().manifests()
  1658        )
  1659
  1660    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
  1661        yield self, self.format(
  1662            """
  1663---
  1664apiVersion: getambassador.io/v3alpha1
  1665kind: Mapping
  1666name:  {self.name}-same-prefix-1
  1667prefix: /tls-context-same/
  1668service: https://{self.target.path.fqdn}
  1669host: tls-context-host-1
  1670"""
  1671        )
  1672        yield self, self.format(
  1673            """
  1674---
  1675apiVersion: getambassador.io/v3alpha1
  1676kind: TLSContext
  1677name: {self.name}-same-context-1
  1678hosts:
  1679- tls-context-host-1
  1680secret: secret.cipher-suites
  1681secret_namespacing: False
  1682max_tls_version: v1.2
  1683cipher_suites:
  1684- ECDHE-RSA-AES128-GCM-SHA256
  1685ecdh_curves:
  1686- P-256
  1687"""
  1688        )
  1689
  1690    def scheme(self) -> str:
  1691        return "https"
  1692
  1693    @staticmethod
  1694    def _go_close_connection_error(url):
  1695        """
  1696        :param url: url passed to the query
  1697        :return: error message string that Go's net/http package throws when server closes connection
  1698        """
  1699        return "Get {}: EOF".format(url)
  1700
  1701    def queries(self):
  1702        yield Query(
  1703            self.url("tls-context-same/"),
  1704            headers={"Host": "tls-context-host-1"},
  1705            expected=200,
  1706            insecure=True,
  1707            sni=True,
  1708            cipherSuites=["TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"],
  1709            maxTLSv="v1.2",
  1710        )
  1711
  1712        yield Query(
  1713            self.url("tls-context-same/"),
  1714            headers={"Host": "tls-context-host-1"},
  1715            expected=200,
  1716            insecure=True,
  1717            sni=True,
  1718            cipherSuites=["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"],
  1719            maxTLSv="v1.2",
  1720            error="tls: handshake failure",
  1721        )
  1722
  1723        yield Query(
  1724            self.url("tls-context-same/"),
  1725            headers={"Host": "tls-context-host-1"},
  1726            expected=200,
  1727            insecure=True,
  1728            sni=True,
  1729            cipherSuites=["TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"],
  1730            ecdhCurves=["X25519"],
  1731            maxTLSv="v1.2",
  1732            error="tls: handshake failure",
  1733        )
  1734
  1735    def check(self):
  1736        assert self.results[0].backend
  1737        assert self.results[0].backend.request
  1738        tls_0_version = self.results[0].backend.request.tls.negotiated_protocol_version
  1739
  1740        assert tls_0_version == "v1.2", f"requesting TLS v1.2 got TLS {tls_0_version}"
  1741
  1742    def requirements(self):
  1743        yield (
  1744            "url",
  1745            Query(
  1746                self.url("ambassador/v0/check_ready"),
  1747                headers={"Host": "tls-context-host-1"},
  1748                insecure=True,
  1749                sni=True,
  1750            ),
  1751        )
  1752        yield (
  1753            "url",
  1754            Query(
  1755                self.url("ambassador/v0/check_alive"),
  1756                headers={"Host": "tls-context-host-1"},
  1757                insecure=True,
  1758                sni=True,
  1759            ),
  1760        )
  1761
  1762
  1763class TLSContextIstioSecretTest(AmbassadorTest):
  1764    # debug = True
  1765
  1766    def init(self):
  1767        self.target = HTTP()
  1768
  1769        if EDGE_STACK:
  1770            self.xfail = "XFailing for now"
  1771
  1772    def manifests(self) -> str:
  1773        return (
  1774            namespace_manifest("secret-namespace")
  1775            + """
  1776---
  1777apiVersion: v1
  1778data:
  1779  cert-chain.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lSQU8wbFh1OVhOYkNrejJJTEhiYlVBbDh3RFFZSktvWklodmNOQVFFTEJRQXcKR0RFV01CUUdBMVVFQ2hNTlkyeDFjM1JsY2k1c2IyTmhiREFlRncweU1EQXhNakF4TmpReE5EbGFGdzB5TURBMApNVGt4TmpReE5EbGFNQUF3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQ3h2RWxuCmd6SldTejR6RGM5TE5od0xCZm1nTStlY3k0T096UEFtSGhnZER2RFhLVE40Qll0bS8veTFRT2tGNG9JeHVMVnAKYW5ULzdHdUJHNzlrbUg1TkpkcWhzV0c1b1h0TWpiZnZnZFJ6dW50UVg1OFI5d0pWT2YwNlo4dHFUYmE4VVI3YQpYZFY1c2VSbGtINU1VWmhVNXkxNzA1ZVNycVBROGVBd1hiazdOejNlTUd4Ujc1NjZOK3g2UDIrcEZmTDF1dEJ3CnRhSVVpYlVNR0liODcwYmtxVmlzSHQ1aC95blkrV3FlclJLREhTLzVRQlZiMytZSXd4N3o1b3FPbDBvZ05YODkKVnlzNFM0NzdXNDBPWGRZaStHeGwwKzFVT2F3NEw2a0tTaWhjVTZJUm1YbWhiUXpRb0VvazN6TDNaR2hWS3FhbwpUaFdqTVhrMkZxS1pNSnBCQWdNQkFBR2pmakI4TUE0R0ExVWREd0VCL3dRRUF3SUZvREFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFRWUlLd1lCQlFVSEF3SXdEQVlEVlIwVEFRSC9CQUl3QURBOUJnTlZIUkVCQWY4RU16QXgKaGk5emNHbG1abVU2THk5amJIVnpkR1Z5TG14dlkyRnNMMjV6TDJGdFltRnpjMkZrYjNJdmMyRXZaR1ZtWVhWcwpkREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBaHQ3c1dSOHEzeFNaM1BsTGFnS0REc1c2UlYyRUJCRkNhR08rCjlJb2lrQXZTdTV2b3VKS3EzVHE0WU9LRzJnbEpvVSs1c2lmL25DYzFva1ZTakNJSnh1UVFhdzd5QkV0WWJaZkYKSXI2WEkzbUtCVC9kWHpOM00yL1g4Q3RBNHI5SFQ4VmxmMitJMHNqb01hVE80WHdPNVQ5eXdoREJXdzdrdThVRApnMjdzTFlHVy9UNzIvT0JGUEcxa2VlRUpva3BhSXZQOVliWS9qSlRWZVVIYk1FODVOckJFMWNndUVnSlVod1VKCkhiam4xcEFKMHZsUWZrVW9mT3VRZkFtZGpHWjc2N2phOE5ldHZBdk9tRExPV2dzQWM4KzRsRXBKVURwcmhlVEoKazBrSFh6cUMyTzN4a250U0QxM2FFa2VUMXJocjM3MXc1OTVJUjgvR1llSis3a3JqRkE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  1780  key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBc2J4Slo0TXlWa3MrTXczUFN6WWNDd1g1b0RQbm5NdURqc3p3Smg0WUhRN3cxeWt6CmVBV0xadi84dFVEcEJlS0NNYmkxYVdwMC8reHJnUnUvWkpoK1RTWGFvYkZodWFGN1RJMjM3NEhVYzdwN1VGK2YKRWZjQ1ZUbjlPbWZMYWsyMnZGRWUybDNWZWJIa1paQitURkdZVk9jdGU5T1hrcTZqMFBIZ01GMjVPemM5M2pCcwpVZStldWpmc2VqOXZxUlh5OWJyUWNMV2lGSW0xREJpRy9POUc1S2xZckI3ZVlmOHAyUGxxbnEwU2d4MHYrVUFWClc5L21DTU1lOCthS2pwZEtJRFYvUFZjck9FdU8rMXVORGwzV0l2aHNaZFB0VkRtc09DK3BDa29vWEZPaUVabDUKb1cwTTBLQktKTjh5OTJSb1ZTcW1xRTRWb3pGNU5oYWltVENhUVFJREFRQUJBb0lCQUI1bXdIK09OMnYvVHRKWQp5RjVyRVB6cHRyc3FaYkd5TmZ5VkhYYkhxd1E5YkFEQnNXWVVQTFlQajJCSmpCSlBua2wyK01EaFRzWC80SnVpCjdXZjlsWTBJcm83OTBtTjROYWp3ak1mUkExQVFVOHQ1cjdIWStITXZpaHNWYWZ2eTh4RGZKMUhldndjajRKZG0KMGRPb0dWQmNnckV0amoydTFhS0YzUDBvNnVndno2SmtSWld2SjZ4SGlya0NETk5MWlpzbHB5UzFHRjZmYm9aTwp1SmFTLzc2S25JS1FQT3hCaE83ME80WHF6am5wMVk1UzduTjRoM1Z2RmVPREcvQ2pWaGhOcE4xV0NadFNvSXBwCk9XOVdONVRvUnZhVDhnelljcG9TOEMzYXVqSzVvV1FiVzdRZys2NXRoWGNqcFpRM0VFSnNaLzNsTWRsbGE3TFcKT2k3Vkhpa0NnWUVBeHBUQjZodnBRcnNXUUhjcDhRdG94RitNUThVL2l5WjZ6dU5BNHZyWFdwUlFDVVg4d1ZiRwowTFNZN1lSVGhuOGtUZ09vWlNWMU9VcThUTjlnOG91UUh6bS9ta1FpV0p0bnNXWGJtNjF3SFozaWNlQ1FxWDU4CmoyUjM2eXBONGpuUENPREVwcDVKWExZLzNFTnZnYTBxSm9ZVWp4UUpHZDgyWUxKRmJrMHZmTzhDZ1lFQTVTQ0MKcHJTR0NBL0dUVkY4MjRmaW1YTkNMcllOVmV1TStqZFdqQUFBZkQzWHpUK1JWeFZsTENTVUluQUdtYjh2djZlcApreHYrdWlBZTg2TDBhUVVDTENSRFF2SjR3MnNPRWkwWWMwTGlKUGdBN1JLeFhwVGUrQ09vS1VmcTZyVi96TTdNCmhCbWtDT2ZoUnRDT3NENGNBcWQ0MzluQjZBVm01K21VV0FqNHU4OENnWUJFTXBSQi9TSG5xKzZoWndzOVgraTAKQUFoZ3dkM253T2hPSXRlRzNCU1hZL1gwcVZkN1luelc4aDdPK3pIZ0w4dmRDdjZLOWdsRENycU9QK3pBZjFPWQpsYkdLbmptWmFvMTY2L3MyaEtMTFdReUtoVS9KRmNwYlNHcXlsWTIzMHBpYWVPNndOZzRGekFVMGRPaFhoWXZEClBTclVWRkluMDNPT1U4cnFiWkdRZXdLQmdFMVJPaVZNOTRtUzRTVElJYXptM3NWUFNuNyt1ZU5MZUNnYk1sNU4KeGR3bTlrSnhkL2I5NWtVT0Z0ckVHTVlhNk43d2tkMXRiZmlhekRjRXZ4c05NSjE2b3lQZE5Ia2xEL3Q4TWlyNgozOXIvd1RnK3ZaR2dCTm1SRnJiUGFPdEkwZFpuMWtXaGJXUC84MW4xR0tGS1pDTlZKZ25Mcm80Ly9HaTN2bkl5Cm5OU3JBb0dBVGVidmRLamtENk5XQmJMWSsrVUVQN1ZLd0pOWlR1VWUvT0FBdlJIKzZaWEV4SkhtM3pjV280TVkKMG8vL2dyNzhBdDM4NEk5QVBwMnQwV3lmTmlaTStWUFh4a1lKTU5IU01mcXdGcVRVSmE3NGttNVUrYnB4Mm1ueAovUlR6aElHMDE4SXN3NHBGeUZ4ekpTSVdCK2VpVEF6NFZsMEw2ZU0yNUp5R3lyU2x0Q2M9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
  1781  root-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMzVENDQWNXZ0F3SUJBZ0lRVGRHUmJPampxWjBEZDUxOVJqdXlzREFOQmdrcWhraUc5dzBCQVFzRkFEQVkKTVJZd0ZBWURWUVFLRXcxamJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01ERXlNREUyTkRBek5Gb1hEVE13TURFeApOekUyTkRBek5Gb3dHREVXTUJRR0ExVUVDaE1OWTJ4MWMzUmxjaTVzYjJOaGJEQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFMNzhadlRtQ2hxYUM5Z0lFUFlWSWYrVkFsU0tJR2JsdktvUUJNNmwKWlNBTmxNQXg3elJQTjFQdVMrV2I5M1hxMXNzN1hEUEY4UmlIL2dCWE05aGZsNUpGTDErbmlLYWR3RHh5UUdXQQpPMUFBQXNmZlpud3NkWDhDOGdCcE5zUkVZYVo5SzExdDI5NmV5WUc1d3ozMW9rZVFYSTVrSU0vdWgxL2wwN3pKClU3eG8zSmVZbHpMZnJSVWhNRnc1Vk5ETkNCY3JldEoyOWgvZzRpS1plM2JDS3laVmJRUkN3VjR5ck12YTA4Z3kKYzRhSGJud1VtRThKT0JvcE5abW1uOHc0bFcwQjFsS1Q3aFhBRldJdW55WVhIOWFabUJJd1pPVk9kV0N4SmZnTQpKSWY1UVJSY0s5MVZGMjYvcUp2RHlwaVpxcHFJcEdQWHJHbHF2dGtTSmwxdHhYMENBd0VBQWFNak1DRXdEZ1lEClZSMFBBUUgvQkFRREFnSUVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUIKQUpjWXl3WkoxeUZpQzRpT0xNbXY4MTZYZEhUSWdRTGlLaXNBdGRqb21TdXhLc0o1eXZ3M2lGdkROSklseEQ4SgoyVVROR2JJTFN2d29qQ1JzQVcyMlJtelpjZG95SXkvcFVIR25EVUpiMk14T0svaEVWU0x4cnN6RHlEK2YwR1liCjdhL1Q2ZmJFbUdYK0JHTnBKZ2lTKytwUm5JMzE3THN6aldtTUlmbVF3T1NtZXNvKzhMSXAxZS9STGVKcThoM0cKREZzcVA4c1BLaHNEM1M1RWNGYU5vSVg4OThVK3UvUWlKd3BoS2lDK3RRRzExeGJZanMxaURNcFJpUGsvSi9NRwpiaTZnQm8zZGdjZ1RWWFdOY2YzeHRiQWErMmkzK3k1V25ydHoyK1d4ZG96cEhpN3FLL1BEbGpwVG5JdkY2Nm0wCjBFYVA0T3ZOY29hNk12MUpoYkFVK0w0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  1782kind: Secret
  1783metadata:
  1784  name: istio.test-tlscontext-istio-secret-1
  1785  namespace: secret-namespace
  1786  labels:
  1787    kat-ambassador-id: tlscontextistiosecret
  1788type: istio.io/key-and-cert
  1789"""
  1790            + super().manifests()
  1791        )
  1792
  1793    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
  1794        yield self, self.format(
  1795            """
  1796---
  1797apiVersion: getambassador.io/v3alpha1
  1798kind: Mapping
  1799name:  {self.name}-istio-prefix-1
  1800prefix: /tls-context-istio/
  1801service: https://{self.target.path.fqdn}
  1802tls: {self.name}-istio-context-1
  1803"""
  1804        )
  1805        yield self, self.format(
  1806            """
  1807---
  1808apiVersion: getambassador.io/v3alpha1
  1809kind: TLSContext
  1810name: {self.name}-istio-context-1
  1811secret: istio.test-tlscontext-istio-secret-1
  1812namespace: secret-namespace
  1813secret_namespacing: False
  1814"""
  1815        )
  1816
  1817    def queries(self):
  1818        yield Query(self.url("ambassador/v0/diag/?json=true&filter=errors"), phase=2)
  1819
  1820    def check(self):
  1821        assert (
  1822            self.results[0].backend is None
  1823        ), f"expected 0 errors, got {len(self.results[0].backend.response)}: received {self.results[0].backend.response}"
  1824
  1825
  1826class TLSCoalescing(AmbassadorTest):
  1827    def init(self):
  1828        self.target = HTTP()
  1829
  1830        if EDGE_STACK:
  1831            self.xfail = "Not yet supported in Edge Stack"
  1832
  1833        self.xfail = "FIXME: IHA"
  1834
  1835    def manifests(self) -> str:
  1836        return (
  1837            f"""
  1838---
  1839apiVersion: v1
  1840metadata:
  1841  name: tlscoalescing-certs
  1842  labels:
  1843    kat-ambassador-id: tlscoalescing
  1844data:
  1845  tls.crt: {TLSCerts["*.domain.com"].k8s_crt}
  1846  tls.key: {TLSCerts["*.domain.com"].k8s_key}
  1847kind: Secret
  1848type: kubernetes.io/tls
  1849"""
  1850            + super().manifests()
  1851        )
  1852
  1853    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
  1854        yield self, self.format(
  1855            """
  1856apiVersion: getambassador.io/v3alpha1
  1857kind: TLSContext
  1858name: tlscoalescing-context
  1859secret: tlscoalescing-certs
  1860alpn_protocols: h2, http/1.1
  1861hosts:
  1862- domain.com
  1863- a.domain.com
  1864- b.domain.com
  1865"""
  1866        )
  1867
  1868    def scheme(self) -> str:
  1869        return "https"
  1870
  1871    @staticmethod
  1872    def _go_close_connection_error(url):
  1873        """
  1874        :param url: url passed to the query
  1875        :return: error message string that Go's net/http package throws when server closes connection
  1876        """
  1877        return "Get {}: EOF".format(url)
  1878
  1879    def queries(self):
  1880        yield Query(
  1881            self.url("ambassador/v0/diag/"),
  1882            headers={"Host": "a.domain.com"},
  1883            insecure=True,
  1884            sni=True,
  1885        )
  1886        yield Query(
  1887            self.url("ambassador/v0/diag/"),
  1888            headers={"Host": "b.domain.com"},
  1889            insecure=True,
  1890            sni=True,
  1891        )
  1892
  1893    def requirements(self):
  1894        yield ("url", Query(self.url("ambassador/v0/check_ready"), insecure=True, sni=True))
  1895
  1896
  1897class TLSInheritFromModule(AmbassadorTest):
  1898    target: ServiceType
  1899
  1900    def init(self):
  1901        self.xfail = "FIXME: IHA"
  1902        self.edge_stack_cleartext_host = False
  1903        self.target = HTTP()
  1904
  1905    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
  1906        # These are annotations instead of resources because the name matters.
  1907        yield self, self.format(
  1908            """
  1909---
  1910apiVersion: getambassador.io/v3alpha1
  1911kind: Module
  1912name: tls
  1913ambassador_id: [{self.ambassador_id}]
  1914config:
  1915  server:
  1916    enabled: True
  1917    redirect_cleartext_from: 8080
  1918"""
  1919        )
  1920
  1921    def manifests(self) -> str:
  1922        return (
  1923            self.format(
  1924                """
  1925---
  1926apiVersion: getambassador.io/v3alpha1
  1927kind: TLSContext
  1928metadata:
  1929  name: {self.path.k8s}
  1930spec:
  1931  ambassador_id: [ {self.ambassador_id} ]
  1932  alpn_protocols: "h2,http/1.1"
  1933  hosts:
  1934  - a.domain.com
  1935  secret: {self.path.k8s}
  1936---
  1937apiVersion: v1
  1938kind: Secret
  1939metadata:
  1940  name: {self.path.k8s}
  1941  labels:
  1942    kat-ambassador-id: {self.ambassador_id}
  1943type: kubernetes.io/tls
  1944data:
  1945  tls.crt: """
  1946                + TLSCerts["a.domain.com"].k8s_crt
  1947                + """
  1948  tls.key: """
  1949                + TLSCerts["a.domain.com"].k8s_key
  1950                + """
  1951---
  1952apiVersion: getambassador.io/v3alpha1
  1953kind: Mapping
  1954metadata:
  1955  name: {self.path.k8s}-target-mapping
  1956spec:
  1957  ambassador_id: [ {self.ambassador_id} ]
  1958  prefix: /foo
  1959  service: {self.target.path.fqdn}
  1960"""
  1961            )
  1962            + super().manifests()
  1963        )
  1964
  1965    def scheme(self) -> str:
  1966        return "https"
  1967
  1968    def queries(self):
  1969        yield Query(self.url("foo", scheme="http"), headers={"Host": "a.domain.com"}, expected=301)
  1970        yield Query(
  1971            self.url("bar", scheme="http"),
  1972            headers={"Host": "a.domain.com"},
  1973            expected=(404 if bug_404_routes else 301),
  1974        )
  1975        yield Query(
  1976            self.url("foo", scheme="https"),
  1977            headers={"Host": "a.domain.com"},
  1978            ca_cert=TLSCerts["a.domain.com"].pubcert,
  1979            sni=True,
  1980            expected=200,
  1981        )
  1982        yield Query(
  1983            self.url("bar", scheme="https"),
  1984            headers={"Host": "a.domain.com"},
  1985            ca_cert=TLSCerts["a.domain.com"].pubcert,
  1986            sni=True,
  1987            expected=404,
  1988        )
  1989
  1990    def requirements(self):
  1991        for r in super().requirements():
  1992            query = r[1]
  1993            query.headers = {"Host": "a.domain.com"}
  1994            query.sni = (
  1995                True  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  1996            )
  1997            query.ca_cert = TLSCerts["a.domain.com"].pubcert
  1998            yield (r[0], query)

View as plain text