Example #1
0
    def __init__(self, ir: 'IR', aconf: Config,
                 rkey: str,      # REQUIRED
                 name: str,      # REQUIRED
                 location: str,  # REQUIRED

                 kind: str="IRMapping",
                 apiVersion: str="ambassador/v1",   # Not a typo! See below.
                 precedence: int=0,
                 rewrite: str="/",
                 **kwargs) -> None:
        # OK, this is a bit of a pain. We want to preserve the name and rkey and
        # such here, unlike most kinds of IRResource. So. Shallow copy the keys
        # we're going to allow from the incoming kwargs...

        new_args = {x: kwargs[x] for x in kwargs.keys() if x in IRHTTPMapping.AllowedKeys}

        # ...then set up the headers (since we need them to compute our group ID).
        hdrs = []
        add_request_hdrs = kwargs.get('add_request_headers', {})

        if 'headers' in kwargs:
            for name, value in kwargs.get('headers', {}).items():
                if value is True:
                    hdrs.append(Header(name))
                else:
                    hdrs.append(Header(name, value))

        if 'regex_headers' in kwargs:
            for name, value in kwargs.get('regex_headers', {}).items():
                hdrs.append(Header(name, value, regex=True))

        if 'host' in kwargs:
            hdrs.append(Header(":authority", kwargs['host'], kwargs.get('host_regex', False)))
            self.tls_context = self.match_tls_context(kwargs['host'], ir)

        if 'service' in kwargs:
            svc = Service(ir.logger, kwargs['service'])

            if 'add_linkerd_headers' in kwargs:
                if kwargs['add_linkerd_headers'] is True:
                    add_request_hdrs['l5d-dst-override'] = svc.hostname_port
            else:
                if 'add_linkerd_headers' in ir.ambassador_module and ir.ambassador_module.add_linkerd_headers is True:
                    add_request_hdrs['l5d-dst-override'] = svc.hostname_port

        if 'method' in kwargs:
            hdrs.append(Header(":method", kwargs['method'], kwargs.get('method_regex', False)))


        # ...and then init the superclass.
        super().__init__(
            ir=ir, aconf=aconf, rkey=rkey, location=location,
            kind=kind, name=name, apiVersion=apiVersion,
            headers=hdrs, add_request_headers=add_request_hdrs,
            precedence=precedence, rewrite=rewrite,
            **new_args
        )

        if 'outlier_detection' in kwargs:
            self.post_error(RichStatus.fromError("outlier_detection is not supported"))
