Skip to content

Commit

Permalink
[tls] Add an extension point for TLS handshaker behavior. (envoyproxy…
Browse files Browse the repository at this point in the history
…#12658)

Additional Description: This PR necessitated decoupling SslHandshakerImpl from ContextConfig a bit. We now pass an int representing the index of the extended_info struct rather than the ContextConfig. 

This PR moves SslHandshakerImpl to its own build target, moves SslHandshaker construction into the ContextConfig, and adds a HandshakerFactoryContext and HandshakerFactory for modifying the ContextConfig's behavior when constructing a Handshaker. This PR also adds a control (requireCertificates) to turn off the release asserts that a context must have certificates.

This PR builds off work in envoyproxy#12571 and refines work done (and abandoned) in envoyproxy#12075. For more discussion please see the comments section of envoyproxy#12075.

Risk Level: Low. This PR does not modify existing handshaking behavior, it just adds an extension point for modifying it.
Testing: A representative alternative implementation was added under :handshaker_test.
Docs Changes: N/a
Release Notes: N/a

Signed-off-by: James Buckland <jbuckland@google.com>
  • Loading branch information
ambuc authored Sep 11, 2020
1 parent 688f8b4 commit 7d6e7a4
Show file tree
Hide file tree
Showing 19 changed files with 1,168 additions and 606 deletions.
6 changes: 5 additions & 1 deletion api/envoy/extensions/transport_sockets/tls/v3/tls.proto
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ message DownstreamTlsContext {
}

// TLS context shared by both client and server TLS contexts.
// [#next-free-field: 13]
// [#next-free-field: 14]
message CommonTlsContext {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CommonTlsContext";

Expand Down Expand Up @@ -238,4 +238,8 @@ message CommonTlsContext {
//
// There is no default for this parameter. If empty, Envoy will not expose ALPN.
repeated string alpn_protocols = 4;

// Custom TLS handshaker. If empty, defaults to native TLS handshaking
// behavior.
config.core.v3.TypedExtensionConfig custom_handshaker = 13;
}
6 changes: 5 additions & 1 deletion api/envoy/extensions/transport_sockets/tls/v4alpha/tls.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions include/envoy/ssl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ envoy_cc_library(
hdrs = ["context_config.h"],
deps = [
":certificate_validation_context_config_interface",
":handshaker_interface",
":tls_certificate_config_interface",
],
)
Expand Down Expand Up @@ -81,7 +82,10 @@ envoy_cc_library(
hdrs = ["handshaker.h"],
external_deps = ["ssl"],
deps = [
"//include/envoy/api:api_interface",
"//include/envoy/config:typed_config_interface",
"//include/envoy/network:connection_interface",
"//include/envoy/network:post_io_action_interface",
"//include/envoy/protobuf:message_validator_interface",
],
)
11 changes: 11 additions & 0 deletions include/envoy/ssl/context_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "envoy/common/pure.h"
#include "envoy/ssl/certificate_validation_context_config.h"
#include "envoy/ssl/handshaker.h"
#include "envoy/ssl/tls_certificate_config.h"

#include "absl/types/optional.h"
Expand Down Expand Up @@ -73,6 +74,16 @@ class ContextConfig {
* @param callback callback that is executed by context config.
*/
virtual void setSecretUpdateCallback(std::function<void()> callback) PURE;

/**
* @return a callback which can be used to create Handshaker instances.
*/
virtual HandshakerFactoryCb createHandshaker() const PURE;

/**
* @return the set of capabilities for handshaker instances created by this context.
*/
virtual HandshakerCapabilities capabilities() const PURE;
};

class ClientContextConfig : public virtual ContextConfig {
Expand Down
72 changes: 70 additions & 2 deletions include/envoy/ssl/handshaker.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#pragma once

#include "envoy/api/api.h"
#include "envoy/config/typed_config.h"
#include "envoy/network/connection.h"
#include "envoy/network/post_io_action.h"
#include "envoy/protobuf/message_validator.h"

#include "openssl/ssl.h"

Expand All @@ -13,9 +16,9 @@ class HandshakeCallbacks {
virtual ~HandshakeCallbacks() = default;

/**
* @return the connection state.
* @return the connection.
*/
virtual Network::Connection::State connectionState() const PURE;
virtual Network::Connection& connection() const PURE;

/**
* A callback which will be executed at most once upon successful completion
Expand Down Expand Up @@ -43,5 +46,70 @@ class Handshaker {
virtual Network::PostIoAction doHandshake() PURE;
};

using HandshakerSharedPtr = std::shared_ptr<Handshaker>;
using HandshakerFactoryCb =
std::function<HandshakerSharedPtr(bssl::UniquePtr<SSL>, int, HandshakeCallbacks*)>;

class HandshakerFactoryContext {
public:
virtual ~HandshakerFactoryContext() = default;

/**
* @return reference to the Api object
*/
virtual Api::Api& api() PURE;

/**
* The list of supported protocols exposed via ALPN, from ContextConfig.
*/
virtual absl::string_view alpnProtocols() const PURE;
};

struct HandshakerCapabilities {
// Whether or not a handshaker implementation provides certificates itself.
bool provides_certificates = false;

// Whether or not a handshaker implementation verifies certificates itself.
bool verifies_peer_certificates = false;

// Whether or not a handshaker implementation handles session resumption
// itself.
bool handles_session_resumption = false;

// Whether or not a handshaker implementation provides its own list of ciphers
// and curves.
bool provides_ciphers_and_curves = false;

// Whether or not a handshaker implementation handles ALPN selection.
bool handles_alpn_selection = false;

// Should return true if this handshaker is FIPS-compliant.
// Envoy will fail to compile if this returns true and `--define=boringssl=fips`.
bool is_fips_compliant = true;
};

class HandshakerFactory : public Config::TypedFactory {
public:
/**
* @returns a callback to create a Handshaker. Accepts the |config| and
* |validation_visitor| for early validation. This virtual base doesn't
* perform MessageUtil::downcastAndValidate, but an implementation should.
*/
virtual HandshakerFactoryCb
createHandshakerCb(const Protobuf::Message& message,
HandshakerFactoryContext& handshaker_factory_context,
ProtobufMessage::ValidationVisitor& validation_visitor) PURE;

std::string category() const override { return "envoy.tls_handshakers"; }

/**
* Implementations should return a struct with their capabilities. See
* HandshakerCapabilities above. For any capability a Handshaker
* implementation explicitly declares, Envoy will not also configure that SSL
* capability.
*/
virtual HandshakerCapabilities capabilities() const PURE;
};

} // namespace Ssl
} // namespace Envoy
26 changes: 26 additions & 0 deletions source/extensions/transport_sockets/tls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,30 @@ envoy_cc_extension(
],
)

