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