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