diff --git a/src/envoy/mixer/BUILD b/src/envoy/mixer/BUILD index 917e627e073..d04b5105029 100644 --- a/src/envoy/mixer/BUILD +++ b/src/envoy/mixer/BUILD @@ -17,15 +17,28 @@ load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar") load("@bazel_tools//tools/build_defs/docker:docker.bzl", "docker_build") +load("@protobuf_git//:protobuf.bzl", "cc_proto_library") + +cc_proto_library( + name = "string_map_proto", + srcs = ["string_map.proto"], + default_runtime = "//external:protobuf", + protoc = "//external:protoc", + visibility = ["//visibility:public"], +) cc_library( name = "filter_lib", srcs = [ + "forward_attribute_filter.cc", "http_control.cc", "http_control.h", "http_filter.cc", + "utils.cc", + "utils.h", ], deps = [ + ":string_map_proto", "//external:mixer_client_lib", "@envoy_git//:envoy-common", ], diff --git a/src/envoy/mixer/README.md b/src/envoy/mixer/README.md index 5bf2198b30d..d7d39f30e03 100644 --- a/src/envoy/mixer/README.md +++ b/src/envoy/mixer/README.md @@ -49,3 +49,51 @@ This Proxy will use Envoy and talk to Mixer server. curl http://localhost:9090/echo -d "hello world" ``` +## How to configurate HTTP filters + +This module has two HTTP filters: +1. mixer filter: intercept all HTTP requests, call the mixer. +2. forward_attribute filter: Forward attributes to the upstream istio/proxy. + +### *mixer* filter: + +This filter will intercept all HTTP requests and call Mixer. Here is its config: + +``` + "filters": [ + "type": "both", + "name": "mixer", + "config": { + "mixer_server": "${MIXER_SERVER}", + "attributes" : { + "attribute_name1": "attribute_value1", + "attribute_name2": "attribute_value2" + } + } +``` + +Notes: +* mixer_server is required +* attributes: these attributes will be send to the mixer + +### *forward_attribute* HTTP filter: + +This filer will forward attributes to the upstream istio/proxy. + +``` + "filters": [ + "type": "decoder", + "name": "forward_attribute", + "config": { + "attributes": { + "attribute_name1": "attribute_value1", + "attribute_name2": "attribute_value2" + } + } +``` + +Notes: +* attributes: these attributes will be forwarded to the upstream istio/proxy. + + + diff --git a/src/envoy/mixer/envoy.conf.template b/src/envoy/mixer/envoy.conf.template index f1a00d1fb3e..c5377632388 100644 --- a/src/envoy/mixer/envoy.conf.template +++ b/src/envoy/mixer/envoy.conf.template @@ -35,7 +35,62 @@ "type": "both", "name": "mixer", "config": { - "mixer_server": "${MIXER_SERVER}" + "mixer_server": "${MIXER_SERVER}", + "attributes": { + "target.uid": "POD222", + "target.namespace": "XYZ222" + } + } + }, + { + "type": "decoder", + "name": "router", + "config": {} + } + ] + } + } + ] + }, + { + "port": 7070, + "bind_to_port": true, + "filters": [ + { + "type": "read", + "name": "http_connection_manager", + "config": { + "codec_type": "auto", + "stat_prefix": "ingress_http", + "route_config": { + "virtual_hosts": [ + { + "name": "backend", + "domains": ["*"], + "routes": [ + { + "timeout_ms": 0, + "prefix": "/", + "cluster": "service2" + } + ] + } + ] + }, + "access_log": [ + { + "path": "/dev/stdout" + } + ], + "filters": [ + { + "type": "decoder", + "name": "forward_attribute", + "config": { + "attributes": { + "source.uid": "POD11", + "source.namespace": "XYZ11" + } } }, { @@ -65,6 +120,17 @@ "url": "tcp://${BACKEND}" } ] + }, + { + "name": "service2", + "connect_timeout_ms": 5000, + "type": "strict_dns", + "lb_type": "round_robin", + "hosts": [ + { + "url": "tcp://localhost:9090" + } + ] } ] } diff --git a/src/envoy/mixer/forward_attribute_filter.cc b/src/envoy/mixer/forward_attribute_filter.cc new file mode 100644 index 00000000000..08d58916f89 --- /dev/null +++ b/src/envoy/mixer/forward_attribute_filter.cc @@ -0,0 +1,114 @@ +/* Copyright 2017 Istio Authors. 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. + */ + +#include "precompiled/precompiled.h" + +#include "common/common/base64.h" +#include "common/common/logger.h" +#include "common/http/headers.h" +#include "common/http/utility.h" +#include "envoy/server/instance.h" +#include "server/config/network/http_connection_manager.h" +#include "src/envoy/mixer/utils.h" + +namespace Http { +namespace ForwardAttribute { +namespace { + +// The Json object name to specify attributes which will be forwarded +// to the upstream istio proxy. +const std::string kJsonNameAttributes("attributes"); + +} // namespace + +class Config : public Logger::Loggable { + private: + std::string attributes_; + + public: + Config(const Json::Object& config) { + Utils::StringMap attributes = + Utils::ExtractStringMap(config, kJsonNameAttributes); + if (!attributes.empty()) { + std::string serialized_str = Utils::SerializeStringMap(attributes); + attributes_ = + Base64::encode(serialized_str.c_str(), serialized_str.size()); + } + } + + const std::string& attributes() const { return attributes_; } +}; + +typedef std::shared_ptr ConfigPtr; + +class ForwardAttributeFilter : public Http::StreamDecoderFilter { + private: + ConfigPtr config_; + + public: + ForwardAttributeFilter(ConfigPtr config) : config_(config) {} + + FilterHeadersStatus decodeHeaders(HeaderMap& headers, + bool end_stream) override { + if (!config_->attributes().empty()) { + headers.addStatic(Utils::kIstioAttributeHeader, config_->attributes()); + } + return FilterHeadersStatus::Continue; + } + + FilterDataStatus decodeData(Buffer::Instance& data, + bool end_stream) override { + return FilterDataStatus::Continue; + } + + FilterTrailersStatus decodeTrailers(HeaderMap& trailers) override { + return FilterTrailersStatus::Continue; + } + + void setDecoderFilterCallbacks( + StreamDecoderFilterCallbacks& callbacks) override {} +}; + +} // namespace ForwardAttribute +} // namespace Http + +namespace Server { +namespace Configuration { + +class ForwardAttributeConfig : public HttpFilterConfigFactory { + public: + HttpFilterFactoryCb tryCreateFilterFactory( + HttpFilterType type, const std::string& name, const Json::Object& config, + const std::string&, Server::Instance& server) override { + if (type != HttpFilterType::Decoder || name != "forward_attribute") { + return nullptr; + } + + Http::ForwardAttribute::ConfigPtr add_header_config( + new Http::ForwardAttribute::Config(config)); + return [add_header_config]( + Http::FilterChainFactoryCallbacks& callbacks) -> void { + std::shared_ptr instance( + new Http::ForwardAttribute::ForwardAttributeFilter( + add_header_config)); + callbacks.addStreamDecoderFilter(Http::StreamDecoderFilterPtr(instance)); + }; + } +}; + +static RegisterHttpFilterConfigFactory register_; + +} // namespace Configuration +} // namespace server diff --git a/src/envoy/mixer/http_control.cc b/src/envoy/mixer/http_control.cc index bbff6b94992..7e1f7deffcd 100644 --- a/src/envoy/mixer/http_control.cc +++ b/src/envoy/mixer/http_control.cc @@ -1,4 +1,5 @@ -/* +/* Copyright 2017 Istio Authors. 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 @@ -13,9 +14,14 @@ */ #include "src/envoy/mixer/http_control.h" + +#include "common/common/base64.h" #include "common/common/utility.h" #include "common/http/utility.h" +#include "src/envoy/mixer/string_map.pb.h" +#include "src/envoy/mixer/utils.h" + using ::google::protobuf::util::Status; using ::istio::mixer_client::Attributes; using ::istio::mixer_client::DoneFunc; @@ -77,7 +83,8 @@ std::string GetLastForwardedHost(const HeaderMap& header_map) { if (entry == nullptr) { return ""; } - auto xff_list = StringUtil::split(entry->value().c_str(), ','); + std::vector xff_list = + StringUtil::split(entry->value().c_str(), ','); if (xff_list.empty()) { return ""; } @@ -89,7 +96,7 @@ void FillRequestHeaderAttributes(const HeaderMap& header_map, // Pass in all headers header_map.iterate( [](const HeaderEntry& header, void* context) { - auto attr = static_cast(context); + Attributes* attr = static_cast(context); attr->attributes[kRequestHeaderPrefix + header.key().c_str()] = StringValue(header.value().c_str()); }, @@ -106,7 +113,7 @@ void FillResponseHeaderAttributes(const HeaderMap& header_map, Attributes* attr) { header_map.iterate( [](const HeaderEntry& header, void* context) { - auto attr = static_cast(context); + Attributes* attr = static_cast(context); attr->attributes[kResponseHeaderPrefix + header.key().c_str()] = StringValue(header.value().c_str()); }, @@ -144,8 +151,19 @@ HttpControl::HttpControl(const std::string& mixer_server, mixer_client_ = ::istio::mixer_client::CreateMixerClient(options); } -void HttpControl::FillCheckAttributes(const HeaderMap& header_map, - Attributes* attr) { +void HttpControl::FillCheckAttributes(HeaderMap& header_map, Attributes* attr) { + // Extract attributes from x-istio-attributes header + const HeaderEntry* entry = header_map.get(Utils::kIstioAttributeHeader); + if (entry) { + ::istio::proxy::mixer::StringMap pb; + std::string str(entry->value().c_str(), entry->value().size()); + pb.ParseFromString(Base64::decode(str)); + for (const auto& it : pb.map()) { + SetStringAttribute(it.first, it.second, attr); + } + header_map.remove(Utils::kIstioAttributeHeader); + } + FillRequestHeaderAttributes(header_map, attr); for (const auto& attribute : config_attributes_) { diff --git a/src/envoy/mixer/http_control.h b/src/envoy/mixer/http_control.h index 65863d950fa..c5938cf369d 100644 --- a/src/envoy/mixer/http_control.h +++ b/src/envoy/mixer/http_control.h @@ -1,4 +1,4 @@ -/* Copyright 2016 Google Inc. All Rights Reserved. +/* Copyright 2017 Istio Authors. 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. @@ -51,7 +51,7 @@ class HttpControl final : public Logger::Loggable { ::istio::mixer_client::DoneFunc on_done); private: - void FillCheckAttributes(const HeaderMap& header_map, + void FillCheckAttributes(HeaderMap& header_map, ::istio::mixer_client::Attributes* attr); // The mixer client diff --git a/src/envoy/mixer/http_filter.cc b/src/envoy/mixer/http_filter.cc index d0796b506dd..a990b0fd7e3 100644 --- a/src/envoy/mixer/http_filter.cc +++ b/src/envoy/mixer/http_filter.cc @@ -1,4 +1,4 @@ -/* Copyright 2016 Google Inc. All Rights Reserved. +/* Copyright 2017 Istio Authors. 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. @@ -21,6 +21,7 @@ #include "envoy/server/instance.h" #include "server/config/network/http_connection_manager.h" #include "src/envoy/mixer/http_control.h" +#include "src/envoy/mixer/utils.h" using ::google::protobuf::util::Status; using StatusCode = ::google::protobuf::util::error::Code; @@ -30,8 +31,11 @@ namespace Http { namespace Mixer { namespace { -// Define lower case string for X-Forwarded-Host. -const LowerCaseString kHeaderNameXFH("x-forwarded-host", false); +// The Json object name for mixer-server. +const std::string kJsonNameMixerServer("mixer_server"); + +// The Json object name for static attributes. +const std::string kJsonNameMixerAttributes("attributes"); // Convert Status::code to HTTP code int HttpCode(int code) { @@ -88,20 +92,16 @@ class Config : public Logger::Loggable { Config(const Json::Object& config, Server::Instance& server) : cm_(server.clusterManager()) { std::string mixer_server; - if (config.hasObject("mixer_server")) { - mixer_server = config.getString("mixer_server"); + if (config.hasObject(kJsonNameMixerServer)) { + mixer_server = config.getString(kJsonNameMixerServer); } else { log().error( "mixer_server is required but not specified in the config: {}", __func__); } - std::map attributes; - if (config.hasObject("attributes")) { - for (const std::string& attr : config.getStringArray("attributes")) { - attributes[attr] = config.getString(attr); - } - } + std::map attributes = + Utils::ExtractStringMap(config, kJsonNameMixerAttributes); http_control_ = std::make_shared(mixer_server, std::move(attributes)); diff --git a/src/envoy/mixer/string_map.proto b/src/envoy/mixer/string_map.proto new file mode 100644 index 00000000000..bd9140c37d7 --- /dev/null +++ b/src/envoy/mixer/string_map.proto @@ -0,0 +1,23 @@ +// Copyright 2017 Istio Authors. +// +// 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. + +syntax = "proto3"; + +package istio.proxy.mixer; + +// A message with a map of string to string. It is used to serialize +// a string map. +message StringMap { + map map = 1; +} diff --git a/src/envoy/mixer/utils.cc b/src/envoy/mixer/utils.cc new file mode 100644 index 00000000000..6dc3a78356d --- /dev/null +++ b/src/envoy/mixer/utils.cc @@ -0,0 +1,50 @@ +/* Copyright 2017 Istio Authors. 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. + */ + +#include "src/envoy/mixer/utils.h" +#include "src/envoy/mixer/string_map.pb.h" + +namespace Http { +namespace Utils { + +const LowerCaseString kIstioAttributeHeader("x-istio-attributes"); + +StringMap ExtractStringMap(const Json::Object& json, const std::string& name) { + StringMap map; + if (json.hasObject(name)) { + Json::ObjectPtr json_obj = json.getObject(name); + Json::Object* raw_obj = json_obj.get(); + json_obj->iterate( + [&map, raw_obj](const std::string& key, const Json::Object&) -> bool { + map[key] = raw_obj->getString(key); + return true; + }); + } + return map; +} + +std::string SerializeStringMap(const StringMap& string_map) { + ::istio::proxy::mixer::StringMap pb; + ::google::protobuf::Map* map_pb = pb.mutable_map(); + for (const auto& it : string_map) { + (*map_pb)[it.first] = it.second; + } + std::string str; + pb.SerializeToString(&str); + return str; +} + +} // namespace Utils +} // namespace Http diff --git a/src/envoy/mixer/utils.h b/src/envoy/mixer/utils.h new file mode 100644 index 00000000000..9273a9a2d8b --- /dev/null +++ b/src/envoy/mixer/utils.h @@ -0,0 +1,39 @@ +/* Copyright 2017 Istio Authors. 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. + */ + +#pragma once + +#include "precompiled/precompiled.h" + +#include "common/http/headers.h" +#include "envoy/json/json_object.h" + +namespace Http { +namespace Utils { + +// The internal header to pass istio attributes. +extern const LowerCaseString kIstioAttributeHeader; + +// The string map. +typedef std::map StringMap; + +// Extracts name/value attributes from a json object. +StringMap ExtractStringMap(const Json::Object& json, const std::string& name); + +// Serialize a string map to string. +std::string SerializeStringMap(const StringMap& map); + +} // namespace Utils +} // namespace Http