Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions configs/sni.yaml.default
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
# The location of the certificate file is relative to proxy.config.ssl.server.cert.path directory.
# client_key - sets the file containing the client private key that corresponds to the certificate for the outbound connection.
# client_sni_policy - policy of SNI on outbound connection.
# ssl_ticket_enabled - enables or disables session tickets for matched inbound TLS connections; parameters = 1 or 0.
# This overrides proxy.config.ssl.server.session_ticket.enable.
# ssl_ticket_number - sets the number of TLSv1.3 session tickets issued for matched inbound TLS connections;
# parameters = INTEGER. This overrides proxy.config.ssl.server.session_ticket.number.
# http2 - adds or removes HTTP/2 (H2) from the protocol list advertised by ATS; parameter required = None, parameters = on or off
# tunnel_route - sets the e2e tunnel route
# forward_route - destination as an FQDN and port, separated by a colon :.
Expand Down
11 changes: 11 additions & 0 deletions doc/admin-guide/files/sni.yaml.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ server_groups_list Inbound Specifies an override to the
`OpenSSL SSL_CTX_set_groups_list <https://docs.openssl.org/3.5/man3/SSL_CTX_set1_curves/>`_
documentation.

ssl_ticket_enabled Inbound Specifies an override to the global
:ts:cv:`proxy.config.ssl.server.session_ticket.enable`
:file:`records.yaml` configuration. Set this to :code:`1` to enable
session tickets or :code:`0` to disable them for matching inbound TLS
connections.

ssl_ticket_number Inbound Specifies an override to the global
:ts:cv:`proxy.config.ssl.server.session_ticket.number`
:file:`records.yaml` configuration. This controls how many TLSv1.3
session tickets are issued for matching inbound TLS connections.

host_sni_policy Inbound One of the values :code:`DISABLED`, :code:`PERMISSIVE`, or :code:`ENFORCED`.

If not specified, the value of :ts:cv:`proxy.config.http.host_sni_policy` is used.
Expand Down
2 changes: 2 additions & 0 deletions include/iocore/net/TLSSNISupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ class TLSSNISupport
std::optional<int32_t> http2_max_priority_frames_per_minute;
std::optional<int32_t> http2_max_rst_stream_frames_per_minute;
std::optional<int32_t> http2_max_continuation_frames_per_minute;
std::optional<int32_t> ssl_ticket_enabled;
std::optional<int32_t> ssl_ticket_number;
std::optional<std::string_view> outbound_sni_policy;
} hints_from_sni;

