Skip to content
Merged
14 changes: 13 additions & 1 deletion doc/admin-guide/files/sni.yaml.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ ip_allow Inbound Specify a list of client IP address, subnets
Here is an example list: 192.168.1.0/24,192.168.10.1-4. This would allow connections
from clients in the 19.168.1.0 network or in the range from 192.168.10.1 to 192.168.1.4.

Alternatively, the path to a file containing
the list of IP addresses can be specified in
the form of ``"@path_to_file"``. The IP
addresses in the file can be either
comma-separated or line-separated. If a
given file path does not begin with ``/``,
it must be relative to the Traffic Server
configuration directory. Here is an example
showing this form of the configuration:

``ip_allow: "@ip_dir/example.com.ip.txt"``

verify_server_policy Outbound One of the values :code:`DISABLED`, :code:`PERMISSIVE`, or :code:`ENFORCED`.

By default this is :ts:cv:`proxy.config.ssl.client.verify.server.policy`.
Expand All @@ -86,7 +98,7 @@ verify_client_ca_certs Both Specifies an alternate set of certificate au
CA certs. Otherwise, there should be up to two nested pairs. The possible keys
are ``file`` and ``dir``. The value for ``file`` must be a file path for a file
containing CA certs. The value for ``dir`` must be a file path for an OpenSSL
X509 hashed directory containing CA certs. If a given file path does not being
X509 hashed directory containing CA certs. If a given file path does not begin
with ``/`` , it must be relative to the |TS| configuration directory.
``verify_client_ca_certs`` can only be used with capbilities provided by
OpenSSL 1.0.2 or later.
Expand Down
1 change: 1 addition & 0 deletions iocore/net/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ add_library(inknet STATIC
UnixUDPConnection.cc
UnixUDPNet.cc
SSLDynlock.cc
SNIActionPerformer.cc
)
target_link_libraries(inknet inkevent records_p)
target_compile_options(inknet PUBLIC -Wno-deprecated-declarations)
Expand Down
3 changes: 2 additions & 1 deletion iocore/net/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ libinknet_a_SOURCES = \
UnixNetVConnection.cc \
UnixUDPConnection.cc \
UnixUDPNet.cc \
SSLDynlock.cc
SSLDynlock.cc \
SNIActionPerformer.cc

if ENABLE_QUIC
if USE_QUICHE
Expand Down
62 changes: 11 additions & 51 deletions iocore/net/P_SNIActionPerformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "P_SSLNetVConnection.h"
#include "SNIActionPerformer.h"
#include "SSLTypes.h"
#include "swoc/TextView.h"

#include "tscore/ink_inet.h"

Expand Down Expand Up @@ -296,60 +297,19 @@ class SNI_IpAllow : public ActionItem
IpMap ip_map;

public:
SNI_IpAllow(std::string &ip_allow_list, const std::string &servername)
{
// the server identified by item.fqdn requires ATS to do IP filtering
if (ip_allow_list.length()) {
IpAddr addr1;
IpAddr addr2;
// check format first
// check if the input is a comma separated list of IPs
ts::TextView content(ip_allow_list);
while (!content.empty()) {
ts::TextView list{content.take_prefix_at(',')};
if (0 != ats_ip_range_parse(list, addr1, addr2)) {
Debug("ssl_sni", "%.*s is not a valid format", static_cast<int>(list.size()), list.data());
break;
} else {
Debug("ssl_sni", "%.*s added to the ip_allow list %s", static_cast<int>(list.size()), list.data(), servername.c_str());
ip_map.fill(IpEndpoint().assign(addr1), IpEndpoint().assign(addr2), reinterpret_cast<void *>(1));
}
}
}
} // end function SNI_IpAllow
SNI_IpAllow(std::string &ip_allow_list, const std::string &servername);