Example #2
0
    def __init__(
            self,
            ir: 'IR',
            aconf: Config,
            rkey: str,  # REQUIRED
            name: str,  # REQUIRED
            location: str,  # REQUIRED
            namespace: Optional[str] = None,
            metadata_labels: Optional[Dict[str, str]] = None,
            kind: str = "IRHTTPMapping",
            apiVersion: str = "getambassador.io/v2",  # Not a typo! See below.
            precedence: int = 0,
            rewrite: str = "/",
            cluster_tag: Optional[str] = None,
            **kwargs) -> None:
        # OK, this is a bit of a pain. We want to preserve the name and rkey and
        # such here, unlike most kinds of IRResource, so we shallow copy the keys
        # we're going to allow from the incoming kwargs.
        #
        # NOTE WELL: things that we explicitly process below should _not_ be listed in
        # AllowedKeys. The point of AllowedKeys is this loop below.

        new_args = {}

        # When we look up defaults, use lookup class "httpmapping"... and yeah, we need the
        # IR, too.
        self.default_class = "httpmapping"
        self.ir = ir

        for key, check_defaults in IRHTTPMapping.AllowedKeys.items():
            # Do we have a keyword arg for this key?
            if key in kwargs:
                # Yes, it wins.
                value = kwargs[key]
                self.ir.logger.info(
                    f"IRHTTPMapping: accept {key}={value} from kwargs")
                new_args[key] = value
            elif check_defaults:
                # No value in kwargs, but we're allowed to check defaults for it.
                value = self.lookup_default(key)

                if value is not None:
                    self.ir.logger.info(
                        f"IRHTTPMapping: accept {key}={value} from defaults")
                    new_args[key] = value

        # add_linkerd_headers is special, because we support setting it as a default
        # in the bare Ambassador module. We should really toss this and use the defaults
        # mechanism, but not for 1.4.3.

        if "add_linkerd_headers" not in new_args:
            # They didn't set it explicitly, so check for the older way.
            add_linkerd_headers = self.ir.ambassador_module.get(
                'add_linkerd_headers', None)

            if add_linkerd_headers != None:
                new_args["add_linkerd_headers"] = add_linkerd_headers

        # OK. On to set up the headers (since we need them to compute our group ID).
        hdrs = []
        query_parameters = []
        regex_rewrite = kwargs.get('regex_rewrite', {})

        if 'headers' in kwargs:
            for name, value in kwargs.get('headers', {}).items():
                if value is True:
                    hdrs.append(KeyValueDecorator(name))
                else:
                    hdrs.append(KeyValueDecorator(name, value))

        if 'regex_headers' in kwargs:
            for name, value in kwargs.get('regex_headers', {}).items():
                hdrs.append(KeyValueDecorator(name, value, regex=True))

        if 'host' in kwargs:
            hdrs.append(
                KeyValueDecorator(":authority", kwargs['host'],
                                  kwargs.get('host_regex', False)))
            self.tls_context = self.match_tls_context(kwargs['host'], ir)

        if 'method' in kwargs:
            hdrs.append(
                KeyValueDecorator(":method", kwargs['method'],
                                  kwargs.get('method_regex', False)))

        # Next up: figure out what headers we need to add to each request. Again, if the key
        # is present in kwargs, the kwargs value wins -- this is important to allow explicitly
        # setting a value of `{}` to override a default!

        add_request_hdrs: dict

        if 'add_request_headers' in kwargs:
            add_request_hdrs = kwargs['add_request_headers']
        else:
            add_request_hdrs = self.lookup_default('add_request_headers', {})

        # Remember that we may need to add the Linkerd headers, too.
        add_linkerd_headers = new_args.get('add_linkerd_headers', False)

        if add_linkerd_headers:
            # Yup. We need the service info for this...

            if 'service' in kwargs:
                svc = Service(ir.logger, kwargs['service'])
                add_request_hdrs['l5d-dst-override'] = svc.hostname_port
            else:
                # Uh. This is pretty much impossible.
                self.post_error("Service is required for HTTP Mappings")

        # XXX BRUTAL HACK HERE:
        # If we _don't_ have an origination context, but our IR has an agent_origination_ctx,
        # force TLS origination because it's the agent. I know, I know. It's a hack.
        if ('tls' not in new_args) and ir.agent_origination_ctx:
            ir.logger.info(
                f"Mapping {name}: Agent forcing origination TLS context to {ir.agent_origination_ctx.name}"
            )
            new_args['tls'] = ir.agent_origination_ctx.name

        if 'query_parameters' in kwargs:
            for name, value in kwargs.get('query_parameters', {}).items():
                if value is True:
                    query_parameters.append(KeyValueDecorator(name))
                else:
                    query_parameters.append(KeyValueDecorator(name, value))

        if 'regex_query_parameters' in kwargs:
            for name, value in kwargs.get('regex_query_parameters',
                                          {}).items():
                query_parameters.append(
                    KeyValueDecorator(name, value, regex=True))

        if 'regex_rewrite' in kwargs:
            if rewrite:
                self.ir.aconf.post_notice(
                    "Cannot specify both rewrite and regex_rewrite: using regex_rewrite and ignoring rewrite"
                )
                rewrite = ""
            rewrite_items = kwargs.get('regex_rewrite', {})
            regex_rewrite = {
                'pattern': rewrite_items.get('pattern', ''),
                'substitution': rewrite_items.get('substitution', '')
            }

        # ...and then init the superclass.
        super().__init__(ir=ir,
                         aconf=aconf,
                         rkey=rkey,
                         location=location,
                         kind=kind,
                         name=name,
                         namespace=namespace,
                         metadata_labels=metadata_labels,
                         apiVersion=apiVersion,
                         headers=hdrs,
                         add_request_headers=add_request_hdrs,
                         precedence=precedence,
                         rewrite=rewrite,
                         cluster_tag=cluster_tag,
                         query_parameters=query_parameters,
                         regex_rewrite=regex_rewrite,
                         **new_args)

        if 'outlier_detection' in kwargs:
            self.post_error(
                RichStatus.fromError("outlier_detection is not supported"))
