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

ssl: add support for SNI. #1984

Merged
merged 53 commits into from
Nov 28, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
76709a0
ssl: add support for SNI.
PiotrSikora Nov 1, 2017
d04afe2
Merge commit '908ddde3adb0634c966d1170319b8c1f571dab95' into piotrsik…
PiotrSikora Nov 7, 2017
0f984ce
review: skip context update in case of one filter chain.
PiotrSikora Nov 3, 2017
47e3df7
review: use ASSERT().
PiotrSikora Nov 3, 2017
227589b
review: fix comment.
PiotrSikora Nov 4, 2017
75abf26
review: use shared lock.
PiotrSikora Nov 7, 2017
0fecd6b
review: add tests.
PiotrSikora Nov 1, 2017
68541bf
review: add docs.
PiotrSikora Nov 7, 2017
33142cc
review: unordered_map find instead of direct access.
PiotrSikora Nov 7, 2017
ca9eb0b
reivew: fix format.
PiotrSikora Nov 7, 2017
7d1b1af
review: fix non-debug build.
PiotrSikora Nov 7, 2017
fc9705f
review: fix non-debug tests.
PiotrSikora Nov 7, 2017
a2f657c
review: fix typo.
PiotrSikora Nov 7, 2017
0dc9b85
review: fix stack-use-after-scope.
PiotrSikora Nov 7, 2017
348e6e8
review: describe algorithm in findSslServerContext().
PiotrSikora Nov 7, 2017
7ce1bbb
review: reject configurations with mixed use of Session Ticket Keys.
PiotrSikora Nov 7, 2017
1733d92
review: fix non-debug tests (again...).
PiotrSikora Nov 7, 2017
909ada7
review: proper use of unordered_map.
PiotrSikora Nov 7, 2017
1dbe7af
review: expand comment on why Session Ticket Keys can't be mixed.
PiotrSikora Nov 7, 2017
00bf76d
review: don't init std::string.
PiotrSikora Nov 9, 2017
5c6e262
review: skip context update early on.
PiotrSikora Nov 9, 2017
ce8ccc6
review: use ctx_.get()
PiotrSikora Nov 9, 2017
0a25690
review: erase(ctx).
PiotrSikora Nov 9, 2017
c29cd5b
review: improve skip_context_update.
PiotrSikora Nov 9, 2017
37e083d
review: add TODO about making it lockless.
PiotrSikora Nov 9, 2017
7f18d00
review: add comment about skip_context_update.
PiotrSikora Nov 9, 2017
80be681
review: rename updateConnection to updateConnectionContext.
PiotrSikora Nov 9, 2017
12e1260
review: add comment about "magic" 5.
PiotrSikora Nov 9, 2017
3f21609
review: fix lock in iterateContexts().
PiotrSikora Nov 9, 2017
c11713e
review: constify few functions.
PiotrSikora Nov 9, 2017
8dc1702
review: constify iterateContexts and use shared lock there.
PiotrSikora Nov 12, 2017
fa77d69
review: rename sslContext() to defaultSslContext().
PiotrSikora Nov 12, 2017
eb3ff75
review: remove invalid domain length restrictions.
PiotrSikora Nov 12, 2017
afc3b62
review: const bool skip_context_update.
PiotrSikora Nov 14, 2017
cd854a2
review: use uint32_t instead of size_t.
PiotrSikora Nov 14, 2017
aa5098b
review: add TODO to upstream SNI parsing to BoringSSL.
PiotrSikora Nov 14, 2017
da1dbaa
review: throw exception for configs with mixed use of STK.
PiotrSikora Nov 14, 2017
4ec1786
review: add ContextManagerImpl::isWildcardServerName().
PiotrSikora Nov 14, 2017
33d27e7
review: remove "x".
PiotrSikora Nov 15, 2017
757abeb
review: make sure all filter chains are equal.
PiotrSikora Nov 15, 2017
5a2fc2a
Merge remote-tracking branch 'origin/master' into piotrsikora/sni
PiotrSikora Nov 15, 2017
ea6660a
review: fixup bad merge.
PiotrSikora Nov 15, 2017
dd3cf76
review: update data-plane-api (for docs).
PiotrSikora Nov 16, 2017
f0a48d4
review: add TODO for refactoring with RouteMatcher::findVirtualHost().
PiotrSikora Nov 16, 2017
ddbaf41
review: add and use RepeatedPtrUtil::hash().
PiotrSikora Nov 16, 2017
6901dc0
reivew: fix format.
PiotrSikora Nov 16, 2017
f519595
review: fix typo.
PiotrSikora Nov 20, 2017
a43e74c
review: constify vars.
PiotrSikora Nov 20, 2017
5700d0c
review: move function prototype before vars.
PiotrSikora Nov 20, 2017
57ed156
review: use Optional<uint64_t>.
PiotrSikora Nov 20, 2017
5520293
review: add tests for listener manager.
PiotrSikora Nov 28, 2017
4ac9fdf
Merge remote-tracking branch 'origin/master' into piotrsikora/sni.
PiotrSikora Nov 28, 2017
122f450
review: remove factory variables.
PiotrSikora Nov 28, 2017
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
11 changes: 10 additions & 1 deletion include/envoy/ssl/context_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ class ContextManager {
/**
* Builds a ServerContext from a ServerContextConfig.
*/
virtual ServerContextPtr createSslServerContext(Stats::Scope& scope,
virtual ServerContextPtr createSslServerContext(const std::string& listener_name,
const std::vector<std::string>& server_names,
Stats::Scope& scope,
ServerContextConfig& config) PURE;

/**
* Find ServerContext for a given listener and server_name.
* @return ServerContext or nullptr in case there is no match.
*/
virtual ServerContext* findSslServerContext(const std::string& listener_name,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be out of scope for this change, but should the ServerContextImpl has a reference to it's ConnectionHandler, then this function could be on ConnectionHandler and not need a listener_name param? This feels awkward because the ServerContext already exists under a listener/ConnectionHandler, so needing a separate listener lookup is strange.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agreed. This is related to my comments about making this lockless and adding TODO. I'm willing live with this to get this shipped, but ultimately don't like the global namespace for this. Really we want listener -> set of contexts -> global context manager. Will let the two of you sort it out.

Copy link
Contributor Author

@PiotrSikora PiotrSikora Nov 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and no. While this indeed could be done much better for this specific use case, we're going to add SDS shortly and then it's going to get a bit more complex.

Personally, I'd defer any optimizations and/or refactoring until SDS is committed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with that.

const std::string& server_name) PURE;

/**
* @return the number of days until the next certificate being managed will expire.
*/
Expand Down
1 change: 1 addition & 0 deletions source/common/ssl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ envoy_cc_library(
"//include/envoy/stats:stats_interface",
"//include/envoy/stats:stats_macros",
"//source/common/common:assert_lib",
"//source/common/common:empty_string",
"//source/common/common:hex_lib",
],
)
66 changes: 63 additions & 3 deletions source/common/ssl/context_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "envoy/runtime/runtime.h"

#include "common/common/assert.h"
#include "common/common/empty_string.h"
#include "common/common/hex.h"

#include "fmt/format.h"
Expand Down Expand Up @@ -355,10 +356,20 @@ bssl::UniquePtr<SSL> ClientContextImpl::newSsl() const {
return ssl_con;
}

ServerContextImpl::ServerContextImpl(ContextManagerImpl& parent, Stats::Scope& scope,
ServerContextConfig& config, Runtime::Loader& runtime)
: ContextImpl(parent, scope, config), runtime_(runtime),
ServerContextImpl::ServerContextImpl(ContextManagerImpl& parent, const std::string& listener_name,
const std::vector<std::string>& server_names,
Stats::Scope& scope, ServerContextConfig& config,
Runtime::Loader& runtime)
: ContextImpl(parent, scope, config), listener_name_(listener_name),
server_names_(server_names), runtime_(runtime),
session_ticket_keys_(config.sessionTicketKeys()) {
SSL_CTX_set_select_certificate_cb(
ctx_.get(), [](const SSL_CLIENT_HELLO* client_hello) -> ssl_select_cert_result_t {
ContextImpl* context_impl = static_cast<ContextImpl*>(
SSL_CTX_get_ex_data(SSL_get_SSL_CTX(client_hello->ssl), sslContextIndex()));
return dynamic_cast<ServerContextImpl*>(context_impl)->processClientHello(client_hello);
});

if (!config.caCertFile().empty()) {
bssl::UniquePtr<STACK_OF(X509_NAME)> list(SSL_load_client_CA_file(config.caCertFile().c_str()));
if (nullptr == list) {
Expand Down Expand Up @@ -473,6 +484,55 @@ ServerContextImpl::ServerContextImpl(ContextManagerImpl& parent, Stats::Scope& s
RELEASE_ASSERT(rc == 1);
}

ssl_select_cert_result_t
ServerContextImpl::processClientHello(const SSL_CLIENT_HELLO* client_hello) {
std::string server_name = EMPTY_STRING;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: std::string default to empty, don't need init.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const uint8_t* data;
size_t len;

if (SSL_early_callback_ctx_extension_get(client_hello, TLSEXT_TYPE_server_name, &data, &len)) {
/* Based on BoringSSL's ext_sni_parse_clienthello(). Match on empty SNI if we encounter any
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: // style comments

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* errors. */
CBS extension;
CBS_init(&extension, data, len);
CBS server_name_list, host_name;
uint8_t name_type;
if (CBS_get_u16_length_prefixed(&extension, &server_name_list) &&
CBS_get_u8(&server_name_list, &name_type) &&
CBS_get_u16_length_prefixed(&server_name_list, &host_name) &&
CBS_len(&server_name_list) == 0 && CBS_len(&extension) == 0 &&
name_type == TLSEXT_NAMETYPE_host_name && CBS_len(&host_name) != 0 &&
CBS_len(&host_name) <= TLSEXT_MAXLEN_host_name && !CBS_contains_zero_byte(&host_name)) {
server_name =
std::string(reinterpret_cast<const char*>(CBS_data(&host_name)), CBS_len(&host_name));
}
}

ServerContext* new_ctx = parent_.findSslServerContext(listener_name_, server_name);

// Reject connection if we didn't find a match.
if (new_ctx == nullptr) {
return ssl_select_cert_error;
}

// Update context if it changed.
if (new_ctx != this) {
ServerContextImpl* new_impl = dynamic_cast<ServerContextImpl*>(new_ctx);
new_impl->updateConnection(client_hello->ssl);
}

return ssl_select_cert_success;
}

void ServerContextImpl::updateConnection(SSL* ssl) {
RELEASE_ASSERT(ctx_);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would just do ASSERT here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


// Note: this updates only served certificates.
SSL_set_SSL_CTX(ssl, ctx_.get());

// TODO(PiotrSikora): update other settings.
}

int ServerContextImpl::sessionTicketProcess(SSL*, uint8_t* key_name, uint8_t* iv,
EVP_CIPHER_CTX* ctx, HMAC_CTX* hmac_ctx, int encrypt) {
const EVP_MD* hmac = EVP_sha256();
Expand Down
18 changes: 12 additions & 6 deletions source/common/ssl/context_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ struct SslStats {

class ContextImpl : public virtual Context {
public:
~ContextImpl() { parent_.releaseContext(this); }

virtual bssl::UniquePtr<SSL> newSsl() const;

/**
Expand Down Expand Up @@ -124,6 +122,7 @@ class ContextImpl : public virtual Context {
class ClientContextImpl : public ContextImpl, public ClientContext {
public:
ClientContextImpl(ContextManagerImpl& parent, Stats::Scope& scope, ClientContextConfig& config);
~ClientContextImpl() { parent_.releaseClientContext(this); }

bssl::UniquePtr<SSL> newSsl() const override;

Expand All @@ -133,19 +132,26 @@ class ClientContextImpl : public ContextImpl, public ClientContext {

class ServerContextImpl : public ContextImpl, public ServerContext {
public:
ServerContextImpl(ContextManagerImpl& parent, Stats::Scope& scope, ServerContextConfig& config,
Runtime::Loader& runtime);
ServerContextImpl(ContextManagerImpl& parent, const std::string& listener_name,
const std::vector<std::string>& server_names, Stats::Scope& scope,
ServerContextConfig& config, Runtime::Loader& runtime);
~ServerContextImpl() { parent_.releaseServerContext(this, listener_name_, server_names_); }

private:
ssl_select_cert_result_t processClientHello(const SSL_CLIENT_HELLO* client_hello);
void updateConnection(SSL* ssl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to attachToConnection(), or setConnectionContext(), or similar. updateConnection doesn't tell me enough about what on the connection is being updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (to updateConnectionContext).


int alpnSelectCallback(const unsigned char** out, unsigned char* outlen, const unsigned char* in,
unsigned int inlen);
int sessionTicketProcess(SSL* ssl, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx,
HMAC_CTX* hmac_ctx, int encrypt);

const std::string listener_name_;
const std::vector<std::string> server_names_;
Runtime::Loader& runtime_;
std::vector<uint8_t> parsed_alt_alpn_protocols_;
const std::vector<ServerContextConfig::SessionTicketKey> session_ticket_keys_;
};

} // Ssl
} // Envoy
} // namespace Ssl
} // namespace Envoy
81 changes: 76 additions & 5 deletions source/common/ssl/context_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
#include <mutex>

#include "common/common/assert.h"
#include "common/common/empty_string.h"
#include "common/ssl/context_impl.h"

namespace Envoy {
namespace Ssl {

ContextManagerImpl::~ContextManagerImpl() { ASSERT(contexts_.empty()); }

void ContextManagerImpl::releaseContext(Context* context) {
void ContextManagerImpl::releaseClientContext(ClientContext* context) {
std::unique_lock<std::mutex> lock(contexts_lock_);

// context may not be found, in the case that a subclass of Context throws
Expand All @@ -20,23 +21,93 @@ void ContextManagerImpl::releaseContext(Context* context) {
contexts_.remove(context);
}

void ContextManagerImpl::releaseServerContext(ServerContext* context,
const std::string& listener_name,
const std::vector<std::string>& server_names) {
std::unique_lock<std::mutex> lock(contexts_lock_);

// Remove mappings.
if (server_names.empty()) {
if (map_exact_[listener_name][EMPTY_STRING] == context) {
map_exact_[listener_name][EMPTY_STRING] = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting to nullptr doesn't remove from the map. Need one of the variants of erase: http://en.cppreference.com/w/cpp/container/unordered_map/erase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sigh, I know that... Good catch, fixed, thanks!

}
} else {
for (const auto& name : server_names) {
if (name.size() > 2 && name[0] == '*' && name[1] == '.') {
if (map_wildcard_[listener_name][name] == context) {
map_wildcard_[listener_name][name] = nullptr;
}
} else {
if (map_exact_[listener_name][name] == context) {
map_exact_[listener_name][name] = nullptr;
}
}
}
}

// context may not be found, in the case that a subclass of Context throws
// in it's constructor. In that case the context did not get added, but
// the destructor of Context will run and call releaseContext().
contexts_.remove(context);
}

ClientContextPtr ContextManagerImpl::createSslClientContext(Stats::Scope& scope,
ClientContextConfig& config) {

ClientContextPtr context(new ClientContextImpl(*this, scope, config));
std::unique_lock<std::mutex> lock(contexts_lock_);
contexts_.emplace_back(context.get());
return context;
}

ServerContextPtr ContextManagerImpl::createSslServerContext(Stats::Scope& scope,
ServerContextConfig& config) {
ServerContextPtr context(new ServerContextImpl(*this, scope, config, runtime_));
ServerContextPtr
ContextManagerImpl::createSslServerContext(const std::string& listener_name,
const std::vector<std::string>& server_names,
Stats::Scope& scope, ServerContextConfig& config) {
ServerContextPtr context(
new ServerContextImpl(*this, listener_name, server_names, scope, config, runtime_));
std::unique_lock<std::mutex> lock(contexts_lock_);
contexts_.emplace_back(context.get());

// Save mappings.
if (server_names.empty()) {
map_exact_[listener_name][EMPTY_STRING] = context.get();
} else {
for (const auto& name : server_names) {
if (name.size() > 2 && name[0] == '*' && name[1] == '.') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This non-trivial condition appears in multiple places. Make it a helper?
bool isWildcardServerName(const std::string& name)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do, but it feels like over-engineering to me.

map_wildcard_[listener_name][name] = context.get();
} else {
map_exact_[listener_name][name] = context.get();
}
}
}

return context;
}

ServerContext* ContextManagerImpl::findSslServerContext(const std::string& listener_name,
const std::string& server_name) {
std::unique_lock<std::mutex> lock(contexts_lock_);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's very non-optimal that we are acquiring a lock here on every handshake, especially regardless of whether SNI is being used or not. In the interest of moving this along, I'm OK living with this for now. At minimum please put in a TODO. The quick fix is that we can make this a shared mutex and only acquire read access when we do a find. We are going to move to C++14 soon and that will become available. I would prefer that this be completely lockless within a single listener, and I can think of ways of doing this, but it would be a larger change. If you want to brainstorm on this let's chat offline or we can just leave as part of the TODO.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the uninitiated, why do we need locks here? is this shared global context across all threads, where the context can be updated by LDS or future SDS updates? Is that the reason for suggesting a r/w lock?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lock is required because we print the contexts as part of admin output, and contexts come and go with listeners. C++14 support has merged so this should be changed to a shared_mutex (I think C++14 only has the timed one, but it works in not timed mode). We can fix the need for a lock in this flow at all later and leave a TODO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (map_exact_[listener_name][server_name] != nullptr) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case that server_name wasn't in the inner map, this inserts it, based on the user-provided SNI value in the handshake. I think you need to use map_exact[listener_name].find(server_name) != end, and assume there are never nullptr's in the map. Similar change needed in other places map_exact_ is used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return map_exact_[listener_name][server_name];
}

// Try to construct and match wildcard domain.
if (server_name.size() >= 5) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't "*.a" be a valid wildcard domain? Either for a gTLD or for something internal to a company? I think we can just drop this condition. The common case will either be something >5 or empty string, and empty string will quickly bail out when trying to find('.'), so I don't think the performance will matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, that's indeed valid wildcard when using internal CAs. I removed this restriction.

size_t pos = server_name.find('.');
if (pos > 0) {
Copy link
Contributor

@ggreenway ggreenway Nov 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (pos != std::string::npos && pos != 0)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewritten to if (pos > 0 && pos < server_name.size() - 1) { (because of change in restrictions). I don't believe that we need pos != std::string::npos anymore, but feel free to correct me, since I'm clearly not a C++ expert.

size_t rpos = server_name.rfind('.');
if (rpos > pos + 1 && rpos != server_name.size() - 1) {
std::string wildcard = '*' + server_name.substr(pos);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: any reason to not use the wildcard matching algorithm that we have for virtual hosts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to reusing the matching code in config_impl. Can we factor that out into a utility?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which code do you mean, exactly? ContextImpl::dNSNameMatch?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

source/common/router/config_impl.h, RouteMatcher does similar work. See "domains" in https://www.envoyproxy.io/envoy/configuration/http_conn_man/route_config/vhost.html#config-http-conn-man-route-table-vhost. Without a strong argument against, I think it makes sense for the matching semantics to be the same between this code and that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd +1 as well for the reason that we might change the implementation to be smarter one day, e.g. trie-based as per @tschroed 's earlier implementation. Probably not needed right now, but just as future proofing.

if (map_wildcard_[listener_name][wildcard] != nullptr) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above re: inserting into the map

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return map_wildcard_[listener_name][wildcard];
}
}
}
}

return map_exact_[listener_name][EMPTY_STRING];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is never going to return nullptr right? But the comment says it might return nullptr if not found. Is this doing the fall back thing that we discussed a few days ago?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can, e.g. if all available filter chains have sni_domains configured, but none of them match.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess my confusion lies in the assumption that all entries of map_exact_ are non null, which does not seem to be the case.

}

size_t ContextManagerImpl::daysUntilFirstCertExpires() {
std::unique_lock<std::mutex> lock(contexts_lock_);
size_t ret = std::numeric_limits<int>::max();
Expand Down
13 changes: 11 additions & 2 deletions source/common/ssl/context_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <functional>
#include <list>
#include <mutex>
#include <unordered_map>

#include "envoy/runtime/runtime.h"
#include "envoy/ssl/context_manager.h"
Expand All @@ -27,20 +28,28 @@ class ContextManagerImpl final : public ContextManager {
* admin purposes. When a caller frees a context it will tell us to release it also from the list
* of contexts.
*/
void releaseContext(Context* context);
void releaseClientContext(ClientContext* context);
void releaseServerContext(ServerContext* context, const std::string& listener_name,
const std::vector<std::string>& server_names);

// Ssl::ContextManager
Ssl::ClientContextPtr createSslClientContext(Stats::Scope& scope,
ClientContextConfig& config) override;
Ssl::ServerContextPtr createSslServerContext(Stats::Scope& scope,
Ssl::ServerContextPtr createSslServerContext(const std::string& listener_name,
const std::vector<std::string>& server_names,
Stats::Scope& scope,
ServerContextConfig& config) override;
Ssl::ServerContext* findSslServerContext(const std::string& listener_name,
const std::string& server_name) override;
size_t daysUntilFirstCertExpires() override;
void iterateContexts(std::function<void(Context&)> callback) override;

private:
Runtime::Loader& runtime_;
std::list<Context*> contexts_;
std::mutex contexts_lock_;
std::unordered_map<std::string, std::unordered_map<std::string, ServerContext*>> map_exact_;
std::unordered_map<std::string, std::unordered_map<std::string, ServerContext*>> map_wildcard_;
};

} // namespace Ssl
Expand Down
29 changes: 17 additions & 12 deletions source/server/listener_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, ListenerManag
Network::Utility::parseInternetAddress(config.address().socket_address().address(),
config.address().socket_address().port_value())),
global_scope_(parent_.server_.stats().createScope("")),
listener_scope_(
parent_.server_.stats().createScope(fmt::format("listener.{}.", address_->asString()))),
bind_to_port_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.deprecated_v1(), bind_to_port, true)),
use_proxy_proto_(
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.filter_chains()[0], use_proxy_proto, false)),
Expand All @@ -88,19 +90,22 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, ListenerManag
local_drain_manager_(parent.factory_.createDrainManager()) {
// TODO(htuch): Support multiple filter chains #1280, add constraint to ensure we have at least on
// filter chain #1308.
ASSERT(config.filter_chains().size() == 1);
const auto& filter_chain = config.filter_chains()[0];

listener_scope_ =
parent_.server_.stats().createScope(fmt::format("listener.{}.", address_->asString()));

if (filter_chain.has_tls_context()) {
Ssl::ServerContextConfigImpl context_config(filter_chain.tls_context());
ssl_context_ = parent_.server_.sslContextManager().createSslServerContext(*listener_scope_,
context_config);
ASSERT(config.filter_chains().size() >= 1);

for (const auto& filter_chain : config.filter_chains()) {
std::vector<std::string> sni_domains(filter_chain.filter_chain_match().sni_domains().begin(),
filter_chain.filter_chain_match().sni_domains().end());
if (filter_factories_.empty() || !filter_chain.filters().empty()) {
// Only one filter chain can have filters until multiple filter chains are properly supported.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need to switch this to making sure all filter chains are the same, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

RELEASE_ASSERT(filter_factories_.empty());
filter_factories_ = parent_.factory_.createFilterFactoryList(filter_chain.filters(), *this);
}
if (filter_chain.has_tls_context()) {
Ssl::ServerContextConfigImpl context_config(filter_chain.tls_context());
tls_contexts_.emplace_back(parent_.server_.sslContextManager().createSslServerContext(
name_, sni_domains, *listener_scope_, context_config));
}
}

filter_factories_ = parent_.factory_.createFilterFactoryList(filter_chain.filters(), *this);
}

ListenerImpl::~ListenerImpl() {
Expand Down
6 changes: 4 additions & 2 deletions source/server/listener_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ class ListenerImpl : public Listener,
Network::FilterChainFactory& filterChainFactory() override { return *this; }
Network::ListenSocket& socket() override { return *socket_; }
bool bindToPort() override { return bind_to_port_; }
Ssl::ServerContext* sslContext() override { return ssl_context_.get(); }
Ssl::ServerContext* sslContext() override {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who calls this? Is the 0th ssl context the right one to return in this case? Maybe this function should go away, or start returning a const std::vectorSsl::ServerContextPtr& ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called when creating listener to get initial SSL_CTX. 0-th element is as good as any, really, since we're going to update it later anyway.

Returning std::vector<Ssl::ServerContextPtr> doesn't make much sense, since caller doesn't have any insight (at least not yet) about which context to chose.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be renamed to "sslDefaultContext()".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to defaultSslContext().

return tls_contexts_.empty() ? nullptr : tls_contexts_[0].get();
}
bool useProxyProto() override { return use_proxy_proto_; }
bool useOriginalDst() override { return use_original_dst_; }
uint32_t perConnectionBufferLimitBytes() override { return per_connection_buffer_limit_bytes_; }
Expand Down Expand Up @@ -236,7 +238,7 @@ class ListenerImpl : public Listener,
Network::ListenSocketSharedPtr socket_;
Stats::ScopePtr global_scope_; // Stats with global named scope, but needed for LDS cleanup.
Stats::ScopePtr listener_scope_; // Stats with listener named scope.
Ssl::ServerContextPtr ssl_context_;
std::vector<Ssl::ServerContextPtr> tls_contexts_;
const bool bind_to_port_;
const bool use_proxy_proto_;
const bool use_original_dst_;
Expand Down
Loading