Line data Source code
1 : /*
2 : * Copyright (c) 2013 Juniper Networks, Inc. All rights reserved.
3 : * Copyright Contributors to the OpenSDN project
4 : */
5 :
6 : #include <boost/bind/bind.hpp>
7 : #include <boost/asio/ip/host_name.hpp>
8 : #include <boost/foreach.hpp>
9 : #include <boost/tokenizer.hpp>
10 : #include <boost/assign/list_of.hpp>
11 :
12 : #if defined(RHEL_MAJOR) && (RHEL_MAJOR >= 9)
13 : extern "C" {
14 : #include <services/metadata_proxy_extern.h>
15 : }
16 : #else
17 : #include <isc/hmacmd5.h>
18 : #include <isc/hmacsha.h>
19 : #endif
20 :
21 : #include "base/contrail_ports.h"
22 : #include "http/http_request.h"
23 : #include "http/http_session.h"
24 : #include "http/http_server.h"
25 : #include "http/client/http_client.h"
26 : #include "http/client/http_curl.h"
27 : #include "io/event_manager.h"
28 : #include "cmn/agent_cmn.h"
29 : #include "init/agent_param.h"
30 : #include "oper/operdb_init.h"
31 : #include "oper/mirror_table.h"
32 : #include "oper/interface_common.h"
33 : #include "oper/global_vrouter.h"
34 : #include "pkt/pkt_handler.h"
35 : #include "services/services_types.h"
36 : #include "services/services_init.h"
37 : #include "services/metadata_proxy.h"
38 : #include "services/metadata_server.h"
39 : #include "services/metadata_server_session.h"
40 : #include "services/metadata_client.h"
41 : #include "services/metadata_client_session.h"
42 : #include "services/services_sandesh.h"
43 :
44 : using namespace boost::placeholders;
45 :
46 : ////////////////////////////////////////////////////////////////////////////////
47 :
48 : #define METADATA_TRACE(obj, arg) \
49 : do { \
50 : std::ostringstream _str; \
51 : _str << arg; \
52 : Metadata##obj::TraceMsg(MetadataTraceBuf, __FILE__, __LINE__, _str.str()); \
53 : } while (false) \
54 :
55 : std::map<uint16_t, std::string>
56 : g_http_error_map = boost::assign::map_list_of<uint16_t, std::string>
57 : (404, "404 Not Found")
58 : (500, "500 Internal Server Error")
59 : (501, "501 Not Implemented")
60 : (502, "502 Bad Gateway")
61 : (503, "503 Service Unavailable")
62 : (504, "504 Gateway Timeout");
63 :
64 0 : static std::string ErrorMessage(uint16_t ec) {
65 0 : std::map<uint16_t, std::string>::iterator iter = g_http_error_map.find(ec);
66 0 : if (iter == g_http_error_map.end())
67 0 : return "";
68 0 : return iter->second;
69 : }
70 :
71 0 : static std::string GetHmacSha256(const std::string &key, const std::string &data) {
72 : #if defined(RHEL_MAJOR) && (RHEL_MAJOR >= 9)
73 0 : const isc_md_type_t *md_type = ISC_MD_SHA256;
74 : unsigned char digest[ISC_MAX_MD_SIZE];
75 0 : unsigned int digestlen = sizeof(digest);
76 0 : isc_result_t result = isc_hmac(md_type, (const unsigned char *)key.c_str(), key.length(),
77 0 : (const unsigned char *)data.c_str(), data.length(),
78 : digest, &digestlen);
79 0 : if (result != ISC_R_SUCCESS) {
80 0 : return "";
81 : }
82 :
83 0 : std::stringstream str;
84 0 : for (unsigned int i = 0; i < digestlen; i++) {
85 0 : str << std::hex << std::setfill('0') << std::setw(2) << (int)digest[i];
86 : }
87 : #else
88 : isc_hmacsha256_t hmacsha256;
89 : isc_hmacsha256_init(&hmacsha256, (const unsigned char *)key.c_str(),
90 : key.length());
91 : isc_hmacsha256_update(&hmacsha256, (const isc_uint8_t *)data.c_str(),
92 : data.length());
93 : unsigned char hmac_sha256_digest[ISC_SHA512_DIGESTLENGTH];
94 : isc_hmacsha256_sign(&hmacsha256, hmac_sha256_digest,
95 : ISC_SHA256_DIGESTLENGTH);
96 : std::stringstream str;
97 : for (unsigned int i = 0; i < ISC_SHA256_DIGESTLENGTH; i++) {
98 : str << std::hex << std::setfill('0') << std::setw(2)
99 : << (int) hmac_sha256_digest[i];
100 : }
101 : #endif
102 0 : return str.str();
103 0 : }
104 :
105 : ////////////////////////////////////////////////////////////////////////////////
106 :
107 2 : MetadataProxy::MetadataProxy(ServicesModule *module,
108 2 : const std::string &secret)
109 2 : : services_(module), shared_secret_(secret),
110 2 : http_server_(new MetadataServer(services_->agent()->event_manager())),
111 2 : http_server6_(new MetadataServer(services_->agent()->event_manager())),
112 4 : http_client_(new MetadataClient(services_->agent()->event_manager())) {
113 :
114 : // Register wildcard entry to match any URL coming on the metadata port
115 2 : http_server_->RegisterHandler(HTTP_WILDCARD_ENTRY,
116 : boost::bind(&MetadataProxy::HandleMetadataRequest, this, _1, _2));
117 2 : http_server_->Initialize
118 2 : (services_->agent()->params()->metadata_proxy_port(),
119 2 : services_->agent()->router_id());
120 :
121 2 : ipv6_service_address_ = Ip6Address::from_string("::");
122 2 : services_->agent()->set_metadata_server_port(http_server_->GetPort());
123 :
124 2 : RegisterListeners();
125 :
126 2 : http_client_->Init();
127 2 : }
128 :
129 4 : MetadataProxy::~MetadataProxy() {
130 2 : this->Shutdown();
131 4 : }
132 :
133 2 : void MetadataProxy::CloseSessions() {
134 2 : for (SessionMap::iterator it = metadata_sessions_.begin();
135 2 : it != metadata_sessions_.end(); ) {
136 0 : SessionMap::iterator next = ++it;
137 0 : CloseClientSession(it->second.conn);
138 0 : CloseServerSession(it->first);
139 0 : it = next;
140 : }
141 :
142 2 : assert(metadata_sessions_.empty());
143 2 : assert(metadata_proxy_sessions_.empty());
144 2 : }
145 :
146 : void
147 4 : MetadataProxy::Shutdown() {
148 4 : if (http_server_) {
149 2 : http_server_->Shutdown();
150 2 : TcpServerManager::DeleteServer(http_server_);
151 2 : http_server_ = NULL;
152 : }
153 4 : if (http_server6_) {
154 2 : UnregisterListeners();
155 2 : http_server6_->Shutdown();
156 2 : TcpServerManager::DeleteServer(http_server6_);
157 2 : http_server6_ = NULL;
158 : }
159 4 : if (http_client_) {
160 2 : http_client_->Shutdown();
161 2 : TcpServerManager::DeleteServer(http_client_);
162 2 : http_client_ = NULL;
163 : }
164 4 : }
165 :
166 : void
167 0 : MetadataProxy::HandleMetadataRequest(HttpSession *session, const HttpRequest *request) {
168 0 : bool conn_close = false;
169 0 : std::vector<std::string> header_options;
170 0 : std::string vm_ip, vm_uuid, vm_project_uuid;
171 0 : metadata_stats_.requests++;
172 0 : IpAddress ip = session->remote_endpoint().address();
173 0 : if (ip.is_v6()) {
174 : // if the address is IPv6 link local (starts with fe80...),
175 : // then strip off from the address
176 : // an interface name added after '%'
177 0 : if (ip.to_string().find("fe80") == 0) {
178 0 : int i_percent = ip.to_string().find('%');
179 0 : std::string ip_str = ip.to_string().substr(0, i_percent);
180 0 : ip = boost::asio::ip::address::from_string(ip_str);
181 0 : }
182 : }
183 :
184 0 : if (!services_->agent()->interface_table()->
185 0 : FindVmUuidFromMetadataIp(ip, &vm_ip, &vm_uuid, &vm_project_uuid)) {
186 0 : METADATA_TRACE(Trace, "Error: Interface Config not available; "
187 : << "; Request for VM : " << ip);
188 0 : ErrorClose(session, 500);
189 0 : if (ip.is_v6()) {
190 0 : http_server6_->DeleteSession(session);
191 : }
192 0 : if (ip.is_v4()) {
193 0 : http_server_->DeleteSession(session);
194 : }
195 0 : delete request;
196 0 : return;
197 : }
198 0 : std::string signature = GetHmacSha256(shared_secret_, vm_uuid);
199 0 : const HttpRequest::HeaderMap &req_header = request->Headers();
200 0 : for (HttpRequest::HeaderMap::const_iterator it = req_header.begin();
201 0 : it != req_header.end(); ++it) {
202 0 : std::string option = boost::to_lower_copy(it->first);
203 0 : if (option == "host") {
204 0 : continue;
205 : }
206 0 : if (option == "connection") {
207 0 : std::string val = boost::to_lower_copy(it->second);
208 0 : if (val == "close")
209 0 : conn_close = true;
210 0 : continue;
211 0 : }
212 0 : header_options.push_back(std::string(it->first + ": " + it->second));
213 0 : }
214 :
215 : // keystone uses uuids without dashes and that is what ends up in
216 : // the nova database entry for the instance. Remove dashes from the
217 : // uuid string representation.
218 0 : boost::replace_all(vm_project_uuid, "-", "");
219 0 : header_options.push_back(std::string("X-Forwarded-For: " + vm_ip));
220 0 : header_options.push_back(std::string("X-Instance-ID: " + vm_uuid));
221 0 : header_options.push_back(std::string("X-Tenant-ID: " + vm_project_uuid));
222 0 : header_options.push_back(std::string("X-Instance-ID-Signature: " +
223 : signature));
224 :
225 0 : std::string uri = request->UrlPath();
226 0 : if (uri.size())
227 0 : uri = uri.substr(1); // ignore the first "/"
228 0 : const std::string &body = request->Body();
229 : {
230 0 : std::string nova_hostname;
231 0 : HttpConnection *conn = GetProxyConnection(session, conn_close,
232 : &nova_hostname);
233 0 : if (!nova_hostname.empty()) {
234 0 : header_options.insert(header_options.begin(),
235 0 : std::string("Host: " + nova_hostname));
236 : }
237 :
238 0 : if (conn) {
239 0 : switch(request->GetMethod()) {
240 0 : case HTTP_GET: {
241 0 : conn->HttpGet(uri, true, false, true, header_options,
242 0 : boost::bind(&MetadataProxy::HandleMetadataResponse,
243 0 : this, conn, HttpSessionPtr(session), _1, _2));
244 0 : METADATA_TRACE(Trace, "GET request for VM : " << vm_ip
245 : << " URL : " << uri);
246 0 : break;
247 : }
248 :
249 0 : case HTTP_HEAD: {
250 0 : conn->HttpHead(uri, true, false, true, header_options,
251 0 : boost::bind(&MetadataProxy::HandleMetadataResponse,
252 0 : this, conn, HttpSessionPtr(session), _1, _2));
253 0 : METADATA_TRACE(Trace, "HEAD request for VM : " << vm_ip
254 : << " URL : " << uri);
255 0 : break;
256 : }
257 :
258 0 : case HTTP_POST: {
259 0 : conn->HttpPost(body, uri, true, false, true, header_options,
260 0 : boost::bind(&MetadataProxy::HandleMetadataResponse,
261 0 : this, conn, HttpSessionPtr(session), _1, _2));
262 0 : METADATA_TRACE(Trace, "POST request for VM : " << vm_ip
263 : << " URL : " << uri);
264 0 : break;
265 : }
266 :
267 0 : case HTTP_PUT: {
268 0 : conn->HttpPut(body, uri, true, false, true, header_options,
269 0 : boost::bind(&MetadataProxy::HandleMetadataResponse,
270 0 : this, conn, HttpSessionPtr(session), _1, _2));
271 0 : METADATA_TRACE(Trace, "PUT request for VM : " << vm_ip
272 : << " URL : " << uri);
273 0 : break;
274 : }
275 :
276 0 : case HTTP_DELETE: {
277 0 : conn->HttpDelete(uri, true, false, true, header_options,
278 0 : boost::bind(&MetadataProxy::HandleMetadataResponse,
279 0 : this, conn, HttpSessionPtr(session), _1, _2));
280 0 : METADATA_TRACE(Trace, "Delete request for VM : " << vm_ip
281 : << " URL : " << uri);
282 0 : break;
283 : }
284 :
285 0 : default:
286 0 : METADATA_TRACE(Trace, "Error: Unsupported Method; "
287 : << "Request Method: " << request->GetMethod()
288 : << "; Request for VM: " << vm_ip);
289 0 : CloseClientSession(conn);
290 0 : ErrorClose(session, 501);
291 0 : if (ip.is_v6()) {
292 0 : http_server6_->DeleteSession(session);
293 : }
294 0 : if (ip.is_v4()) {
295 0 : http_server_->DeleteSession(session);
296 : }
297 0 : break;
298 : }
299 : } else {
300 0 : METADATA_TRACE(Trace, "Error: Config not available; "
301 : << "Request Method: " << request->GetMethod()
302 : << "; Request for VM : " << vm_ip);
303 0 : ErrorClose(session, 500);
304 0 : if (ip.is_v6()) {
305 0 : http_server6_->DeleteSession(session);
306 : }
307 0 : if (ip.is_v4()) {
308 0 : http_server_->DeleteSession(session);
309 : }
310 : }
311 0 : }
312 :
313 0 : delete request;
314 0 : }
315 :
316 : // Metadata Response from Nova API service
317 : void
318 0 : MetadataProxy::HandleMetadataResponse(HttpConnection *conn, HttpSessionPtr session,
319 : std::string &msg, boost::system::error_code &ec) {
320 0 : bool delete_session = false;
321 0 : boost::asio::ip::address ip = session->remote_endpoint().address();
322 : {
323 : // Ignore if session is closed in the meantime
324 0 : SessionMap::iterator it = metadata_sessions_.find(session.get());
325 0 : if (it == metadata_sessions_.end())
326 0 : return;
327 :
328 0 : std::string vm_ip, vm_uuid, vm_project_uuid;
329 :
330 0 : if (ip.to_string().find("fe80") == 0) {
331 0 : int i_percent = ip.to_string().find('%');
332 0 : std::string ip_str = ip.to_string().substr(0, i_percent);
333 0 : ip = boost::asio::ip::address::from_string(ip_str);
334 0 : }
335 :
336 0 : if(!services_->agent()->interface_table()->
337 0 : FindVmUuidFromMetadataIp(ip, &vm_ip, &vm_uuid, &vm_project_uuid)) {
338 0 : LOG(ERROR, "UUID was not found for ip=" << ip.to_string() <<
339 : ", in MetadataProxy::HandleMetadataResponse" <<
340 : std::endl);
341 0 : return;
342 : }
343 :
344 0 : if (!ec) {
345 0 : METADATA_TRACE(Trace, "Metadata for VM : " << vm_ip << " Response : " << msg);
346 0 : session->Send(reinterpret_cast<const u_int8_t *>(msg.c_str()),
347 : msg.length(), NULL);
348 : } else {
349 0 : METADATA_TRACE(Trace, "Metadata for VM : " << vm_ip << " Error : " <<
350 : boost::system::system_error(ec).what());
351 0 : CloseClientSession(conn);
352 0 : ErrorClose(session.get(), 502);
353 0 : delete_session = true;
354 0 : goto done;
355 : }
356 :
357 0 : metadata_stats_.responses++;
358 0 : if (!ec && it->second.close_req) {
359 0 : std::stringstream str(msg);
360 0 : std::string option;
361 0 : str >> option;
362 0 : if (option == "Content-Length:") {
363 0 : str >> it->second.content_len;
364 0 : } else if (msg == "\r\n") {
365 0 : it->second.header_end = true;
366 0 : if (it->second.header_end && !it->second.content_len) {
367 0 : CloseClientSession(it->second.conn);
368 0 : CloseServerSession(session.get());
369 0 : delete_session = true;
370 : }
371 0 : } else if (it->second.header_end) {
372 0 : it->second.data_sent += msg.length();
373 0 : if (it->second.data_sent >= it->second.content_len) {
374 0 : CloseClientSession(it->second.conn);
375 0 : CloseServerSession(session.get());
376 0 : delete_session = true;
377 : }
378 : }
379 0 : }
380 0 : }
381 :
382 0 : done:
383 0 : if (delete_session) {
384 0 : if (ip.is_v6()) {
385 0 : http_server6_->DeleteSession(session.get());
386 : }
387 0 : if (ip.is_v4()) {
388 0 : http_server_->DeleteSession(session.get());
389 : }
390 : }
391 : }
392 :
393 : void
394 0 : MetadataProxy::OnServerSessionEvent(HttpSession *session, TcpSession::Event event) {
395 0 : switch (event) {
396 0 : case TcpSession::CLOSE: {
397 0 : SessionMap::iterator it = metadata_sessions_.find(session);
398 0 : if (it == metadata_sessions_.end())
399 0 : break;
400 0 : CloseClientSession(it->second.conn);
401 0 : metadata_sessions_.erase(it);
402 0 : break;
403 : }
404 :
405 0 : default:
406 0 : break;
407 : }
408 0 : }
409 :
410 : void
411 0 : MetadataProxy::OnClientSessionEvent(HttpClientSession *session, TcpSession::Event event) {
412 0 : boost::asio::ip::address ip = session->remote_endpoint().address();
413 0 : switch (event) {
414 0 : case TcpSession::CLOSE: {
415 : {
416 : ConnectionSessionMap::iterator it =
417 0 : metadata_proxy_sessions_.find(session->Connection());
418 0 : if (it == metadata_proxy_sessions_.end())
419 0 : break;
420 0 : CloseServerSession(it->second);
421 0 : CloseClientSession(session->Connection());
422 : }
423 0 : if (ip.is_v6()) {
424 0 : http_server6_->DeleteSession(session);
425 : }
426 0 : if (ip.is_v4()) {
427 0 : http_server_->DeleteSession(session);
428 : }
429 0 : break;
430 : }
431 :
432 0 : default:
433 0 : break;
434 : }
435 0 : }
436 :
437 : HttpConnection *
438 0 : MetadataProxy::GetProxyConnection(HttpSession *session, bool conn_close,
439 : std::string *nova_hostname) {
440 0 : SessionMap::iterator it = metadata_sessions_.find(session);
441 0 : if (it != metadata_sessions_.end()) {
442 0 : it->second.close_req = conn_close;
443 0 : return it->second.conn;
444 : }
445 :
446 0 : uint16_t linklocal_port = session->local_port();
447 0 : IpAddress linklocal_server = session->local_endpoint().address();
448 : uint16_t nova_port;
449 0 : Ip4Address nova_server;
450 0 : std::string md_service_name;
451 :
452 0 : if (linklocal_server.is_v4() &&
453 0 : !services_->agent()->oper_db()->global_vrouter()->FindLinkLocalService(
454 : GlobalVrouter::kMetadataService, &linklocal_server, &linklocal_port,
455 : nova_hostname, &nova_server, &nova_port)) {
456 0 : return NULL;
457 : }
458 :
459 0 : if (linklocal_server.is_v6() &&
460 0 : !services_->agent()->oper_db()->global_vrouter()->FindLinkLocalService(
461 : GlobalVrouter::kMetadataService6, &linklocal_server, &linklocal_port,
462 : nova_hostname, &nova_server, &nova_port)) {
463 0 : return NULL;
464 : }
465 :
466 0 : HttpConnection *conn = (nova_hostname != 0 && !nova_hostname->empty()) ?
467 0 : http_client_->CreateConnection(*nova_hostname, nova_port) :
468 0 : http_client_->CreateConnection(boost::asio::ip::tcp::endpoint(nova_server, nova_port));
469 :
470 0 : map<CURLoption, int> *curl_options = conn->curl_options();
471 0 : curl_options->insert(std::make_pair(CURLOPT_HTTP_TRANSFER_DECODING, 0L));
472 0 : conn->set_use_ssl(services_->agent()->params()->metadata_use_ssl());
473 0 : if (conn->use_ssl()) {
474 0 : conn->set_client_cert(
475 0 : services_->agent()->params()->metadata_client_cert());
476 0 : conn->set_client_cert_type(
477 0 : services_->agent()->params()->metadata_client_cert_type());
478 0 : conn->set_client_key(
479 0 : services_->agent()->params()->metadata_client_key());
480 0 : conn->set_ca_cert(
481 0 : services_->agent()->params()->metadata_ca_cert());
482 : }
483 0 : conn->RegisterEventCb(
484 : boost::bind(&MetadataProxy::OnClientSessionEvent, this, _1, _2));
485 0 : session->RegisterEventCb(
486 : boost::bind(&MetadataProxy::OnServerSessionEvent, this, _1, _2));
487 0 : SessionData data(conn, conn_close);
488 0 : metadata_sessions_.insert(SessionPair(session, data));
489 0 : metadata_proxy_sessions_.insert(ConnectionSessionPair(conn, session));
490 0 : metadata_stats_.proxy_sessions++;
491 0 : return conn;
492 0 : }
493 :
494 : void
495 0 : MetadataProxy::CloseServerSession(HttpSession *session) {
496 0 : session->Close();
497 0 : metadata_sessions_.erase(session);
498 0 : }
499 :
500 : void
501 0 : MetadataProxy::CloseClientSession(HttpConnection *conn) {
502 0 : HttpClient *client = conn->client();
503 0 : client->RemoveConnection(conn);
504 0 : metadata_proxy_sessions_.erase(conn);
505 0 : }
506 :
507 : void
508 0 : MetadataProxy::ErrorClose(HttpSession *session, uint16_t error) {
509 0 : std::string message = ErrorMessage(error);
510 : char body[512];
511 0 : snprintf(body, sizeof(body), "<html>\n"
512 : "<head>\n"
513 : " <title>%s</title>\n"
514 : "</head>\n"
515 : "</html>\n", message.c_str());
516 : char response[1024];
517 0 : snprintf(response, sizeof(response),
518 : "HTTP/1.1 %s\n"
519 : "Content-Type: text/html; charset=UTF-8\n"
520 : "Content-Length: %u\n"
521 0 : "\n%s", message.c_str(), (unsigned int) strlen(body), body);
522 0 : session->Send(reinterpret_cast<const u_int8_t *>(response),
523 : strlen(response), NULL);
524 0 : CloseServerSession(session);
525 0 : metadata_stats_.internal_errors++;
526 0 : }
|