Example #3
0
    def __init__(
            self,
            ir: 'IR',
            aconf: Config,
            rkey: str,  # REQUIRED
            name: str,  # REQUIRED
            location: str,  # REQUIRED
            service: str,  # REQUIRED
            namespace: Optional[str] = None,
            metadata_labels: Optional[Dict[str, str]] = None,
            kind: str = "IRHTTPMapping",
            apiVersion:
        str = "x.getambassador.io/v3alpha1",  # Not a typo! See below.
            precedence: int = 0,
            rewrite: str = "/",
            cluster_tag: Optional[str] = None,
            **kwargs) -> None:
        # OK, this is a bit of a pain. We want to preserve the name and rkey and
        # such here, unlike most kinds of IRResource, so we shallow copy the keys
        # we're going to allow from the incoming kwargs.
        #
        # NOTE WELL: things that we explicitly process below should _not_ be listed in
        # AllowedKeys. The point of AllowedKeys is this loop below.

        new_args = {}

        # When we look up defaults, use lookup class "httpmapping"... and yeah, we need the
        # IR, too.
        self.default_class = "httpmapping"
        self.ir = ir

        for key, check_defaults in IRHTTPMapping.AllowedKeys.items():
            # Do we have a keyword arg for this key?
            if key in kwargs:
                # Yes, it wins.
                value = kwargs[key]
                new_args[key] = value
            elif check_defaults:
                # No value in kwargs, but we're allowed to check defaults for it.
                value = self.lookup_default(key)

                if value is not None:
                    new_args[key] = value

        # add_linkerd_headers is special, because we support setting it as a default
        # in the bare Ambassador module. We should really toss this and use the defaults
        # mechanism, but not for 1.4.3.

        if "add_linkerd_headers" not in new_args:
            # They didn't set it explicitly, so check for the older way.
            add_linkerd_headers = self.ir.ambassador_module.get(
                'add_linkerd_headers', None)

            if add_linkerd_headers != None:
                new_args["add_linkerd_headers"] = add_linkerd_headers

        # OK. On to set up the headers (since we need them to compute our group ID).
        hdrs = []
        query_parameters = []
        regex_rewrite = kwargs.get('regex_rewrite', {})

        # Start by assuming that nothing in our arguments mentions hosts (so no host and no host_regex).
        host = None
        host_regex = False

        # Also start self.host as unspecified.
        self.host = None

        # OK. Start by looking for a :authority header match.
        if 'headers' in kwargs:
            for name, value in kwargs.get('headers', {}).items():
                if value is True:
                    hdrs.append(KeyValueDecorator(name))
                else:
                    # An exact match on the :authority header is special -- treat it like
                    # they set the "host" element (but note that we'll allow the actual
                    # "host" element to override it later).
                    if name.lower() == ':authority':
                        # This is an _exact_ match, so it mustn't contain a "*" -- that's illegal in the DNS.
                        if "*" in value:
                            # We can't call self.post_error() yet, because we're not initialized yet. So we cheat a bit
                            # and defer the error for later.
                            new_args[
                                "_deferred_error"] = f":authority exact-match '{value}' contains *, which cannot match anything."
                            ir.logger.debug(
                                "IRHTTPMapping %s: self.host contains * (%s, :authority)",
                                name, value)
                        else:
                            # No globs, just save it. (We'll end up using it as a glob later, in the Envoy
                            # config part of the world, but that's OK -- a glob with no "*" in it will always
                            # match only itself.)
                            host = value
                            ir.logger.debug(
                                "IRHTTPMapping %s: self.host == %s (:authority)",
                                name, self.host)
                            # DO NOT save the ':authority' match here -- we'll pick it up after we've checked
                            # for hostname, too.
                    else:
                        # It's not an :authority match, so we're good.
                        hdrs.append(KeyValueDecorator(name, value))

        if 'regex_headers' in kwargs:
            # DON'T do anything special with a regex :authority match: we can't
            # do host-based filtering within the IR for it anyway.
            for name, value in kwargs.get('regex_headers', {}).items():
                hdrs.append(KeyValueDecorator(name, value, regex=True))

        if 'host' in kwargs:
            # It's deliberate that we'll allow kwargs['host'] to silently override an exact :authority
            # header match.
            host = kwargs['host']
            host_regex = kwargs.get('host_regex', False)

            # If it's not a regex, it's an exact match -- make sure it doesn't contain a '*'.
            if not host_regex:
                if "*" in host:
                    # We can't call self.post_error() yet, because we're not initialized yet. So we cheat a bit
                    # and defer the error for later.
                    new_args[
                        "_deferred_error"] = f"host exact-match {host} contains *, which cannot match anything."
                    ir.logger.debug(
                        "IRHTTPMapping %s: self.host contains * (%s, host)",
                        name, host)
                else:
                    ir.logger.debug("IRHTTPMapping %s: self.host == %s (host)",
                                    name, self.host)

        # Finally, check for 'hostname'.
        if 'hostname' in kwargs:
            # It's deliberate that we allow kwargs['hostname'] to override anything else -- even a regex host.
            # Yell about it, though.
            if host:
                ir.logger.warning(
                    "Mapping %s in namespace %s: both host and hostname are set, using hostname and ignoring host",
                    name, namespace)

            # No need to be so careful about "*" here, since hostname is defined to be a glob.
            host = kwargs['hostname']
            host_regex = False
            ir.logger.debug("IRHTTPMapping %s: self.host gl~ %s (hostname)",
                            name, self.host)

        # If we have a host, include a ":authority" match. We're treating this as if it were
        # an exact match, but that's because the ":authority" match is handling specially by
        # Envoy.
        if host:
            hdrs.append(KeyValueDecorator(":authority", host, host_regex))

            # Finally, if our host isn't a regex, save it in self.host.
            if not host_regex:
                self.host = host

        if 'method' in kwargs:
            hdrs.append(
                KeyValueDecorator(":method", kwargs['method'],
                                  kwargs.get('method_regex', False)))

        if 'use_websocket' in new_args:
            allow_upgrade = new_args.setdefault('allow_upgrade', [])
            if 'websocket' not in allow_upgrade:
                allow_upgrade.append('websocket')
            del new_args['use_websocket']

        # Next up: figure out what headers we need to add to each request. Again, if the key
        # is present in kwargs, the kwargs value wins -- this is important to allow explicitly
        # setting a value of `{}` to override a default!

        add_request_hdrs: dict
        add_response_hdrs: dict

        if 'add_request_headers' in kwargs:
            add_request_hdrs = kwargs['add_request_headers']
        else:
            add_request_hdrs = self.lookup_default('add_request_headers', {})

        if 'add_response_headers' in kwargs:
            add_response_hdrs = kwargs['add_response_headers']
        else:
            add_response_hdrs = self.lookup_default('add_response_headers', {})

        # Remember that we may need to add the Linkerd headers, too.
        add_linkerd_headers = new_args.get('add_linkerd_headers', False)

        # XXX The resolver lookup code is duplicated from IRBaseMapping.setup --
        # needs to be fixed after 1.6.1.
        resolver_name = kwargs.get(
            'resolver') or self.ir.ambassador_module.get(
                'resolver', 'kubernetes-service')

        assert (resolver_name
                )  # for mypy -- resolver_name cannot be None at this point
        resolver = self.ir.get_resolver(resolver_name)

        if resolver:
            resolver_kind = resolver.kind
        else:
            # In IRBaseMapping.setup, we post an error if the resolver is unknown.
            # Here, we just don't bother; we're only using it for service
            # qualification.
            resolver_kind = 'KubernetesBogusResolver'

        service = normalize_service_name(ir,
                                         service,
                                         namespace,
                                         resolver_kind,
                                         rkey=rkey)
        self.ir.logger.debug(
            f"Mapping {name} service qualified to {repr(service)}")

        svc = Service(ir.logger, service)

        if add_linkerd_headers:
            add_request_hdrs['l5d-dst-override'] = svc.hostname_port

        # XXX BRUTAL HACK HERE:
        # If we _don't_ have an origination context, but our IR has an agent_origination_ctx,
        # force TLS origination because it's the agent. I know, I know. It's a hack.
        if ('tls' not in new_args) and ir.agent_origination_ctx:
            ir.logger.debug(
                f"Mapping {name}: Agent forcing origination TLS context to {ir.agent_origination_ctx.name}"
            )
            new_args['tls'] = ir.agent_origination_ctx.name

        if 'query_parameters' in kwargs:
            for name, value in kwargs.get('query_parameters', {}).items():
                if value is True:
                    query_parameters.append(KeyValueDecorator(name))
                else:
                    query_parameters.append(KeyValueDecorator(name, value))

        if 'regex_query_parameters' in kwargs:
            for name, value in kwargs.get('regex_query_parameters',
                                          {}).items():
                query_parameters.append(
                    KeyValueDecorator(name, value, regex=True))

        if 'regex_rewrite' in kwargs:
            if rewrite and rewrite != "/":
                self.ir.aconf.post_notice(
                    "Cannot specify both rewrite and regex_rewrite: using regex_rewrite and ignoring rewrite"
                )
            rewrite = ""
            rewrite_items = kwargs.get('regex_rewrite', {})
            regex_rewrite = {
                'pattern': rewrite_items.get('pattern', ''),
                'substitution': rewrite_items.get('substitution', '')
            }

        # ...and then init the superclass.
        super().__init__(ir=ir,
                         aconf=aconf,
                         rkey=rkey,
                         location=location,
                         service=service,
                         kind=kind,
                         name=name,
                         namespace=namespace,
                         metadata_labels=metadata_labels,
                         apiVersion=apiVersion,
                         headers=hdrs,
                         add_request_headers=add_request_hdrs,
                         add_response_headers=add_response_hdrs,
                         precedence=precedence,
                         rewrite=rewrite,
                         cluster_tag=cluster_tag,
                         query_parameters=query_parameters,
                         regex_rewrite=regex_rewrite,
                         **new_args)

        if 'outlier_detection' in kwargs:
            self.post_error(
                RichStatus.fromError("outlier_detection is not supported"))
