...

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

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

     1from typing import Generator, Tuple, Union
     2
     3from abstract_tests import HTTP, AmbassadorTest, Node, ServiceType
     4from ambassador import Config
     5from kat.harness import EDGE_STACK, Query
     6from tests.integration.manifests import namespace_manifest
     7from tests.selfsigned import TLSCerts
     8from tests.utils import create_crl_pem_b64
     9
    10# STILL TO ADD:
    11# Host referencing a Secret in another namespace?
    12# Mappings without host attributes (infer via Host resource)
    13# Host where a TLSContext with the inferred name already exists
    14
    15bug_single_insecure_action = False  # Do all Hosts have to have the same insecure.action?
    16bug_forced_star = (
    17    True  # Do we erroneously send replies in cleartext instead of TLS for unknown hosts?
    18)
    19bug_404_routes = (
    20    True  # Do we erroneously send 404 responses directly instead of redirect-to-tls first?
    21)
    22bug_clientcert_reset = True  # Do we sometimes just close the connection instead of sending back tls certificate_required?
    23
    24
    25class HostCRDSingle(AmbassadorTest):
    26    """
    27    HostCRDSingle: a single Host with a manually-configured TLS. Since the Host is handling the
    28    TLSContext, we expect both OSS and Edge Stack to redirect cleartext from 8080 to 8443 here.
    29    """
    30
    31    target: ServiceType
    32
    33    def init(self):
    34        self.edge_stack_cleartext_host = False
    35        self.target = HTTP()
    36
    37    def manifests(self) -> str:
    38        return (
    39            self.format(
    40                """
    41---
    42apiVersion: v1
    43kind: Secret
    44metadata:
    45  name: {self.path.k8s}-secret
    46  labels:
    47    kat-ambassador-id: {self.ambassador_id}
    48type: kubernetes.io/tls
    49data:
    50  tls.crt: """
    51                + TLSCerts["localhost"].k8s_crt
    52                + """
    53  tls.key: """
    54                + TLSCerts["localhost"].k8s_key
    55                + """
    56---
    57apiVersion: getambassador.io/v3alpha1
    58kind: Host
    59metadata:
    60  name: {self.path.k8s}-host
    61  labels:
    62    kat-ambassador-id: {self.ambassador_id}
    63spec:
    64  ambassador_id: [ {self.ambassador_id} ]
    65  hostname: {self.path.fqdn}
    66  acmeProvider:
    67    authority: none
    68  tlsSecret:
    69    name: {self.path.k8s}-secret
    70  mappingSelector:
    71    matchLabels:
    72      hostname: {self.path.fqdn}
    73---
    74apiVersion: getambassador.io/v3alpha1
    75kind: Mapping
    76metadata:
    77  name: {self.path.k8s}-target-mapping
    78  labels:
    79    hostname: {self.path.fqdn}
    80spec:
    81  ambassador_id: [ {self.ambassador_id} ]
    82  prefix: /target/
    83  service: {self.target.path.fqdn}
    84"""
    85            )
    86            + super().manifests()
    87        )
    88
    89    def scheme(self) -> str:
    90        return "https"
    91
    92    def queries(self):
    93        yield Query(self.url("target/"), insecure=True)
    94        yield Query(self.url("target/", scheme="http"), expected=301)
    95
    96
    97class HostCRDNo8080(AmbassadorTest):
    98    """
    99    HostCRDNo8080: a single Host with manually-configured TLS that explicitly turns off redirection
   100    from 8080.
   101    """
   102
   103    target: ServiceType
   104
   105    def init(self):
   106        self.edge_stack_cleartext_host = False
   107        self.add_default_http_listener = False
   108        self.add_default_https_listener = False
   109        self.target = HTTP()
   110
   111    def manifests(self) -> str:
   112        return (
   113            self.format(
   114                """
   115---
   116apiVersion: v1
   117kind: Secret
   118metadata:
   119  name: {self.path.k8s}-secret
   120  labels:
   121    kat-ambassador-id: {self.ambassador_id}
   122type: kubernetes.io/tls
   123data:
   124  tls.crt: """
   125                + TLSCerts["localhost"].k8s_crt
   126                + """
   127  tls.key: """
   128                + TLSCerts["localhost"].k8s_key
   129                + """
   130---
   131apiVersion: getambassador.io/v3alpha1
   132kind: Listener
   133metadata:
   134  name: {self.path.k8s}-listener
   135  labels:
   136    kat-ambassador-id: {self.ambassador_id}
   137spec:
   138  ambassador_id: [ {self.ambassador_id} ]
   139  port: 8443
   140  protocol: HTTPS
   141  securityModel: XFP
   142  hostBinding:
   143    namespace:
   144      from: ALL
   145---
   146apiVersion: getambassador.io/v3alpha1
   147kind: Host
   148metadata:
   149  name: {self.path.k8s}-host
   150  labels:
   151    kat-ambassador-id: {self.ambassador_id}
   152spec:
   153  ambassador_id: [ {self.ambassador_id} ]
   154  hostname: {self.path.fqdn}
   155  acmeProvider:
   156    authority: none
   157  tlsSecret:
   158    name: {self.path.k8s}-secret
   159  mappingSelector:
   160    matchLabels:
   161      hostname: {self.path.fqdn}
   162  requestPolicy:
   163    insecure:
   164      action: Reject
   165---
   166apiVersion: getambassador.io/v3alpha1
   167kind: Mapping
   168metadata:
   169  name: {self.path.k8s}-target-mapping
   170  labels:
   171    hostname: {self.path.fqdn}
   172spec:
   173  ambassador_id: [ {self.ambassador_id} ]
   174  prefix: /target/
   175  service: {self.target.path.fqdn}
   176"""
   177            )
   178            + super().manifests()
   179        )
   180
   181    def scheme(self) -> str:
   182        return "https"
   183
   184    def queries(self):
   185        yield Query(self.url("target/"), insecure=True)
   186        yield Query(self.url("target/", scheme="http"), error=["EOF", "connection refused"])
   187
   188
   189class HostCRDManualContext(AmbassadorTest):
   190    """
   191    A single Host with a manually-specified TLS secret and a manually-specified TLSContext,
   192    too.
   193    """
   194
   195    target: ServiceType
   196
   197    def init(self):
   198        self.edge_stack_cleartext_host = False
   199
   200        self.target = HTTP()
   201
   202    def manifests(self) -> str:
   203        return (
   204            self.format(
   205                """
   206---
   207apiVersion: v1
   208kind: Secret
   209metadata:
   210  name: {self.path.k8s}-manual-secret
   211  labels:
   212    kat-ambassador-id: {self.ambassador_id}
   213type: kubernetes.io/tls
   214data:
   215  tls.crt: """
   216                + TLSCerts["localhost"].k8s_crt
   217                + """
   218  tls.key: """
   219                + TLSCerts["localhost"].k8s_key
   220                + """
   221---
   222apiVersion: getambassador.io/v3alpha1
   223kind: Host
   224metadata:
   225  name: {self.path.k8s}-manual-host
   226  labels:
   227    kat-ambassador-id: {self.ambassador_id}
   228spec:
   229  ambassador_id: [ {self.ambassador_id} ]
   230  hostname: {self.path.fqdn}
   231  acmeProvider:
   232    authority: none
   233  mappingSelector:
   234    matchLabels:
   235      hostname: {self.path.k8s}-manual-hostname
   236  tlsSecret:
   237    name: {self.path.k8s}-manual-secret
   238---
   239apiVersion: getambassador.io/v3alpha1
   240kind: TLSContext
   241metadata:
   242  name: {self.path.k8s}-manual-host-context
   243  labels:
   244    kat-ambassador-id: {self.ambassador_id}
   245spec:
   246  ambassador_id: [ {self.ambassador_id} ]
   247  hosts:
   248  - {self.path.fqdn}
   249  secret: {self.path.k8s}-manual-secret
   250  min_tls_version: v1.2
   251  max_tls_version: v1.3
   252---
   253apiVersion: getambassador.io/v3alpha1
   254kind: Mapping
   255metadata:
   256  name: {self.path.k8s}-target-mapping
   257  labels:
   258    hostname: {self.path.k8s}-manual-hostname
   259spec:
   260  ambassador_id: [ {self.ambassador_id} ]
   261  prefix: /target/
   262  service: {self.target.path.fqdn}
   263"""
   264            )
   265            + super().manifests()
   266        )
   267
   268    def scheme(self) -> str:
   269        return "https"
   270
   271    def queries(self):
   272        yield Query(self.url("target/tls-1.2-1.3"), insecure=True, minTLSv="v1.2", maxTLSv="v1.3")
   273
   274        yield Query(
   275            self.url("target/tls-1.0-1.0"),
   276            insecure=True,
   277            minTLSv="v1.0",
   278            maxTLSv="v1.0",
   279            error=[
   280                "tls: server selected unsupported protocol version 303",
   281                "tls: no supported versions satisfy MinVersion and MaxVersion",
   282                "tls: protocol version not supported",
   283            ],
   284        )
   285
   286        yield Query(self.url("target/cleartext", scheme="http"), expected=301)
   287
   288
   289class HostCRDManualContextCRL(AmbassadorTest):
   290    """
   291    A single Host with a manually-specified TLS secret, a manually-specified TLSContext and
   292    a manually specified mTLS config with CRL list too.
   293    """
   294
   295    target: ServiceType
   296
   297    def init(self):
   298        if Config.envoy_api_version == "V2":
   299            self.skip_node = True
   300        self.add_default_http_listener = False
   301        self.add_default_https_listener = False
   302
   303        self.target = HTTP()
   304
   305    def manifests(self) -> str:
   306        return (
   307            self.format(
   308                """
   309---
   310apiVersion: getambassador.io/v3alpha1
   311kind: Listener
   312metadata:
   313  name: {self.path.k8s}-listener
   314  labels:
   315    kat-ambassador-id: {self.ambassador_id}
   316spec:
   317  ambassador_id: [ {self.ambassador_id} ]
   318  port: 8443
   319  protocol: HTTPS
   320  securityModel: XFP
   321  hostBinding:
   322    namespace:
   323      from: SELF
   324---
   325apiVersion: v1
   326kind: Secret
   327metadata:
   328  name: {self.path.k8s}-server-manual-crl-secret
   329  labels:
   330    kat-ambassador-id: {self.ambassador_id}
   331type: kubernetes.io/tls
   332data:
   333  tls.crt: """
   334                + TLSCerts["ambassador.example.com"].k8s_crt
   335                + """
   336  tls.key: """
   337                + TLSCerts["ambassador.example.com"].k8s_key
   338                + """
   339---
   340apiVersion: v1
   341kind: Secret
   342metadata:
   343  name: {self.path.k8s}-ca-manual-crl-secret
   344  labels:
   345    kat-ambassador-id: {self.ambassador_id}
   346type: kubernetes.io/tls
   347data:
   348  tls.crt: """
   349                + TLSCerts["master.datawire.io"].k8s_crt
   350                + """
   351  tls.key: ""
   352---
   353apiVersion: v1
   354kind: Secret
   355metadata:
   356  name: {self.path.k8s}-crl-manual-crl-secret
   357  labels:
   358    kat-ambassador-id: {self.ambassador_id}
   359type: Opaque
   360data:
   361  crl.pem: """
   362                + create_crl_pem_b64(
   363                    TLSCerts["master.datawire.io"].pubcert,
   364                    TLSCerts["master.datawire.io"].privkey,
   365                    [TLSCerts["presto.example.com"].pubcert],
   366                )
   367                + """
   368---
   369apiVersion: getambassador.io/v3alpha1
   370kind: Host
   371metadata:
   372  name: {self.path.k8s}-manual-crl-host
   373  labels:
   374    kat-ambassador-id: {self.ambassador_id}
   375spec:
   376  ambassador_id: [ {self.ambassador_id} ]
   377  hostname: ambassador.example.com
   378  acmeProvider:
   379    authority: none
   380  mappingSelector:
   381    matchLabels:
   382      hostname: {self.path.k8s}-manual-crl-hostname
   383  tlsSecret:
   384    name: {self.path.k8s}-server-manual-crl-secret
   385---
   386apiVersion: getambassador.io/v3alpha1
   387kind: TLSContext
   388metadata:
   389  name: {self.path.k8s}-manual-crl-host-context
   390  labels:
   391    kat-ambassador-id: {self.ambassador_id}
   392spec:
   393  ambassador_id: [ {self.ambassador_id} ]
   394  hosts:
   395  - ambassador.example.com
   396  ca_secret: {self.path.k8s}-ca-manual-crl-secret
   397  secret: {self.path.k8s}-server-manual-crl-secret
   398  cert_required: true
   399  crl_secret: {self.path.k8s}-crl-manual-crl-secret
   400---
   401apiVersion: getambassador.io/v3alpha1
   402kind: Mapping
   403metadata:
   404  name: {self.path.k8s}-target-mapping
   405  labels:
   406    hostname: {self.path.k8s}-manual-crl-hostname
   407spec:
   408  ambassador_id: [ {self.ambassador_id} ]
   409  hostname: ambassador.example.com
   410  prefix: /
   411  service: {self.target.path.fqdn}
   412"""
   413            )
   414            + super().manifests()
   415        )
   416
   417    def scheme(self) -> str:
   418        return "https"
   419
   420    def queries(self):
   421        base = {
   422            "url": self.url(""),
   423            "ca_cert": TLSCerts["master.datawire.io"].pubcert,
   424            "headers": {"Host": "ambassador.example.com"},
   425            "sni": True,  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
   426        }
   427
   428        yield Query(**base, error="tls: certificate required")
   429
   430        yield Query(
   431            **base,
   432            client_crt=TLSCerts["presto.example.com"].pubcert,
   433            client_key=TLSCerts["presto.example.com"].privkey,
   434            error="tls: revoked certificate"
   435        )
   436
   437    def requirements(self):
   438        yield ("pod", self.path.k8s)
   439
   440
   441class HostCRDSeparateTLSContext(AmbassadorTest):
   442    target: ServiceType
   443
   444    def init(self):
   445        self.edge_stack_cleartext_host = False
   446        self.target = HTTP()
   447
   448    def manifests(self) -> str:
   449        return (
   450            self.format(
   451                """
   452---
   453apiVersion: v1
   454kind: Secret
   455metadata:
   456  name: {self.path.k8s}-secret
   457  labels:
   458    kat-ambassador-id: {self.ambassador_id}
   459type: kubernetes.io/tls
   460data:
   461  tls.crt: """
   462                + TLSCerts["localhost"].k8s_crt
   463                + """
   464  tls.key: """
   465                + TLSCerts["localhost"].k8s_key
   466                + """
   467---
   468apiVersion: getambassador.io/v3alpha1
   469kind: Host
   470metadata:
   471  name: {self.path.k8s}-manual-host-separate
   472  labels:
   473    kat-ambassador-id: {self.ambassador_id}
   474spec:
   475  ambassador_id: [ {self.ambassador_id} ]
   476  hostname: {self.path.fqdn}
   477  acmeProvider:
   478    authority: none
   479  mappingSelector:
   480    matchLabels:
   481      hostname: {self.path.fqdn}
   482  tlsSecret:
   483    name: {self.path.k8s}-secret
   484  tlsContext:
   485    name: {self.path.k8s}-separate-tls-context
   486---
   487apiVersion: getambassador.io/v3alpha1
   488kind: TLSContext
   489metadata:
   490  name: {self.path.k8s}-separate-tls-context
   491  labels:
   492    kat-ambassador-id: {self.ambassador_id}
   493spec:
   494  ambassador_id: [ {self.ambassador_id} ]
   495  secret: {self.path.k8s}-secret
   496  min_tls_version: v1.2
   497  max_tls_version: v1.3
   498---
   499apiVersion: getambassador.io/v3alpha1
   500kind: Mapping
   501metadata:
   502  name: {self.path.k8s}-target-mapping-separate
   503  labels:
   504    hostname: {self.path.fqdn}
   505spec:
   506  ambassador_id: [ {self.ambassador_id} ]
   507  prefix: /target/
   508  service: {self.target.path.fqdn}
   509"""
   510            )
   511            + super().manifests()
   512        )
   513
   514    def scheme(self) -> str:
   515        return "https"
   516
   517    def queries(self):
   518        yield Query(self.url("target/"), insecure=True, minTLSv="v1.2", maxTLSv="v1.3")
   519
   520        yield Query(
   521            self.url("target/"),
   522            insecure=True,
   523            minTLSv="v1.0",
   524            maxTLSv="v1.0",
   525            error=[
   526                "tls: server selected unsupported protocol version 303",
   527                "tls: no supported versions satisfy MinVersion and MaxVersion",
   528                "tls: protocol version not supported",
   529            ],
   530        )
   531
   532
   533class HostCRDTLSConfig(AmbassadorTest):
   534    target: ServiceType
   535
   536    def init(self):
   537        self.edge_stack_cleartext_host = False
   538        self.target = HTTP()
   539
   540    def manifests(self) -> str:
   541        return (
   542            self.format(
   543                """
   544---
   545apiVersion: v1
   546kind: Secret
   547metadata:
   548  name: {self.path.k8s}-secret
   549  labels:
   550    kat-ambassador-id: {self.ambassador_id}
   551type: kubernetes.io/tls
   552data:
   553  tls.crt: """
   554                + TLSCerts["localhost"].k8s_crt
   555                + """
   556  tls.key: """
   557                + TLSCerts["localhost"].k8s_key
   558                + """
   559---
   560apiVersion: getambassador.io/v3alpha1
   561kind: Host
   562metadata:
   563  name: {self.path.k8s}-manual-host-tls
   564  labels:
   565    kat-ambassador-id: {self.ambassador_id}
   566spec:
   567  ambassador_id: [ {self.ambassador_id} ]
   568  hostname: {self.path.fqdn}
   569  acmeProvider:
   570    authority: none
   571  mappingSelector:
   572    matchLabels:
   573      hostname: {self.path.fqdn}
   574  tlsSecret:
   575    name: {self.path.k8s}-secret
   576  tls:
   577    min_tls_version: v1.2
   578    max_tls_version: v1.3
   579---
   580apiVersion: getambassador.io/v3alpha1
   581kind: Mapping
   582metadata:
   583  name: {self.path.k8s}-target-mapping
   584  labels:
   585    hostname: {self.path.fqdn}
   586spec:
   587  ambassador_id: [ {self.ambassador_id} ]
   588  prefix: /target/
   589  service: {self.target.path.fqdn}
   590"""
   591            )
   592            + super().manifests()
   593        )
   594
   595    def scheme(self) -> str:
   596        return "https"
   597
   598    def queries(self):
   599        yield Query(self.url("target/"), insecure=True, minTLSv="v1.2", maxTLSv="v1.3")
   600
   601        yield Query(
   602            self.url("target/"),
   603            insecure=True,
   604            minTLSv="v1.0",
   605            maxTLSv="v1.0",
   606            error=[
   607                "tls: server selected unsupported protocol version 303",
   608                "tls: no supported versions satisfy MinVersion and MaxVersion",
   609                "tls: protocol version not supported",
   610            ],
   611        )
   612
   613
   614class HostCRDClearText(AmbassadorTest):
   615    """
   616    A single Host specifying cleartext only. Since it's just cleartext, no redirection comes
   617    into play.
   618    """
   619
   620    target: ServiceType
   621
   622    def init(self):
   623        self.edge_stack_cleartext_host = False
   624
   625        # Only add the default HTTP listener (we're mimicking the no-TLS case here.)
   626        self.add_default_http_listener = True
   627        self.add_default_https_listener = False
   628
   629        self.target = HTTP()
   630
   631    def manifests(self) -> str:
   632        return (
   633            self.format(
   634                """
   635---
   636apiVersion: getambassador.io/v3alpha1
   637kind: Host
   638metadata:
   639  name: {self.path.k8s}-cleartext-host
   640  labels:
   641    kat-ambassador-id: {self.ambassador_id}
   642spec:
   643  ambassador_id: [ {self.ambassador_id} ]
   644  hostname: {self.path.fqdn}
   645  acmeProvider:
   646    authority: none
   647  mappingSelector:
   648    matchLabels:
   649      hostname: {self.path.k8s}-host-cleartext
   650  requestPolicy:
   651    insecure:
   652      action: Route
   653---
   654apiVersion: getambassador.io/v3alpha1
   655kind: Mapping
   656metadata:
   657  name: {self.path.k8s}-cleartext-target-mapping
   658  labels:
   659    hostname: {self.path.k8s}-host-cleartext
   660spec:
   661  ambassador_id: [ {self.ambassador_id} ]
   662  prefix: /target/
   663  service: {self.target.path.fqdn}
   664"""
   665            )
   666            + super().manifests()
   667        )
   668
   669    def scheme(self) -> str:
   670        return "http"
   671
   672    def queries(self):
   673        yield Query(self.url("target/"), insecure=True)
   674        yield Query(self.url("target/", scheme="https"), error=["EOF", "connection refused"])
   675
   676
   677class HostCRDDouble(AmbassadorTest):
   678    """
   679    HostCRDDouble: "double" is actually a misnomer. We have multiple Hosts, each with a
   680    manually-configured TLS secrets, and varying insecure actions:
   681    - tls-context-host-1: Route
   682    - tls-context-host-2: Redirect
   683    - tls-context-host-3: Reject
   684
   685    We also have Mappings that specify Host matches, and we test the various combinations.
   686
   687    XXX In the future, the hostname matches should be unnecessary, as it should use
   688    metadata.labels.hostname.
   689    """
   690
   691    target1: ServiceType
   692    target2: ServiceType
   693    target3: ServiceType
   694    targetshared: ServiceType
   695
   696    def init(self):
   697        self.edge_stack_cleartext_host = False
   698        self.target1 = HTTP(name="target1")
   699        self.target2 = HTTP(name="target2")
   700        self.target3 = HTTP(name="target3")
   701        self.targetshared = HTTP(name="targetshared")
   702
   703    def manifests(self) -> str:
   704        return (
   705            self.format(
   706                """
   707---
   708apiVersion: getambassador.io/v3alpha1
   709kind: Host
   710metadata:
   711  name: {self.path.k8s}-host-1
   712  labels:
   713    kat-ambassador-id: {self.ambassador_id}
   714spec:
   715  ambassador_id: [ {self.ambassador_id} ]
   716  hostname: tls-context-host-1
   717  acmeProvider:
   718    authority: none
   719  mappingSelector:
   720    matchLabels:
   721      hostname: tls-context-host-1
   722  tlsSecret:
   723    name: {self.path.k8s}-test-tlscontext-secret-1
   724  requestPolicy:
   725    insecure:
   726      action: Route
   727---
   728apiVersion: v1
   729kind: Secret
   730metadata:
   731  name: {self.path.k8s}-test-tlscontext-secret-1
   732  labels:
   733    kat-ambassador-id: {self.ambassador_id}
   734type: kubernetes.io/tls
   735data:
   736  tls.crt: """
   737                + TLSCerts["tls-context-host-1"].k8s_crt
   738                + """
   739  tls.key: """
   740                + TLSCerts["tls-context-host-1"].k8s_key
   741                + """
   742---
   743apiVersion: getambassador.io/v3alpha1
   744kind: Mapping
   745metadata:
   746  name: {self.path.k8s}-host-1-mapping
   747  labels:
   748    hostname: tls-context-host-1
   749    kat-ambassador-id: {self.ambassador_id}
   750spec:
   751  ambassador_id: [ {self.ambassador_id} ]
   752  host: "tls-context-host-1"
   753  prefix: /target-1/
   754  service: {self.target1.path.fqdn}
   755
   756---
   757apiVersion: getambassador.io/v3alpha1
   758kind: Host
   759metadata:
   760  name: {self.path.k8s}-host-2
   761  labels:
   762    kat-ambassador-id: {self.ambassador_id}
   763spec:
   764  ambassador_id: [ {self.ambassador_id} ]
   765  hostname: tls-context-host-2
   766  acmeProvider:
   767    authority: none
   768  tlsSecret:
   769    name: {self.path.k8s}-test-tlscontext-secret-2
   770  requestPolicy:
   771    insecure:
   772      action: Redirect
   773---
   774apiVersion: v1
   775kind: Secret
   776metadata:
   777  name: {self.path.k8s}-test-tlscontext-secret-2
   778  labels:
   779    kat-ambassador-id: {self.ambassador_id}
   780type: kubernetes.io/tls
   781data:
   782  tls.crt: """
   783                + TLSCerts["tls-context-host-2"].k8s_crt
   784                + """
   785  tls.key: """
   786                + TLSCerts["tls-context-host-2"].k8s_key
   787                + """
   788---
   789apiVersion: getambassador.io/v3alpha1
   790kind: Mapping
   791metadata:
   792  name: {self.path.k8s}-host-2-mapping
   793  labels:
   794    hostname: tls-context-host-2
   795    kat-ambassador-id: {self.ambassador_id}
   796spec:
   797  ambassador_id: [ {self.ambassador_id} ]
   798  host: "tls-context-host-2"
   799  prefix: /target-2/
   800  service: {self.target2.path.fqdn}
   801
   802---
   803apiVersion: getambassador.io/v3alpha1
   804kind: Host
   805metadata:
   806  name: {self.path.k8s}-host-3
   807  labels:
   808    kat-ambassador-id: {self.ambassador_id}
   809spec:
   810  ambassador_id: [ {self.ambassador_id} ]
   811  hostname: ambassador.example.com
   812  acmeProvider:
   813    authority: none
   814  mappingSelector:
   815    matchLabels:
   816      hostname: ambassador.example.com
   817  tlsSecret:
   818    name: {self.path.k8s}-test-tlscontext-secret-3
   819  requestPolicy:
   820    insecure:
   821      action: Reject
   822---
   823apiVersion: v1
   824kind: Secret
   825metadata:
   826  name: {self.path.k8s}-test-tlscontext-secret-3
   827  labels:
   828    kat-ambassador-id: {self.ambassador_id}
   829type: kubernetes.io/tls
   830data:
   831  tls.crt: """
   832                + TLSCerts["ambassador.example.com"].k8s_crt
   833                + """
   834  tls.key: """
   835                + TLSCerts["ambassador.example.com"].k8s_key
   836                + """
   837---
   838apiVersion: getambassador.io/v3alpha1
   839kind: Mapping
   840metadata:
   841  name: {self.path.k8s}-host-3-mapping
   842  labels:
   843    hostname: ambassador.example.com
   844    kat-ambassador-id: {self.ambassador_id}
   845spec:
   846  ambassador_id: [ {self.ambassador_id} ]
   847  host: "ambassador.example.com"
   848  prefix: /target-3/
   849  service: {self.target3.path.fqdn}
   850---
   851# Add a bogus ACME mapping so that we can distinguish "invalid
   852# challenge" from "rejected".
   853apiVersion: getambassador.io/v3alpha1
   854kind: Mapping
   855metadata:
   856  name: {self.path.k8s}-host-3-acme
   857  labels:
   858    hostname: ambassador.example.com
   859    kat-ambassador-id: {self.ambassador_id}
   860spec:
   861  ambassador_id: [ {self.ambassador_id} ]
   862  host: "ambassador.example.com"
   863  prefix: /.well-known/acme-challenge/
   864  service: {self.target3.path.fqdn}
   865
   866---
   867apiVersion: getambassador.io/v3alpha1
   868kind: Mapping
   869metadata:
   870  name: {self.path.k8s}-host-shared-mapping
   871  labels:
   872    kat-ambassador-id: {self.ambassador_id}
   873spec:
   874  ambassador_id: [ {self.ambassador_id} ]
   875  hostname: "*"
   876  prefix: /target-shared/
   877  service: {self.targetshared.path.fqdn}
   878"""
   879            )
   880            + super().manifests()
   881        )
   882
   883    def scheme(self) -> str:
   884        return "https"
   885
   886    def queries(self):
   887        # 0: Get some info from diagd for self.check() to inspect
   888        yield Query(
   889            self.url("ambassador/v0/diag/?json=true&filter=errors"),
   890            headers={"Host": "tls-context-host-1"},
   891            insecure=True,
   892            sni=True,
   893        )
   894
   895        # 1-5: Host #1 - TLS
   896        yield Query(
   897            self.url("target-1/", scheme="https"),
   898            headers={"Host": "tls-context-host-1"},
   899            insecure=True,
   900            sni=True,
   901            expected=200,
   902        )
   903        yield Query(
   904            self.url("target-2/", scheme="https"),
   905            headers={"Host": "tls-context-host-1"},
   906            insecure=True,
   907            sni=True,
   908            expected=404,
   909        )
   910        yield Query(
   911            self.url("target-3/", scheme="https"),
   912            headers={"Host": "tls-context-host-1"},
   913            insecure=True,
   914            sni=True,
   915            expected=404,
   916        )
   917        yield Query(
   918            self.url("target-shared/", scheme="https"),
   919            headers={"Host": "tls-context-host-1"},
   920            insecure=True,
   921            sni=True,
   922            expected=200,
   923        )
   924        yield Query(
   925            self.url(".well-known/acme-challenge/foo", scheme="https"),
   926            headers={"Host": "tls-context-host-1"},
   927            insecure=True,
   928            sni=True,
   929            expected=404,
   930        )
   931        # 6-10: Host #1 - cleartext (action: Route)
   932        yield Query(
   933            self.url("target-1/", scheme="http"),
   934            headers={"Host": "tls-context-host-1"},
   935            expected=200,
   936        )
   937        yield Query(
   938            self.url("target-2/", scheme="http"),
   939            headers={"Host": "tls-context-host-1"},
   940            expected=404,
   941        )
   942        yield Query(
   943            self.url("target-3/", scheme="http"),
   944            headers={"Host": "tls-context-host-1"},
   945            expected=404,
   946        )
   947        yield Query(
   948            self.url("target-shared/", scheme="http"),
   949            headers={"Host": "tls-context-host-1"},
   950            expected=200,
   951        )
   952        yield Query(
   953            self.url(".well-known/acme-challenge/foo", scheme="http"),
   954            headers={"Host": "tls-context-host-1"},
   955            expected=404,
   956        )
   957
   958        # 11-15: Host #2 - TLS
   959        yield Query(
   960            self.url("target-1/", scheme="https"),
   961            headers={"Host": "tls-context-host-2"},
   962            insecure=True,
   963            sni=True,
   964            expected=404,
   965        )
   966        yield Query(
   967            self.url("target-2/", scheme="https"),
   968            headers={"Host": "tls-context-host-2"},
   969            insecure=True,
   970            sni=True,
   971            expected=200,
   972        )
   973        yield Query(
   974            self.url("target-3/", scheme="https"),
   975            headers={"Host": "tls-context-host-2"},
   976            insecure=True,
   977            sni=True,
   978            expected=404,
   979        )
   980        yield Query(
   981            self.url("target-shared/", scheme="https"),
   982            headers={"Host": "tls-context-host-2"},
   983            insecure=True,
   984            sni=True,
   985            expected=200,
   986        )
   987        yield Query(
   988            self.url(".well-known/acme-challenge/foo", scheme="https"),
   989            headers={"Host": "tls-context-host-2"},
   990            insecure=True,
   991            sni=True,
   992            expected=404,
   993        )
   994        # 16-20: Host #2 - cleartext (action: Redirect)
   995        yield Query(
   996            self.url("target-1/", scheme="http"),
   997            headers={"Host": "tls-context-host-2"},
   998            expected=404,
   999        )
  1000        yield Query(
  1001            self.url("target-2/", scheme="http"),
  1002            headers={"Host": "tls-context-host-2"},
  1003            expected=301,
  1004        )
  1005        yield Query(
  1006            self.url("target-3/", scheme="http"),
  1007            headers={"Host": "tls-context-host-2"},
  1008            expected=404,
  1009        )
  1010        yield Query(
  1011            self.url("target-shared/", scheme="http"),
  1012            headers={"Host": "tls-context-host-2"},
  1013            expected=301,
  1014        )
  1015        yield Query(
  1016            self.url(".well-known/acme-challenge/foo", scheme="http"),
  1017            headers={"Host": "tls-context-host-2"},
  1018            expected=404,
  1019        )
  1020
  1021        # 21-25: Host #3 - TLS
  1022        yield Query(
  1023            self.url("target-1/", scheme="https"),
  1024            headers={"Host": "ambassador.example.com"},
  1025            insecure=True,
  1026            sni=True,
  1027            expected=404,
  1028        )
  1029        yield Query(
  1030            self.url("target-2/", scheme="https"),
  1031            headers={"Host": "ambassador.example.com"},
  1032            insecure=True,
  1033            sni=True,
  1034            expected=404,
  1035        )
  1036        yield Query(
  1037            self.url("target-3/", scheme="https"),
  1038            headers={"Host": "ambassador.example.com"},
  1039            insecure=True,
  1040            sni=True,
  1041            expected=200,
  1042        )
  1043        yield Query(
  1044            self.url("target-shared/", scheme="https"),
  1045            headers={"Host": "ambassador.example.com"},
  1046            insecure=True,
  1047            sni=True,
  1048            expected=200,
  1049        )
  1050        yield Query(
  1051            self.url(".well-known/acme-challenge/foo", scheme="https"),
  1052            headers={"Host": "ambassador.example.com"},
  1053            insecure=True,
  1054            sni=True,
  1055            expected=200,
  1056        )
  1057        # 26-30: Host #3 - cleartext (action: Reject)
  1058        yield Query(
  1059            self.url("target-1/", scheme="http"),
  1060            headers={"Host": "ambassador.example.com"},
  1061            expected=404,
  1062        )
  1063        yield Query(
  1064            self.url("target-2/", scheme="http"),
  1065            headers={"Host": "ambassador.example.com"},
  1066            expected=404,
  1067        )
  1068        yield Query(
  1069            self.url("target-3/", scheme="http"),
  1070            headers={"Host": "ambassador.example.com"},
  1071            expected=404,
  1072        )
  1073        yield Query(
  1074            self.url("target-shared/", scheme="http"),
  1075            headers={"Host": "ambassador.example.com"},
  1076            expected=404,
  1077        )
  1078        yield Query(
  1079            self.url(".well-known/acme-challenge/foo", scheme="http"),
  1080            headers={"Host": "ambassador.example.com"},
  1081            expected=200,
  1082        )
  1083
  1084    def check(self):
  1085        # XXX Ew. If self.results[0].json is empty, the harness won't convert it to a response.
  1086        errors = self.results[0].json or []
  1087        num_errors = len(errors)
  1088        assert num_errors == 0, "expected 0 errors, got {} -\n{}".format(num_errors, errors)
  1089
  1090        idx = 0
  1091
  1092        for result in self.results:
  1093            if result.status == 200 and result.query.headers and result.tls:
  1094                host_header = result.query.headers["Host"]
  1095                tls_common_name = result.tls[0]["Subject"]["CommonName"]
  1096
  1097                assert host_header == tls_common_name, "test %d wanted CN %s, but got %s" % (
  1098                    idx,
  1099                    host_header,
  1100                    tls_common_name,
  1101                )
  1102
  1103            idx += 1
  1104
  1105    def requirements(self):
  1106        # We're replacing super()'s requirements deliberately here. Without a Host header they can't work.
  1107        yield (
  1108            "url",
  1109            Query(
  1110                self.url("ambassador/v0/check_ready"),
  1111                headers={"Host": "tls-context-host-1"},
  1112                insecure=True,
  1113                sni=True,
  1114            ),
  1115        )
  1116        yield (
  1117            "url",
  1118            Query(
  1119                self.url("ambassador/v0/check_alive"),
  1120                headers={"Host": "tls-context-host-1"},
  1121                insecure=True,
  1122                sni=True,
  1123            ),
  1124        )
  1125        yield (
  1126            "url",
  1127            Query(
  1128                self.url("ambassador/v0/check_ready"),
  1129                headers={"Host": "tls-context-host-2"},
  1130                insecure=True,
  1131                sni=True,
  1132            ),
  1133        )
  1134        yield (
  1135            "url",
  1136            Query(
  1137                self.url("ambassador/v0/check_alive"),
  1138                headers={"Host": "tls-context-host-2"},
  1139                insecure=True,
  1140                sni=True,
  1141            ),
  1142        )
  1143
  1144
  1145class HostCRDWildcards(AmbassadorTest):
  1146    """This test could be expanded to include more scenarios, like testing
  1147    handling of precedence between suffix-match host globs and
  1148    prefix-match host-globs.  But this is a solid start.
  1149
  1150    """
  1151
  1152    target: ServiceType
  1153
  1154    def init(self):
  1155        self.target = HTTP()
  1156
  1157    def manifests(self) -> str:
  1158        return (
  1159            self.format(
  1160                """
  1161---
  1162apiVersion: getambassador.io/v3alpha1
  1163kind: Host
  1164metadata:
  1165  name: {self.path.k8s}-wc
  1166  labels:
  1167    kat-ambassador-id: {self.ambassador_id}
  1168spec:
  1169  ambassador_id: [ {self.ambassador_id} ]
  1170  hostname: "*"
  1171  acmeProvider:
  1172    authority: none
  1173  tlsSecret:
  1174    name: {self.path.k8s}-tls
  1175---
  1176apiVersion: getambassador.io/v3alpha1
  1177kind: Host
  1178metadata:
  1179  name: {self.path.k8s}-wc.domain.com
  1180  labels:
  1181    kat-ambassador-id: {self.ambassador_id}
  1182spec:
  1183  ambassador_id: [ {self.ambassador_id} ]
  1184  hostname: "*.domain.com"
  1185  acmeProvider:
  1186    authority: none
  1187  tlsSecret:
  1188    name: {self.path.k8s}-tls
  1189  requestPolicy:
  1190    insecure:
  1191      action: Route
  1192---
  1193apiVersion: getambassador.io/v3alpha1
  1194kind: Host
  1195metadata:
  1196  name: {self.path.k8s}-a.domain.com
  1197  labels:
  1198    kat-ambassador-id: {self.ambassador_id}
  1199spec:
  1200  ambassador_id: [ {self.ambassador_id} ]
  1201  hostname: "a.domain.com"
  1202  acmeProvider:
  1203    authority: none
  1204  tlsSecret:
  1205    name: {self.path.k8s}-tls
  1206---
  1207apiVersion: v1
  1208kind: Secret
  1209metadata:
  1210  name: {self.path.k8s}-tls
  1211  labels:
  1212    kat-ambassador-id: {self.ambassador_id}
  1213type: kubernetes.io/tls
  1214data:
  1215  tls.crt: """
  1216                + TLSCerts["a.domain.com"].k8s_crt
  1217                + """
  1218  tls.key: """
  1219                + TLSCerts["a.domain.com"].k8s_key
  1220                + """
  1221---
  1222apiVersion: getambassador.io/v3alpha1
  1223kind: Mapping
  1224metadata:
  1225  name: {self.path.k8s}
  1226  labels:
  1227    kat-ambassador-id: {self.ambassador_id}
  1228spec:
  1229  ambassador_id: [ {self.ambassador_id} ]
  1230  hostname: "*"
  1231  prefix: /foo/
  1232  service: {self.target.path.fqdn}
  1233"""
  1234            )
  1235            + super().manifests()
  1236        )
  1237
  1238    def insecure(self, suffix):
  1239        return {
  1240            "url": self.url("foo/%s" % suffix, scheme="http"),
  1241        }
  1242
  1243    def secure(self, suffix):
  1244        return {
  1245            "url": self.url("foo/%s" % suffix, scheme="https"),
  1246            "ca_cert": TLSCerts["*.domain.com"].pubcert,
  1247            "sni": True,
  1248        }
  1249
  1250    def queries(self):
  1251        yield Query(
  1252            **self.secure("0-200"), headers={"Host": "a.domain.com"}, expected=200
  1253        )  # Host=a.domain.com
  1254        yield Query(
  1255            **self.secure("1-200"), headers={"Host": "wc.domain.com"}, expected=200
  1256        )  # Host=*.domain.com
  1257        yield Query(**self.secure("2-200"), headers={"Host": "127.0.0.1"}, expected=200)  # Host=*
  1258
  1259        yield Query(
  1260            **self.insecure("3-301"), headers={"Host": "a.domain.com"}, expected=301
  1261        )  # Host=a.domain.com
  1262        yield Query(
  1263            **self.insecure("4-200"), headers={"Host": "wc.domain.com"}, expected=200
  1264        )  # Host=*.domain.com
  1265        yield Query(**self.insecure("5-301"), headers={"Host": "127.0.0.1"}, expected=301)  # Host=*
  1266
  1267    def scheme(self) -> str:
  1268        return "https"
  1269
  1270    def requirements(self):
  1271        for r in super().requirements():
  1272            query = r[1]
  1273            query.headers = {"Host": "127.0.0.1"}
  1274            query.sni = (
  1275                True  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  1276            )
  1277            query.ca_cert = TLSCerts["*.domain.com"].pubcert
  1278            yield (r[0], query)
  1279
  1280
  1281class HostCRDClientCertCrossNamespace(AmbassadorTest):
  1282    target: ServiceType
  1283
  1284    def init(self):
  1285        self.target = HTTP()
  1286
  1287    def manifests(self) -> str:
  1288        # All of the things referenced from a Host have a '.' in their
  1289        # name, to make sure that Ambassador is correctly interpreting
  1290        # the '.' as a namespace-separator (or not).  Because most of
  1291        # the references are core.v1.LocalObjectReferences, the '.' is
  1292        # not taken as a namespace-separator, but it is for the
  1293        # tls.ca_secret.  And for ca_secret we still put the '.' in
  1294        # the name so that we check that it's choosing the correct '.'
  1295        # as the separator.
  1296        return (
  1297            namespace_manifest("alt-namespace")
  1298            + self.format(
  1299                """
  1300---
  1301apiVersion: getambassador.io/v3alpha1
  1302kind: Host
  1303metadata:
  1304  name: {self.path.k8s}
  1305  labels:
  1306    kat-ambassador-id: {self.ambassador_id}
  1307spec:
  1308  ambassador_id: [ {self.ambassador_id} ]
  1309  hostname: ambassador.example.com
  1310  acmeProvider:
  1311    authority: none
  1312  tlsSecret:
  1313    name: {self.path.k8s}.server
  1314  tls:
  1315    # ca_secret supports cross-namespace references, so test it
  1316    ca_secret: {self.path.k8s}.ca.alt-namespace
  1317    cert_required: true
  1318---
  1319apiVersion: v1
  1320kind: Secret
  1321metadata:
  1322  name: {self.path.k8s}.ca
  1323  namespace: alt-namespace
  1324  labels:
  1325    kat-ambassador-id: {self.ambassador_id}
  1326type: kubernetes.io/tls
  1327data:
  1328  tls.crt: """
  1329                + TLSCerts["master.datawire.io"].k8s_crt
  1330                + """
  1331  tls.key: ""
  1332---
  1333apiVersion: v1
  1334kind: Secret
  1335metadata:
  1336  name: {self.path.k8s}.server
  1337  labels:
  1338    kat-ambassador-id: {self.ambassador_id}
  1339type: kubernetes.io/tls
  1340data:
  1341  tls.crt: """
  1342                + TLSCerts["ambassador.example.com"].k8s_crt
  1343                + """
  1344  tls.key: """
  1345                + TLSCerts["ambassador.example.com"].k8s_key
  1346                + """
  1347---
  1348apiVersion: getambassador.io/v3alpha1
  1349kind: Mapping
  1350metadata:
  1351  name: {self.path.k8s}
  1352  labels:
  1353    kat-ambassador-id: {self.ambassador_id}
  1354spec:
  1355  ambassador_id: [ {self.ambassador_id} ]
  1356  hostname: "*"
  1357  prefix: /
  1358  service: {self.target.path.fqdn}
  1359"""
  1360            )
  1361            + super().manifests()
  1362        )
  1363
  1364    def scheme(self) -> str:
  1365        return "https"
  1366
  1367    def queries(self):
  1368        base = {
  1369            "url": self.url(""),
  1370            "ca_cert": TLSCerts["master.datawire.io"].pubcert,
  1371            "headers": {"Host": "ambassador.example.com"},
  1372            "sni": True,  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  1373        }
  1374
  1375        yield Query(
  1376            **base,
  1377            client_crt=TLSCerts["presto.example.com"].pubcert,
  1378            client_key=TLSCerts["presto.example.com"].privkey
  1379        )
  1380
  1381        # Check that it requires the client cert.
  1382        #
  1383        # In TLS < 1.3, there's not a dedicated alert code for "the client forgot to include a certificate",
  1384        # so we get a generic alert=40 ("handshake_failure").
  1385        yield Query(**base, maxTLSv="v1.2", error="tls: handshake failure")
  1386        # TLS 1.3 added a dedicated alert=116 ("certificate_required") for that scenario.
  1387        yield Query(
  1388            **base,
  1389            minTLSv="v1.3",
  1390            error=(
  1391                ["tls: certificate required"]
  1392                + (
  1393                    ["write: connection reset by peer", "write: broken pipe"]
  1394                    if bug_clientcert_reset
  1395                    else []
  1396                )
  1397            )
  1398        )
  1399
  1400        # Check that it's validating the client cert against the CA cert.
  1401        yield Query(
  1402            **base,
  1403            client_crt=TLSCerts["localhost"].pubcert,
  1404            client_key=TLSCerts["localhost"].privkey,
  1405            maxTLSv="v1.2",
  1406            error="tls: handshake failure"
  1407        )
  1408
  1409    def requirements(self):
  1410        for r in super().requirements():
  1411            query = r[1]
  1412            query.headers = {"Host": "ambassador.example.com"}
  1413            query.sni = (
  1414                True  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  1415            )
  1416            query.ca_cert = TLSCerts["master.datawire.io"].pubcert
  1417            query.client_cert = TLSCerts["presto.example.com"].pubcert
  1418            query.client_key = TLSCerts["presto.example.com"].privkey
  1419            yield (r[0], query)
  1420
  1421
  1422class HostCRDClientCertSameNamespace(AmbassadorTest):
  1423    target: ServiceType
  1424
  1425    def init(self):
  1426        self.target = HTTP()
  1427        self.add_default_http_listener = False
  1428        self.add_default_https_listener = False
  1429
  1430    def manifests(self) -> str:
  1431        # Same as HostCRDClientCertCrossNamespace, all of the things
  1432        # referenced by a Host have a '.' in their name; except
  1433        # (unlike HostCRDClientCertCrossNamespace) the ca_secret
  1434        # doesn't, so that we can check that it chooses the correct
  1435        # namespace when a ".{namespace}" suffix isn't specified.
  1436        return (
  1437            namespace_manifest("alt2-namespace")
  1438            + self.format(
  1439                """
  1440---
  1441apiVersion: getambassador.io/v3alpha1
  1442kind: Listener
  1443metadata:
  1444  name: ambassador-listener-8443    # This name is to match existing test stuff
  1445  namespace: alt2-namespace
  1446  labels:
  1447    kat-ambassador-id: {self.ambassador_id}
  1448spec:
  1449  ambassador_id: [ {self.ambassador_id} ]
  1450  port: 8443
  1451  protocol: HTTPS
  1452  securityModel: XFP
  1453  hostBinding:
  1454    namespace:
  1455      from: SELF
  1456---
  1457apiVersion: getambassador.io/v3alpha1
  1458kind: Host
  1459metadata:
  1460  name: {self.path.k8s}
  1461  namespace: alt2-namespace
  1462  labels:
  1463    kat-ambassador-id: {self.ambassador_id}
  1464spec:
  1465  ambassador_id: [ {self.ambassador_id} ]
  1466  hostname: ambassador.example.com
  1467  acmeProvider:
  1468    authority: none
  1469  tlsSecret:
  1470    name: {self.path.k8s}.server
  1471  tls:
  1472    # ca_secret supports cross-namespace references, so test it
  1473    ca_secret: {self.path.k8s}-ca
  1474    cert_required: true
  1475---
  1476apiVersion: v1
  1477kind: Secret
  1478metadata:
  1479  name: {self.path.k8s}-ca
  1480  namespace: alt2-namespace
  1481  labels:
  1482    kat-ambassador-id: {self.ambassador_id}
  1483type: kubernetes.io/tls
  1484data:
  1485  tls.crt: """
  1486                + TLSCerts["master.datawire.io"].k8s_crt
  1487                + """
  1488  tls.key: ""
  1489---
  1490apiVersion: v1
  1491kind: Secret
  1492metadata:
  1493  name: {self.path.k8s}.server
  1494  namespace: alt2-namespace
  1495  labels:
  1496    kat-ambassador-id: {self.ambassador_id}
  1497type: kubernetes.io/tls
  1498data:
  1499  tls.crt: """
  1500                + TLSCerts["ambassador.example.com"].k8s_crt
  1501                + """
  1502  tls.key: """
  1503                + TLSCerts["ambassador.example.com"].k8s_key
  1504                + """
  1505---
  1506apiVersion: getambassador.io/v3alpha1
  1507kind: Mapping
  1508metadata:
  1509  name: {self.path.k8s}
  1510  labels:
  1511    kat-ambassador-id: {self.ambassador_id}
  1512spec:
  1513  ambassador_id: [ {self.ambassador_id} ]
  1514  hostname: "*"
  1515  prefix: /
  1516  service: {self.target.path.fqdn}
  1517"""
  1518            )
  1519            + super().manifests()
  1520        )
  1521
  1522    def scheme(self) -> str:
  1523        return "https"
  1524
  1525    def queries(self):
  1526        base = {
  1527            "url": self.url(""),
  1528            "ca_cert": TLSCerts["master.datawire.io"].pubcert,
  1529            "headers": {"Host": "ambassador.example.com"},
  1530            "sni": True,  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  1531        }
  1532
  1533        yield Query(
  1534            **base,
  1535            client_crt=TLSCerts["presto.example.com"].pubcert,
  1536            client_key=TLSCerts["presto.example.com"].privkey
  1537        )
  1538
  1539        # Check that it requires the client cert.
  1540        #
  1541        # In TLS < 1.3, there's not a dedicated alert code for "the client forgot to include a certificate",
  1542        # so we get a generic alert=40 ("handshake_failure").
  1543        yield Query(**base, maxTLSv="v1.2", error="tls: handshake failure")
  1544        # TLS 1.3 added a dedicated alert=116 ("certificate_required") for that scenario.
  1545        yield Query(
  1546            **base,
  1547            minTLSv="v1.3",
  1548            error=(
  1549                ["tls: certificate required"]
  1550                + (
  1551                    ["write: connection reset by peer", "write: broken pipe"]
  1552                    if bug_clientcert_reset
  1553                    else []
  1554                )
  1555            )
  1556        )
  1557
  1558        # Check that it's validating the client cert against the CA cert.
  1559        yield Query(
  1560            **base,
  1561            client_crt=TLSCerts["localhost"].pubcert,
  1562            client_key=TLSCerts["localhost"].privkey,
  1563            maxTLSv="v1.2",
  1564            error="tls: handshake failure"
  1565        )
  1566
  1567    def requirements(self):
  1568        for r in super().requirements():
  1569            query = r[1]
  1570            query.headers = {"Host": "ambassador.example.com"}
  1571            query.sni = (
  1572                True  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  1573            )
  1574            query.ca_cert = TLSCerts["master.datawire.io"].pubcert
  1575            query.client_cert = TLSCerts["presto.example.com"].pubcert
  1576            query.client_key = TLSCerts["presto.example.com"].privkey
  1577            yield (r[0], query)
  1578
  1579
  1580class HostCRDClientCertCRLEmptyList(AmbassadorTest):
  1581    target: ServiceType
  1582
  1583    def init(self):
  1584        if Config.envoy_api_version == "V2":
  1585            self.skip_node = True
  1586        self.target = HTTP()
  1587        self.add_default_http_listener = False
  1588        self.add_default_https_listener = False
  1589
  1590    def manifests(self) -> str:
  1591        # Similar to HostCRDClientCertSameNamespace, except we also
  1592        # include a Certificate Revocation List in the TLS config
  1593        return (
  1594            namespace_manifest("alt3-namespace")
  1595            + self.format(
  1596                """
  1597---
  1598apiVersion: getambassador.io/v3alpha1
  1599kind: Listener
  1600metadata:
  1601  name: ambassador-listener-8443    # This name is to match existing test stuff
  1602  namespace: alt3-namespace
  1603  labels:
  1604    kat-ambassador-id: {self.ambassador_id}
  1605spec:
  1606  ambassador_id: [ {self.ambassador_id} ]
  1607  port: 8443
  1608  protocol: HTTPS
  1609  securityModel: XFP
  1610  hostBinding:
  1611    namespace:
  1612      from: SELF
  1613---
  1614apiVersion: getambassador.io/v3alpha1
  1615kind: Host
  1616metadata:
  1617  name: {self.path.k8s}
  1618  namespace: alt3-namespace
  1619  labels:
  1620    kat-ambassador-id: {self.ambassador_id}
  1621spec:
  1622  ambassador_id: [ {self.ambassador_id} ]
  1623  hostname: ambassador.example.com
  1624  acmeProvider:
  1625    authority: none
  1626  tlsSecret:
  1627    name: {self.path.k8s}.server
  1628  tls:
  1629    ca_secret: {self.path.k8s}-ca
  1630    cert_required: true
  1631    crl_secret: {self.path.k8s}-crl
  1632---
  1633apiVersion: v1
  1634kind: Secret
  1635metadata:
  1636  name: {self.path.k8s}-ca
  1637  namespace: alt3-namespace
  1638  labels:
  1639    kat-ambassador-id: {self.ambassador_id}
  1640type: kubernetes.io/tls
  1641data:
  1642  tls.crt: """
  1643                + TLSCerts["master.datawire.io"].k8s_crt
  1644                + """
  1645  tls.key: ""
  1646---
  1647apiVersion: v1
  1648kind: Secret
  1649metadata:
  1650  name: {self.path.k8s}-crl
  1651  namespace: alt3-namespace
  1652  labels:
  1653    kat-ambassador-id: {self.ambassador_id}
  1654type: Opaque
  1655data:
  1656  crl.pem: """
  1657                + create_crl_pem_b64(
  1658                    TLSCerts["master.datawire.io"].pubcert,
  1659                    TLSCerts["master.datawire.io"].privkey,
  1660                    [],
  1661                )
  1662                + """
  1663---
  1664apiVersion: v1
  1665kind: Secret
  1666metadata:
  1667  name: {self.path.k8s}.server
  1668  namespace: alt3-namespace
  1669  labels:
  1670    kat-ambassador-id: {self.ambassador_id}
  1671type: kubernetes.io/tls
  1672data:
  1673  tls.crt: """
  1674                + TLSCerts["ambassador.example.com"].k8s_crt
  1675                + """
  1676  tls.key: """
  1677                + TLSCerts["ambassador.example.com"].k8s_key
  1678                + """
  1679---
  1680apiVersion: getambassador.io/v3alpha1
  1681kind: Mapping
  1682metadata:
  1683  name: {self.path.k8s}
  1684  labels:
  1685    kat-ambassador-id: {self.ambassador_id}
  1686spec:
  1687  ambassador_id: [ {self.ambassador_id} ]
  1688  hostname: "*"
  1689  prefix: /
  1690  service: {self.target.path.fqdn}
  1691"""
  1692            )
  1693            + super().manifests()
  1694        )
  1695
  1696    def scheme(self) -> str:
  1697        return "https"
  1698
  1699    def queries(self):
  1700        base = {
  1701            "url": self.url(""),
  1702            "ca_cert": TLSCerts["master.datawire.io"].pubcert,
  1703            "headers": {"Host": "ambassador.example.com"},
  1704            "sni": True,  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  1705        }
  1706
  1707        yield Query(**base, error="tls: certificate required")
  1708
  1709        yield Query(
  1710            **base,
  1711            client_crt=TLSCerts["presto.example.com"].pubcert,
  1712            client_key=TLSCerts["presto.example.com"].privkey
  1713        )
  1714
  1715    def requirements(self):
  1716        yield ("pod", self.path.k8s)
  1717
  1718
  1719class HostCRDClientCertCRLRevokeList(AmbassadorTest):
  1720    target: ServiceType
  1721
  1722    def init(self):
  1723        if Config.envoy_api_version == "V2":
  1724            self.skip_node = True
  1725        self.target = HTTP()
  1726        self.add_default_http_listener = False
  1727        self.add_default_https_listener = False
  1728
  1729    def manifests(self) -> str:
  1730        # Similar to HostCRDClientCertSameNamespace, except we also
  1731        # include a Certificate Revocation List in the TLS config
  1732        return (
  1733            namespace_manifest("alt4-namespace")
  1734            + self.format(
  1735                """
  1736---
  1737apiVersion: getambassador.io/v3alpha1
  1738kind: Listener
  1739metadata:
  1740  name: ambassador-listener-8443    # This name is to match existing test stuff
  1741  namespace: alt4-namespace
  1742  labels:
  1743    kat-ambassador-id: {self.ambassador_id}
  1744spec:
  1745  ambassador_id: [ {self.ambassador_id} ]
  1746  port: 8443
  1747  protocol: HTTPS
  1748  securityModel: XFP
  1749  hostBinding:
  1750    namespace:
  1751      from: SELF
  1752---
  1753apiVersion: getambassador.io/v3alpha1
  1754kind: Host
  1755metadata:
  1756  name: {self.path.k8s}
  1757  namespace: alt4-namespace
  1758  labels:
  1759    kat-ambassador-id: {self.ambassador_id}
  1760spec:
  1761  ambassador_id: [ {self.ambassador_id} ]
  1762  hostname: ambassador.example.com
  1763  acmeProvider:
  1764    authority: none
  1765  tlsSecret:
  1766    name: {self.path.k8s}.server
  1767  tls:
  1768    ca_secret: {self.path.k8s}-ca
  1769    cert_required: true
  1770    crl_secret: {self.path.k8s}-crl
  1771---
  1772apiVersion: v1
  1773kind: Secret
  1774metadata:
  1775  name: {self.path.k8s}-ca
  1776  namespace: alt4-namespace
  1777  labels:
  1778    kat-ambassador-id: {self.ambassador_id}
  1779type: kubernetes.io/tls
  1780data:
  1781  tls.crt: """
  1782                + TLSCerts["master.datawire.io"].k8s_crt
  1783                + """
  1784  tls.key: ""
  1785---
  1786apiVersion: v1
  1787kind: Secret
  1788metadata:
  1789  name: {self.path.k8s}-crl
  1790  namespace: alt4-namespace
  1791  labels:
  1792    kat-ambassador-id: {self.ambassador_id}
  1793type: Opaque
  1794data:
  1795  crl.pem: """
  1796                + create_crl_pem_b64(
  1797                    TLSCerts["master.datawire.io"].pubcert,
  1798                    TLSCerts["master.datawire.io"].privkey,
  1799                    [TLSCerts["presto.example.com"].pubcert],
  1800                )
  1801                + """
  1802---
  1803apiVersion: v1
  1804kind: Secret
  1805metadata:
  1806  name: {self.path.k8s}.server
  1807  namespace: alt4-namespace
  1808  labels:
  1809    kat-ambassador-id: {self.ambassador_id}
  1810type: kubernetes.io/tls
  1811data:
  1812  tls.crt: """
  1813                + TLSCerts["ambassador.example.com"].k8s_crt
  1814                + """
  1815  tls.key: """
  1816                + TLSCerts["ambassador.example.com"].k8s_key
  1817                + """
  1818---
  1819apiVersion: getambassador.io/v3alpha1
  1820kind: Mapping
  1821metadata:
  1822  name: {self.path.k8s}
  1823  labels:
  1824    kat-ambassador-id: {self.ambassador_id}
  1825spec:
  1826  ambassador_id: [ {self.ambassador_id} ]
  1827  hostname: "*"
  1828  prefix: /
  1829  service: {self.target.path.fqdn}
  1830"""
  1831            )
  1832            + super().manifests()
  1833        )
  1834
  1835    def scheme(self) -> str:
  1836        return "https"
  1837
  1838    def queries(self):
  1839        base = {
  1840            "url": self.url(""),
  1841            "ca_cert": TLSCerts["master.datawire.io"].pubcert,
  1842            "headers": {"Host": "ambassador.example.com"},
  1843            "sni": True,  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  1844        }
  1845
  1846        yield Query(**base, error="tls: certificate required")
  1847
  1848        yield Query(
  1849            **base,
  1850            client_crt=TLSCerts["presto.example.com"].pubcert,
  1851            client_key=TLSCerts["presto.example.com"].privkey,
  1852            error="tls: revoked certificate"
  1853        )
  1854
  1855    def requirements(self):
  1856        yield ("pod", self.path.k8s)
  1857
  1858
  1859class HostCRDRootRedirectCongratulations(AmbassadorTest):
  1860    def manifests(self) -> str:
  1861        return (
  1862            self.format(
  1863                """
  1864---
  1865apiVersion: getambassador.io/v3alpha1
  1866kind: Host
  1867metadata:
  1868  name: {self.path.k8s}
  1869  labels:
  1870    kat-ambassador-id: {self.ambassador_id}
  1871spec:
  1872  ambassador_id: [ {self.ambassador_id} ]
  1873  hostname: tls-context-host-1
  1874  acmeProvider:
  1875    authority: none
  1876  mappingSelector:
  1877    matchLabels:
  1878      hostname: tls-context-host-1
  1879  tlsSecret:
  1880    name: {self.path.k8s}-test-tlscontext-secret-1
  1881  requestPolicy:
  1882    insecure:
  1883      action: Redirect
  1884---
  1885apiVersion: v1
  1886kind: Secret
  1887metadata:
  1888  name: {self.path.k8s}-test-tlscontext-secret-1
  1889  labels:
  1890    kat-ambassador-id: {self.ambassador_id}
  1891type: kubernetes.io/tls
  1892data:
  1893  tls.crt: """
  1894                + TLSCerts["tls-context-host-1"].k8s_crt
  1895                + """
  1896  tls.key: """
  1897                + TLSCerts["tls-context-host-1"].k8s_key
  1898                + """
  1899"""
  1900            )
  1901            + super().manifests()
  1902        )
  1903
  1904    def scheme(self) -> str:
  1905        return "https"
  1906
  1907    def queries(self):
  1908        yield Query(
  1909            self.url("", scheme="http"),
  1910            headers={"Host": "tls-context-host-1"},
  1911            expected=(404 if (EDGE_STACK or bug_404_routes) else 301),
  1912        )
  1913        yield Query(
  1914            self.url("other", scheme="http"),
  1915            headers={"Host": "tls-context-host-1"},
  1916            expected=(404 if bug_404_routes else 301),
  1917        )
  1918
  1919        yield Query(
  1920            self.url("", scheme="https"),
  1921            headers={"Host": "tls-context-host-1"},
  1922            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  1923            sni=True,
  1924            expected=404,
  1925        )
  1926        yield Query(
  1927            self.url("other", scheme="https"),
  1928            headers={"Host": "tls-context-host-1"},
  1929            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  1930            sni=True,
  1931            expected=404,
  1932        )
  1933
  1934    def requirements(self):
  1935        for r in super().requirements():
  1936            query = r[1]
  1937            query.headers = {"Host": "tls-context-host-1"}
  1938            query.sni = (
  1939                True  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  1940            )
  1941            query.ca_cert = TLSCerts["tls-context-host-1"].pubcert
  1942            yield (r[0], query)
  1943
  1944
  1945class HostCRDRootRedirectSlashMapping(AmbassadorTest):
  1946    target: ServiceType
  1947
  1948    def init(self):
  1949        self.target = HTTP()
  1950
  1951    def manifests(self) -> str:
  1952        return (
  1953            self.format(
  1954                """
  1955---
  1956apiVersion: getambassador.io/v3alpha1
  1957kind: Host
  1958metadata:
  1959  name: {self.path.k8s}
  1960  labels:
  1961    kat-ambassador-id: {self.ambassador_id}
  1962spec:
  1963  ambassador_id: [ {self.ambassador_id} ]
  1964  hostname: tls-context-host-1
  1965  acmeProvider:
  1966    authority: none
  1967  mappingSelector:
  1968    matchLabels:
  1969      hostname: {self.path.fqdn}
  1970  tlsSecret:
  1971    name: {self.path.k8s}-test-tlscontext-secret-1
  1972  requestPolicy:
  1973    insecure:
  1974      action: Redirect
  1975---
  1976apiVersion: v1
  1977kind: Secret
  1978metadata:
  1979  name: {self.path.k8s}-test-tlscontext-secret-1
  1980  labels:
  1981    kat-ambassador-id: {self.ambassador_id}
  1982type: kubernetes.io/tls
  1983data:
  1984  tls.crt: """
  1985                + TLSCerts["tls-context-host-1"].k8s_crt
  1986                + """
  1987  tls.key: """
  1988                + TLSCerts["tls-context-host-1"].k8s_key
  1989                + """
  1990---
  1991apiVersion: getambassador.io/v3alpha1
  1992kind: Mapping
  1993metadata:
  1994  name: {self.path.k8s}-target-mapping
  1995  labels:
  1996    hostname: {self.path.fqdn}
  1997spec:
  1998  ambassador_id: [ {self.ambassador_id} ]
  1999  prefix: /
  2000  service: {self.target.path.fqdn}
  2001"""
  2002            )
  2003            + super().manifests()
  2004        )
  2005
  2006    def scheme(self) -> str:
  2007        return "https"
  2008
  2009    def queries(self):
  2010        yield Query(
  2011            self.url("", scheme="http"), headers={"Host": "tls-context-host-1"}, expected=301
  2012        )
  2013        yield Query(
  2014            self.url("other", scheme="http"), headers={"Host": "tls-context-host-1"}, expected=301
  2015        )
  2016
  2017        yield Query(
  2018            self.url("", scheme="https"),
  2019            headers={"Host": "tls-context-host-1"},
  2020            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2021            sni=True,
  2022            expected=200,
  2023        )
  2024        yield Query(
  2025            self.url("other", scheme="https"),
  2026            headers={"Host": "tls-context-host-1"},
  2027            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2028            sni=True,
  2029            expected=200,
  2030        )
  2031
  2032    def requirements(self):
  2033        for r in super().requirements():
  2034            query = r[1]
  2035            query.headers = {"Host": "tls-context-host-1"}
  2036            query.sni = (
  2037                True  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  2038            )
  2039            query.ca_cert = TLSCerts["tls-context-host-1"].pubcert
  2040            yield (r[0], query)
  2041
  2042
  2043class HostCRDRootRedirectRE2Mapping(AmbassadorTest):
  2044    target: ServiceType
  2045
  2046    def init(self):
  2047        self.target = HTTP()
  2048
  2049    def manifests(self) -> str:
  2050        return (
  2051            self.format(
  2052                """
  2053---
  2054apiVersion: getambassador.io/v3alpha1
  2055kind: Host
  2056metadata:
  2057  name: {self.path.k8s}
  2058  labels:
  2059    kat-ambassador-id: {self.ambassador_id}
  2060spec:
  2061  ambassador_id: [ {self.ambassador_id} ]
  2062  hostname: tls-context-host-1
  2063  acmeProvider:
  2064    authority: none
  2065  mappingSelector:
  2066    matchLabels:
  2067      hostname: {self.path.fqdn}
  2068  tlsSecret:
  2069    name: {self.path.k8s}-test-tlscontext-secret-1
  2070  requestPolicy:
  2071    insecure:
  2072      action: Redirect
  2073---
  2074apiVersion: v1
  2075kind: Secret
  2076metadata:
  2077  name: {self.path.k8s}-test-tlscontext-secret-1
  2078  labels:
  2079    kat-ambassador-id: {self.ambassador_id}
  2080type: kubernetes.io/tls
  2081data:
  2082  tls.crt: """
  2083                + TLSCerts["tls-context-host-1"].k8s_crt
  2084                + """
  2085  tls.key: """
  2086                + TLSCerts["tls-context-host-1"].k8s_key
  2087                + """
  2088---
  2089apiVersion: getambassador.io/v3alpha1
  2090kind: Mapping
  2091metadata:
  2092  name: {self.path.k8s}-target-mapping
  2093  labels:
  2094    hostname: {self.path.fqdn}
  2095spec:
  2096  ambassador_id: [ {self.ambassador_id} ]
  2097  prefix: "/[[:word:]]*" # :word: is in RE2 but not ECMAScript RegExp or Python 're'
  2098  prefix_regex: true
  2099  service: {self.target.path.fqdn}
  2100"""
  2101            )
  2102            + super().manifests()
  2103        )
  2104
  2105    def scheme(self) -> str:
  2106        return "https"
  2107
  2108    def queries(self):
  2109        yield Query(
  2110            self.url("", scheme="http"), headers={"Host": "tls-context-host-1"}, expected=301
  2111        )
  2112        yield Query(
  2113            self.url("other", scheme="http"), headers={"Host": "tls-context-host-1"}, expected=301
  2114        )
  2115        yield Query(
  2116            self.url("-other", scheme="http"),
  2117            headers={"Host": "tls-context-host-1"},
  2118            expected=(404 if bug_404_routes else 301),
  2119        )
  2120
  2121        yield Query(
  2122            self.url("", scheme="https"),
  2123            headers={"Host": "tls-context-host-1"},
  2124            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2125            sni=True,
  2126            expected=200,
  2127        )
  2128        yield Query(
  2129            self.url("other", scheme="https"),
  2130            headers={"Host": "tls-context-host-1"},
  2131            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2132            sni=True,
  2133            expected=200,
  2134        )
  2135        yield Query(
  2136            self.url("-other", scheme="https"),
  2137            headers={"Host": "tls-context-host-1"},
  2138            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2139            sni=True,
  2140            expected=404,
  2141        )
  2142
  2143    def requirements(self):
  2144        for r in super().requirements():
  2145            query = r[1]
  2146            query.headers = {"Host": "tls-context-host-1"}
  2147            query.sni = (
  2148                True  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  2149            )
  2150            query.ca_cert = TLSCerts["tls-context-host-1"].pubcert
  2151            yield (r[0], query)
  2152
  2153
  2154class HostCRDRootRedirectECMARegexMapping(AmbassadorTest):
  2155    target: ServiceType
  2156
  2157    def init(self):
  2158        if Config.envoy_api_version == "V3":
  2159            self.skip_node = True
  2160        self.target = HTTP()
  2161
  2162    def config(self) -> Generator[Union[str, Tuple[Node, str]], None, None]:
  2163        yield self, self.format(
  2164            """
  2165---
  2166apiVersion: getambassador.io/v3alpha1
  2167kind: Module
  2168name: ambassador
  2169config:
  2170  regex_type: unsafe
  2171"""
  2172        )
  2173
  2174    def manifests(self) -> str:
  2175        return (
  2176            self.format(
  2177                """
  2178---
  2179apiVersion: getambassador.io/v3alpha1
  2180kind: Host
  2181metadata:
  2182  name: {self.path.k8s}
  2183  labels:
  2184    kat-ambassador-id: {self.ambassador_id}
  2185spec:
  2186  ambassador_id: [ {self.ambassador_id} ]
  2187  hostname: tls-context-host-1
  2188  acmeProvider:
  2189    authority: none
  2190  mappingSelector:
  2191    matchLabels:
  2192      hostname: {self.path.fqdn}
  2193  tlsSecret:
  2194    name: {self.path.k8s}-test-tlscontext-secret-1
  2195  requestPolicy:
  2196    insecure:
  2197      action: Redirect
  2198---
  2199apiVersion: v1
  2200kind: Secret
  2201metadata:
  2202  name: {self.path.k8s}-test-tlscontext-secret-1
  2203  labels:
  2204    kat-ambassador-id: {self.ambassador_id}
  2205type: kubernetes.io/tls
  2206data:
  2207  tls.crt: """
  2208                + TLSCerts["tls-context-host-1"].k8s_crt
  2209                + """
  2210  tls.key: """
  2211                + TLSCerts["tls-context-host-1"].k8s_key
  2212                + """
  2213---
  2214apiVersion: getambassador.io/v3alpha1
  2215kind: Mapping
  2216metadata:
  2217  name: {self.path.k8s}-target-mapping
  2218  labels:
  2219    hostname: {self.path.fqdn}
  2220spec:
  2221  ambassador_id: [ {self.ambassador_id} ]
  2222  prefix: "/(?!-).*" # (?!re) is valid ECMAScript RegExp but not RE2... unfortunately it's also valid Python 're'
  2223  prefix_regex: true
  2224  service: {self.target.path.fqdn}
  2225"""
  2226            )
  2227            + super().manifests()
  2228        )
  2229
  2230    def scheme(self) -> str:
  2231        return "https"
  2232
  2233    def queries(self):
  2234        yield Query(
  2235            self.url("", scheme="http"), headers={"Host": "tls-context-host-1"}, expected=301
  2236        )
  2237        yield Query(
  2238            self.url("other", scheme="http"), headers={"Host": "tls-context-host-1"}, expected=301
  2239        )
  2240        yield Query(
  2241            self.url("-other", scheme="http"),
  2242            headers={"Host": "tls-context-host-1"},
  2243            expected=(404 if bug_404_routes else 301),
  2244        )
  2245
  2246        yield Query(
  2247            self.url("", scheme="https"),
  2248            headers={"Host": "tls-context-host-1"},
  2249            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2250            sni=True,
  2251            expected=200,
  2252        )
  2253        yield Query(
  2254            self.url("other", scheme="https"),
  2255            headers={"Host": "tls-context-host-1"},
  2256            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2257            sni=True,
  2258            expected=200,
  2259        )
  2260        yield Query(
  2261            self.url("-other", scheme="https"),
  2262            headers={"Host": "tls-context-host-1"},
  2263            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2264            sni=True,
  2265            expected=404,
  2266        )
  2267
  2268    def requirements(self):
  2269        for r in super().requirements():
  2270            query = r[1]
  2271            query.headers = {"Host": "tls-context-host-1"}
  2272            query.sni = (
  2273                True  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  2274            )
  2275            query.ca_cert = TLSCerts["tls-context-host-1"].pubcert
  2276            yield (r[0], query)
  2277
  2278
  2279class HostCRDForcedStar(AmbassadorTest):
  2280    """This test verifies that Ambassador responds properly if we try
  2281    talking to it on a hostname that it doesn't recognize.
  2282    """
  2283
  2284    target: ServiceType
  2285
  2286    def init(self):
  2287        # We turn off edge_stack_cleartext_host, and manually
  2288        # duplicate the edge_stack_cleartext_host YAML in manifests(),
  2289        # since we want that even when testing a non-edge-stack build.
  2290        #
  2291        # The reason for that is that we're testing that it doesn't
  2292        # accidentally consider a cleartext hostname="*" to be a TLS
  2293        # hostname="*".
  2294        self.edge_stack_cleartext_host = False
  2295        self.target = HTTP()
  2296
  2297    def manifests(self) -> str:
  2298        return (
  2299            self.format(
  2300                """
  2301---
  2302apiVersion: getambassador.io/v3alpha1
  2303kind: Host
  2304metadata:
  2305  name: {self.path.k8s}-cleartext-host
  2306  labels:
  2307    kat-ambassador-id: {self.ambassador_id}
  2308spec:
  2309  ambassador_id: [ "{self.ambassador_id}" ]
  2310  hostname: "*"
  2311  acmeProvider:
  2312    authority: none
  2313  requestPolicy:
  2314    insecure:
  2315      action: Redirect
  2316---
  2317apiVersion: getambassador.io/v3alpha1
  2318kind: Host
  2319metadata:
  2320  name: {self.path.k8s}-tls-host
  2321  labels:
  2322    kat-ambassador-id: {self.ambassador_id}
  2323spec:
  2324  ambassador_id: [ {self.ambassador_id} ]
  2325  hostname: tls-context-host-1
  2326  acmeProvider:
  2327    authority: none
  2328  tlsSecret:
  2329    name: {self.path.k8s}-test-tlscontext-secret-1
  2330  requestPolicy:
  2331    insecure:
  2332      action: Route
  2333---
  2334apiVersion: v1
  2335kind: Secret
  2336metadata:
  2337  name: {self.path.k8s}-test-tlscontext-secret-1
  2338  labels:
  2339    kat-ambassador-id: {self.ambassador_id}
  2340type: kubernetes.io/tls
  2341data:
  2342  tls.crt: """
  2343                + TLSCerts["tls-context-host-1"].k8s_crt
  2344                + """
  2345  tls.key: """
  2346                + TLSCerts["tls-context-host-1"].k8s_key
  2347                + """
  2348---
  2349apiVersion: getambassador.io/v3alpha1
  2350kind: Mapping
  2351metadata:
  2352  name: {self.path.k8s}-target-mapping
  2353spec:
  2354  ambassador_id: [ {self.ambassador_id} ]
  2355  hostname: "*"
  2356  prefix: /foo/
  2357  service: {self.target.path.fqdn}
  2358"""
  2359            )
  2360            + super().manifests()
  2361        )
  2362
  2363    def scheme(self) -> str:
  2364        return "https"
  2365
  2366    def queries(self):
  2367        # For each of these, we'll first try it on a recognized hostname ("tls-context-host-1") as a
  2368        # sanity check, and then we'll try the same query for an unrecongized hostname
  2369        # ("nonmatching-host") to make sure that it is handled the same way.
  2370
  2371        # 0-1: cleartext 200/301
  2372        yield Query(
  2373            self.url("foo/0-200", scheme="http"),
  2374            headers={"Host": "tls-context-host-1"},
  2375            expected=200,
  2376        )
  2377        yield Query(
  2378            self.url("foo/1-301", scheme="http"),
  2379            headers={"Host": "nonmatching-hostname"},
  2380            expected=301,
  2381        )
  2382
  2383        # 2-3: cleartext 404
  2384        yield Query(
  2385            self.url("bar/2-404", scheme="http"),
  2386            headers={"Host": "tls-context-host-1"},
  2387            expected=404,
  2388        )
  2389        yield Query(
  2390            self.url("bar/3-301-or-404", scheme="http"),
  2391            headers={"Host": "nonmatching-hostname"},
  2392            expected=404 if bug_404_routes else 301,
  2393        )
  2394
  2395        # 4-5: TLS 200
  2396        yield Query(
  2397            self.url("foo/4-200", scheme="https"),
  2398            headers={"Host": "tls-context-host-1"},
  2399            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2400            sni=True,
  2401            expected=200,
  2402        )
  2403        yield Query(
  2404            self.url("foo/5-200", scheme="https"),
  2405            headers={"Host": "nonmatching-hostname"},
  2406            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2407            sni=True,
  2408            insecure=True,
  2409            expected=200,
  2410            error=("http: server gave HTTP response to HTTPS client" if bug_forced_star else None),
  2411        )
  2412
  2413        # 6-7: TLS 404
  2414        yield Query(
  2415            self.url("bar/6-404", scheme="https"),
  2416            headers={"Host": "tls-context-host-1"},
  2417            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2418            sni=True,
  2419            expected=404,
  2420        )
  2421        yield Query(
  2422            self.url("bar/7-404", scheme="https"),
  2423            headers={"Host": "nonmatching-hostname"},
  2424            ca_cert=TLSCerts["tls-context-host-1"].pubcert,
  2425            sni=True,
  2426            insecure=True,
  2427            expected=404,
  2428            error=("http: server gave HTTP response to HTTPS client" if bug_forced_star else None),
  2429        )
  2430
  2431    def requirements(self):
  2432        for r in super().requirements():
  2433            query = r[1]
  2434            query.headers = {"Host": "tls-context-host-1"}
  2435            query.sni = (
  2436                True  # Use query.headers["Host"] instead of urlparse(query.url).hostname for SNI
  2437            )
  2438            query.ca_cert = TLSCerts["tls-context-host-1"].pubcert
  2439            yield (r[0], query)
  2440
  2441
  2442class HostCRDCrossNamespaceNoNamespacing(AmbassadorTest):
  2443    """
  2444    HostCRDCrossNamespaceNoNamespacing tests that the value of tls_secret_namespacing does not interfere
  2445    with the function of being able to specify atlsSecret in another namespace due to the way that the
  2446    implicit TLSContext handles secret names.
  2447    """
  2448
  2449    target: ServiceType
  2450
  2451    def init(self):
  2452        self.edge_stack_cleartext_host = False
  2453        self.target1 = HTTP(name="target")
  2454
  2455    def manifests(self) -> str:
  2456        return (
  2457            namespace_manifest("foobar")
  2458            + self.format(
  2459                """
  2460---
  2461apiVersion: getambassador.io/v3alpha1
  2462kind: Host
  2463metadata:
  2464  name: {self.path.k8s}-host-1
  2465  labels:
  2466    kat-ambassador-id: {self.ambassador_id}
  2467spec:
  2468  ambassador_id: [ {self.ambassador_id} ]
  2469  hostname: tls-context-host-1
  2470  acmeProvider:
  2471    authority: none
  2472  mappingSelector:
  2473    matchLabels:
  2474      hostname: tls-context-host-1
  2475  tlsSecret:
  2476    name: {self.path.k8s}.test.tlscontext.secret
  2477    namespace: foobar
  2478---
  2479apiVersion: v1
  2480kind: Secret
  2481metadata:
  2482  name: {self.path.k8s}.test.tlscontext.secret
  2483  namespace: foobar
  2484  labels:
  2485    kat-ambassador-id: {self.ambassador_id}
  2486type: kubernetes.io/tls
  2487data:
  2488  tls.crt: """
  2489                + TLSCerts["tls-context-host-1"].k8s_crt
  2490                + """
  2491  tls.key: """
  2492                + TLSCerts["tls-context-host-1"].k8s_key
  2493                + """
  2494---
  2495apiVersion: getambassador.io/v3alpha1
  2496kind: Mapping
  2497metadata:
  2498  name: {self.path.k8s}-host-1-mapping
  2499  labels:
  2500    hostname: tls-context-host-1
  2501    kat-ambassador-id: {self.ambassador_id}
  2502spec:
  2503  ambassador_id: [ {self.ambassador_id} ]
  2504  hostname: tls-context-host-1
  2505  prefix: /target/
  2506  service: {self.target1.path.fqdn}
  2507"""
  2508            )
  2509            + super().manifests()
  2510        )
  2511
  2512    def scheme(self) -> str:
  2513        return "https"
  2514
  2515    def queries(self):
  2516        # Get some info from diagd for self.check() to inspect
  2517        yield Query(
  2518            self.url("ambassador/v0/diag/?json=true&filter=errors"),
  2519            headers={"Host": "tls-context-host-1"},
  2520            insecure=True,
  2521            sni=True,
  2522        )
  2523
  2524        yield Query(
  2525            self.url("target/", scheme="https"),
  2526            headers={"Host": "tls-context-host-1"},
  2527            sni=True,
  2528            insecure=True,
  2529            expected=200,
  2530        )
  2531        yield Query(
  2532            self.url("target/", scheme="http"),
  2533            headers={"Host": "tls-context-host-1"},
  2534            expected=301,
  2535        )
  2536
  2537    def check(self):
  2538        # XXX If self.results[0].json is empty, the harness won't convert it to a response.
  2539        errors = self.results[0].json or []
  2540        num_errors = len(errors)
  2541        assert num_errors == 0, "expected 0 errors, got {} -\n{}".format(num_errors, errors)
  2542
  2543        idx = 0
  2544
  2545        for result in self.results:
  2546            if result.status == 200 and result.query.headers and result.tls:
  2547                host_header = result.query.headers["Host"]
  2548                tls_common_name = result.tls[0]["Subject"]["CommonName"]
  2549
  2550                assert host_header == tls_common_name, "test %d wanted CN %s, but got %s" % (
  2551                    idx,
  2552                    host_header,
  2553                    tls_common_name,
  2554                )
  2555
  2556            idx += 1
  2557
  2558    def requirements(self):
  2559        # We're replacing super()'s requirements deliberately here. Without a Host header they can't work.
  2560        yield (
  2561            "url",
  2562            Query(
  2563                self.url("ambassador/v0/check_ready"),
  2564                headers={"Host": "tls-context-host-1"},
  2565                insecure=True,
  2566                sni=True,
  2567            ),
  2568        )
  2569        yield (
  2570            "url",
  2571            Query(
  2572                self.url("ambassador/v0/check_alive"),
  2573                headers={"Host": "tls-context-host-1"},
  2574                insecure=True,
  2575                sni=True,
  2576            ),
  2577        )
  2578
  2579
  2580class HostCRDDoubleCrossNamespace(AmbassadorTest):
  2581    """
  2582    HostCRDDouble: We have two Hosts, each with a
  2583    manually-configured TLS secret, the secrets have
  2584    the same name but are in different namespaces.
  2585    """
  2586
  2587    target1: ServiceType
  2588    target2: ServiceType
  2589
  2590    def init(self):
  2591        self.edge_stack_cleartext_host = False
  2592        self.target1 = HTTP(name="target1")
  2593        self.target2 = HTTP(name="target2")
  2594
  2595    def manifests(self) -> str:
  2596        return (
  2597            namespace_manifest("bar")
  2598            + namespace_manifest("foo")
  2599            + self.format(
  2600                """
  2601---
  2602apiVersion: getambassador.io/v3alpha1
  2603kind: Host
  2604metadata:
  2605  name: {self.path.k8s}-host-1
  2606  labels:
  2607    kat-ambassador-id: {self.ambassador_id}
  2608spec:
  2609  ambassador_id: [ {self.ambassador_id} ]
  2610  hostname: tls-context-host-1
  2611  acmeProvider:
  2612    authority: none
  2613  mappingSelector:
  2614    matchLabels:
  2615      hostname: tls-context-host-1
  2616  tlsSecret:
  2617    name: {self.path.k8s}-test-tlscontext-secret
  2618    namespace: foo
  2619---
  2620apiVersion: v1
  2621kind: Secret
  2622metadata:
  2623  name: {self.path.k8s}-test-tlscontext-secret
  2624  namespace: foo
  2625  labels:
  2626    kat-ambassador-id: {self.ambassador_id}
  2627type: kubernetes.io/tls
  2628data:
  2629  tls.crt: """
  2630                + TLSCerts["tls-context-host-1"].k8s_crt
  2631                + """
  2632  tls.key: """
  2633                + TLSCerts["tls-context-host-1"].k8s_key
  2634                + """
  2635---
  2636apiVersion: getambassador.io/v3alpha1
  2637kind: Mapping
  2638metadata:
  2639  name: {self.path.k8s}-host-1-mapping
  2640  labels:
  2641    hostname: tls-context-host-1
  2642    kat-ambassador-id: {self.ambassador_id}
  2643spec:
  2644  ambassador_id: [ {self.ambassador_id} ]
  2645  hostname: tls-context-host-1
  2646  prefix: /target-1/
  2647  service: {self.target1.path.fqdn}
  2648
  2649---
  2650apiVersion: getambassador.io/v3alpha1
  2651kind: Host
  2652metadata:
  2653  name: {self.path.k8s}-host-2
  2654  labels:
  2655    kat-ambassador-id: {self.ambassador_id}
  2656spec:
  2657  ambassador_id: [ {self.ambassador_id} ]
  2658  hostname: tls-context-host-2
  2659  acmeProvider:
  2660    authority: none
  2661  tlsSecret:
  2662    name: {self.path.k8s}-test-tlscontext-secret
  2663    namespace: bar
  2664---
  2665apiVersion: v1
  2666kind: Secret
  2667metadata:
  2668  name: {self.path.k8s}-test-tlscontext-secret
  2669  namespace: bar
  2670  labels:
  2671    kat-ambassador-id: {self.ambassador_id}
  2672type: kubernetes.io/tls
  2673data:
  2674  tls.crt: """
  2675                + TLSCerts["tls-context-host-2"].k8s_crt
  2676                + """
  2677  tls.key: """
  2678                + TLSCerts["tls-context-host-2"].k8s_key
  2679                + """
  2680---
  2681apiVersion: getambassador.io/v3alpha1
  2682kind: Mapping
  2683metadata:
  2684  name: {self.path.k8s}-host-2-mapping
  2685  labels:
  2686    hostname: tls-context-host-2
  2687    kat-ambassador-id: {self.ambassador_id}
  2688spec:
  2689  ambassador_id: [ {self.ambassador_id} ]
  2690  hostname: tls-context-host-2
  2691  prefix: /target-2/
  2692  service: {self.target2.path.fqdn}
  2693"""
  2694            )
  2695            + super().manifests()
  2696        )
  2697
  2698    def scheme(self) -> str:
  2699        return "https"
  2700
  2701    def queries(self):
  2702        # Get some info from diagd for self.check() to inspect
  2703        yield Query(
  2704            self.url("ambassador/v0/diag/?json=true&filter=errors"),
  2705            headers={"Host": "tls-context-host-1"},
  2706            insecure=True,
  2707            sni=True,
  2708        )
  2709
  2710        # Host #1 - TLS
  2711        yield Query(
  2712            self.url("target-1/", scheme="https"),
  2713            headers={"Host": "tls-context-host-1"},
  2714            sni=True,
  2715            insecure=True,
  2716            expected=200,
  2717        )
  2718        yield Query(
  2719            self.url("target-2/", scheme="https"),
  2720            headers={"Host": "tls-context-host-1"},
  2721            sni=True,
  2722            insecure=True,
  2723            expected=404,
  2724        )
  2725        yield Query(
  2726            self.url("target-1/", scheme="http"),
  2727            headers={"Host": "tls-context-host-1"},
  2728            expected=301,
  2729        )
  2730
  2731        # Host #2 - TLS
  2732        yield Query(
  2733            self.url("target-1/", scheme="https"),
  2734            headers={"Host": "tls-context-host-2"},
  2735            sni=True,
  2736            insecure=True,
  2737            expected=404,
  2738        )
  2739        yield Query(
  2740            self.url("target-2/", scheme="https"),
  2741            headers={"Host": "tls-context-host-2"},
  2742            sni=True,
  2743            insecure=True,
  2744            expected=200,
  2745        )
  2746        yield Query(
  2747            self.url("target-2/", scheme="http"),
  2748            headers={"Host": "tls-context-host-2"},
  2749            expected=301,
  2750        )
  2751
  2752    def check(self):
  2753        # XXX If self.results[0].json is empty, the harness won't convert it to a response.
  2754        errors = self.results[0].json or []
  2755        num_errors = len(errors)
  2756        assert num_errors == 0, "expected 0 errors, got {} -\n{}".format(num_errors, errors)
  2757
  2758        idx = 0
  2759
  2760        for result in self.results:
  2761            if result.status == 200 and result.query.headers and result.tls:
  2762                host_header = result.query.headers["Host"]
  2763                tls_common_name = result.tls[0]["Subject"]["CommonName"]
  2764
  2765                assert host_header == tls_common_name, "test %d wanted CN %s, but got %s" % (
  2766                    idx,
  2767                    host_header,
  2768                    tls_common_name,
  2769                )
  2770
  2771            idx += 1
  2772
  2773    def requirements(self):
  2774        # We're replacing super()'s requirements deliberately here. Without a Host header they can't work.
  2775        yield (
  2776            "url",
  2777            Query(
  2778                self.url("ambassador/v0/check_ready"),
  2779                headers={"Host": "tls-context-host-1"},
  2780                insecure=True,
  2781                sni=True,
  2782            ),
  2783        )
  2784        yield (
  2785            "url",
  2786            Query(
  2787                self.url("ambassador/v0/check_alive"),
  2788                headers={"Host": "tls-context-host-1"},
  2789                insecure=True,
  2790                sni=True,
  2791            ),
  2792        )
  2793        yield (
  2794            "url",
  2795            Query(
  2796                self.url("ambassador/v0/check_ready"),
  2797                headers={"Host": "tls-context-host-2"},
  2798                insecure=True,
  2799                sni=True,
  2800            ),
  2801        )
  2802        yield (
  2803            "url",
  2804            Query(
  2805                self.url("ambassador/v0/check_alive"),
  2806                headers={"Host": "tls-context-host-2"},
  2807                insecure=True,
  2808                sni=True,
  2809            ),
  2810        )

View as plain text