Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
29 changes: 29 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,35 @@ 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.

Each item contains a group key identifying the server group name and optionally,
a weight that determines how frequently that group is selected when multiple
entries are present.

Any omitted weight defaults to 100.
For example:

.. code-block:: yaml

sni:
- fqdn: example1.com
server_groups_list:
- group: "group_1"
weight: 20
- group: "group_2"
weight: 80
- fqdn: example2.com
server_groups_list:
- group: "group_1"
- fqdn: example3.com
server_groups_list: "group_1"

In this configuration:
- connections with SNI example1.com will be directed to group_1 about 20%
of the time and to group_2 about 80% of the time
- Connections for example2.com and example3.com will always use group_1.



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
65 changes: 35 additions & 30 deletions include/iocore/net/YamlSNIConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ TSDECL(server_max_early_data);

class ActionItem;

struct SNIServerGroupsList {
std::string group;
int weight = 100;
};

struct YamlSNIConfig {
enum class Policy : uint8_t { DISABLED = 0, PERMISSIVE, ENFORCED, UNSET };
enum class Property : uint8_t { NONE = 0, SIGNATURE_MASK = 0x1, NAME_MASK = 0x2, ALL_MASK = 0x3, UNSET };
Expand All @@ -91,36 +96,36 @@ struct YamlSNIConfig {

std::vector<ts::port_range_t> inbound_port_ranges;

std::optional<bool> offer_h2; // Has no value by default, so do not initialize!
std::optional<bool> offer_quic; // Has no value by default, so do not initialize!
uint8_t verify_client_level = 255;
std::string verify_client_ca_file;
std::string verify_client_ca_dir;
uint8_t host_sni_policy = 255;
SNIRoutingType tunnel_type = SNIRoutingType::NONE;
std::string tunnel_destination;
Policy verify_server_policy = Policy::UNSET;
Property verify_server_properties = Property::UNSET;
std::string client_cert;
std::string client_key;
std::string client_sni_policy;
std::string server_cipher_suite;
std::string server_TLSv1_3_cipher_suites;
std::string server_groups_list;
std::string ip_allow;
bool protocol_unset = true;
unsigned long protocol_mask;
int valid_tls_version_min_in = -1;
int valid_tls_version_max_in = -1;
std::vector<int> tunnel_alpn{};
std::optional<int> http2_buffer_water_mark;
std::optional<int> http2_max_settings_frames_per_minute;
std::optional<int> http2_max_ping_frames_per_minute;
std::optional<int> http2_max_priority_frames_per_minute;
std::optional<int> http2_max_rst_stream_frames_per_minute;
std::optional<int> http2_max_continuation_frames_per_minute;
uint32_t server_max_early_data = 0;
std::optional<int> http2_initial_window_size_in;
std::optional<bool> offer_h2; // Has no value by default, so do not initialize!
std::optional<bool> offer_quic; // Has no value by default, so do not initialize!
uint8_t verify_client_level = 255;
std::string verify_client_ca_file;
std::string verify_client_ca_dir;
uint8_t host_sni_policy = 255;
SNIRoutingType tunnel_type = SNIRoutingType::NONE;
std::string tunnel_destination;
Policy verify_server_policy = Policy::UNSET;
Property verify_server_properties = Property::UNSET;
std::string client_cert;
std::string client_key;
std::string client_sni_policy;
std::string server_cipher_suite;
std::string server_TLSv1_3_cipher_suites;
std::vector<SNIServerGroupsList> server_groups_list;
std::string ip_allow;
bool protocol_unset = true;
unsigned long protocol_mask;
int valid_tls_version_min_in = -1;
int valid_tls_version_max_in = -1;
std::vector<int> tunnel_alpn{};
std::optional<int> http2_buffer_water_mark;
std::optional<int> http2_max_settings_frames_per_minute;
std::optional<int> http2_max_ping_frames_per_minute;
std::optional<int> http2_max_priority_frames_per_minute;
std::optional<int> http2_max_rst_stream_frames_per_minute;
std::optional<int> http2_max_continuation_frames_per_minute;
uint32_t server_max_early_data = 0;
std::optional<int> http2_initial_window_size_in;

bool tunnel_prewarm_srv = false;
uint32_t tunnel_prewarm_min = 0;
Expand Down
28 changes: 25 additions & 3 deletions src/iocore/net/SNIActionPerformer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -516,10 +516,32 @@ ServerGroupsList::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) cons
if (tbs == nullptr) {
return SSL_TLSEXT_ERR_OK;
}
Dbg(dbg_ctl_ssl_sni, "Setting groups list from server_groups_list to %s", server_groups_list.c_str());

if (!tbs->set_groups_list(server_groups_list)) {
Error("Invalid server_groups_list: %s", server_groups_list.c_str());
int total = 0;
for (auto const &g : server_groups_list) {
total += g.weight;
}
if (total < 1) {
Warning("server_groups_list has invalid weights (sum <= 0)");
return SSL_TLSEXT_ERR_ALERT_WARNING;
}

int r = random() % total;
int culmative = 0;
std::string group;
for (auto const &g : server_groups_list) {
int start = culmative;
culmative += g.weight;
if (r >= start && r < culmative) {
group = g.group;
break;
}
}

Dbg(dbg_ctl_ssl_sni, "selecting server group '%s' (rand=%i, total_sum=%i)", group.c_str(), r, total);

if (!tbs->set_groups_list(group)) {
Warning("Invalid server group '%s' in SNI configuration", group.c_str());
return SSL_TLSEXT_ERR_ALERT_WARNING;
}
return SSL_TLSEXT_ERR_OK;
Expand Down
4 changes: 2 additions & 2 deletions src/iocore/net/SNIActionPerformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,11 @@ class ServerTLSv1_3CipherSuites : public ActionItem
class ServerGroupsList : public ActionItem
{
public:
ServerGroupsList(std::string const &p) : server_groups_list(p) {}
ServerGroupsList(std::vector<SNIServerGroupsList> const &p) : server_groups_list(p) {}
~ServerGroupsList() override {}

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

private:
std::string const server_groups_list{};
std::vector<SNIServerGroupsList> const server_groups_list{};
};
19 changes: 18 additions & 1 deletion src/iocore/net/YamlSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,24 @@ template <> struct convert<YamlSNIConfig::Item> {
item.server_TLSv1_3_cipher_suites = node[TS_server_TLSv1_3_cipher_suites].as<std::string>();
}
if (node[TS_server_groups_list]) {
item.server_groups_list = node[TS_server_groups_list].as<std::string>();
SNIServerGroupsList input;
if (node[TS_server_groups_list].IsScalar()) {
input.group = node[TS_server_groups_list].as<std::string>();
item.server_groups_list.emplace_back(input);
} else {
for (auto const &it : node[TS_server_groups_list]) {
if (it["group"]) {
input.group = it["group"].as<std::string>();
}
if (it["weight"]) {
input.weight = it["weight"].as<int>();
if (input.weight < 1) {
throw YAML::ParserException(node[TS_server_groups_list].Mark(), "server_groups_list weight must be greater than 0");
}
}
item.server_groups_list.emplace_back(std::move(input));
}
}
}
if (node[TS_ip_allow]) {
item.ip_allow = node[TS_ip_allow].as<std::string>();
Expand Down
7 changes: 3 additions & 4 deletions tests/gold_tests/tls/tls_sni_groups.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@
ts=ts)
tr.ReturnCode = 0
tr.StillRunningAfter = ts
ts.Disk.traffic_out.Content += Testers.ContainsExpression(
"Setting groups list from server_groups_list to x25519", "Should log setting the server groups")
ts.Disk.traffic_out.Content += Testers.ContainsExpression("selecting server group 'x25519'", "Should log setting the server groups")
tr.Processes.Default.Streams.all = Testers.IncludesExpression(
f"SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 / x25519", "Curl should log using x25519 in the SSL connection")

Expand All @@ -89,7 +88,7 @@
tr.StillRunningAfter = ts
tr.StillRunningAfter = server
ts.Disk.diags_log.Content = Testers.ContainsExpression(
"ERROR: Invalid server_groups_list: ABC123", "Curl attempt should have failed")
"WARNING: Invalid server group 'ABC123' in SNI configuration", "Curl attempt should have failed")

# Hybrid ECDH PQ key exchange TLS groups were added in OpenSSL 3.5
if Condition.HasOpenSSLVersion("3.5.0"):
Expand All @@ -101,7 +100,7 @@
tr.ReturnCode = 0
tr.StillRunningAfter = ts
ts.Disk.traffic_out.Content += Testers.ContainsExpression(
"Setting groups list from server_groups_list to X25519MLKEM768", "Should log setting the server groups")
"selecting server group 'X25519MLKEM768'", "Should log setting the server groups")
tr.Processes.Default.Streams.all = Testers.IncludesExpression(
f"SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768",
f"Curl should log using X25519MLKEM768 in the SSL connection")