diff --git a/src/envoy/BUILD b/src/envoy/BUILD index 157daef58b5e..a6b243d0bab4 100644 --- a/src/envoy/BUILD +++ b/src/envoy/BUILD @@ -28,6 +28,7 @@ envoy_cc_binary( "//extensions/metadata_exchange:metadata_exchange_lib", "//extensions/stackdriver:stackdriver_plugin", "//extensions/stats:stats_plugin", + "//src/envoy/http/alpn:config_lib", "//src/envoy/http/authn:filter_lib", "//src/envoy/http/jwt_auth:http_filter_factory", "//src/envoy/http/mixer:filter_lib", diff --git a/src/envoy/http/alpn/BUILD b/src/envoy/http/alpn/BUILD new file mode 100644 index 000000000000..519a20336e3f --- /dev/null +++ b/src/envoy/http/alpn/BUILD @@ -0,0 +1,83 @@ +# Copyright 2019 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. +# +################################################################################ +# + +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_cc_test", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "alpn_filter", + srcs = ["alpn_filter.cc"], + hdrs = ["alpn_filter.h"], + repository = "@envoy", + deps = [ + "//external:alpn_filter_config_cc_proto", + "@envoy//include/envoy/http:filter_interface", + "@envoy//source/common/network:application_protocol_lib", + "@envoy//source/extensions/filters/http/common:pass_through_filter_lib", + ], +) + +envoy_cc_library( + name = "config_lib", + srcs = ["config.cc"], + hdrs = ["config.h"], + repository = "@envoy", + visibility = ["//visibility:public"], + deps = [ + ":alpn_filter", + "//src/envoy/utils:filter_names_lib", + "@envoy//include/envoy/registry", + "@envoy//source/exe:envoy_common_lib", + "@envoy//source/extensions/filters/http/common:factory_base_lib", + ], +) + +envoy_cc_test( + name = "alpn_test", + srcs = [ + "alpn_test.cc", + ], + repository = "@envoy", + deps = [ + ":alpn_filter", + ":config_lib", + "@envoy//test/mocks/http:http_mocks", + "@envoy//test/mocks/local_info:local_info_mocks", + "@envoy//test/mocks/network:network_mocks", + "@envoy//test/mocks/protobuf:protobuf_mocks", + ], +) + +envoy_cc_test( + name = "config_test", + srcs = [ + "config_test.cc", + ], + repository = "@envoy", + deps = [ + ":alpn_filter", + ":config_lib", + "@envoy//test/mocks/server:server_mocks", + "@envoy//test/test_common:utility_lib", + ], +) diff --git a/src/envoy/http/alpn/alpn_filter.cc b/src/envoy/http/alpn/alpn_filter.cc new file mode 100644 index 000000000000..405febffae1a --- /dev/null +++ b/src/envoy/http/alpn/alpn_filter.cc @@ -0,0 +1,46 @@ +/* Copyright 2019 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/http/alpn/alpn_filter.h" + +#include "common/network/application_protocol.h" + +namespace Envoy { +namespace Http { +namespace Alpn { + +AlpnFilterConfig::AlpnFilterConfig( + const istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig + &proto_config) + : alpn_override_(proto_config.alpn_override().begin(), + proto_config.alpn_override().end()) {} + +Http::FilterHeadersStatus AlpnFilter::decodeHeaders(Http::HeaderMap &, bool) { + const auto &alpn_override = config_->getAlpnOverride(); + if (!alpn_override.empty()) { + ENVOY_LOG(debug, "override with {} ALPNs", alpn_override.size()); + decoder_callbacks_->streamInfo().filterState().setData( + Network::ApplicationProtocols::key(), + std::make_unique(alpn_override), + Envoy::StreamInfo::FilterState::StateType::ReadOnly); + } else { + ENVOY_LOG(debug, "ALPN override is empty"); + } + return Http::FilterHeadersStatus::Continue; +} + +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/http/alpn/alpn_filter.h b/src/envoy/http/alpn/alpn_filter.h new file mode 100644 index 000000000000..757043e25882 --- /dev/null +++ b/src/envoy/http/alpn/alpn_filter.h @@ -0,0 +1,58 @@ +/* Copyright 2019 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 "envoy/config/filter/http/alpn/v2alpha1/config.pb.h" +#include "extensions/filters/http/common/pass_through_filter.h" + +namespace Envoy { +namespace Http { +namespace Alpn { + +class AlpnFilterConfig { + public: + AlpnFilterConfig() = default; + explicit AlpnFilterConfig( + const istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig + &proto_config); + + const std::vector &getAlpnOverride() const { + return alpn_override_; + } + + private: + const std::vector alpn_override_; +}; + +using AlpnFilterConfigSharedPtr = std::shared_ptr; + +class AlpnFilter : public Http::PassThroughDecoderFilter, + Logger::Loggable { + public: + explicit AlpnFilter(const AlpnFilterConfigSharedPtr &config) + : config_(config) {} + + // Http::PassThroughDecoderFilter + Http::FilterHeadersStatus decodeHeaders(Http::HeaderMap &headers, + bool end_stream) override; + + private: + const AlpnFilterConfigSharedPtr config_; +}; + +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/http/alpn/alpn_test.cc b/src/envoy/http/alpn/alpn_test.cc new file mode 100644 index 000000000000..4a354604630b --- /dev/null +++ b/src/envoy/http/alpn/alpn_test.cc @@ -0,0 +1,88 @@ +/* Copyright 2019 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 "common/network/application_protocol.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "src/envoy/http/alpn/alpn_filter.h" +#include "test/mocks/http/mocks.h" + +using istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Http { +namespace Alpn { +namespace { + +class AlpnFilterTest : public testing::Test { + public: + std::unique_ptr makeDefaultFilter() { + auto default_config = std::make_shared(); + auto filter = std::make_unique(default_config); + filter->setDecoderFilterCallbacks(callbacks_); + return filter; + } + + std::unique_ptr makeAlpnOverrideFilter( + const std::vector &alpn) { + FilterConfig proto_config; + for (const auto &protocol : alpn) { + proto_config.add_alpn_override(protocol); + } + auto config = std::make_shared(proto_config); + auto filter = std::make_unique(config); + filter->setDecoderFilterCallbacks(callbacks_); + return filter; + } + + protected: + NiceMock callbacks_; + Http::TestHeaderMapImpl headers_; +}; + +TEST_F(AlpnFilterTest, NoAlpnOverride) { + NiceMock stream_info; + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + auto filter = makeDefaultFilter(); + EXPECT_CALL(stream_info, filterState()).Times(0); + EXPECT_EQ(filter->decodeHeaders(headers_, false), + Http::FilterHeadersStatus::Continue); +} + +TEST_F(AlpnFilterTest, OverrideAlpn) { + NiceMock stream_info; + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + std::vector alpn{"foo", "bar", "baz"}; + auto filter = makeAlpnOverrideFilter(alpn); + Envoy::StreamInfo::FilterStateImpl filter_state; + EXPECT_CALL(stream_info, filterState()).WillOnce(ReturnRef(filter_state)); + EXPECT_EQ(filter->decodeHeaders(headers_, false), + Http::FilterHeadersStatus::Continue); + EXPECT_TRUE(filter_state.hasData( + Network::ApplicationProtocols::key())); + auto alpn_override = filter_state + .getDataReadOnly( + Network::ApplicationProtocols::key()) + .value(); + EXPECT_EQ(alpn_override, alpn); +} + +} // namespace +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/http/alpn/config.cc b/src/envoy/http/alpn/config.cc new file mode 100644 index 000000000000..2ee868d26bf0 --- /dev/null +++ b/src/envoy/http/alpn/config.cc @@ -0,0 +1,67 @@ +/* Copyright 2019 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/http/alpn/config.h" + +#include "common/protobuf/message_validator_impl.h" +#include "src/envoy/http/alpn/alpn_filter.h" +#include "src/envoy/utils/filter_names.h" + +using istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig; + +namespace Envoy { +namespace Http { +namespace Alpn { + +Http::FilterFactoryCb AlpnConfigFactory::createFilterFactory( + const Json::Object &config, const std::string &, + Server::Configuration::FactoryContext &) { + FilterConfig filter_config; + MessageUtil::loadFromJson(config.asJsonString(), filter_config, + ProtobufMessage::getNullValidationVisitor()); + return createFilterFactory(filter_config); +} + +Http::FilterFactoryCb AlpnConfigFactory::createFilterFactoryFromProto( + const Protobuf::Message &config, const std::string &, + Server::Configuration::FactoryContext &) { + return createFilterFactory(dynamic_cast(config)); +} + +ProtobufTypes::MessagePtr AlpnConfigFactory::createEmptyConfigProto() { + return ProtobufTypes::MessagePtr{new FilterConfig}; +} + +std::string AlpnConfigFactory::name() { return Utils::IstioFilterName::kAlpn; } + +Http::FilterFactoryCb AlpnConfigFactory::createFilterFactory( + const FilterConfig &proto_config) { + AlpnFilterConfigSharedPtr filter_config{ + std::make_shared(proto_config)}; + return [filter_config](Http::FilterChainFactoryCallbacks &callbacks) -> void { + callbacks.addStreamDecoderFilter( + std::make_unique(filter_config)); + }; +} + +/** + * Static registration for the alpn override filter. @see RegisterFactory. + */ +REGISTER_FACTORY(AlpnConfigFactory, + Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/http/alpn/config.h b/src/envoy/http/alpn/config.h new file mode 100644 index 000000000000..93d0136f9b1b --- /dev/null +++ b/src/envoy/http/alpn/config.h @@ -0,0 +1,49 @@ +/* Copyright 2019 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 "envoy/config/filter/http/alpn/v2alpha1/config.pb.h" +#include "extensions/filters/http/common/factory_base.h" + +namespace Envoy { +namespace Http { +namespace Alpn { + +/** + * Config registration for the alpn filter. + */ +class AlpnConfigFactory + : public Server::Configuration::NamedHttpFilterConfigFactory { + public: + // Server::Configuration::NamedHttpFilterConfigFactory + Http::FilterFactoryCb createFilterFactory( + const Json::Object &config, const std::string &stat_prefix, + Server::Configuration::FactoryContext &context) override; + Http::FilterFactoryCb createFilterFactoryFromProto( + const Protobuf::Message &config, const std::string &stat_prefix, + Server::Configuration::FactoryContext &context) override; + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + std::string name() override; + + private: + Http::FilterFactoryCb createFilterFactory( + const istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig + &config_pb); +}; + +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/http/alpn/config_test.cc b/src/envoy/http/alpn/config_test.cc new file mode 100644 index 000000000000..76ddb6b88eca --- /dev/null +++ b/src/envoy/http/alpn/config_test.cc @@ -0,0 +1,64 @@ +/* Copyright 2019 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/http/alpn/config.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "src/envoy/http/alpn/alpn_filter.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/utility.h" + +using testing::_; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +using istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig; + +namespace Envoy { +namespace Http { +namespace Alpn { +namespace { + +TEST(AlpnFilterConfigTest, OverrideAlpn) { + const std::string yaml = R"EOF( + alpn_override: ["foo", "bar"] + )EOF"; + + FilterConfig proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + AlpnConfigFactory factory; + NiceMock context; + Http::FilterFactoryCb cb = + factory.createFilterFactoryFromProto(proto_config, "stats", context); + Http::MockFilterChainFactoryCallbacks filter_callback; + Http::StreamDecoderFilterSharedPtr added_filter; + EXPECT_CALL(filter_callback, addStreamDecoderFilter(_)) + .WillOnce( + Invoke([&added_filter](Http::StreamDecoderFilterSharedPtr filter) { + added_filter = std::move(filter); + })); + + cb(filter_callback); + EXPECT_NE(dynamic_cast(added_filter.get()), nullptr); +} + +} // namespace +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/utils/filter_names.cc b/src/envoy/utils/filter_names.cc index 2626766b5c2e..eded8bbbebd2 100644 --- a/src/envoy/utils/filter_names.cc +++ b/src/envoy/utils/filter_names.cc @@ -21,6 +21,7 @@ namespace Utils { // TODO: using more standard naming, e.g istio.jwt, istio.authn const char IstioFilterName::kJwt[] = "jwt-auth"; const char IstioFilterName::kAuthentication[] = "istio_authn"; +const char IstioFilterName::kAlpn[] = "istio.alpn"; } // namespace Utils } // namespace Envoy diff --git a/src/envoy/utils/filter_names.h b/src/envoy/utils/filter_names.h index 64b79dfff2c3..690fac9fd3c3 100644 --- a/src/envoy/utils/filter_names.h +++ b/src/envoy/utils/filter_names.h @@ -26,6 +26,7 @@ namespace Utils { struct IstioFilterName { static const char kJwt[]; static const char kAuthentication[]; + static const char kAlpn[]; }; } // namespace Utils