Repository: emissary-ingress/emissary

Issue:
Support redirecting cleartext traffic to HTTPS
In the `server` configuration for TLS, you can now specify `redirect_cleartext_from` with a port number (usually 80) to cause cleartext traffic to that port to be redirected to HTTPS on port 443. A common setup would be

```
---
apiVersion: ambassador/v0
kind:  Module
name:  tls
config:
  server:
    enabled: True
    redirect_cleartext_from: 80
```

The default is no redirection of cleartext.



Discussion:

Resolving patch:
diff --git a/ambassador/ambassador/config.py b/ambassador/ambassador/config.py
--- a/ambassador/ambassador/config.py
+++ b/ambassador/ambassador/config.py
@@ -206,6 +206,7 @@ def __init__(self, config_dir_path, k8s=False, schema_dir_path=None, template_di
 
         self.sources = {
             "--internal--": {
+                "_source": "--internal--",
                 "kind": "Internal",
                 "version": "v0",
                 "name": "Ambassador Internals",
@@ -214,6 +215,7 @@ def __init__(self, config_dir_path, k8s=False, schema_dir_path=None, template_di
                 "description": "The '--internal--' source marks objects created by Ambassador's internal logic."
             },
             "--diagnostics--": {
+                "_source": "--diagnostics--",
                 "kind": "diagnostics",
                 "version": "v0",
                 "name": "Ambassador Diagnostics",
@@ -786,23 +788,46 @@ def generate_intermediate_config(self):
 
             handler(module_name, modules[module_name])
 
-        # # Once modules are handled, we can set up our listeners...
-        self.envoy_config['listeners'] = SourcedDict(
+        # Once modules are handled, we can set up our admin config...
+        self.envoy_config['admin'] = SourcedDict(
             _from=self.ambassador_module,
-            service_port=self.ambassador_module["service_port"],
             admin_port=self.ambassador_module["admin_port"]
         )
 
-        self.default_liveness_probe['service'] = self.diag_service()
-        self.default_readiness_probe['service'] = self.diag_service()
-        self.default_diagnostics['service'] = self.diag_service()
+        # ...and our listeners.
+        primary_listener = SourcedDict(
+            _from=self.ambassador_module,
+            service_port=self.ambassador_module["service_port"],
+            require_tls=False
+        )
 
+        redirect_cleartext_from = None
+        tmod = self.ambassador_module.get('tls_config', None)
+           
         # ...TLS config, if necessary...
-        if self.ambassador_module['tls_config']:
+        if tmod:
             # self.logger.debug("USING TLS")
-            self.envoy_config['tls'] = self.ambassador_module['tls_config']
+            primary_listener['tls'] = tmod
+            redirect_cleartext_from = tmod.get('redirect_cleartext_from')
+
+        self.envoy_config['listeners'] = [ primary_listener ]
+
+        if redirect_cleartext_from:
+            primary_listener['require_tls'] = True
+
+            self.envoy_config['listeners'].append(SourcedDict(
+                _from=self.ambassador_module,
+                service_port=redirect_cleartext_from,
+                require_tls=True
+                # Note: no TLS context here, this is a cleartext listener.
+                # We can set require_tls True because we can let the upstream
+                # tell us about that.
+            ))
+
+        self.default_liveness_probe['service'] = self.diag_service()
+        self.default_readiness_probe['service'] = self.diag_service()
+        self.default_diagnostics['service'] = self.diag_service()
 
-        # ...and probes, if configured.
         for name, cur, dflt in [ 
             ("liveness",    self.ambassador_module['liveness_probe'],
                             self.default_liveness_probe),
@@ -1302,9 +1327,9 @@ def diagnostic_overview(self):
             # self.logger.debug("overview -- filename %s, source_keys %d" %
             #                   (filename, len(source_keys)))
 
-            # Skip '--internal--' etc.
-            if filename.startswith('--'):
-                continue
+            # # Skip '--internal--' etc.
+            # if filename.startswith('--'):
+            #     continue
 
             source_dict = source_files.setdefault(
                 filename,


Test patch:
diff --git a/ambassador/tests/000-default/gold.intermediate.json b/ambassador/tests/000-default/gold.intermediate.json
--- a/ambassador/tests/000-default/gold.intermediate.json
+++ b/ambassador/tests/000-default/gold.intermediate.json
@@ -1,8 +1,15 @@
 {
     "envoy_config": {
+        "admin": [
+            {
+                "_source": "--internal--",
+                "admin_port": 8001
+            }
+        ],
         "clusters": [
             {
                 "_referenced_by": [
+                    "--internal--",
                     "mapping-diag.yaml.1"
                 ],
                 "_service": "127.0.0.1:8877",
@@ -57,6 +64,11 @@
             }
         ],
         "filters": [
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "cors"
+            },
             {
                 "_source": "auth.yaml.1",
                 "config": {
@@ -71,12 +83,18 @@
                 },
                 "name": "extauth",
                 "type": "decoder"
+            },
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "router",
+                "type": "decoder"
             }
         ],
         "listeners": [
             {
                 "_source": "--internal--",
-                "admin_port": 8001,
+                "require_tls": false,
                 "service_port": 80
             }
         ],
@@ -98,6 +116,22 @@
                 "prefix": "/httpbin/",
                 "prefix_rewrite": "/"
             },
+            {
+                "_group_id": "0a42187b7b3d283e0079ddab01825e1c71fad9f6",
+                "_method": "GET",
+                "_referenced_by": [
+                    "--internal--"
+                ],
+                "_source": "--internal--",
+                "clusters": [
+                    {
+                        "name": "cluster_127_0_0_1_8877",
+                        "weight": 100.0
+                    }
+                ],
+                "prefix": "/ambassador/v0/check_ready",
+                "prefix_rewrite": "/ambassador/v0/check_ready"
+            },
             {
                 "_group_id": "3e935e6af2ad12df1441fee5d2cf17a69303b2ad",
                 "_method": "GET",
@@ -121,6 +155,22 @@
                 "prefix": "/cqrs/",
                 "prefix_rewrite": "/qotm/quote/"
             },
+            {
+                "_group_id": "6151893fbc2f1f730a1bb946b2eefac391f0d2ec",
+                "_method": "GET",
+                "_referenced_by": [
+                    "--internal--"
+                ],
+                "_source": "--internal--",
+                "clusters": [
+                    {
+                        "name": "cluster_127_0_0_1_8877",
+                        "weight": 100.0
+                    }
+                ],
+                "prefix": "/ambassador/v0/check_alive",
+                "prefix_rewrite": "/ambassador/v0/check_alive"
+            },
             {
                 "_group_id": "688e9f91e06f33c943c0c6373a5bdd32e647f7c4",
                 "_method": "GET",
@@ -153,6 +203,22 @@
                 "prefix": "/qotm/",
                 "prefix_rewrite": "/qotm/"
             },
+            {
+                "_group_id": "b69eeb747b38f5e5fd3955d044ea7797d67d024f",
+                "_method": "GET",
+                "_referenced_by": [
+                    "--internal--"
+                ],
+                "_source": "--internal--",
+                "clusters": [
+                    {
+                        "name": "cluster_127_0_0_1_8877",
+                        "weight": 100.0
+                    }
+                ],
+                "prefix": "/ambassador/v0/",
+                "prefix_rewrite": "/ambassador/v0/"
+            },
             {
                 "_group_id": "c78d3e3dc6c4a44ace22414fd99b17305dd29a37",
                 "_method": "PUT",
@@ -201,6 +267,7 @@
     },
     "sources": {
         "--diagnostics--": {
+            "_source": "--diagnostics--",
             "description": "The '--diagnostics--' source marks objects created by Ambassador to assist with diagnostic output.",
             "filename": "--diagnostics--",
             "index": 0,
@@ -209,6 +276,7 @@
             "version": "v0"
         },
         "--internal--": {
+            "_source": "--internal--",
             "description": "The '--internal--' source marks objects created by Ambassador's internal logic.",
             "filename": "--internal--",
             "index": 0,
diff --git a/ambassador/tests/001-broader-v0/gold.intermediate.json b/ambassador/tests/001-broader-v0/gold.intermediate.json
--- a/ambassador/tests/001-broader-v0/gold.intermediate.json
+++ b/ambassador/tests/001-broader-v0/gold.intermediate.json
@@ -1,5 +1,11 @@
 {
     "envoy_config": {
+        "admin": [
+            {
+                "_source": "ambassador.yaml.1",
+                "admin_port": 8001
+            }
+        ],
         "breakers": [
             {
                 "_referenced_by": [
@@ -205,6 +211,11 @@
             }
         ],
         "filters": [
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "cors"
+            },
             {
                 "_source": "auth.yaml.1",
                 "config": {
@@ -217,13 +228,26 @@
                 },
                 "name": "extauth",
                 "type": "decoder"
+            },
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "router",
+                "type": "decoder"
             }
         ],
         "listeners": [
             {
                 "_source": "ambassador.yaml.1",
-                "admin_port": 8001,
-                "service_port": 443
+                "require_tls": false,
+                "service_port": 443,
+                "tls": {
+                    "_source": "ambassador.yaml.1",
+                    "cacert_chain_file": "/etc/ambassador-config/ca_cert_chain/tls.crt",
+                    "cert_chain_file": "/etc/ambassador-config/certs/tls.crt",
+                    "cert_required": true,
+                    "private_key_file": "/etc/ambassador-config/certs/tls.key"
+                }
             }
         ],
         "outliers": [
@@ -510,15 +534,6 @@
                 "prefix": "/evil/",
                 "prefix_rewrite": "/"
             }
-        ],
-        "tls": [
-            {
-                "_source": "ambassador.yaml.1",
-                "cacert_chain_file": "/etc/ambassador-config/ca_cert_chain/tls.crt",
-                "cert_chain_file": "/etc/ambassador-config/certs/tls.crt",
-                "cert_required": true,
-                "private_key_file": "/etc/ambassador-config/certs/tls.key"
-            }
         ]
     },
     "source_map": {
@@ -568,6 +583,7 @@
     },
     "sources": {
         "--diagnostics--": {
+            "_source": "--diagnostics--",
             "description": "The '--diagnostics--' source marks objects created by Ambassador to assist with diagnostic output.",
             "filename": "--diagnostics--",
             "index": 0,
@@ -576,6 +592,7 @@
             "version": "v0"
         },
         "--internal--": {
+            "_source": "--internal--",
             "description": "The '--internal--' source marks objects created by Ambassador's internal logic.",
             "filename": "--internal--",
             "index": 0,
diff --git a/ambassador/tests/002-tls-module-1/gold.intermediate.json b/ambassador/tests/002-tls-module-1/gold.intermediate.json
--- a/ambassador/tests/002-tls-module-1/gold.intermediate.json
+++ b/ambassador/tests/002-tls-module-1/gold.intermediate.json
@@ -1,5 +1,11 @@
 {
     "envoy_config": {
+        "admin": [
+            {
+                "_source": "ambassador.yaml.1",
+                "admin_port": 4242
+            }
+        ],
         "clusters": [
             {
                 "_referenced_by": [
@@ -28,12 +34,31 @@
                 ]
             }
         ],
-        "filters": [],
+        "filters": [
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "cors"
+            },
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "router",
+                "type": "decoder"
+            }
+        ],
         "listeners": [
             {
                 "_source": "ambassador.yaml.1",
-                "admin_port": 4242,
-                "service_port": 443
+                "require_tls": false,
+                "service_port": 443,
+                "tls": {
+                    "_source": "tls.yaml.1",
+                    "cacert_chain_file": "/etc/ambassador-config/ca_cert_chain/tls.crt",
+                    "cert_chain_file": "/etc/ambassador-config/certs/tls.crt",
+                    "cert_required": true,
+                    "private_key_file": "/etc/ambassador-config/certs/tls.key"
+                }
             }
         ],
         "routes": [
@@ -85,15 +110,6 @@
                 "prefix": "/ambassador/v0/",
                 "prefix_rewrite": "/ambassador/v0/"
             }
-        ],
-        "tls": [
-            {
-                "_source": "tls.yaml.1",
-                "cacert_chain_file": "/etc/ambassador-config/ca_cert_chain/tls.crt",
-                "cert_chain_file": "/etc/ambassador-config/certs/tls.crt",
-                "cert_required": true,
-                "private_key_file": "/etc/ambassador-config/certs/tls.key"
-            }
         ]
     },
     "source_map": {
@@ -112,6 +128,7 @@
     },
     "sources": {
         "--diagnostics--": {
+            "_source": "--diagnostics--",
             "description": "The '--diagnostics--' source marks objects created by Ambassador to assist with diagnostic output.",
             "filename": "--diagnostics--",
             "index": 0,
@@ -120,6 +137,7 @@
             "version": "v0"
         },
         "--internal--": {
+            "_source": "--internal--",
             "description": "The '--internal--' source marks objects created by Ambassador's internal logic.",
             "filename": "--internal--",
             "index": 0,
diff --git a/ambassador/tests/003-tls-module-2/gold.intermediate.json b/ambassador/tests/003-tls-module-2/gold.intermediate.json
--- a/ambassador/tests/003-tls-module-2/gold.intermediate.json
+++ b/ambassador/tests/003-tls-module-2/gold.intermediate.json
@@ -1,5 +1,11 @@
 {
     "envoy_config": {
+        "admin": [
+            {
+                "_source": "ambassador.yaml.1",
+                "admin_port": 4242
+            }
+        ],
         "clusters": [
             {
                 "_referenced_by": [
@@ -28,12 +34,31 @@
                 ]
             }
         ],
-        "filters": [],
+        "filters": [
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "cors"
+            },
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "router",
+                "type": "decoder"
+            }
+        ],
         "listeners": [
             {
                 "_source": "ambassador.yaml.1",
-                "admin_port": 4242,
-                "service_port": 443
+                "require_tls": false,
+                "service_port": 443,
+                "tls": {
+                    "_source": "ambassador.yaml.1",
+                    "cacert_chain_file": "/etc/ambassador-config/ca_cert_chain/tls.crt",
+                    "cert_chain_file": "/etc/ambassador-config/certs/tls.crt",
+                    "cert_required": true,
+                    "private_key_file": "/etc/ambassador-config/certs/tls.key"
+                }
             }
         ],
         "routes": [
@@ -85,15 +110,6 @@
                 "prefix": "/ambassador/v0/",
                 "prefix_rewrite": "/ambassador/v0/"
             }
-        ],
-        "tls": [
-            {
-                "_source": "ambassador.yaml.1",
-                "cacert_chain_file": "/etc/ambassador-config/ca_cert_chain/tls.crt",
-                "cert_chain_file": "/etc/ambassador-config/certs/tls.crt",
-                "cert_required": true,
-                "private_key_file": "/etc/ambassador-config/certs/tls.key"
-            }
         ]
     },
     "source_map": {
@@ -112,6 +128,7 @@
     },
     "sources": {
         "--diagnostics--": {
+            "_source": "--diagnostics--",
             "description": "The '--diagnostics--' source marks objects created by Ambassador to assist with diagnostic output.",
             "filename": "--diagnostics--",
             "index": 0,
@@ -120,6 +137,7 @@
             "version": "v0"
         },
         "--internal--": {
+            "_source": "--internal--",
             "description": "The '--internal--' source marks objects created by Ambassador's internal logic.",
             "filename": "--internal--",
             "index": 0,
diff --git a/ambassador/tests/004-tls-without-ambassador-module/gold.intermediate.json b/ambassador/tests/004-tls-without-ambassador-module/gold.intermediate.json
--- a/ambassador/tests/004-tls-without-ambassador-module/gold.intermediate.json
+++ b/ambassador/tests/004-tls-without-ambassador-module/gold.intermediate.json
@@ -1,5 +1,11 @@
 {
     "envoy_config": {
+        "admin": [
+            {
+                "_source": "tls.yaml.1",
+                "admin_port": 8001
+            }
+        ],
         "clusters": [
             {
                 "_referenced_by": [
@@ -28,12 +34,31 @@
                 ]
             }
         ],
-        "filters": [],
+        "filters": [
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "cors"
+            },
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "router",
+                "type": "decoder"
+            }
+        ],
         "listeners": [
             {
                 "_source": "tls.yaml.1",
-                "admin_port": 8001,
-                "service_port": 443
+                "require_tls": false,
+                "service_port": 443,
+                "tls": {
+                    "_source": "tls.yaml.1",
+                    "cacert_chain_file": "/etc/ambassador-config/ca_cert_chain/tls.crt",
+                    "cert_chain_file": "/etc/ambassador-config/certs/tls.crt",
+                    "cert_required": true,
+                    "private_key_file": "/etc/ambassador-config/certs/tls.key"
+                }
             }
         ],
         "routes": [
@@ -101,15 +126,6 @@
                 "prefix": "/ambassador/v0/",
                 "prefix_rewrite": "/ambassador/v0/"
             }
-        ],
-        "tls": [
-            {
-                "_source": "tls.yaml.1",
-                "cacert_chain_file": "/etc/ambassador-config/ca_cert_chain/tls.crt",
-                "cert_chain_file": "/etc/ambassador-config/certs/tls.crt",
-                "cert_required": true,
-                "private_key_file": "/etc/ambassador-config/certs/tls.key"
-            }
         ]
     },
     "source_map": {
@@ -125,6 +141,7 @@
     },
     "sources": {
         "--diagnostics--": {
+            "_source": "--diagnostics--",
             "description": "The '--diagnostics--' source marks objects created by Ambassador to assist with diagnostic output.",
             "filename": "--diagnostics--",
             "index": 0,
@@ -133,6 +150,7 @@
             "version": "v0"
         },
         "--internal--": {
+            "_source": "--internal--",
             "description": "The '--internal--' source marks objects created by Ambassador's internal logic.",
             "filename": "--internal--",
             "index": 0,
diff --git a/ambassador/tests/005-canary/gold.intermediate.json b/ambassador/tests/005-canary/gold.intermediate.json
--- a/ambassador/tests/005-canary/gold.intermediate.json
+++ b/ambassador/tests/005-canary/gold.intermediate.json
@@ -1,8 +1,15 @@
 {
     "envoy_config": {
+        "admin": [
+            {
+                "_source": "--internal--",
+                "admin_port": 8001
+            }
+        ],
         "clusters": [
             {
                 "_referenced_by": [
+                    "--internal--",
                     "ambassador.yaml.1"
                 ],
                 "_service": "127.0.0.1:8877",
@@ -41,15 +48,43 @@
                 ]
             }
         ],
-        "filters": [],
+        "filters": [
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "cors"
+            },
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "router",
+                "type": "decoder"
+            }
+        ],
         "listeners": [
             {
                 "_source": "--internal--",
-                "admin_port": 8001,
+                "require_tls": false,
                 "service_port": 80
             }
         ],
         "routes": [
+            {
+                "_group_id": "0a42187b7b3d283e0079ddab01825e1c71fad9f6",
+                "_method": "GET",
+                "_referenced_by": [
+                    "--internal--"
+                ],
+                "_source": "--internal--",
+                "clusters": [
+                    {
+                        "name": "cluster_127_0_0_1_8877",
+                        "weight": 100.0
+                    }
+                ],
+                "prefix": "/ambassador/v0/check_ready",
+                "prefix_rewrite": "/ambassador/v0/check_ready"
+            },
             {
                 "_group_id": "16fb9d91fbd83c170f865b77420a902000b27841",
                 "_method": "GET",
@@ -71,6 +106,22 @@
                 "prefix": "/demo/",
                 "prefix_rewrite": "/"
             },
+            {
+                "_group_id": "6151893fbc2f1f730a1bb946b2eefac391f0d2ec",
+                "_method": "GET",
+                "_referenced_by": [
+                    "--internal--"
+                ],
+                "_source": "--internal--",
+                "clusters": [
+                    {
+                        "name": "cluster_127_0_0_1_8877",
+                        "weight": 100.0
+                    }
+                ],
+                "prefix": "/ambassador/v0/check_alive",
+                "prefix_rewrite": "/ambassador/v0/check_alive"
+            },
             {
                 "_group_id": "688e9f91e06f33c943c0c6373a5bdd32e647f7c4",
                 "_method": "GET",
@@ -86,6 +137,22 @@
                 ],
                 "prefix": "/ambassador/",
                 "prefix_rewrite": "/ambassador/"
+            },
+            {
+                "_group_id": "b69eeb747b38f5e5fd3955d044ea7797d67d024f",
+                "_method": "GET",
+                "_referenced_by": [
+                    "--internal--"
+                ],
+                "_source": "--internal--",
+                "clusters": [
+                    {
+                        "name": "cluster_127_0_0_1_8877",
+                        "weight": 100.0
+                    }
+                ],
+                "prefix": "/ambassador/v0/",
+                "prefix_rewrite": "/ambassador/v0/"
             }
         ]
     },
@@ -105,6 +172,7 @@
     },
     "sources": {
         "--diagnostics--": {
+            "_source": "--diagnostics--",
             "description": "The '--diagnostics--' source marks objects created by Ambassador to assist with diagnostic output.",
             "filename": "--diagnostics--",
             "index": 0,
@@ -113,6 +181,7 @@
             "version": "v0"
         },
         "--internal--": {
+            "_source": "--internal--",
             "description": "The '--internal--' source marks objects created by Ambassador's internal logic.",
             "filename": "--internal--",
             "index": 0,
diff --git a/ambassador/tests/006-headers-and-host/gold.intermediate.json b/ambassador/tests/006-headers-and-host/gold.intermediate.json
--- a/ambassador/tests/006-headers-and-host/gold.intermediate.json
+++ b/ambassador/tests/006-headers-and-host/gold.intermediate.json
@@ -1,8 +1,15 @@
 {
     "envoy_config": {
+        "admin": [
+            {
+                "_source": "--internal--",
+                "admin_port": 8001
+            }
+        ],
         "clusters": [
             {
                 "_referenced_by": [
+                    "--internal--",
                     "mapping-diag.yaml.1"
                 ],
                 "_service": "127.0.0.1:8877",
@@ -83,6 +90,11 @@
             }
         ],
         "filters": [
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "cors"
+            },
             {
                 "_source": "auth.yaml.1",
                 "config": {
@@ -97,16 +109,38 @@
                 },
                 "name": "extauth",
                 "type": "decoder"
+            },
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "router",
+                "type": "decoder"
             }
         ],
         "listeners": [
             {
                 "_source": "--internal--",
-                "admin_port": 8001,
+                "require_tls": false,
                 "service_port": 80
             }
         ],
         "routes": [
+            {
+                "_group_id": "0a42187b7b3d283e0079ddab01825e1c71fad9f6",
+                "_method": "GET",
+                "_referenced_by": [
+                    "--internal--"
+                ],
+                "_source": "--internal--",
+                "clusters": [
+                    {
+                        "name": "cluster_127_0_0_1_8877",
+                        "weight": 100.0
+                    }
+                ],
+                "prefix": "/ambassador/v0/check_ready",
+                "prefix_rewrite": "/ambassador/v0/check_ready"
+            },
             {
                 "_group_id": "3d6174456a466eb4f342d4aa4165bd8c2b99ce1e",
                 "_method": "GET",
@@ -130,6 +164,22 @@
                 "prefix": "/qotm/",
                 "prefix_rewrite": "/qotm/"
             },
+            {
+                "_group_id": "6151893fbc2f1f730a1bb946b2eefac391f0d2ec",
+                "_method": "GET",
+                "_referenced_by": [
+                    "--internal--"
+                ],
+                "_source": "--internal--",
+                "clusters": [
+                    {
+                        "name": "cluster_127_0_0_1_8877",
+                        "weight": 100.0
+                    }
+                ],
+                "prefix": "/ambassador/v0/check_alive",
+                "prefix_rewrite": "/ambassador/v0/check_alive"
+            },
             {
                 "_group_id": "688e9f91e06f33c943c0c6373a5bdd32e647f7c4",
                 "_method": "GET",
@@ -160,17 +210,36 @@
                     }
                 ],
                 "cors": {
-                    "expose_headers": "X-Custom-Header",
+                    "allow_credentials": true,
                     "allow_headers": "Content-Type",
-                    "max_age": "86400",
                     "allow_methods": "POST, GET, OPTIONS",
-                    "allow_origin": ["http://foo.example", "http://bar.example"],
-                    "allow_credentials": true,
-                    "enabled": true
+                    "allow_origin": [
+                        "http://foo.example",
+                        "http://bar.example"
+                    ],
+                    "enabled": true,
+                    "expose_headers": "X-Custom-Header",
+                    "max_age": "86400"
                 },
                 "prefix": "/cors-creds/",
                 "prefix_rewrite": "/"
             },
+            {
+                "_group_id": "b69eeb747b38f5e5fd3955d044ea7797d67d024f",
+                "_method": "GET",
+                "_referenced_by": [
+                    "--internal--"
+                ],
+                "_source": "--internal--",
+                "clusters": [
+                    {
+                        "name": "cluster_127_0_0_1_8877",
+                        "weight": 100.0
+                    }
+                ],
+                "prefix": "/ambassador/v0/",
+                "prefix_rewrite": "/ambassador/v0/"
+            },
             {
                 "_group_id": "c208ab7e4e146b9a8bbdebadc7b33f99bc635ab7",
                 "_method": "GET",
@@ -275,7 +344,9 @@
                     }
                 ],
                 "cors": {
-                    "allow_origin": ["*"],
+                    "allow_origin": [
+                        "*"
+                    ],
                     "enabled": true
                 },
                 "prefix": "/cors-all/",
@@ -308,6 +379,7 @@
     },
     "sources": {
         "--diagnostics--": {
+            "_source": "--diagnostics--",
             "description": "The '--diagnostics--' source marks objects created by Ambassador to assist with diagnostic output.",
             "filename": "--diagnostics--",
             "index": 0,
@@ -316,6 +388,7 @@
             "version": "v0"
         },
         "--internal--": {
+            "_source": "--internal--",
             "description": "The '--internal--' source marks objects created by Ambassador's internal logic.",
             "filename": "--internal--",
             "index": 0,
diff --git a/ambassador/tests/007-originating-tls/gold.intermediate.json b/ambassador/tests/007-originating-tls/gold.intermediate.json
--- a/ambassador/tests/007-originating-tls/gold.intermediate.json
+++ b/ambassador/tests/007-originating-tls/gold.intermediate.json
@@ -1,5 +1,11 @@
 {
     "envoy_config": {
+        "admin": [
+            {
+                "_source": "ambassador.yaml.1",
+                "admin_port": 4242
+            }
+        ],
         "clusters": [
             {
                 "_referenced_by": [
@@ -46,12 +52,29 @@
                 ]
             }
         ],
-        "filters": [],
+        "filters": [
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "cors"
+            },
+            {
+                "_source": "--internal--",
+                "config": {},
+                "name": "router",
+                "type": "decoder"
+            }
+        ],
         "listeners": [
             {
                 "_source": "ambassador.yaml.1",
-                "admin_port": 4242,
-                "service_port": 443
+                "require_tls": false,
+                "service_port": 443,
+                "tls": {
+                    "_source": "tls.yaml.1",
+                    "cert_chain_file": "/etc/ambassador-config/certs/tls.crt",
+                    "private_key_file": "/etc/ambassador-config/certs/tls.key"
+                }
             }
         ],
         "routes": [
@@ -103,13 +126,6 @@
                 "prefix": "/ambassador/v0/",
                 "prefix_rewrite": "/ambassador/v0/"
             }
-        ],
-        "tls": [
-            {
-                "_source": "tls.yaml.1",
-                "cert_chain_file": "/etc/ambassador-config/certs/tls.crt",
-                "private_key_file": "/etc/ambassador-config/certs/tls.key"
-            }
         ]
     },
     "source_map": {
@@ -128,6 +144,7 @@
     },
     "sources": {
         "--diagnostics--": {
+            "_source": "--diagnostics--",
             "description": "The '--diagnostics--' source marks objects created by Ambassador to assist with diagnostic output.",
             "filename": "--diagnostics--",
             "index": 0,
@@ -136,6 +153,7 @@
             "version": "v0"
         },
         "--internal--": {
+            "_source": "--internal--",
             "description": "The '--internal--' source marks objects created by Ambassador's internal logic.",
             "filename": "--internal--",
             "index": 0,
diff --git a/ambassador/tests/ambassador_test.py b/ambassador/tests/ambassador_test.py
--- a/ambassador/tests/ambassador_test.py
+++ b/ambassador/tests/ambassador_test.py
@@ -186,5 +186,9 @@ def test_diag(testname, dirpath, configdir):
     if errors:
         print("---- ERRORS")
         print("%s" % "\n".join(errors))
-
+        print("---- OVERVIEW ----")
+        print("%s" % results['overview'])
+        print("---- RECONSTITUTED ----")
+        print("%s" % results['reconstituted'])
+    
     assert errorcount == 0, ("failing, errors: %d" % errorcount)
diff --git a/ambassador/tests/diag_paranoia.py b/ambassador/tests/diag_paranoia.py
--- a/ambassador/tests/diag_paranoia.py
+++ b/ambassador/tests/diag_paranoia.py
@@ -24,13 +24,14 @@ def mark_referenced_by(obj, refby):
         return False
 
 Uniqifiers = {
+    'admin': lambda x: x['admin_port'],
     'breakers': lambda x: x['name'],
     'outliers': lambda x: x['name'],
     'filters': lambda x: x['name'],
     'tls': lambda x: "TLS",
-    'listeners': lambda x: '%s-%s' % (x['service_port'], x['admin_port']),
+    'listeners': lambda x: '%s-%s' % (x['service_port'], x.get('require_tls', False)),
     'routes': lambda x: x['_group_id'],
-    'sources': lambda x: '%s.%d' % (x['filename'], x['index']) if ('index' in x) else x['filename']
+    'sources': lambda x: '%s.%d' % (x['filename'], x['index']) if (('index' in x) and (x['filename'] != "--internal--")) else x['filename']
 }
 
 def filtered_overview(ov):
@@ -46,11 +47,11 @@ def filtered_overview(ov):
 
         if isinstance(ov[key], list):
             for obj in ov[key]:
-                if obj.get('_source', None) == '--internal--':
-                    continue
+                # if obj.get('_source', None) == '--internal--':
+                #     continue
 
                 if '_referenced_by' in obj:
-                    obj['_referenced_by'] = sorted([ x for x in obj['_referenced_by'] if x != '--internal--' ])
+                    obj['_referenced_by'] = sorted([ x for x in obj['_referenced_by'] ])
 
                 filtered_element.append(obj)
 
@@ -70,18 +71,28 @@ def diag_paranoia(configdir, outputdir):
     aconf = Config(configdir)
     ov = aconf.diagnostic_overview()
 
-    # print("==== OV")
-    # print(prettify(ov))
-
     reconstituted = {}
     errors = []
     warnings = []
     missing_uniqifiers = {}
 
-    for source in ov['sources']:
-        source_filename = source['filename']
+    source_info = [
+        {
+            "filename": x['filename'],
+            "sources": [ key for key in x['objects'].keys() ]
+        }
+        for x in ov['sources']
+    ]
+
+    source_info.insert(0, {
+        "filename": "--internal--",
+        "sources": [ "--internal--" ]
+    })
+
+    for si in source_info:
+        source_filename = si['filename']
 
-        for source_key in source['objects'].keys():
+        for source_key in si['sources']:
             intermediate = aconf.get_intermediate_for(source_key)
 
             # print("==== %s" % source_key)
@@ -104,7 +115,7 @@ def diag_paranoia(configdir, outputdir):
                             rcluster = rclusters[cname]
                             # print("%s: extant cluster %s" % (source_key, prettify(rclusters[cname])))
 
-                            if not mark_referenced_by(rcluster, source_key):
+                            if not mark_referenced_by(rcluster, source_key) and (source_key != "--internal--"):
                                 errors.append('%s: already appears in cluster %s?' %
                                               (source_key, rcluster['name']))
 
@@ -133,6 +144,7 @@ def diag_paranoia(configdir, outputdir):
                         continue
 
                     for obj in intermediate[key]:
+                        # print(obj)
                         u = uniqifier(obj)
 
                         rcon = reconstituted.setdefault(key, {})
@@ -187,18 +199,14 @@ def diag_paranoia(configdir, outputdir):
 
             reconstituted_lists[key] = sorted(reconstituted[key].values(), key=uniqifier)
 
-    # If there's no listener block in the reconstituted set, that implies that 
-    # the configuration doesn't override the listener state. Go ahead and add the
-    # default in.
+    # # If there's no listener block in the reconstituted set, that implies that 
+    # # the configuration doesn't override the listener state. Go ahead and add the
+    # # default in.
 
-    if 'listeners' not in reconstituted_lists:
-        reconstituted_lists['listeners'] = [
-            {
-                "_source": "--internal--",
-                "admin_port": 8001,
-                "service_port": 80
-            }
-        ]
+    # l = reconstituted_lists.get('listeners', [])
+
+    # if not l:
+    #     reconstituted_lists['listeners'] = []
 
     # If there're no 'filters' in the reconstituted set, uh, there were no filters
     # defined. Create an empty list.
@@ -219,18 +227,23 @@ def diag_paranoia(configdir, outputdir):
                                       lineterm=""))
 
     if udiff:
-        errors.append("%s\n-- DIFF --\n%s\n-- OVERVIEW --\n%s\n\n-- RECONSTITUTED --\n%s\n" %
+        errors.append("%s\n-- DIFF --\n%s\n" %
                       ("mismatch between overview and reconstituted diagnostics",
-                       "\n".join(udiff), pretty_filtered_overview, pretty_reconstituted_lists))
+                       "\n".join(udiff)))
 
     return {
         'errors': errors,
-        'warnings': warnings
+        'warnings': warnings,
+        'overview': pretty_filtered_overview,
+        'reconstituted': pretty_reconstituted_lists
     }
 
 if __name__ == "__main__":
     results = diag_paranoia(sys.argv[1], ".")
 
+    open("ov", "w").write(results['overview'])
+    open("rl", "w").write(results['reconstituted'])
+
     if (results['warnings']):
         print("\n".join(['WARNING: %s' % x for x in results['warnings']]))
 
diff --git a/end-to-end/000-no-base/test.sh b/end-to-end/000-no-base/test.sh
--- a/end-to-end/000-no-base/test.sh
+++ b/end-to-end/000-no-base/test.sh
@@ -11,7 +11,7 @@ PATH="${ROOT}:${PATH}"
 
 source ${ROOT}/utils.sh
 
-shred_and_reclaim
+initialize_cluster
 
 kubectl cluster-info
 
diff --git a/end-to-end/001-simple/test.sh b/end-to-end/001-simple/test.sh
--- a/end-to-end/001-simple/test.sh
+++ b/end-to-end/001-simple/test.sh
@@ -11,7 +11,7 @@ PATH="${ROOT}:${PATH}"
 
 source ${ROOT}/utils.sh
 
-shred_and_reclaim
+initialize_cluster
 
 kubectl cluster-info
 
@@ -45,6 +45,8 @@ if ! check_diag "$BASEURL" 2 "QoTM annotated"; then
     exit 1
 fi
 
+sleep 5  # ???
+
 if ! qtest.py $CLUSTER:$APORT test-1.yaml; then
     exit 1
 fi
@@ -63,6 +65,8 @@ if ! check_diag "$BASEURL" 3 "Auth annotated"; then
     exit 1
 fi
 
+sleep 5  # ???
+
 if ! qtest.py $CLUSTER:$APORT test-2.yaml; then
     exit 1
 fi
diff --git a/end-to-end/002-canary/test.sh b/end-to-end/002-canary/test.sh
--- a/end-to-end/002-canary/test.sh
+++ b/end-to-end/002-canary/test.sh
@@ -11,7 +11,7 @@ PATH="${ROOT}:${PATH}"
 
 source ${ROOT}/utils.sh
 
-shred_and_reclaim
+initialize_cluster
 
 kubectl cluster-info
 
@@ -26,7 +26,7 @@ wait_for_pods
 
 CLUSTER=$(cluster_ip)
 APORT=$(service_port ambassador)
-DEMOTEST_POD=$(kubectl get pods  | grep demotest | awk ' { print $1 }')
+DEMOTEST_POD=$(demotest_pod)
 
 BASEURL="http://${CLUSTER}:${APORT}"
 
diff --git a/end-to-end/003-headers-and-host/test.sh b/end-to-end/003-headers-and-host/test.sh
--- a/end-to-end/003-headers-and-host/test.sh
+++ b/end-to-end/003-headers-and-host/test.sh
@@ -11,7 +11,7 @@ PATH="${ROOT}:${PATH}"
 
 source ${ROOT}/utils.sh
 
-shred_and_reclaim
+initialize_cluster
 
 kubectl cluster-info
 
@@ -28,7 +28,7 @@ wait_for_pods
 
 CLUSTER=$(cluster_ip)
 APORT=$(service_port ambassador)
-DEMOTEST_POD=$(kubectl get pods  | grep demotest | awk ' { print $1 }')
+DEMOTEST_POD=$(demotest_pod)
 
 BASEURL="http://${CLUSTER}:${APORT}"
 
@@ -41,8 +41,6 @@ if ! check_diag "$BASEURL" 1 "No canary active"; then
     exit 1
 fi
 
-set -x
-
 if ! kubectl exec -i $DEMOTEST_POD -- python3 demotest.py "$BASEURL" /dev/fd/0 < cors.yaml; then
     exit 1
 fi
@@ -77,7 +75,7 @@ wait_for_demo_weights "$BASEURL" x-demo-mode=canary 100
 
 sleep 5
 
-if ! kubectl exec -i $DEMOTEST_POD -- python3 demotest.py "$BASEURL" /dev/fd/0 < demo-3.yaml; then
+if ! kubectl exec -i "$DEMOTEST_POD" -- python3 demotest.py "$BASEURL" /dev/fd/0 < demo-3.yaml; then
     exit 1
 fi
 
diff --git a/end-to-end/004-tls-1/test.sh b/end-to-end/004-tls-1/test.sh
--- a/end-to-end/004-tls-1/test.sh
+++ b/end-to-end/004-tls-1/test.sh
@@ -11,7 +11,7 @@ PATH="${ROOT}:${PATH}"
 
 source ${ROOT}/utils.sh
 
-shred_and_reclaim
+initialize_cluster
 
 kubectl cluster-info
 
@@ -31,11 +31,13 @@ wait_for_pods
 
 CLUSTER=$(cluster_ip)
 APORT=$(service_port ambassador)
-DEMOTEST_POD=$(kubectl get pods  | grep demotest | awk ' { print $1 }')
+DEMOTEST_POD=$(demotest_pod)
 
 BASEURL="https://${CLUSTER}:${APORT}"
+HTTPURL="http://${CLUSTER}:$(service_port ambassador 1)"
 
 echo "Base URL $BASEURL"
+echo "HTTP URL $HTTPURL"
 echo "Diag URL $BASEURL/ambassador/v0/diag/"
 
 wait_for_ready "$BASEURL"
@@ -44,6 +46,29 @@ if ! check_diag "$BASEURL" 1 "No canary active"; then
     exit 1
 fi
 
+status=$(curl -s --write-out "%{http_code} %{redirect_url}" "$HTTPURL/demo/")
+rc=$?
+
+if [ $rc -ne 0 ]; then
+    echo "HTTP redirect check failed ($rc): $status" >&2
+    exit 1
+fi
+
+code=$(echo "$status" | cut -d' ' -f1)
+redirect_url=$(echo "$status" | cut -d' ' -f2-)
+
+if [ "$code" != "301" ]; then
+    echo "HTTP redirect check got $code instead of 301: $status" >&2
+    exit 1
+fi
+
+if [ $(echo "$redirect_url" | grep -s -c "^https://${CLUSTER}[/:]") -ne 1 ]; then
+    echo "HTTP redirect check goes somewhere weird: $status" >&2
+    exit 1
+fi
+
+echo "HTTP redirect check passed"
+
 if ! kubectl exec -i $DEMOTEST_POD -- python3 demotest.py "$BASEURL" /dev/fd/0 < demo-1.yaml; then
     exit 1
 fi
diff --git a/end-to-end/005-single-namespace/test.sh b/end-to-end/005-single-namespace/test.sh
--- a/end-to-end/005-single-namespace/test.sh
+++ b/end-to-end/005-single-namespace/test.sh
@@ -11,7 +11,7 @@ PATH="${ROOT}:${PATH}"
 
 source ${ROOT}/utils.sh
 
-shred_and_reclaim
+initialize_cluster
 
 kubectl cluster-info
 
@@ -28,7 +28,7 @@ wait_for_pods
 
 CLUSTER=$(cluster_ip)
 APORT=$(service_port ambassador)
-DEMOTEST_POD=$(kubectl get pods  | grep demotest | awk ' { print $1 }')
+DEMOTEST_POD=$(demotest_pod)
 
 BASEURL="http://${CLUSTER}:${APORT}"
 
diff --git a/end-to-end/006-auth-canary/test.sh b/end-to-end/006-auth-canary/test.sh
--- a/end-to-end/006-auth-canary/test.sh
+++ b/end-to-end/006-auth-canary/test.sh
@@ -11,7 +11,7 @@ PATH="${ROOT}:${PATH}"
 
 source ${ROOT}/utils.sh
 
-shred_and_reclaim
+initialize_cluster
 
 kubectl cluster-info