diff --git a/doc/admin-guide/files/sni.yaml.en.rst b/doc/admin-guide/files/sni.yaml.en.rst index cf15490ff73..36563d62023 100644 --- a/doc/admin-guide/files/sni.yaml.en.rst +++ b/doc/admin-guide/files/sni.yaml.en.rst @@ -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`. @@ -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. diff --git a/iocore/net/CMakeLists.txt b/iocore/net/CMakeLists.txt index d046c7c0003..16c0bf66737 100644 --- a/iocore/net/CMakeLists.txt +++ b/iocore/net/CMakeLists.txt @@ -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) diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am index eb2e19de635..fac842765cc 100644 --- a/iocore/net/Makefile.am +++ b/iocore/net/Makefile.am @@ -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 diff --git a/iocore/net/P_SNIActionPerformer.h b/iocore/net/P_SNIActionPerformer.h index 9d6d98480b8..9b53c53fb3a 100644 --- a/iocore/net/P_SNIActionPerformer.h +++ b/iocore/net/P_SNIActionPerformer.h @@ -35,6 +35,7 @@ #include "P_SSLNetVConnection.h" #include "SNIActionPerformer.h" #include "SSLTypes.h" +#include "swoc/TextView.h" #include "tscore/ink_inet.h" @@ -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(list.size()), list.data()); - break; - } else { - Debug("ssl_sni", "%.*s added to the ip_allow list %s", static_cast(list.size()), list.data(), servername.c_str()); - ip_map.fill(IpEndpoint().assign(addr1), IpEndpoint().assign(addr2), reinterpret_cast(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(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); }; /** diff --git a/iocore/net/SNIActionPerformer.cc b/iocore/net/SNIActionPerformer.cc new file mode 100644 index 00000000000..3d9fec0280b --- /dev/null +++ b/iocore/net/SNIActionPerformer.cc @@ -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(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(list.size()), list.data()); + break; + } else { + Debug("ssl_sni", "%.*s added to the ip_allow list %.*s", static_cast(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(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); +}