...

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

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

View as plain text