Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

alpn: fixing config validation for socket matchers #19284

Merged
merged 3 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Incompatible Behavior Changes
-----------------------------
*Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required*

* auto_config: :ref:`auto_config: <envoy_v3_api_field_extensions.upstreams.http.v3.HttpProtocolOptions.auto_config>` now verifies that any transport sockets configured via :ref:`transport_socket_matches <envoy_v3_api_field_config.cluster.v3.Cluster.transport_socket_matches>` support ALPN. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.correctly_validate_alpn`` to false..
* xds: ``*`` became a reserved name for a wildcard resource that can be subscribed to and unsubscribed from at any time. This is a requirement for implementing the on-demand xDSes (like on-demand CDS) that can subscribe to specific resources next to their wildcard subscription. If such xDS is subscribed to both wildcard resource and to other specific resource, then in stream reconnection scenario, the xDS will not send an empty initial request, but a request containing ``*`` for wildcard subscription and the rest of the resources the xDS is subscribed to. If the xDS is only subscribed to wildcard resource, it will try to send a legacy wildcard request. This behavior implements the recent changes in :ref:`xDS protocol <xds_protocol>` and can be temporarily reverted by setting the ``envoy.restart_features.explicit_wildcard_resource`` runtime guard to false.

Minor Behavior Changes
Expand Down
5 changes: 5 additions & 0 deletions envoy/upstream/host_description.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ class TransportSocketMatcher {
* @return the match information of the transport socket selected.
*/
virtual MatchData resolve(const envoy::config::core::v3::Metadata* metadata) const PURE;

/*
* return true if all matches support ALPN, false otherwise.
*/
virtual bool allMatchesSupportAlpn() const PURE;
};