envoy_cc_library(
name = "ssl_handshaker_lib",
srcs = ["ssl_handshaker.cc"],
hdrs = ["ssl_handshaker.h"],
external_deps = ["ssl"],
deps = [
":context_lib",
":utility_lib",
"//include/envoy/network:connection_interface",
"//include/envoy/network:transport_socket_interface",
"//include/envoy/ssl:handshaker_interface",
"//include/envoy/ssl:ssl_socket_extended_info_interface",
"//include/envoy/ssl:ssl_socket_state",
"//include/envoy/ssl/private_key:private_key_callbacks_interface",
"//include/envoy/ssl/private_key:private_key_interface",
"//include/envoy/stats:stats_macros",
"//source/common/common:assert_lib",
"//source/common/common:empty_string",
"//source/common/common:minimal_logger_lib",
"//source/common/common:thread_annotations",
"//source/common/http:headers_lib",
],
)

envoy_cc_library(
name = "io_handle_bio_lib",
srcs = ["io_handle_bio.cc"],
Expand Down Expand Up @@ -56,6 +80,7 @@ envoy_cc_library(
":context_config_lib",
":context_lib",
":io_handle_bio_lib",
":ssl_handshaker_lib",
":utility_lib",
"//include/envoy/network:connection_interface",
"//include/envoy/network:transport_socket_interface",
Expand Down Expand Up @@ -83,6 +108,7 @@ envoy_cc_library(
# TLS is core functionality.
visibility = ["//visibility:public"],
deps = [
":ssl_handshaker_lib",
"//include/envoy/secret:secret_callbacks_interface",
"//include/envoy/secret:secret_provider_interface",
"//include/envoy/server:transport_socket_config_interface",
Expand Down
39 changes: 33 additions & 6 deletions source/extensions/transport_sockets/tls/context_config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "common/secret/sds_api.h"
#include "common/ssl/certificate_validation_context_config_impl.h"

#include "extensions/transport_sockets/tls/ssl_handshaker.h"

#include "openssl/ssl.h"

namespace Envoy {
Expand Down Expand Up @@ -213,6 +215,25 @@ ContextConfigImpl::ContextConfigImpl(
}
}
}

HandshakerFactoryContextImpl handshaker_factory_context(api_, alpn_protocols_);
Ssl::HandshakerFactory* handshaker_factory;
if (config.has_custom_handshaker()) {
// If a custom handshaker is configured, derive the factory from the config.
const auto& handshaker_config = config.custom_handshaker();
handshaker_factory =
&Config::Utility::getAndCheckFactory<Ssl::HandshakerFactory>(handshaker_config);
handshaker_factory_cb_ = handshaker_factory->createHandshakerCb(
handshaker_config.typed_config(), handshaker_factory_context,
factory_context.messageValidationVisitor());
} else {
// Otherwise, derive the config from the default factory.
handshaker_factory = HandshakerFactoryImpl::getDefaultHandshakerFactory();
handshaker_factory_cb_ = handshaker_factory->createHandshakerCb(
*handshaker_factory->createEmptyConfigProto(), handshaker_factory_context,
factory_context.messageValidationVisitor());
}
capabilities_ = handshaker_factory->capabilities();
}

Ssl::CertificateValidationContextConfigPtr ContextConfigImpl::getCombinedValidationContextConfig(
Expand Down Expand Up @@ -270,6 +291,10 @@ void ContextConfigImpl::setSecretUpdateCallback(std::function<void()> callback)
}
}

Ssl::HandshakerFactoryCb ContextConfigImpl::createHandshaker() const {
return handshaker_factory_cb_;
}

ContextConfigImpl::~ContextConfigImpl() {
if (tc_update_callback_handle_) {
tc_update_callback_handle_->remove();
Expand Down Expand Up @@ -400,12 +425,14 @@ ServerContextConfigImpl::ServerContextConfigImpl(
}
}

if ((config.common_tls_context().tls_certificates().size() +
config.common_tls_context().tls_certificate_sds_secret_configs().size()) == 0) {
throw EnvoyException("No TLS certificates found for server context");
} else if (!config.common_tls_context().tls_certificates().empty() &&
!config.common_tls_context().tls_certificate_sds_secret_configs().empty()) {
throw EnvoyException("SDS and non-SDS TLS certificates may not be mixed in server contexts");
if (!capabilities().provides_certificates) {
if ((config.common_tls_context().tls_certificates().size() +
config.common_tls_context().tls_certificate_sds_secret_configs().size()) == 0) {
throw EnvoyException("No TLS certificates found for server context");
} else if (!config.common_tls_context().tls_certificates().empty() &&
!config.common_tls_context().tls_certificate_sds_secret_configs().empty()) {
throw EnvoyException("SDS and non-SDS TLS certificates may not be mixed in server contexts");
}
}

if (config.has_session_timeout()) {
Expand Down
5 changes: 5 additions & 0 deletions source/extensions/transport_sockets/tls/context_config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig {
}

void setSecretUpdateCallback(std::function<void()> callback) override;
Ssl::HandshakerFactoryCb createHandshaker() const override;
Ssl::HandshakerCapabilities capabilities() const override { return capabilities_; }

Ssl::CertificateValidationContextConfigPtr getCombinedValidationContextConfig(
const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext&
Expand Down Expand Up @@ -94,6 +96,9 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig {
Envoy::Common::CallbackHandle* cvc_validation_callback_handle_{};
const unsigned min_protocol_version_;
const unsigned max_protocol_version_;

Ssl::HandshakerFactoryCb handshaker_factory_cb_;
Ssl::HandshakerCapabilities capabilities_;
};

class ClientContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::ClientContextConfig {
Expand Down
Loading

0 comments on commit 7d6e7a4

Please sign in to comment.