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