using TransportSocketMatcherPtr = std::unique_ptr<TransportSocketMatcher>;
Expand Down
1 change: 1 addition & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ constexpr const char* runtime_features[] = {
"envoy.reloadable_features.allow_response_for_timeout",
"envoy.reloadable_features.conn_pool_delete_when_idle",
"envoy.reloadable_features.correct_scheme_and_xfp",
"envoy.reloadable_features.correctly_validate_alpn",
"envoy.reloadable_features.disable_tls_inspector_injection",
"envoy.reloadable_features.fix_added_trailers",
"envoy.reloadable_features.grpc_bridge_stats_disabled",
Expand Down
12 changes: 12 additions & 0 deletions source/common/upstream/transport_socket_match_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ class TransportSocketMatcherImpl : public Logger::Loggable<Logger::Id::upstream>

MatchData resolve(const envoy::config::core::v3::Metadata* metadata) const override;

bool allMatchesSupportAlpn() const override {
if (!default_match_.factory->supportsAlpn()) {
return false;
}
for (const auto& match : matches_) {
if (!match.factory->supportsAlpn()) {
return false;
}
}
return true;
}

protected:
TransportSocketMatchStats generateStats(const std::string& prefix);
Stats::Scope& stats_scope_;
Expand Down
18 changes: 13 additions & 5 deletions source/common/upstream/upstream_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,7 @@ ClusterImplBase::ClusterImplBase(

auto socket_matcher = std::make_unique<TransportSocketMatcherImpl>(
cluster.transport_socket_matches(), factory_context, socket_factory, *stats_scope);
const bool matcher_supports_alpn = socket_matcher->allMatchesSupportAlpn();
auto& dispatcher = factory_context.mainThreadDispatcher();
info_ = std::shared_ptr<const ClusterInfoImpl>(
new ClusterInfoImpl(cluster, factory_context.clusterManager().bindConfig(), runtime,
Expand All @@ -1060,11 +1061,18 @@ ClusterImplBase::ClusterImplBase(
std::unique_ptr<const Event::DispatcherThreadDeletable>(self));
});

if ((info_->features() & ClusterInfoImpl::Features::USE_ALPN) &&
!raw_factory_pointer->supportsAlpn()) {
throw EnvoyException(
fmt::format("ALPN configured for cluster {} which has a non-ALPN transport socket: {}",
cluster.name(), cluster.DebugString()));
if ((info_->features() & ClusterInfoImpl::Features::USE_ALPN)) {
if (!raw_factory_pointer->supportsAlpn()) {
throw EnvoyException(
fmt::format("ALPN configured for cluster {} which has a non-ALPN transport socket: {}",
cluster.name(), cluster.DebugString()));
}
if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.correctly_validate_alpn") &&
!matcher_supports_alpn) {
throw EnvoyException(fmt::format(
"ALPN configured for cluster {} which has a non-ALPN transport socket matcher: {}",
cluster.name(), cluster.DebugString()));
}
}

if (info_->features() & ClusterInfoImpl::Features::HTTP3) {
Expand Down
68 changes: 65 additions & 3 deletions test/common/upstream/transport_socket_matcher_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@ class FakeTransportSocketFactory : public Network::TransportSocketFactory {
public:
MOCK_METHOD(bool, implementsSecureTransport, (), (const));
MOCK_METHOD(bool, usesProxyProtocolOptions, (), (const));
MOCK_METHOD(bool, supportsAlpn, (), (const));
MOCK_METHOD(Network::TransportSocketPtr, createTransportSocket,
(Network::TransportSocketOptionsConstSharedPtr), (const));
FakeTransportSocketFactory(std::string id) : id_(std::move(id)) {}
FakeTransportSocketFactory(std::string id, bool alpn) : supports_alpn_(alpn), id_(std::move(id)) {
ON_CALL(*this, supportsAlpn).WillByDefault(Invoke([this]() { return supports_alpn_; }));
}
std::string id() const { return id_; }

bool supports_alpn_;

private:
const std::string id_;
};
Expand All @@ -58,20 +63,22 @@ class FooTransportSocketFactory
if (!node.id().empty()) {
id = node.id();
}
return std::make_unique<FakeTransportSocketFactory>(id);
return std::make_unique<NiceMock<FakeTransportSocketFactory>>(id, supports_alpn_);
}

ProtobufTypes::MessagePtr createEmptyConfigProto() override {
return std::make_unique<envoy::config::core::v3::Node>();
}

std::string name() const override { return "foo"; }
bool supports_alpn_{};
};

class TransportSocketMatcherTest : public testing::Test {
public:
TransportSocketMatcherTest()
: registration_(factory_), mock_default_factory_(new FakeTransportSocketFactory("default")),
: registration_(factory_),
mock_default_factory_(new NiceMock<FakeTransportSocketFactory>("default", false)),
stats_scope_(stats_store_.createScope("transport_socket_match.test")) {}

void init(const std::vector<std::string>& match_yaml) {
Expand Down Expand Up @@ -116,6 +123,61 @@ name: "enableFooSocket"

envoy::config::core::v3::Metadata metadata;
validate(metadata, "default");

// Neither the defaults nor matcher support ALPN.
EXPECT_FALSE(matcher_->allMatchesSupportAlpn());
}

TEST_F(TransportSocketMatcherTest, AlpnSupport) {
mock_default_factory_.reset(new NiceMock<FakeTransportSocketFactory>("default", true));
factory_.supports_alpn_ = true;
init({R"EOF(
name: "enableFooSocket"
match:
hasSidecar: "true"
transport_socket:
name: "foo"
typed_config:
"@type": type.googleapis.com/envoy.config.core.v3.Node
id: "abc"
)EOF"});

// Both default and matcher support ALPN
EXPECT_TRUE(matcher_->allMatchesSupportAlpn());
}

TEST_F(TransportSocketMatcherTest, NoDefaultAlpnSupport) {
factory_.supports_alpn_ = true;
init({R"EOF(
name: "enableFooSocket"
match:
hasSidecar: "true"
transport_socket:
name: "foo"
typed_config:
"@type": type.googleapis.com/envoy.config.core.v3.Node
id: "abc"
)EOF"});

// The default doesn't support ALPN, matchers do.
EXPECT_FALSE(matcher_->allMatchesSupportAlpn());
}

TEST_F(TransportSocketMatcherTest, NoMatcherAlpnSupport) {
mock_default_factory_.reset(new NiceMock<FakeTransportSocketFactory>("default", true));
init({R"EOF(
name: "enableFooSocket"
match:
hasSidecar: "true"
transport_socket:
name: "foo"
typed_config:
"@type": type.googleapis.com/envoy.config.core.v3.Node
id: "abc"
)EOF"});

// The default doesn't support ALPN, matchers do.
EXPECT_FALSE(matcher_->allMatchesSupportAlpn());
}

TEST_F(TransportSocketMatcherTest, BasicMatch) {
Expand Down
151 changes: 151 additions & 0 deletions test/common/upstream/upstream_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3801,6 +3801,157 @@ TEST_F(ClusterInfoImplTest, Http3BadConfig) {
}
#endif // ENVOY_ENABLE_QUIC

TEST_F(ClusterInfoImplTest, Http2Auto) {
const std::string yaml = TestEnvironment::substitute(R"EOF(
name: name
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: MAGLEV
load_assignment:
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: foo.bar.com
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
tls_certificates:
- certificate_chain:
filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem"
private_key:
filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
validation_context:
trusted_ca:
filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
match_typed_subject_alt_names:
- matcher:
exact: localhost
san_type: URI
- matcher:
exact: 127.0.0.1
san_type: IP_ADDRESS
)EOF",
Network::Address::IpVersion::v4);

auto cluster1 = makeCluster(yaml);
ASSERT_TRUE(cluster1->info()->idleTimeout().has_value());
EXPECT_EQ(std::chrono::hours(1), cluster1->info()->idleTimeout().value());

const std::string auto_http2 = R"EOF(
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
auto_config:
http2_protocol_options: {}
common_http_protocol_options:
idle_timeout: 1s
)EOF";

auto auto_h2 = makeCluster(yaml + auto_http2);
EXPECT_EQ(Http::Protocol::Http2,
auto_h2->info()->upstreamHttpProtocol({Http::Protocol::Http10})[0]);
}

TEST_F(ClusterInfoImplTest, Http2AutoNoAlpn) {
const std::string yaml = TestEnvironment::substitute(R"EOF(
name: name
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: MAGLEV
load_assignment:
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: foo.bar.com
port_value: 443
)EOF",
Network::Address::IpVersion::v4);

auto cluster1 = makeCluster(yaml);
ASSERT_TRUE(cluster1->info()->idleTimeout().has_value());
EXPECT_EQ(std::chrono::hours(1), cluster1->info()->idleTimeout().value());

const std::string auto_http2 = R"EOF(
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
auto_config:
http2_protocol_options: {}
common_http_protocol_options:
idle_timeout: 1s
)EOF";

EXPECT_THROW_WITH_REGEX(makeCluster(yaml + auto_http2), EnvoyException,
".*which has a non-ALPN transport socket:.*");
}

TEST_F(ClusterInfoImplTest, Http2AutoWithNonAlpnMatcher) {
const std::string yaml = TestEnvironment::substitute(R"EOF(
name: name
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: MAGLEV
load_assignment:
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: foo.bar.com
port_value: 443
transport_socket_matches:
- match:
name: insecure-mode
transport_socket:
name: envoy.transport_sockets.raw_buffer
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
tls_certificates:
- certificate_chain:
filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem"
private_key:
filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
validation_context:
trusted_ca:
filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
match_typed_subject_alt_names:
- matcher:
exact: localhost
san_type: URI
- matcher:
exact: 127.0.0.1
san_type: IP_ADDRESS
)EOF",
Network::Address::IpVersion::v4);

auto cluster1 = makeCluster(yaml);
ASSERT_TRUE(cluster1->info()->idleTimeout().has_value());
EXPECT_EQ(std::chrono::hours(1), cluster1->info()->idleTimeout().value());

const std::string auto_http2 = R"EOF(
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
auto_config:
http2_protocol_options: {}
common_http_protocol_options:
idle_timeout: 1s
)EOF";

EXPECT_THROW_WITH_REGEX(makeCluster(yaml + auto_http2), EnvoyException,
".*which has a non-ALPN transport socket matcher:.*");
}

// Validate empty singleton for HostsPerLocalityImpl.
TEST(HostsPerLocalityImpl, Empty) {
EXPECT_FALSE(HostsPerLocalityImpl::empty()->hasLocalLocality());
Expand Down
1 change: 1 addition & 0 deletions test/mocks/upstream/transport_socket_match.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class MockTransportSocketMatcher : public TransportSocketMatcher {
~MockTransportSocketMatcher() override;
MOCK_METHOD(TransportSocketMatcher::MatchData, resolve,
(const envoy::config::core::v3::Metadata*), (const));
MOCK_METHOD(bool, allMatchesSupportAlpn, (), (const));

Network::TransportSocketFactoryPtr socket_factory_;
Stats::TestUtil::TestStore stats_store_;
Expand Down