Example #4
0
    def __init__(self, ir: 'IR', aconf: Config,
                 rkey: str,      # REQUIRED
                 name: str,      # REQUIRED
                 location: str,  # REQUIRED
                 namespace: Optional[str] = None,
                 metadata_labels: Optional[Dict[str, str]] = None,
                 kind: str="IRHTTPMapping",
                 apiVersion: str="getambassador.io/v2",   # Not a typo! See below.
                 precedence: int=0,
                 rewrite: str="/",
                 cluster_tag: Optional[str]=None,
                 **kwargs) -> None:
        # OK, this is a bit of a pain. We want to preserve the name and rkey and
        # such here, unlike most kinds of IRResource. So. Shallow copy the keys
        # we're going to allow from the incoming kwargs...

        new_args = {x: kwargs[x] for x in kwargs.keys() if x in IRHTTPMapping.AllowedKeys}

        # ...then set up the headers (since we need them to compute our group ID).
        hdrs = []
        add_request_hdrs = kwargs.get('add_request_headers', {})

        if 'headers' in kwargs:
            for name, value in kwargs.get('headers', {}).items():
                if value is True:
                    hdrs.append(Header(name))
                else:
                    hdrs.append(Header(name, value))

        if 'regex_headers' in kwargs:
            for name, value in kwargs.get('regex_headers', {}).items():
                hdrs.append(Header(name, value, regex=True))

        if 'host' in kwargs:
            hdrs.append(Header(":authority", kwargs['host'], kwargs.get('host_regex', False)))
            self.tls_context = self.match_tls_context(kwargs['host'], ir)

        if 'service' in kwargs:
            svc = Service(ir.logger, kwargs['service'])

            if 'add_linkerd_headers' in kwargs:
                if kwargs['add_linkerd_headers'] is True:
                    add_request_hdrs['l5d-dst-override'] = svc.hostname_port
            else:
                if 'add_linkerd_headers' in ir.ambassador_module and ir.ambassador_module.add_linkerd_headers is True:
                    add_request_hdrs['l5d-dst-override'] = svc.hostname_port

        if 'method' in kwargs:
            hdrs.append(Header(":method", kwargs['method'], kwargs.get('method_regex', False)))

        # XXX BRUTAL HACK HERE:
        # If we _don't_ have an origination context, but our IR has an agent_origination_ctx,
        # force TLS origination because it's the agent. I know, I know. It's a hack.
        if ('tls' not in new_args) and ir.agent_origination_ctx:
            ir.logger.info(f"Mapping {name}: Agent forcing origination TLS context to {ir.agent_origination_ctx.name}")
            new_args['tls'] = ir.agent_origination_ctx.name

        # ...and then init the superclass.
        super().__init__(
            ir=ir, aconf=aconf, rkey=rkey, location=location,
            kind=kind, name=name, namespace=namespace, metadata_labels=metadata_labels,
            apiVersion=apiVersion, headers=hdrs, add_request_headers=add_request_hdrs,
            precedence=precedence, rewrite=rewrite, cluster_tag=cluster_tag,
            **new_args
        )

        if 'outlier_detection' in kwargs:
            self.post_error(RichStatus.fromError("outlier_detection is not supported"))