Expand Down
4 changes: 4 additions & 0 deletions include/iocore/net/YamlSNIConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ TSDECL(client_sni_policy);
TSDECL(server_cipher_suite);
TSDECL(server_TLSv1_3_cipher_suites);
TSDECL(server_groups_list);
TSDECL(ssl_ticket_enabled);
TSDECL(ssl_ticket_number);
TSDECL(ip_allow);
TSDECL(valid_tls_versions_in);
TSDECL(valid_tls_version_min_in);
Expand Down Expand Up @@ -107,6 +109,8 @@ struct YamlSNIConfig {
std::string server_cipher_suite;
std::string server_TLSv1_3_cipher_suites;
std::string server_groups_list;
std::optional<int> ssl_ticket_enabled;
std::optional<int> ssl_ticket_number;
std::string ip_allow;
bool protocol_unset = true;
unsigned long protocol_mask;
Expand Down
34 changes: 34 additions & 0 deletions src/iocore/net/SNIActionPerformer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,40 @@ ServerMaxEarlyData::SNIAction([[maybe_unused]] SSL &ssl, const Context & /* ctx
return SSL_TLSEXT_ERR_OK;
}

int
ServerSessionTicketEnabled::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const
{
#if TS_HAS_TLS_SESSION_TICKET
if (auto snis = TLSSNISupport::getInstance(&ssl)) {
const char *servername = snis->get_sni_server_name();
Dbg(dbg_ctl_ssl_sni, "Setting session ticket support from sni.yaml to %d for fqdn [%s]", session_ticket_enabled, servername);
snis->hints_from_sni.ssl_ticket_enabled = session_ticket_enabled;
}

// Apply the ticket enable/disable flag immediately so the current handshake
// sees the per-SNI override before TLS session ticket processing kicks in.
if (session_ticket_enabled != 0) {
SSL_clear_options(&ssl, SSL_OP_NO_TICKET);
} else {
SSL_set_options(&ssl, SSL_OP_NO_TICKET);
}
#endif
return SSL_TLSEXT_ERR_OK;
}

int
ServerSessionTicketNumber::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const
{
#if TS_HAS_TLS_SESSION_TICKET
if (auto snis = TLSSNISupport::getInstance(&ssl)) {
const char *servername = snis->get_sni_server_name();
Dbg(dbg_ctl_ssl_sni, "Setting session ticket count from sni.yaml to %d for fqdn [%s]", session_ticket_number, servername);
snis->hints_from_sni.ssl_ticket_number = session_ticket_number;
}
#endif
return SSL_TLSEXT_ERR_OK;
}

int
ServerCipherSuite::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const
{
Expand Down
30 changes: 30 additions & 0 deletions src/iocore/net/SNIActionPerformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,36 @@ class ServerMaxEarlyData : public ActionItem
#endif
};

/**
Override session ticket support by ssl_ticket_enabled in sni.yaml
*/
class ServerSessionTicketEnabled : public ActionItem
{
public:
ServerSessionTicketEnabled(int value) : session_ticket_enabled(value) {}
~ServerSessionTicketEnabled() override {}

int SNIAction(SSL &ssl, const Context &ctx) const override;

private:
int session_ticket_enabled = 0;
};

/**
Override the number of issued TLSv1.3 session tickets by ssl_ticket_number in sni.yaml
*/
class ServerSessionTicketNumber : public ActionItem
{
public:
ServerSessionTicketNumber(int value) : session_ticket_number(value) {}
~ServerSessionTicketNumber() override {}

int SNIAction(SSL &ssl, const Context &ctx) const override;

private:
int session_ticket_number = 0;
};

/**
Override proxy.config.ssl.server.cipher_suite by server_cipher_suite in sni.yaml
*/
Expand Down
101 changes: 86 additions & 15 deletions src/iocore/net/SSLUtils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ static DbgCtl dbg_ctl_ssl_session_cache{"ssl.session_cache"};
static DbgCtl dbg_ctl_ssl_error{"ssl.error"};
static DbgCtl dbg_ctl_ssl_verify{"ssl_verify"};

#if TS_HAS_TLS_SESSION_TICKET
static bool ssl_context_enable_ticket_callback(SSL_CTX *ctx);
static bool ssl_apply_sni_session_ticket_properties(SSL *ssl);
static bool ssl_set_session_ticket_number(SSL *ssl, size_t num_tickets);
#endif

/* Using pthread thread ID and mutex functions directly, instead of
* ATS this_ethread / ProxyMutex, so that other linked libraries
* may use pthreads and openssl without confusing us here. (TS-2271).
Expand Down Expand Up @@ -304,15 +310,8 @@ ssl_cert_callback(SSL *ssl, [[maybe_unused]] void *arg)
setClientCertCACerts(ssl, sslnetvc->get_ca_cert_file(), sslnetvc->get_ca_cert_dir());
}

// Reset the ticket callback if needed
SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared<SSLMultiCertConfigParams>();
if (sslMultiCertSettings->session_ticket_enabled != 0) {
#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket);
#else
SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket);
#endif
if (!ssl_apply_sni_session_ticket_properties(ssl)) {
retval = 0;
}
}
#endif
Expand Down Expand Up @@ -493,6 +492,77 @@ ssl_context_enable_dhe(const char *dhparams_file, SSL_CTX *ctx)
return ctx;
}

#if TS_HAS_TLS_SESSION_TICKET
static bool
ssl_context_enable_ticket_callback(SSL_CTX *ctx)
{
#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
if (SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket) == 0) {
#else
if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket) == 0) {
#endif
Error("failed to set session ticket callback");
return false;
}
return true;
}

static bool
ssl_set_session_ticket_number(SSL *ssl, size_t num_tickets)
{
#if defined(OPENSSL_IS_BORINGSSL)
// BoringSSL only exposes SSL_CTX_set_num_tickets(), so the per-connection
// sni.yaml override is not available here.
(void)ssl;
(void)num_tickets;
return true;
#else
return SSL_set_num_tickets(ssl, num_tickets) == 1;
#endif
}

static bool
ssl_apply_sni_session_ticket_properties(SSL *ssl)
{
auto snis = TLSSNISupport::getInstance(ssl);
if (snis == nullptr) {
return true;
}

auto const &hints = snis->hints_from_sni;
if (!hints.ssl_ticket_enabled.has_value() && !hints.ssl_ticket_number.has_value()) {
return true;
}

std::optional<size_t> num_tickets;

if (hints.ssl_ticket_enabled.has_value()) {
if (hints.ssl_ticket_enabled.value() != 0) {
SSL_clear_options(ssl, SSL_OP_NO_TICKET);
Dbg(dbg_ctl_ssl_load, "Enabled session tickets due to sni.yaml override");
} else {
SSL_set_options(ssl, SSL_OP_NO_TICKET);
num_tickets = 0;
Dbg(dbg_ctl_ssl_load, "Disabled session tickets due to sni.yaml override");
}
}

if ((!hints.ssl_ticket_enabled.has_value() || hints.ssl_ticket_enabled.value() != 0) && hints.ssl_ticket_number.has_value()) {
num_tickets = hints.ssl_ticket_number.value() > 0 ? static_cast<size_t>(hints.ssl_ticket_number.value()) : 0;
}

if (num_tickets.has_value()) {
if (!ssl_set_session_ticket_number(ssl, num_tickets.value())) {
Error("failed to set session ticket number from sni.yaml");
return false;
}
Dbg(dbg_ctl_ssl_load, "Set session ticket number from sni.yaml to %zu", num_tickets.value());
}

return true;
}
#endif

static ssl_ticket_key_block *
ssl_context_enable_tickets(SSL_CTX *ctx, const char *ticket_key_path)
{
Expand All @@ -509,12 +579,7 @@ ssl_context_enable_tickets(SSL_CTX *ctx, const char *ticket_key_path)
// Setting the callback can only fail if OpenSSL does not recognize the
// SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB constant. we set the callback first
// so that we don't leave a ticket_key pointer attached if it fails.
#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
if (SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket) == 0) {
#else
if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket) == 0) {
#endif
Error("failed to set session ticket callback");
if (!ssl_context_enable_ticket_callback(ctx)) {
ticket_block_free(keyblock);
return nullptr;
}
Expand Down Expand Up @@ -1179,6 +1244,12 @@ SSLMultiCertConfigLoader::init_server_ssl_ctx(CertLoadData const &data, const SS
}
}

#if TS_HAS_TLS_SESSION_TICKET
if (!ssl_context_enable_ticket_callback(ctx)) {
goto fail;
}
#endif

if (!this->_setup_client_cert_verification(ctx)) {
goto fail;
}
Expand Down
1 change: 1 addition & 0 deletions src/iocore/net/TLSSNISupport.cc
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ TLSSNISupport::set_sni_server_name(SSL *ssl, char const *name)
void
TLSSNISupport::_clear()
{
hints_from_sni = {};
_sni_server_name.reset();
}

Expand Down
14 changes: 14 additions & 0 deletions src/iocore/net/YamlSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ YamlSNIConfig::Item::populate_sni_actions(action_vector_t &actions)
if (!server_groups_list.empty()) {
actions.push_back(std::make_unique<ServerGroupsList>(server_groups_list));
}
if (ssl_ticket_enabled.has_value()) {
actions.push_back(std::make_unique<ServerSessionTicketEnabled>(ssl_ticket_enabled.value()));
}
if (ssl_ticket_number.has_value()) {
actions.push_back(std::make_unique<ServerSessionTicketNumber>(ssl_ticket_number.value()));
}
if (http2_buffer_water_mark.has_value()) {
actions.push_back(std::make_unique<HTTP2BufferWaterMark>(http2_buffer_water_mark.value()));
}
Expand Down Expand Up @@ -230,6 +236,8 @@ std::set<std::string> valid_sni_config_keys = {TS_fqdn,
TS_server_TLSv1_3_cipher_suites,
#endif
TS_server_groups_list,
TS_ssl_ticket_enabled,
TS_ssl_ticket_number,
TS_http2,
TS_http2_buffer_water_mark,
TS_http2_initial_window_size_in,
Expand Down Expand Up @@ -465,6 +473,12 @@ template <> struct convert<YamlSNIConfig::Item> {
if (node[TS_server_groups_list]) {
item.server_groups_list = node[TS_server_groups_list].as<std::string>();
}
if (node[TS_ssl_ticket_enabled]) {
item.ssl_ticket_enabled = node[TS_ssl_ticket_enabled].as<int>();
}
if (node[TS_ssl_ticket_number]) {
item.ssl_ticket_number = node[TS_ssl_ticket_number].as<int>();
}
if (node[TS_ip_allow]) {
item.ip_allow = node[TS_ip_allow].as<std::string>();
}
Expand Down
5 changes: 5 additions & 0 deletions src/iocore/net/unit_tests/sni_conf_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ sni:

# test glob in the middle, this will be an exact match
- fqdn: "cat.*.com"

# test session ticket overrides
- fqdn: tickets.com
ssl_ticket_enabled: 1
ssl_ticket_number: 3
7 changes: 7 additions & 0 deletions src/iocore/net/unit_tests/test_SSLSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ TEST_CASE("Test SSLSNIConfig")
REQUIRE(actions.first->size() == 3);
}

SECTION("The config matches an SNI for tickets.com")
{
auto const &actions{params.get("tickets.com", 443)};
REQUIRE(actions.first);
REQUIRE(actions.first->size() == 4); ///< ticket enabled + ticket number + early data + fqdn
}

SECTION("Matching order")
{
auto const &actions{params.get("foo.bar.com", 443)};
Expand Down
11 changes: 10 additions & 1 deletion src/iocore/net/unit_tests/test_YamlSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately")
FAIL(errorstream.str());
}
REQUIRE(zret.is_ok());
REQUIRE(conf.items.size() == 10);
REQUIRE(conf.items.size() == 11);

SECTION("If no ports were specified, port range should contain all ports.")
{
Expand Down Expand Up @@ -103,6 +103,15 @@ TEST_CASE("YamlSNIConfig sets port ranges appropriately")
{
CHECK(conf.items[2].inbound_port_ranges.size() == 1);
}

SECTION("Session ticket overrides are parsed.")
{
auto const &item{conf.items[10]};
REQUIRE(item.ssl_ticket_enabled.has_value());
CHECK(item.ssl_ticket_enabled.value() == 1);
REQUIRE(item.ssl_ticket_number.has_value());
CHECK(item.ssl_ticket_number.value() == 3);
}
}

TEST_CASE("YamlConfig handles bad ports appropriately.")
Expand Down
Loading