int
SNIAction(TLSSNISupport *snis, const Context &ctx) const override
{
// i.e, ip filtering is not required
if (ip_map.count() == 0) {
return SSL_TLSEXT_ERR_OK;
}
int SNIAction(TLSSNISupport *snis, const Context &ctx) const override;

auto ssl_vc = dynamic_cast<SSLNetVConnection *>(snis);
auto ip = ssl_vc->get_remote_endpoint();

// check the allowed ips
if (ip_map.contains(ip)) {
return SSL_TLSEXT_ERR_OK;
} else {
char buff[256];
ats_ip_ntop(&ip.sa, buff, sizeof(buff));
Debug("ssl_sni", "%s is not allowed. Denying connection", buff);
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
}
bool TestClientSNIAction(const char *servrername, const IpEndpoint &ep, int &policy) const override;

bool
TestClientSNIAction(const char *servrername, const IpEndpoint &ep, int &policy) const override
{
bool retval = false;
if (ip_map.count() > 0) {
// Only triggers if the map didn't contain the address
retval = !ip_map.contains(ep);
}
return retval;
}
protected:
/** Load the map from @a text.
*
* @param content A list of IP addresses in text form, separated by commas or newlines.
* @param server_name Server named, used only for debugging messages.
*/
void load(swoc::TextView content, swoc::TextView server_name);
};

/**
Expand Down
95 changes: 95 additions & 0 deletions iocore/net/SNIActionPerformer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/** @file

Implementation of SNIActionPerformer

@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.
*/

#include "P_SNIActionPerformer.h"
#include "swoc/swoc_file.h"
#include "swoc/BufferWriter.h"
#include "swoc/bwf_std.h"

SNI_IpAllow::SNI_IpAllow(std::string &ip_allow_list, std::string const &servername)
{
swoc::TextView content{ip_allow_list};
if (content && content[0] == '@') {
std::error_code ec;
swoc::file::path path{content.remove_prefix(1)};
if (path.is_relative()) {
path = swoc::file::path(Layout::get()->sysconfdir) / path;
}
ip_allow_list = swoc::file::load(path, ec);
if (ec) {
swoc::LocalBufferWriter<1024> w;
w.print("SNIConfig unable to load file {} - {}", path.string(), ec);
Warning("%.*s", int(w.size()), w.data());
}
}
this->load(ip_allow_list, servername);
}

void
SNI_IpAllow::load(swoc::TextView content, swoc::TextView server_name)
{
IpAddr addr1;
IpAddr addr2;
static constexpr swoc::TextView delim{",\n"};
static void *MARK{reinterpret_cast<void *>(1)};

while (!content.ltrim(delim).empty()) {
swoc::TextView list{content.take_prefix_at(delim)};
if (0 != ats_ip_range_parse(list, addr1, addr2)) {
Debug("ssl_sni", "%.*s is not a valid format", static_cast<int>(list.size()), list.data());
break;
} else {
Debug("ssl_sni", "%.*s added to the ip_allow list %.*s", static_cast<int>(list.size()), list.data(), int(server_name.size()),
server_name.data());
ip_map.fill(IpEndpoint().assign(addr1), IpEndpoint().assign(addr2), MARK);
}
}
}

int
SNI_IpAllow::SNIAction(TLSSNISupport *snis, ActionItem::Context const &ctx) const
{
// i.e, ip filtering is not required
if (ip_map.count() == 0) {
return SSL_TLSEXT_ERR_OK;
}

auto ssl_vc = dynamic_cast<SSLNetVConnection *>(snis);
auto ip = ssl_vc->get_remote_endpoint();

// check the allowed ips
if (ip_map.contains(ip)) {
return SSL_TLSEXT_ERR_OK;
} else {
char buff[256];
ats_ip_ntop(&ip.sa, buff, sizeof(buff));
Debug("ssl_sni", "%s is not allowed. Denying connection", buff);
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
}

bool
SNI_IpAllow::TestClientSNIAction(char const *servrername, IpEndpoint const &ep, int &policy) const
{
return ip_map.contains(ep);
}