Example #5
0
def v2filter_authv1(auth: IRAuth, v2config: 'V2Config'):
    del v2config  # silence unused-variable warning

    assert auth.cluster
    cluster = typecast(IRCluster, auth.cluster)

    if auth.api_version != "ambassador/v1":
        auth.ir.logger.warning("IRAuth_v1 working on %s, mismatched at %s" %
                               (auth.name, auth.api_version))

    assert auth.proto

    raw_body_info: Optional[Dict[str, int]] = auth.get('include_body')

    if not raw_body_info and auth.get('allow_request_body', False):
        raw_body_info = {'max_bytes': 4096, 'allow_partial': True}

    body_info: Optional[Dict[str, int]] = None

    if raw_body_info:
        body_info = {}

        if 'max_bytes' in raw_body_info:
            body_info['max_request_bytes'] = raw_body_info['max_bytes']

        if 'allow_partial' in raw_body_info:
            body_info['allow_partial_message'] = raw_body_info['allow_partial']

    auth_info: Dict[str, Any] = {}

    if auth.proto == "http":
        allowed_authorization_headers = []
        headers_to_add = []

        for key in list(
                set(auth.allowed_authorization_headers).union(
                    AllowedAuthorizationHeaders)):
            allowed_authorization_headers.append({"exact": key})

        allowed_request_headers = []

        for key in list(
                set(auth.allowed_request_headers).union(
                    AllowedRequestHeaders)):
            allowed_request_headers.append({"exact": key})

        if auth.get('add_linkerd_headers', False):
            svc = Service(auth.ir.logger, auth_cluster_uri(auth, cluster))
            headers_to_add.append({
                'key': 'l5d-dst-override',
                'value': svc.hostname_port
            })

        auth_info = {
            'name': 'envoy.ext_authz',
            'config': {
                'http_service': {
                    'server_uri': {
                        'uri': auth_cluster_uri(auth, cluster),
                        'cluster': cluster.name,
                        'timeout': "%0.3fs" % (float(auth.timeout_ms) / 1000.0)
                    },
                    'path_prefix': auth.path_prefix,
                    'authorization_request': {
                        'allowed_headers': {
                            'patterns': allowed_request_headers
                        },
                        'headers_to_add': headers_to_add
                    },
                    'authorization_response': {
                        'allowed_upstream_headers': {
                            'patterns': allowed_authorization_headers
                        },
                        'allowed_client_headers': {
                            'patterns': allowed_authorization_headers
                        }
                    }
                },
            }
        }

    if auth.proto == "grpc":
        auth_info = {
            'name': 'envoy.ext_authz',
            'config': {
                'grpc_service': {
                    'envoy_grpc': {
                        'cluster_name': cluster.name
                    },
                    'timeout': "%0.3fs" % (float(auth.timeout_ms) / 1000.0)
                },
                'use_alpha': True
            }
        }

    if auth_info:
        if body_info:
            auth_info['config']['with_request_body'] = body_info

        if 'failure_mode_allow' in auth:
            auth_info['config']["failure_mode_allow"] = auth.failure_mode_allow

        if 'status_on_error' in auth:
            status_on_error: Optional[Dict[str,
                                           int]] = auth.get('status_on_error')
            auth_info['config']["status_on_error"] = status_on_error

        return auth_info

    # If here, something's gone horribly wrong.
    auth.post_error("Protocol '%s' is not supported, auth not enabled" %
                    auth.proto)
    return None
