# Copyright 2020 Datawire. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License ######## # This is a debugging tool that, given an envoy.json _generated by Ambassador_, will # dump a simplified view of the routing table implemented by that envoy.json. # # **NOTE WELL** that this is not a general-purpose Envoy visualization tool. It is # very specific to Ambassador. ######## import functools import json import re from collections import OrderedDict from typing import Any, Optional import click import dpath.util def lookup(x: Any, path: str) -> Optional[Any]: try: return dpath.util.get(x, path) except KeyError: return None reSecret = re.compile(r"^.*/snapshots/([^/]+)/secrets-decoded/([^/]+)/[0-9A-F]+\.(.+)$") # Use this instead of click.option click_option = functools.partial(click.option, show_default=True) click_option_no_default = functools.partial(click.option, show_default=False) @click.command(help="Show a simplified Envoy config breakdown") @click.argument("envoy-config-path", type=click.Path(exists=True, readable=True)) def main(envoy_config_path: str) -> None: econf = json.load(open(envoy_config_path, "r")) listeners = lookup(econf, "/static_resources/listeners") or [] for listener in listeners: bind_addr = lookup(listener, "/address/socket_address/address") port = lookup(listener, "/address/socket_address/port_value") proto = lookup(listener, "/address/socket_address/protocol") lfilters = lookup(listener, "/listener_filters") lfiltstr = "" if lfilters: lfilter_names = [x["name"].replace("envoy.listener.", "") for x in lfilters] lfiltstr = f" [using {', '.join(lfilter_names)}]" print(f"LISTENER on {proto} {bind_addr}:{port}{lfiltstr}") for chain in listener["filter_chains"]: upp = chain.get("use_proxy_proto", False) match_proto = lookup(chain, "/filter_chain_match/transport_protocol") match_domains = lookup(chain, "/filter_chain_match/server_names") match_domain_str = "*" if match_domains: match_domain_str = "/".join(match_domains) chain_options = [] if match_proto == "tls": chain_options.append("TLS-only") ctx = lookup(chain, "/tls_context/common_tls_context/tls_certificates/0") or None ctx_name = None if ctx: for el in ctx.values(): fname = el.get("filename") if fname: m = reSecret.match(fname) if m: secret_name = m.group(2) secret_namespace = m.group(1) ctx_name = f"{secret_name}.{secret_namespace}" break if ctx_name: chain_options.append(f"ctx {ctx_name}") if upp: chain_options.append("with PROXY proto") chain_option_str = "" if chain_options: chain_option_str = f" ({', '.join(chain_options)})" print(f"... CHAIN {match_domain_str}{chain_option_str}") filters = chain["filters"] for filter in filters: if filter["name"] == "envoy.http_connection_manager": vhosts = lookup(filter, "/config/route_config/virtual_hosts") or [] for vhost in vhosts: domains = vhost["domains"] routes = vhost["routes"] print(f"... ... VHOST {', '.join(domains)}") actions: dict = OrderedDict() for route in routes: match = route["match"] pfx = match.get("prefix") or "-no-prefix-" headers = match.get("headers") or [] security = "insecure" authority = "" header_count = 0 action = "Unknown" if headers: for hdr in headers: if (hdr["name"] == "x-forwarded-proto") and ( hdr.get("exact_match") == "https" ): security = "secure" elif hdr["name"] == ":authority": authority = f"@{hdr['exact_match']}" else: header_count += 1 hdr_str = "" if header_count > 0: hdr_str = f" ({header_count} additional header{'' if header_count == 1 else 's'})" target = f"{pfx}{authority}{hdr_str}" action_list = actions.setdefault(target, []) if route.get("route"): action = "Route" elif route.get("redirect"): action = "Redirect" action_list.append(f"{action} {security}") for target, action_list in actions.items(): print(f"... ... ... {target}: {'; '.join(action_list)}") if __name__ == "__main__": main()