diff --git a/cmd/ambex/main.go b/cmd/ambex/main.go index e39caa2740f..d20df2e34b8 100644 --- a/cmd/ambex/main.go +++ b/cmd/ambex/main.go @@ -64,10 +64,15 @@ import ( "github.com/datawire/ambassador/pkg/envoy-control-plane/cache" "github.com/datawire/ambassador/pkg/envoy-control-plane/server" - // envoy protobuf + // envoy protobuf -- Be sure to import the package of any types that the Python + // emits a "@type" of in the generated config, even if that package is otherwise + // not used by ambex. v2 "github.com/datawire/ambassador/pkg/api/envoy/api/v2" + _ "github.com/datawire/ambassador/pkg/api/envoy/api/v2/auth" core "github.com/datawire/ambassador/pkg/api/envoy/api/v2/core" + _ "github.com/datawire/ambassador/pkg/api/envoy/config/accesslog/v2" bootstrap "github.com/datawire/ambassador/pkg/api/envoy/config/bootstrap/v2" + _ "github.com/datawire/ambassador/pkg/api/envoy/config/filter/network/http_connection_manager/v2" discovery "github.com/datawire/ambassador/pkg/api/envoy/service/discovery/v2" ) diff --git a/python/ambassador/envoy/v2/v2bootstrap.py b/python/ambassador/envoy/v2/v2bootstrap.py index 374059eb923..c0ef3460b67 100644 --- a/python/ambassador/envoy/v2/v2bootstrap.py +++ b/python/ambassador/envoy/v2/v2bootstrap.py @@ -32,7 +32,27 @@ def __init__(self, config: 'V2Config') -> None: "cds_config": { "ads": {} }, "lds_config": { "ads": {} } }, - "admin": dict(config.admin) + "admin": dict(config.admin), + 'layered_runtime': { + 'layers': [ + { + 'name': 'static_layer', + 'static_layer': { + # For now, we enable the deprecated & disallowed_by_default "HTTP_JSON_V1" Zipkin + # collector_endpoint_version because it repesents the Zipkin v1 API, while the + # non-deprecated options HTTP_JSON and HTTP_PROTO are the Zipkin v2 API; switching + # top one of them would change how Envoy talks to the outside world. + 'envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1': True, + # Give our users more time to migrate to v2; we've said that we'll continue + # supporting both for a while even after we change the default. + 'envoy.deprecated_features:envoy.config.filter.http.ext_authz.v2.ExtAuthz.use_alpha': True, + # We haven't yet told users that we'll be deprecating `regex_type: unsafe`. + 'envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex': True, # HTTP path + 'envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match': True # HTTP header + } + } + ] + } }) clusters = [{ diff --git a/python/ambassador/envoy/v2/v2cluster.py b/python/ambassador/envoy/v2/v2cluster.py index fff93c25eab..6fb4c3ea62c 100644 --- a/python/ambassador/envoy/v2/v2cluster.py +++ b/python/ambassador/envoy/v2/v2cluster.py @@ -74,13 +74,20 @@ def __init__(self, config: 'V2Config', cluster: IRCluster) -> None: # minimal `tls_context` to enable HTTPS origination. if ctx.get('_ambassador_enabled', False): - fields['tls_context'] = { + envoy_ctx = { 'common_tls_context': {} } else: envoy_ctx = V2TLSContext(ctx=ctx, host_rewrite=cluster.get('host_rewrite', None)) - if envoy_ctx: - fields['tls_context'] = envoy_ctx + + if envoy_ctx: + fields['transport_socket'] = { + 'name': 'envoy.transport_sockets.tls', + 'typed_config': { + '@type': 'type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext', + **envoy_ctx + } + } keepalive = cluster.get('keepalive', None) diff --git a/python/ambassador/envoy/v2/v2config.py b/python/ambassador/envoy/v2/v2config.py index 1cba371f9a7..014564e2d03 100644 --- a/python/ambassador/envoy/v2/v2config.py +++ b/python/ambassador/envoy/v2/v2config.py @@ -56,9 +56,11 @@ def __init__(self, ir: IR) -> None: V2Bootstrap.generate(self) def as_dict(self) -> Dict[str, Any]: + bootstrap_config, ads_config = self.split_config() + d = { - 'bootstrap': self.bootstrap, - 'static_resources': self.static_resources + 'bootstrap': bootstrap_config, + **ads_config } return d @@ -66,7 +68,27 @@ def as_dict(self) -> Dict[str, Any]: def split_config(self) -> Tuple[Dict[str, Any], Dict[str, Any]]: ads_config = { '@type': '/envoy.config.bootstrap.v2.Bootstrap', - 'static_resources': self.static_resources + 'static_resources': self.static_resources, + 'layered_runtime': { + 'layers': [ + { + 'name': 'static_layer', + 'static_layer': { + # For now, we enable the deprecated & disallowed_by_default "HTTP_JSON_V1" Zipkin + # collector_endpoint_version because it repesents the Zipkin v1 API, while the + # non-deprecated options HTTP_JSON and HTTP_PROTO are the Zipkin v2 API; switching + # top one of them would change how Envoy talks to the outside world. + 'envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1': True, + # Give our users more time to migrate to v2; we've said that we'll continue + # supporting both for a while even after we change the default. + 'envoy.deprecated_features:envoy.config.filter.http.ext_authz.v2.ExtAuthz.use_alpha': True, + # We haven't yet told users that we'll be deprecating `regex_type: unsafe`. + 'envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex': True, # HTTP path + 'envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match': True # HTTP header + } + } + ] + } } bootstrap_config = dict(self.bootstrap) diff --git a/python/ambassador/envoy/v2/v2listener.py b/python/ambassador/envoy/v2/v2listener.py index 7d8f3463d63..8654fb1622f 100644 --- a/python/ambassador/envoy/v2/v2listener.py +++ b/python/ambassador/envoy/v2/v2listener.py @@ -662,6 +662,7 @@ def __init__(self, config: 'V2Config', service_port: int) -> None: self.first_vhost: Optional[V2VirtualHost] = None self.http_filters: List[dict] = [] self.listener_filters: List[dict] = [] + self.traffic_direction: str = "UNSPECIFIED" self.config.ir.logger.debug(f"V2Listener {self.name} created") @@ -731,7 +732,8 @@ def __init__(self, config: 'V2Config', service_port: int) -> None: self.access_log.append({ 'name': 'envoy.file_access_log', - 'config': { + 'typed_config': { + '@type': 'type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog', 'path': self.config.ir.ambassador_module.envoy_log_path, 'json_format': json_format } @@ -746,7 +748,8 @@ def __init__(self, config: 'V2Config', service_port: int) -> None: self.config.ir.logger.debug("V2Listener: Using log_format '%s'" % log_format) self.access_log.append({ 'name': 'envoy.file_access_log', - 'config': { + 'typed_config': { + '@type': 'type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog', 'path': self.config.ir.ambassador_module.envoy_log_path, 'format': log_format + '\n' } @@ -788,9 +791,8 @@ def __init__(self, config: 'V2Config', service_port: int) -> None: if self.config.ir.tracing: self.base_http_config["generate_request_id"] = True - self.base_http_config["tracing"] = { - "operation_name": "egress" - } + self.base_http_config["tracing"] = {} + self.traffic_direction = "OUTBOUND" req_hdrs = self.config.ir.tracing.get('tag_headers', []) @@ -886,7 +888,10 @@ def finalize(self) -> None: filter_chain["filters"] = [ { "name": "envoy.http_connection_manager", - "config": http_config + "typed_config": { + "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager", + **http_config + } } ] @@ -903,7 +908,8 @@ def as_dict(self) -> dict: "name": self.name, "address": self.address, "filter_chains": self.filter_chains, - "listener_filters": self.listener_filters + "listener_filters": self.listener_filters, + "traffic_direction": self.traffic_direction } def pretty(self) -> dict: diff --git a/python/ambassador/envoy/v2/v2route.py b/python/ambassador/envoy/v2/v2route.py index fe7986b4458..e9dc60370e4 100644 --- a/python/ambassador/envoy/v2/v2route.py +++ b/python/ambassador/envoy/v2/v2route.py @@ -120,10 +120,14 @@ def __init__(self, config: 'V2Config', group: IRHTTPMappingGroup, mapping: IRBas request_headers_to_remove = group.get('remove_request_headers', None) if request_headers_to_remove: + if type(request_headers_to_remove) != list: + request_headers_to_remove = [ request_headers_to_remove ] self['request_headers_to_remove'] = request_headers_to_remove response_headers_to_remove = group.get('remove_response_headers', None) if response_headers_to_remove: + if type(response_headers_to_remove) != list: + response_headers_to_remove = [ response_headers_to_remove ] self['response_headers_to_remove'] = response_headers_to_remove host_redirect = group.get('host_redirect', None) diff --git a/python/ambassador/ir/ircors.py b/python/ambassador/ir/ircors.py index d553bbb4075..e0d5c5a7e5e 100644 --- a/python/ambassador/ir/ircors.py +++ b/python/ambassador/ir/ircors.py @@ -28,18 +28,15 @@ def __init__(self, ir: 'IR', aconf: Config, def setup(self, ir: 'IR', aconf: Config) -> bool: # 'origins' cannot be treated like other keys, because if it's a # list, then it remains as is, but if it's a string, then it's - # converted to a list + # converted to a list. It has already been validated by the + # JSON schema to either be a string or a list of strings. origins = self.pop('origins', None) if origins is not None: - if type(origins) is list: - self.allow_origin = origins - elif type(origins) is str: - self.allow_origin = origins.split(',') - else: - self.post_error(RichStatus.fromError("invalid CORS origin - {}".format(origins), - module=self)) - return False + if type(origins) is not list: + origins = origins.split(',') + + self.allow_origin_string_match = [{'exact': origin} for origin in origins] for from_key, to_key in [ ( 'max_age', 'max_age' ), ( 'credentials', 'allow_credentials' ), diff --git a/python/tests/t_listeneridletimeout.py b/python/tests/t_listeneridletimeout.py index 65cee147868..ed4b3e3d277 100644 --- a/python/tests/t_listeneridletimeout.py +++ b/python/tests/t_listeneridletimeout.py @@ -32,7 +32,7 @@ def queries(self): yield Query(self.url("config_dump"), phase=2) def check(self): - expected_val = '30.000s' + expected_val = '30s' actual_val = '' body = json.loads(self.results[0].body) for config_obj in body.get('configs'): @@ -45,7 +45,7 @@ def check(self): for filters in filter_chains: for filter in filters.get('filters'): if filter.get('name') == 'envoy.http_connection_manager': - filter_config = filter.get('config') + filter_config = filter.get('typed_config') common_http_protocol_options = filter_config.get('common_http_protocol_options') if common_http_protocol_options: actual_val = common_http_protocol_options.get('idle_timeout', '') @@ -56,4 +56,4 @@ def check(self): assert False, "Expected to find common_http_protocol_options.idle_timeout property on listener" else: assert False, "Expected to find common_http_protocol_options property on listener" - assert found_idle_timeout, "Expected common_http_protocol_options.idle_timeout = {}, Got common_http_protocol_options.idle_timeout = {}".format(expected_val, actual_val) \ No newline at end of file + assert found_idle_timeout, "Expected common_http_protocol_options.idle_timeout = {}, Got common_http_protocol_options.idle_timeout = {}".format(expected_val, actual_val) diff --git a/python/tests/t_logservice.py b/python/tests/t_logservice.py index 86ec289a12e..f301483963d 100644 --- a/python/tests/t_logservice.py +++ b/python/tests/t_logservice.py @@ -123,7 +123,7 @@ def check(self): for dal in config_obj.get('dynamic_active_listeners'): for filter_chain in dal.get('listener').get('filter_chains'): for filter_obj in filter_chain.get('filters'): - access_logs = filter_obj.get('config').get('access_log') + access_logs = filter_obj.get('typed_config').get('access_log') found_configured_access_log = False for access_log in access_logs: if access_log.get('name') == 'envoy.http_grpc_access_log' and access_log.get('config').get('common_config').get('grpc_service').get('envoy_grpc').get('cluster_name') == 'cluster_logging_stenography_25565_default':