Example #6
0
    res_source = 'mapping'

    if not res_name:
        res_name = global_resolver
        res_source = 'defaults'

    ctx_name = mapping.get('tls', None)

    logger.debug(f'Mapping {mname}: resolver {res_name} from {res_source}, service {mapping.service}, tls {ctx_name}')

    if res_name:
        resolver = resolvers.get(res_name, None)
        logger.debug(f'-> resolver {resolver}')

        if resolver:
            svc = Service(logger, mapping.service, ctx_name)

            if resolver.kind == 'ConsulResolver':
                logger.debug(f'Mapping {mname} uses Consul resolver {res_name}')

                # At the moment, we stuff the resolver's datacenter into the association
                # ID for this watch. The ResourceFetcher relies on that.

                consul_watches.append(
                    {
                        "id": resolver.datacenter,
                        "consul-address": resolver.address,
                        "datacenter": resolver.datacenter,
                        "service-name": svc.hostname
                    }
                )
Example #7
0
    def load_yaml(self, yaml_stream):
        self.aconf = Config()

        fetcher = ResourceFetcher(self.logger, self.aconf, watch_only=True)
        fetcher.parse_watt(yaml_stream.read())

        self.aconf.load_all(fetcher.sorted())

        # We can lift mappings straight from the aconf...
        mappings = self.aconf.get_config('mappings') or {}

        # ...but we need the fake IR to deal with resolvers and TLS contexts.
        self.fake = FakeIR(self.aconf, logger=self.logger)

        self.logger.debug("IR: %s" % self.fake.as_json())

        resolvers = self.fake.resolvers
        contexts = self.fake.tls_contexts

        self.logger.debug(f'mappings: {len(mappings)}')
        self.logger.debug(f'resolvers: {len(resolvers)}')
        self.logger.debug(f'contexts: {len(contexts)}')

        global_resolver = self.fake.ambassador_module.get('resolver', None)

        global_label_selector = os.environ.get('AMBASSADOR_LABEL_SELECTOR', '')
        self.logger.debug('label-selector: %s' % global_label_selector)

        # watch the AES Secret if the edge stack is running
        if self.fake.edge_stack_allowed:
            aes_secret_name = os.getenv(ENV_AES_SECRET_NAME, DEFAULT_AES_SECRET_NAME)
            aes_secret_namespace = os.getenv(ENV_AES_SECRET_NAMESPACE, Config.ambassador_namespace)
            self.logger.debug(f'edge stack detected: need secret {aes_secret_name}.{aes_secret_namespace}')
            self.add_kube_watch(f'Secret {aes_secret_name}', 'secret', namespace=aes_secret_namespace,
                                field_selector=f"metadata.name={aes_secret_name}")

        # Walk hosts.
        for host in self.fake.get_hosts():
            sel = host.get('selector') or {}
            match_labels = sel.get('matchLabels') or {}

            label_selectors: List[str] = []

            if global_label_selector:
                label_selectors.append(global_label_selector)

            if match_labels:
                label_selectors += [ f"{l}={v}" for l, v in match_labels.items() ]

            label_selector = ','.join(label_selectors) if label_selectors else None

            for wanted_kind in ['service', 'secret']:
                self.add_kube_watch(f"Host {host.name}", wanted_kind, host.namespace,
                                    label_selector=label_selector)

        for mname, mapping in mappings.items():
            res_name = mapping.get('resolver', None)
            res_source = 'mapping'

            if not res_name:
                res_name = global_resolver
                res_source = 'defaults'

            ctx_name = mapping.get('tls', None)

            self.logger.debug(
                f'Mapping {mname}: resolver {res_name} from {res_source}, service {mapping.service}, tls {ctx_name}')

            if res_name:
                resolver = resolvers.get(res_name, None)
                self.logger.debug(f'-> resolver {resolver}')

                if resolver:
                    svc = Service(logger, mapping.service, ctx_name)

                    if resolver.kind == 'ConsulResolver':
                        self.logger.debug(f'Mapping {mname} uses Consul resolver {res_name}')

                        # At the moment, we stuff the resolver's datacenter into the association
                        # ID for this watch. The ResourceFetcher relies on that.

                        self.consul_watches.append(
                            {
                                "id": resolver.datacenter,
                                "consul-address": resolver.address,
                                "datacenter": resolver.datacenter,
                                "service-name": svc.hostname
                            }
                        )
                    elif resolver.kind == 'KubernetesEndpointResolver':
                        host = svc.hostname
                        namespace = Config.ambassador_namespace

                        if not host:
                            # This is really kind of impossible.
                            self.logger.error(f"KubernetesEndpointResolver {res_name} has no 'hostname'")
                            continue

                        if "." in host:
                            (host, namespace) = host.split(".", 2)[0:2]

                        self.logger.debug(f'...kube endpoints: svc {svc.hostname} -> host {host} namespace {namespace}')

                        self.add_kube_watch(f"endpoint", "endpoints", namespace,
                                            label_selector=global_label_selector,
                                            field_selector=f"metadata.name={host}")

        for secret_key, secret_info in self.fake.secret_recorder.needed.items():
            self.logger.debug(f'need secret {secret_info.name}.{secret_info.namespace}')

            self.add_kube_watch(f"needed secret", "secret", secret_info.namespace,
                                label_selector=global_label_selector,
                                field_selector=f"metadata.name={secret_info.name}")

        if self.fake.edge_stack_allowed:
            # If the edge stack is allowed, make sure we watch for our fallback context.
            self.add_kube_watch("Fallback TLSContext", "TLSContext", namespace=Config.ambassador_namespace)

        ambassador_basedir = os.environ.get('AMBASSADOR_CONFIG_BASE_DIR', '/ambassador')

        if os.path.exists(os.path.join(ambassador_basedir, '.ambassadorinstallations_ok')):
            self.add_kube_watch("AmbassadorInstallations", "ambassadorinstallations.getambassador.io", Config.ambassador_namespace)

        ambassador_knative_requested = (os.environ.get("AMBASSADOR_KNATIVE_SUPPORT", "-unset-").lower() == 'true')

        if ambassador_knative_requested:
            self.logger.debug('Looking for Knative support...')

            if os.path.exists(os.path.join(ambassador_basedir, '.knative_clusteringress_ok')):
                # Watch for clusteringresses.networking.internal.knative.dev in any namespace and with any labels.

                self.logger.debug('watching for clusteringresses.networking.internal.knative.dev')
                self.add_kube_watch("Knative clusteringresses", "clusteringresses.networking.internal.knative.dev",
                                    None)

            if os.path.exists(os.path.join(ambassador_basedir, '.knative_ingress_ok')):
                # Watch for ingresses.networking.internal.knative.dev in any namespace and
                # with any labels.

                self.add_kube_watch("Knative ingresses", "ingresses.networking.internal.knative.dev", None)

        self.watchset = {
            "kubernetes-watches": self.kube_watches,
            "consul-watches": self.consul_watches
        }

        save_dir = os.environ.get('AMBASSADOR_WATCH_DIR', '/tmp')

        if save_dir:
            watchset = dump_json(self.watchset)
            with open(os.path.join(save_dir, 'watch.json'), "w") as output:
                output.write(watchset)