1 /* Copyright (c) 2018 Percona LLC and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or
4 modify it under the terms of the GNU General Public License
5 as published by the Free Software Foundation; version 2 of
6 the License.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
16
17 #include "vault_curl.h"
18 #include <algorithm>
19 #include <boost/core/noncopyable.hpp>
20 #include <boost/scope_exit.hpp>
21 #include "my_rdtsc.h"
22 #include "mysql/service_thd_wait.h"
23 #include "plugin/keyring/common/secure_string.h"
24 #include "sql/current_thd.h"
25 #include "sql/mysqld.h"
26 #include "sql/sql_error.h"
27 #include "vault_base64.h"
28
29 namespace keyring {
30
31 static const constexpr size_t max_response_size = 32000000;
32 static MY_TIMER_INFO curl_timer_info;
33 static ulonglong last_ping_time;
34 static bool was_thd_wait_started = false;
35 #ifndef NDEBUG
36 static const constexpr ulonglong slow_connection_threshold = 100; // [ms]
37 #endif
38
39 class Thd_wait_end_guard {
40 public:
~Thd_wait_end_guard()41 ~Thd_wait_end_guard() {
42 DBUG_EXECUTE_IF("vault_network_lag", { was_thd_wait_started = false; });
43 DBUG_ASSERT(!was_thd_wait_started);
44 if (was_thd_wait_started) {
45 // This should never be called as thd_wait_end should be called at the end
46 // of CURL I/O operation. However, in production the call to thd_wait_end
47 // cannot be missed. Thus we limit our trust to CURL lib and make sure
48 // thd_wait_end was called.
49 thd_wait_end(current_thd);
50 was_thd_wait_started = false;
51 }
52 }
53 };
54
55 class Curl_session_guard : private boost::noncopyable {
56 public:
Curl_session_guard(CURL * curl)57 Curl_session_guard(CURL *curl) noexcept : curl(curl) {}
~Curl_session_guard()58 ~Curl_session_guard() {
59 if (curl != nullptr) curl_easy_cleanup(curl);
60 }
61
62 private:
63 CURL *curl;
64 };
65
write_response_memory(void * contents,size_t size,size_t nmemb,void * userp)66 static size_t write_response_memory(void *contents, size_t size, size_t nmemb,
67 void *userp) noexcept {
68 size_t realsize = size * nmemb;
69 if (size != 0 && realsize / size != nmemb) return 0; // overflow
70 Secure_ostringstream *read_data = static_cast<Secure_ostringstream *>(userp);
71 size_t ss_pos = read_data->tellp();
72 read_data->seekp(0, std::ios::end);
73 size_t number_of_read_bytes = read_data->tellp();
74 read_data->seekp(ss_pos);
75
76 if (number_of_read_bytes + realsize > max_response_size)
77 return 0; // response size limit exceeded
78
79 read_data->write(static_cast<char *>(contents), realsize);
80 if (!read_data->good()) return 0;
81 return realsize;
82 }
83
progress_callback(void * clientp MY_ATTRIBUTE ((unused)),double dltotal,double dlnow,double ultotal MY_ATTRIBUTE ((unused)),double ulnow MY_ATTRIBUTE ((unused)))84 int progress_callback(void *clientp MY_ATTRIBUTE((unused)), double dltotal,
85 double dlnow, double ultotal MY_ATTRIBUTE((unused)),
86 double ulnow MY_ATTRIBUTE((unused))) noexcept {
87 ulonglong curr_ping_time = my_timer_milliseconds();
88
89 DBUG_EXECUTE_IF("vault_network_lag", {
90 curr_ping_time = last_ping_time + slow_connection_threshold + 10;
91 dltotal = 1;
92 dlnow = 0;
93 });
94
95 BOOST_SCOPE_EXIT(&curr_ping_time, &last_ping_time) {
96 last_ping_time = curr_ping_time;
97 }
98 BOOST_SCOPE_EXIT_END
99
100 //***To keep compiler happy, remove when PS-244 gets resolved
101 (void) dltotal;
102 (void)dlnow;
103 //****
104
105 // The calls to threadpool are disabled till bug PS-244 gets resolved.
106 /* <--Uncomment when PS-244 gets resolved
107 if (!was_thd_wait_started)
108 {
109 if ((dlnow < dltotal || ulnow < ultotal) && last_ping_time - curr_ping_time
110 > slow_connection_threshold)
111 {
112 // there is a good chance that connection is slow, thus we can let know
113 the threadpool that there is time
114 // to start new thread(s)
115 thd_wait_begin(current_thd, THD_WAIT_NET);
116 was_thd_wait_started = true;
117 }
118 }
119 else if ((dlnow == dltotal && ulnow == ultotal) || last_ping_time -
120 curr_ping_time <= slow_connection_threshold)
121 {
122 // connection has speed up or we have finished transfering
123 thd_wait_end(current_thd);
124 was_thd_wait_started = false;
125 }*/ // <--Uncomment when PS-244 gets resolved
126 return 0;
127 }
128
get_error_from_curl(CURLcode curl_code)129 std::string Vault_curl::get_error_from_curl(CURLcode curl_code) {
130 size_t len = strlen(curl_errbuf);
131 std::ostringstream ss;
132 if (curl_code != CURLE_OK) {
133 ss << "CURL returned this error code: " << curl_code;
134 ss << " with error message : ";
135 if (len)
136 ss << curl_errbuf;
137 else
138 ss << curl_easy_strerror(curl_code);
139 }
140 return ss.str();
141 }
142
init(const Vault_credentials & vault_credentials)143 bool Vault_curl::init(const Vault_credentials &vault_credentials) {
144 this->token_header =
145 "X-Vault-Token:" + get_credential(vault_credentials, "token");
146 this->vault_url = get_credential(vault_credentials, "vault_url") + "/v1/" +
147 get_credential(vault_credentials, "secret_mount_point");
148 this->vault_ca = get_credential(vault_credentials, "vault_ca");
149 if (this->vault_ca.empty()) {
150 logger->log(
151 MY_WARNING_LEVEL,
152 "There is no vault_ca specified in keyring_vault's configuration file. "
153 "Please make sure that Vault's CA certificate is trusted by the "
154 "machine from "
155 "which you intend to connect to Vault.");
156 }
157 my_timer_init(&curl_timer_info);
158 return false;
159 }
160
setup_curl_session(CURL * curl)161 bool Vault_curl::setup_curl_session(CURL *curl) {
162 CURLcode curl_res = CURLE_OK;
163 read_data_ss.str("");
164 read_data_ss.clear();
165 curl_errbuf[0] = '\0';
166 if (list != nullptr) {
167 curl_slist_free_all(list);
168 list = nullptr;
169 }
170
171 last_ping_time = my_timer_milliseconds();
172
173 if ((list = curl_slist_append(list, token_header.c_str())) == nullptr ||
174 (list = curl_slist_append(list, "Content-Type: application/json")) ==
175 nullptr ||
176 (curl_res = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf)) !=
177 CURLE_OK ||
178 (curl_res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
179 write_response_memory)) != CURLE_OK ||
180 (curl_res = curl_easy_setopt(curl, CURLOPT_WRITEDATA,
181 static_cast<void *>(&read_data_ss))) !=
182 CURLE_OK ||
183 (curl_res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list)) !=
184 CURLE_OK ||
185 (curl_res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1)) !=
186 CURLE_OK ||
187 (curl_res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L)) !=
188 CURLE_OK ||
189 (!vault_ca.empty() &&
190 (curl_res = curl_easy_setopt(curl, CURLOPT_CAINFO, vault_ca.c_str())) !=
191 CURLE_OK) ||
192 (curl_res = curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL)) !=
193 CURLE_OK ||
194 (curl_res = curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
195 progress_callback)) != CURLE_OK ||
196 (curl_res = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L)) != CURLE_OK ||
197 (curl_res = curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout)) !=
198 CURLE_OK ||
199 (curl_res = curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout)) !=
200 CURLE_OK) {
201 logger->log(MY_ERROR_LEVEL, get_error_from_curl(curl_res).c_str());
202 return true;
203 }
204 return false;
205 }
206
list_keys(Secure_string * response)207 bool Vault_curl::list_keys(Secure_string *response) {
208 CURLcode curl_res = CURLE_OK;
209 long http_code = 0;
210
211 Thd_wait_end_guard thd_wait_end_guard;
212 (void)thd_wait_end_guard; // silence unused variable error
213
214 CURL *curl = curl_easy_init();
215 if (curl == nullptr) {
216 logger->log(MY_ERROR_LEVEL, "Cannot initialize curl session");
217 return true;
218 }
219 Curl_session_guard curl_session_guard(curl);
220
221 if (setup_curl_session(curl) ||
222 (curl_res = curl_easy_setopt(curl, CURLOPT_URL,
223 (vault_url + "?list=true").c_str())) !=
224 CURLE_OK ||
225 (curl_res = curl_easy_perform(curl)) != CURLE_OK ||
226 (curl_res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE,
227 &http_code)) != CURLE_OK) {
228 logger->log(MY_ERROR_LEVEL, get_error_from_curl(curl_res).c_str());
229 return true;
230 }
231 if (http_code == 404) {
232 *response = ""; // no keys found
233 return false;
234 }
235 *response = read_data_ss.str();
236 return http_code / 100 != 2; // 2** are success return codes
237 }
238
encode_key_signature(const Vault_key & key,Secure_string * encoded_key_signature)239 bool Vault_curl::encode_key_signature(const Vault_key &key,
240 Secure_string *encoded_key_signature) {
241 if (Vault_base64::encode(
242 key.get_key_signature()->c_str(), key.get_key_signature()->length(),
243 encoded_key_signature, Vault_base64::Format::SINGLE_LINE)) {
244 logger->log(MY_ERROR_LEVEL, "Could not encode key's signature in base64");
245 return true;
246 }
247 return false;
248 }
249
get_key_url(const Vault_key & key,Secure_string * key_url)250 bool Vault_curl::get_key_url(const Vault_key &key, Secure_string *key_url) {
251 Secure_string encoded_key_signature;
252 if (encode_key_signature(key, &encoded_key_signature)) return true;
253 *key_url = vault_url + '/' + encoded_key_signature.c_str();
254 return false;
255 }
256
write_key(const Vault_key & key,Secure_string * response)257 bool Vault_curl::write_key(const Vault_key &key, Secure_string *response) {
258 Secure_string encoded_key_data;
259 if (Vault_base64::encode(reinterpret_cast<const char *>(key.get_key_data()),
260 key.get_key_data_size(), &encoded_key_data,
261 Vault_base64::Format::SINGLE_LINE)) {
262 logger->log(MY_ERROR_LEVEL, "Could not encode a key in base64");
263 return true;
264 }
265 CURLcode curl_res = CURLE_OK;
266 Secure_string postdata = "{\"type\":\"";
267 postdata += key.get_key_type_as_string()->c_str();
268 postdata += "\",\"";
269 postdata += "value\":\"" + encoded_key_data + "\"}";
270
271 Secure_string key_url;
272 if (get_key_url(key, &key_url)) return true;
273
274 Thd_wait_end_guard thd_wait_end_guard;
275 (void)thd_wait_end_guard; // silence unused variable error
276
277 CURL *curl = curl_easy_init();
278 if (curl == nullptr) {
279 logger->log(MY_ERROR_LEVEL, "Cannot initialize curl session");
280 return true;
281 }
282 Curl_session_guard curl_session_guard(curl);
283
284 if (setup_curl_session(curl) ||
285 (curl_res = curl_easy_setopt(curl, CURLOPT_URL, key_url.c_str())) !=
286 CURLE_OK ||
287 (curl_res = curl_easy_setopt(curl, CURLOPT_POSTFIELDS,
288 postdata.c_str())) != CURLE_OK ||
289 (curl_res = curl_easy_perform(curl)) != CURLE_OK) {
290 logger->log(MY_ERROR_LEVEL, get_error_from_curl(curl_res).c_str());
291 return true;
292 }
293 *response = read_data_ss.str();
294 return false;
295 }
296
read_key(const Vault_key & key,Secure_string * response)297 bool Vault_curl::read_key(const Vault_key &key, Secure_string *response) {
298 Secure_string key_url;
299 if (get_key_url(key, &key_url)) return true;
300 CURLcode curl_res = CURLE_OK;
301
302 Thd_wait_end_guard thd_wait_end_guard;
303 (void)thd_wait_end_guard; // silence unused variable error
304
305 CURL *curl = curl_easy_init();
306 if (curl == NULL) {
307 logger->log(MY_ERROR_LEVEL, "Cannot initialize curl session");
308 return true;
309 }
310 Curl_session_guard curl_session_guard(curl);
311
312 if (setup_curl_session(curl) ||
313 (curl_res = curl_easy_setopt(curl, CURLOPT_URL, key_url.c_str())) !=
314 CURLE_OK ||
315 (curl_res = curl_easy_perform(curl)) != CURLE_OK) {
316 logger->log(MY_ERROR_LEVEL, get_error_from_curl(curl_res).c_str());
317 return true;
318 }
319 *response = read_data_ss.str();
320 return false;
321 }
322
delete_key(const Vault_key & key,Secure_string * response)323 bool Vault_curl::delete_key(const Vault_key &key, Secure_string *response) {
324 Secure_string key_url;
325 if (get_key_url(key, &key_url)) return true;
326 CURLcode curl_res = CURLE_OK;
327
328 Thd_wait_end_guard thd_wait_end_guard;
329 (void)thd_wait_end_guard; // silence unused variable error
330 CURL *curl = curl_easy_init();
331 if (curl == NULL) {
332 logger->log(MY_ERROR_LEVEL, "Cannot initialize curl session");
333 return true;
334 }
335 Curl_session_guard curl_session_guard(curl);
336
337 if (setup_curl_session(curl) ||
338 (curl_res = curl_easy_setopt(curl, CURLOPT_URL, key_url.c_str())) !=
339 CURLE_OK ||
340 (curl_res = curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE")) !=
341 CURLE_OK ||
342 (curl_res = curl_easy_perform(curl)) != CURLE_OK) {
343 logger->log(MY_ERROR_LEVEL, get_error_from_curl(curl_res).c_str());
344 return true;
345 }
346 *response = read_data_ss.str();
347 return false;
348 }
349
350 } // namespace keyring
351