blob: ee92eb0463508dcb07b169318a2f1b02543cd28c [file] [log] [blame]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*************************** -*- Mod: C++ -*- ******************************
SSLSNIConfig.cc
Created On : 05/02/2017
Description:
SNI based Configuration in ATS
****************************************************************************/
#include "P_SSLSNI.h"
#include "tscore/Diags.h"
#include "tscore/SimpleTokenizer.h"
#include "tscore/ink_memory.h"
#include "tscpp/util/TextView.h"
#include "tscore/I_Layout.h"
#include <sstream>
#include <pcre.h>
static constexpr int OVECSIZE{30};
const NextHopProperty *
SNIConfigParams::getPropertyConfig(const std::string &servername) const
{
const NextHopProperty *nps = nullptr;
for (auto &&item : next_hop_list) {
if (pcre_exec(item.match, nullptr, servername.c_str(), servername.length(), 0, 0, nullptr, 0) >= 0) {
// Found a match
nps = &item.prop;
break;
}
}
return nps;
}
void
SNIConfigParams::loadSNIConfig()
{
for (auto &item : Y_sni.items) {
auto ai = sni_action_list.emplace(sni_action_list.end());
ai->setGlobName(item.fqdn);
Debug("ssl", "name: %s", item.fqdn.data());
// set SNI based actions to be called in the ssl_servername_only callback
if (item.offer_h2.has_value()) {
ai->actions.push_back(std::make_unique<ControlH2>(item.offer_h2.value()));
}
if (item.verify_client_level != 255) {
ai->actions.push_back(
std::make_unique<VerifyClient>(item.verify_client_level, item.verify_client_ca_file, item.verify_client_ca_dir));
}
if (item.host_sni_policy != 255) {
ai->actions.push_back(std::make_unique<HostSniPolicy>(item.host_sni_policy));
}
if (!item.protocol_unset) {
ai->actions.push_back(std::make_unique<TLSValidProtocols>(item.protocol_mask));
}
if (item.tunnel_destination.length() > 0) {
ai->actions.push_back(std::make_unique<TunnelDestination>(item.tunnel_destination, item.tunnel_type, item.tunnel_alpn));
}
if (!item.client_sni_policy.empty()) {
ai->actions.push_back(std::make_unique<OutboundSNIPolicy>(item.client_sni_policy));
}
ai->actions.push_back(std::make_unique<SNI_IpAllow>(item.ip_allow, item.fqdn));
// set the next hop properties
auto nps = next_hop_list.emplace(next_hop_list.end());
SSLConfig::scoped_config params;
// Load if we have at least specified the client certificate
if (!item.client_cert.empty()) {
nps->prop.client_cert_file = Layout::get()->relative_to(params->clientCertPathOnly, item.client_cert.data());
if (!item.client_key.empty()) {
nps->prop.client_key_file = Layout::get()->relative_to(params->clientKeyPathOnly, item.client_key.data());
}
params->getCTX(nps->prop.client_cert_file.c_str(),
nps->prop.client_key_file.empty() ? nullptr : nps->prop.client_key_file.c_str(), params->clientCACertFilename,
params->clientCACertPath);
}
nps->setGlobName(item.fqdn);
nps->prop.verifyServerPolicy = item.verify_server_policy;
nps->prop.verifyServerProperties = item.verify_server_properties;
} // end for
}
int SNIConfig::configid = 0;
/*definition of member functions of SNIConfigParams*/
SNIConfigParams::SNIConfigParams() {}
std::pair<const actionVector *, ActionItem::Context>
SNIConfigParams::get(const std::string &servername) const
{
int ovector[OVECSIZE];
ActionItem::Context context;
for (const auto &retval : sni_action_list) {
int length = servername.length();
if (retval.match == nullptr && length == 0) {
return {&retval.actions, context};
} else if (auto offset = pcre_exec(retval.match, nullptr, servername.c_str(), length, 0, 0, ovector, OVECSIZE); offset >= 0) {
if (offset == 1) {
// first pair identify the portion of the subject string matched by the entire pattern
if (ovector[0] == 0 && ovector[1] == length) {
// full match
return {&retval.actions, context};
} else {
continue;
}
}
// If contains groups
if (offset == 0) {
// reset to max if too many.
offset = OVECSIZE / 3;
}
const char *psubStrMatchStr = nullptr;
std::vector<std::string> groups;
for (int strnum = 1; strnum < offset; strnum++) {
pcre_get_substring(servername.c_str(), ovector, offset, strnum, &(psubStrMatchStr));
groups.emplace_back(psubStrMatchStr);
}
context._fqdn_wildcard_captured_groups = std::move(groups);
if (psubStrMatchStr) {
pcre_free_substring(psubStrMatchStr);
}
return {&retval.actions, context};
}
}
return {nullptr, context};
}
int
SNIConfigParams::Initialize()
{
sni_filename = ats_stringdup(RecConfigReadConfigPath("proxy.config.ssl.servername.filename"));
Note("%s loading ...", sni_filename);
struct stat sbuf;
if (stat(sni_filename, &sbuf) == -1 && errno == ENOENT) {
Note("%s failed to load", sni_filename);
Warning("Loading SNI configuration - filename: %s doesn't exist", sni_filename);
return 1;
}
ts::Errata zret = Y_sni.loader(sni_filename);
if (!zret.isOK()) {
std::stringstream errMsg;
errMsg << zret;
Error("%s failed to load: %s", sni_filename, errMsg.str().c_str());
return 1;
}
loadSNIConfig();
Note("%s finished loading", sni_filename);
return 0;
}
SNIConfigParams::~SNIConfigParams()
{
// sni_action_list and next_hop_list should cleanup with the params object
}
/*definition of member functions of SNIConfig*/
void
SNIConfig::startup()
{
reconfigure();
}
void
SNIConfig::reconfigure()
{
Debug("ssl", "Reload SNI file");
SNIConfigParams *params = new SNIConfigParams;
params->Initialize();
configid = configProcessor.set(configid, params);
}
SNIConfigParams *
SNIConfig::acquire()
{
return (SNIConfigParams *)configProcessor.get(configid);
}
void
SNIConfig::release(SNIConfigParams *params)
{
configProcessor.release(configid, params);
}
// See if any of the client-side actions would trigger for this combination of servername and
// client IP
// host_sni_policy is an in/out paramter. It starts with the global policy from the records.config
// setting proxy.config.http.host_sni_policy and is possibly overridden if the sni policy
// contains a host_sni_policy entry
bool
SNIConfig::TestClientAction(const char *servername, const IpEndpoint &ep, int &host_sni_policy)
{
bool retval = false;
SNIConfig::scoped_config params;
const auto &actions = params->get(servername);
if (actions.first) {
for (auto &&item : *actions.first) {
retval |= item->TestClientSNIAction(servername, ep, host_sni_policy);
}
}
return retval;
}