1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 /*************************** -*- Mod: C++ -*- ******************************
25   SSLSNIConfig.cc
26    Created On      : 05/02/2017
27 
28    Description:
29    SNI based Configuration in ATS
30  ****************************************************************************/
31 
32 #include "P_SSLSNI.h"
33 #include "tscore/Diags.h"
34 #include "tscore/SimpleTokenizer.h"
35 #include "tscore/ink_memory.h"
36 #include "tscpp/util/TextView.h"
37 #include "tscore/I_Layout.h"
38 #include <sstream>
39 #include <pcre.h>
40 
41 static constexpr int OVECSIZE{30};
42 
43 const NextHopProperty *
getPropertyConfig(const std::string & servername) const44 SNIConfigParams::getPropertyConfig(const std::string &servername) const
45 {
46   const NextHopProperty *nps = nullptr;
47   for (auto &&item : next_hop_list) {
48     if (pcre_exec(item.match, nullptr, servername.c_str(), servername.length(), 0, 0, nullptr, 0) >= 0) {
49       // Found a match
50       nps = &item.prop;
51       break;
52     }
53   }
54   return nps;
55 }
56 
57 void
loadSNIConfig()58 SNIConfigParams::loadSNIConfig()
59 {
60   for (auto &item : Y_sni.items) {
61     auto ai = sni_action_list.emplace(sni_action_list.end());
62     ai->setGlobName(item.fqdn);
63     Debug("ssl", "name: %s", item.fqdn.data());
64 
65     // set SNI based actions to be called in the ssl_servername_only callback
66     if (item.offer_h2.has_value()) {
67       ai->actions.push_back(std::make_unique<ControlH2>(item.offer_h2.value()));
68     }
69     if (item.verify_client_level != 255) {
70       ai->actions.push_back(
71         std::make_unique<VerifyClient>(item.verify_client_level, item.verify_client_ca_file, item.verify_client_ca_dir));
72     }
73     if (item.host_sni_policy != 255) {
74       ai->actions.push_back(std::make_unique<HostSniPolicy>(item.host_sni_policy));
75     }
76     if (!item.protocol_unset) {
77       ai->actions.push_back(std::make_unique<TLSValidProtocols>(item.protocol_mask));
78     }
79     if (item.tunnel_destination.length() > 0) {
80       ai->actions.push_back(std::make_unique<TunnelDestination>(item.tunnel_destination, item.tunnel_type, item.tunnel_alpn));
81     }
82 
83     ai->actions.push_back(std::make_unique<SNI_IpAllow>(item.ip_allow, item.fqdn));
84 
85     // set the next hop properties
86     auto nps = next_hop_list.emplace(next_hop_list.end());
87 
88     SSLConfig::scoped_config params;
89     // Load if we have at least specified the client certificate
90     if (!item.client_cert.empty()) {
91       nps->prop.client_cert_file = Layout::get()->relative_to(params->clientCertPathOnly, item.client_cert.data());
92       if (!item.client_key.empty()) {
93         nps->prop.client_key_file = Layout::get()->relative_to(params->clientKeyPathOnly, item.client_key.data());
94       }
95 
96       params->getCTX(nps->prop.client_cert_file.c_str(),
97                      nps->prop.client_key_file.empty() ? nullptr : nps->prop.client_key_file.c_str(), params->clientCACertFilename,
98                      params->clientCACertPath);
99     }
100 
101     nps->setGlobName(item.fqdn);
102     nps->prop.verifyServerPolicy     = item.verify_server_policy;
103     nps->prop.verifyServerProperties = item.verify_server_properties;
104   } // end for
105 }
106 
107 int SNIConfig::configid = 0;
108 /*definition of member functions of SNIConfigParams*/
SNIConfigParams()109 SNIConfigParams::SNIConfigParams() {}
110 
111 std::pair<const actionVector *, ActionItem::Context>
get(const std::string & servername) const112 SNIConfigParams::get(const std::string &servername) const
113 {
114   int ovector[OVECSIZE];
115   ActionItem::Context context;
116 
117   for (const auto &retval : sni_action_list) {
118     int length = servername.length();
119     if (retval.match == nullptr && length == 0) {
120       return {&retval.actions, context};
121     } else if (auto offset = pcre_exec(retval.match, nullptr, servername.c_str(), length, 0, 0, ovector, OVECSIZE); offset >= 0) {
122       if (offset == 1) {
123         // first pair identify the portion of the subject string matched by the entire pattern
124         if (ovector[0] == 0 && ovector[1] == length) {
125           // full match
126           return {&retval.actions, context};
127         } else {
128           continue;
129         }
130       }
131       // If contains groups
132       if (offset == 0) {
133         // reset to max if too many.
134         offset = OVECSIZE / 3;
135       }
136 
137       const char *psubStrMatchStr = nullptr;
138       std::vector<std::string> groups;
139       for (int strnum = 1; strnum < offset; strnum++) {
140         pcre_get_substring(servername.c_str(), ovector, offset, strnum, &(psubStrMatchStr));
141         groups.emplace_back(psubStrMatchStr);
142       }
143       context._fqdn_wildcard_captured_groups = std::move(groups);
144       if (psubStrMatchStr) {
145         pcre_free_substring(psubStrMatchStr);
146       }
147 
148       return {&retval.actions, context};
149     }
150   }
151   return {nullptr, context};
152 }
153 
154 int
Initialize()155 SNIConfigParams::Initialize()
156 {
157   sni_filename = ats_stringdup(RecConfigReadConfigPath("proxy.config.ssl.servername.filename"));
158 
159   Note("%s loading ...", sni_filename);
160 
161   struct stat sbuf;
162   if (stat(sni_filename, &sbuf) == -1 && errno == ENOENT) {
163     Note("%s failed to load", sni_filename);
164     Warning("Loading SNI configuration - filename: %s doesn't exist", sni_filename);
165     return 1;
166   }
167 
168   ts::Errata zret = Y_sni.loader(sni_filename);
169   if (!zret.isOK()) {
170     std::stringstream errMsg;
171     errMsg << zret;
172     Error("%s failed to load: %s", sni_filename, errMsg.str().c_str());
173     return 1;
174   }
175 
176   loadSNIConfig();
177   Note("%s finished loading", sni_filename);
178 
179   return 0;
180 }
181 
~SNIConfigParams()182 SNIConfigParams::~SNIConfigParams()
183 {
184   // sni_action_list and next_hop_list should cleanup with the params object
185 }
186 
187 /*definition of member functions of SNIConfig*/
188 void
startup()189 SNIConfig::startup()
190 {
191   reconfigure();
192 }
193 
194 void
reconfigure()195 SNIConfig::reconfigure()
196 {
197   Debug("ssl", "Reload SNI file");
198   SNIConfigParams *params = new SNIConfigParams;
199 
200   params->Initialize();
201   configid = configProcessor.set(configid, params);
202 }
203 
204 SNIConfigParams *
acquire()205 SNIConfig::acquire()
206 {
207   return (SNIConfigParams *)configProcessor.get(configid);
208 }
209 
210 void
release(SNIConfigParams * params)211 SNIConfig::release(SNIConfigParams *params)
212 {
213   configProcessor.release(configid, params);
214 }
215 
216 // See if any of the client-side actions would trigger for this combination of servername and
217 // client IP
218 // host_sni_policy is an in/out parameter.  It starts with the global policy from the records.config
219 // setting proxy.config.http.host_sni_policy and is possibly overridden if the sni policy
220 // contains a host_sni_policy entry
221 bool
TestClientAction(const char * servername,const IpEndpoint & ep,int & host_sni_policy)222 SNIConfig::TestClientAction(const char *servername, const IpEndpoint &ep, int &host_sni_policy)
223 {
224   bool retval = false;
225   SNIConfig::scoped_config params;
226 
227   const auto &actions = params->get(servername);
228   if (actions.first) {
229     for (auto &&item : *actions.first) {
230       retval |= item->TestClientSNIAction(servername, ep, host_sni_policy);
231     }
232   }
233   return retval;
234 }
235