17 #include "hazelcast/util/Preconditions.h"
18 #include "hazelcast/client/aws/aws_client.h"
19 #include "hazelcast/client/client_properties.h"
20 #include "hazelcast/client/config/client_aws_config.h"
21 #include "hazelcast/logger.h"
23 #ifdef HZ_BUILD_WITH_SSL
27 #include <boost/algorithm/string/replace.hpp>
28 #include <boost/date_time.hpp>
29 #include <boost/property_tree/xml_parser.hpp>
30 #include <boost/property_tree/json_parser.hpp>
32 #include "hazelcast/client/aws/utility/aws_url_encoder.h"
33 #include "hazelcast/client/aws/impl/Constants.h"
34 #include "hazelcast/client/aws/security/ec2_request_signer.h"
35 #include "hazelcast/client/aws/impl/Filter.h"
36 #include "hazelcast/client/aws/impl/DescribeInstances.h"
37 #include "hazelcast/client/aws/utility/cloud_utility.h"
38 #include "hazelcast/util/SyncHttpsClient.h"
39 #include "hazelcast/util/SyncHttpClient.h"
43 #include <openssl/ssl.h>
49 std::string ec2_request_signer::NEW_LINE =
"\n";
50 size_t ec2_request_signer::DATE_LENGTH = 8;
52 ec2_request_signer::ec2_request_signer(
53 const config::client_aws_config& aws_config,
54 const std::string& timestamp,
55 const std::string& endpoint)
56 : aws_config_(aws_config)
57 , timestamp_(timestamp)
61 ec2_request_signer::~ec2_request_signer() =
default;
64 ec2_request_signer::sign(
65 const std::unordered_map<std::string, std::string>& attributes)
67 std::string canonicalRequest = get_canonicalized_request(attributes);
68 std::string stringToSign = create_string_to_sign(canonicalRequest);
69 std::vector<unsigned char> signingKey = derive_signing_key();
71 return create_signature(stringToSign, signingKey);
75 ec2_request_signer::create_formatted_credential()
const
77 std::stringstream out;
78 out << aws_config_.get_access_key() <<
'/'
79 << timestamp_.substr(0, DATE_LENGTH) <<
'/' << aws_config_.get_region()
80 <<
'/' <<
"ec2/aws4_request";
85 ec2_request_signer::get_canonicalized_query_string(
86 const std::unordered_map<std::string, std::string>& attributes)
const
88 std::vector<std::string> components = get_list_of_entries(attributes);
89 std::sort(components.begin(), components.end());
90 return get_canonicalized_query_string(components);
95 ec2_request_signer::get_canonicalized_request(
96 const std::unordered_map<std::string, std::string>& attributes)
const
98 std::ostringstream out;
99 out << impl::Constants::GET << NEW_LINE <<
'/' << NEW_LINE
100 << get_canonicalized_query_string(attributes) << NEW_LINE
101 << get_canonical_headers() << NEW_LINE <<
"host" << NEW_LINE
102 << sha256_hashhex(
"");
107 ec2_request_signer::get_canonical_headers()
const
109 std::ostringstream out;
110 out <<
"host:" << endpoint_ << NEW_LINE;
115 ec2_request_signer::get_canonicalized_query_string(
116 const std::vector<std::string>& list)
const
118 std::ostringstream result;
119 std::vector<std::string>::const_iterator it = list.begin();
122 for (; it != list.end(); ++it) {
123 result <<
"&" << *it;
128 std::vector<std::string>
129 ec2_request_signer::get_list_of_entries(
130 const std::unordered_map<std::string, std::string>& entries)
const
132 std::vector<std::string> components;
133 for (
const auto& entry : entries) {
134 components.push_back(format_attribute(entry.first, entry.second));
140 ec2_request_signer::format_attribute(
const std::string& key,
141 const std::string& value)
143 std::ostringstream out;
144 out << utility::aws_url_encoder::url_encode(key) <<
'='
145 << utility::aws_url_encoder::url_encode(value);
151 ec2_request_signer::create_string_to_sign(
152 const std::string& canonical_request)
const
154 std::ostringstream out;
155 out << impl::Constants::SIGNATURE_METHOD_V4 << NEW_LINE << timestamp_
156 << NEW_LINE << get_credential_scope() << NEW_LINE
157 << sha256_hashhex(canonical_request);
162 ec2_request_signer::get_credential_scope()
const
166 std::ostringstream out;
167 out << timestamp_.substr(0, DATE_LENGTH) <<
"/" << aws_config_.get_region()
168 <<
"/ec2/aws4_request";
173 std::vector<unsigned char>
174 ec2_request_signer::derive_signing_key()
const
176 const std::string& signKey = aws_config_.get_secret_key();
177 std::string dateStamp = timestamp_.substr(0, DATE_LENGTH);
181 unsigned char kDate[32];
182 std::string key = std::string(
"AWS4") + signKey;
183 int kDateLen = hmac_sh_a256_bytes(key, dateStamp, kDate);
185 unsigned char kRegion[32];
187 hmac_sh_a256_bytes(kDate, kDateLen, aws_config_.get_region(), kRegion);
189 unsigned char kService[32];
190 int kServiceLen = hmac_sh_a256_bytes(kRegion, kRegionLen,
"ec2", kService);
192 std::vector<unsigned char> mSigning(32);
193 hmac_sh_a256_bytes(kService, kServiceLen,
"aws4_request", &mSigning[0]);
199 ec2_request_signer::create_signature(
200 const std::string& string_to_sign,
201 const std::vector<unsigned char>& signing_key)
const
203 return hmac_sh_a256_hex(signing_key, string_to_sign);
207 ec2_request_signer::hmac_sh_a256_hex(
const std::vector<unsigned char>& key,
208 const std::string& msg)
const
210 unsigned char hash[32];
212 unsigned int len = hmac_sh_a256_bytes(key, msg, hash);
214 return convert_to_hex_string(hash, len);
218 ec2_request_signer::convert_to_hex_string(
const unsigned char* buffer,
219 unsigned int len)
const
221 std::stringstream ss;
222 ss << std::hex << std::setfill(
'0');
223 for (
unsigned int i = 0; i < len; i++) {
224 ss << std::hex << std::setw(2) << (
unsigned int)buffer[i];
231 ec2_request_signer::hmac_sh_a256_bytes(
const void* key,
233 const std::string& msg,
234 unsigned char* hash)
const
236 return hmac_sh_a256_bytes(
237 key, key_len, (
unsigned char*)&msg[0], msg.length(), hash);
241 ec2_request_signer::hmac_sh_a256_bytes(
const std::string& key,
242 const std::string& msg,
243 unsigned char* hash)
const
245 return hmac_sh_a256_bytes(
246 &key[0], (
int)key.length(), (
unsigned char*)&msg[0], msg.length(), hash);
250 ec2_request_signer::hmac_sh_a256_bytes(
const std::vector<unsigned char>& key,
251 const std::string& msg,
252 unsigned char* hash)
const
254 return hmac_sh_a256_bytes(
255 &key[0], (
int)key.size(), (
unsigned char*)&msg[0], msg.length(), hash);
259 ec2_request_signer::hmac_sh_a256_bytes(
const void* key_buffer,
261 const unsigned char* data,
263 unsigned char* hash)
const
265 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
266 HMAC_CTX* hmac = HMAC_CTX_new();
268 HMAC_CTX* hmac =
new HMAC_CTX;
272 HMAC_Init_ex(hmac, key_buffer, key_len, EVP_sha256(), NULL);
273 HMAC_Update(hmac, data, data_len);
274 unsigned int len = 32;
275 HMAC_Final(hmac, hash, &len);
277 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
280 HMAC_CTX_cleanup(hmac);
288 ec2_request_signer::sha256_hashhex(
const std::string& in)
const
290 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
291 EVP_MD_CTX* ctx_ptr = EVP_MD_CTX_new();
294 EVP_MD_CTX* ctx_ptr = &ctx;
295 EVP_MD_CTX_init(ctx_ptr);
298 unsigned int hash_len = 0;
299 unsigned char hash[EVP_MAX_MD_SIZE];
301 EVP_DigestInit_ex(ctx_ptr, EVP_sha256(),
nullptr);
302 EVP_DigestUpdate(ctx_ptr, in.c_str(), in.size());
303 EVP_DigestFinal_ex(ctx_ptr, hash, &hash_len);
305 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
306 EVP_MD_CTX_free(ctx_ptr);
308 EVP_MD_CTX_cleanup(ctx_ptr);
311 return convert_to_hex_string(hash, hash_len);
316 const char* Constants::DATE_FORMAT =
"%Y%m%dT%H%M%SZ";
317 const char* Constants::DOC_VERSION =
"2016-11-15";
318 const char* Constants::SIGNATURE_METHOD_V4 =
"AWS4-HMAC-SHA256";
319 const char* Constants::GET =
"GET";
320 const char* Constants::ECS_CREDENTIALS_ENV_VAR_NAME =
321 "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI";
323 Filter::Filter() =
default;
334 Filter::add_filter(
const std::string& name,
const std::string& value)
336 std::stringstream out;
337 unsigned long index = filters_.size() + 1;
338 out <<
"Filter." << index <<
".Name";
339 filters_[out.str()] = name;
342 out <<
"Filter." << index <<
".Value.1";
343 filters_[out.str()] = value;
346 const std::unordered_map<std::string, std::string>&
347 Filter::get_filters()
352 const std::string DescribeInstances::QUERY_PREFIX =
"/?";
353 const std::string DescribeInstances::IAM_ROLE_ENDPOINT =
"169.254.169.254";
354 const std::string DescribeInstances::IAM_ROLE_QUERY =
355 "/latest/meta-data/iam/security-credentials/";
356 const std::string DescribeInstances::IAM_TASK_ROLE_ENDPOINT =
"169.254.170.2";
358 DescribeInstances::DescribeInstances(
359 std::chrono::steady_clock::duration timeout,
360 config::client_aws_config& aws_config,
361 const std::string& endpoint,
364 , aws_config_(aws_config)
365 , endpoint_(endpoint)
368 check_keys_from_iam_roles();
370 std::string timeStamp = get_formatted_timestamp();
371 rs_ = std::unique_ptr<security::ec2_request_signer>(
372 new security::ec2_request_signer(aws_config, timeStamp, endpoint));
373 attributes_[
"Action"] =
"DescribeInstances";
374 attributes_[
"Version"] = impl::Constants::DOC_VERSION;
375 attributes_[
"X-Amz-Algorithm"] = impl::Constants::SIGNATURE_METHOD_V4;
376 attributes_[
"X-Amz-Credential"] = rs_->create_formatted_credential();
377 attributes_[
"X-Amz-Date"] = timeStamp;
378 attributes_[
"X-Amz-SignedHeaders"] =
"host";
379 attributes_[
"X-Amz-Expires"] =
"30";
383 DescribeInstances::~DescribeInstances() =
default;
385 std::unordered_map<std::string, std::string>
386 DescribeInstances::execute()
388 std::string signature = rs_->sign(attributes_);
389 attributes_[
"X-Amz-Signature"] = signature;
391 std::istream& stream = call_service();
392 return utility::cloud_utility::unmarshal_the_response(stream, logger_);
396 DescribeInstances::get_formatted_timestamp()
398 using namespace boost::posix_time;
399 ptime now = second_clock::universal_time();
401 std::ostringstream out;
402 std::locale timeLocale(out.getloc(),
403 new time_facet(impl::Constants::DATE_FORMAT));
404 out.imbue(timeLocale);
410 DescribeInstances::call_service()
412 std::string query = rs_->get_canonicalized_query_string(attributes_);
414 std::unique_ptr<util::SyncHttpsClient>(
new util::SyncHttpsClient(
415 endpoint_.c_str(), QUERY_PREFIX + query, timeout_));
416 return https_client_->connect_and_get_response();
420 DescribeInstances::check_keys_from_iam_roles()
422 if (aws_config_.get_access_key().empty() ||
423 !aws_config_.get_iam_role().empty()) {
424 try_get_default_iam_role();
425 if (!aws_config_.get_iam_role().empty()) {
426 get_keys_from_iam_role();
428 get_keys_from_iam_task_role();
434 DescribeInstances::try_get_default_iam_role()
437 if (!(aws_config_.get_iam_role().empty() ||
438 aws_config_.get_iam_role() ==
"DEFAULT")) {
443 util::SyncHttpClient httpClient(IAM_ROLE_ENDPOINT, IAM_ROLE_QUERY);
444 std::string roleName;
445 std::istream& responseStream = httpClient.open_connection();
446 responseStream >> roleName;
447 aws_config_.set_iam_role(roleName);
448 }
catch (exception::io& e) {
449 BOOST_THROW_EXCEPTION(exception::invalid_configuration(
450 "tryGetDefaultIamRole",
451 std::string(
"Invalid Aws Configuration. ") + e.what()));
456 DescribeInstances::get_keys_from_iam_task_role()
461 #if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
462 #pragma warning(push)
466 const char* uri = std::getenv(Constants::ECS_CREDENTIALS_ENV_VAR_NAME);
467 #if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
471 BOOST_THROW_EXCEPTION(exception::illegal_argument(
472 "getKeysFromIamTaskRole",
473 "Could not acquire credentials! Did not find declared AWS access key "
474 "or IAM Role, and could not discover IAM Task Role or default "
478 util::SyncHttpClient httpClient(IAM_TASK_ROLE_ENDPOINT, uri);
481 std::istream& istream = httpClient.open_connection();
482 parse_and_store_role_creds(istream);
483 }
catch (exception::iexception& e) {
484 std::stringstream out;
485 out <<
"Unable to retrieve credentials from IAM Task Role. URI: " << uri
486 <<
". \n " << e.what();
487 BOOST_THROW_EXCEPTION(exception::invalid_configuration(
488 "getKeysFromIamTaskRole", out.str()));
493 DescribeInstances::get_keys_from_iam_role()
495 std::string query =
"/latest/meta-data/iam/security-credentials/" +
496 aws_config_.get_iam_role();
498 util::SyncHttpClient httpClient(IAM_ROLE_ENDPOINT, query);
501 std::istream& istream = httpClient.open_connection();
502 parse_and_store_role_creds(istream);
503 }
catch (exception::iexception& e) {
504 std::stringstream out;
505 out <<
"Unable to retrieve credentials from IAM Task Role. URI: "
506 << query <<
". \n " << e.what();
507 BOOST_THROW_EXCEPTION(
508 exception::invalid_configuration(
"getKeysFromIamRole", out.str()));
513 DescribeInstances::parse_and_store_role_creds(std::istream& in)
515 utility::cloud_utility::unmarshal_json_response(
516 in, aws_config_, attributes_);
523 DescribeInstances::add_filters()
526 if (!aws_config_.get_tag_key().empty()) {
527 if (!aws_config_.get_tag_value().empty()) {
528 filter.add_filter(std::string(
"tag:") + aws_config_.get_tag_key(),
529 aws_config_.get_tag_value());
531 filter.add_filter(
"tag-key", aws_config_.get_tag_key());
533 }
else if (!aws_config_.get_tag_value().empty()) {
534 filter.add_filter(
"tag-value", aws_config_.get_tag_value());
537 if (!aws_config_.get_security_group_name().empty()) {
538 filter.add_filter(
"instance.group-name",
539 aws_config_.get_security_group_name());
542 filter.add_filter(
"instance-state-name",
"running");
543 const std::unordered_map<std::string, std::string>& filters =
544 filter.get_filters();
545 attributes_.insert(filters.begin(), filters.end());
552 aws_url_encoder::url_encode(
const std::string& value)
554 std::string result = escape_encode(value);
555 boost::replace_all(result,
"+",
"%20");
560 aws_url_encoder::escape_encode(
const std::string& value)
562 std::ostringstream escaped;
566 for (std::string::const_iterator i = value.begin(), n = value.end(); i != n;
568 std::string::value_type c = (*i);
571 if (isalnum(c) || c ==
'-' || c ==
'_' || c ==
'.' || c ==
'~') {
577 escaped << std::uppercase;
578 escaped <<
'%' << std::setw(2) << int((
unsigned char)c);
579 escaped << std::nouppercase;
582 return escaped.str();
585 std::unordered_map<std::string, std::string>
586 cloud_utility::unmarshal_the_response(std::istream& stream, logger& lg)
588 std::unordered_map<std::string, std::string> privatePublicPairs;
592 pt::read_xml(stream, tree);
593 }
catch (pt::xml_parser_error& e) {
597 boost::str(boost::format(
"The parsed xml stream has errors: %1%") %
599 return privatePublicPairs;
604 for (pt::ptree::value_type& item :
605 tree.get_child(
"DescribeInstancesResponse.reservationSet")) {
606 for (pt::ptree::value_type& instanceItem :
607 item.second.get_child(
"instancesSet")) {
609 instanceItem.second.get_optional<std::string>(
"privateIpAddress");
611 instanceItem.second.get_optional<std::string>(
"ipAddress");
613 auto prIp = privateIp.value_or(
"");
614 auto pubIp = publicIp.value_or(
"");
617 privatePublicPairs[prIp] = pubIp;
621 boost::format(
"Accepting EC2 instance [%1%][%2%]") %
623 .get_optional<std::string>(
"tagset.item.value")
629 return privatePublicPairs;
633 cloud_utility::unmarshal_json_response(
634 std::istream& stream,
635 config::client_aws_config& aws_config,
636 std::unordered_map<std::string, std::string>& attributes)
639 pt::read_json(stream, json);
640 aws_config.set_access_key(
641 json.get_optional<std::string>(
"AccessKeyId").get_value_or(
""));
642 aws_config.set_secret_key(
643 json.get_optional<std::string>(
"SecretAccessKey").get_value_or(
""));
644 attributes[
"X-Amz-Security-Token"] =
645 json.get_optional<std::string>(
"Token").get_value_or(
"");
650 aws_client::aws_client(std::chrono::steady_clock::duration timeout,
651 config::client_aws_config& aws_config,
652 const client_properties& client_properties,
655 , aws_config_(aws_config)
658 this->endpoint_ = aws_config.get_host_header();
659 if (!aws_config.get_region().empty() &&
660 aws_config.get_region().length() > 0) {
661 if (aws_config.get_host_header().find(
"ec2.") != 0) {
662 BOOST_THROW_EXCEPTION(exception::invalid_configuration(
663 "aws_client::aws_client",
664 "HostHeader should start with \"ec2.\" prefix"));
666 boost::replace_all(this->endpoint_,
668 std::string(
"ec2.") + aws_config.get_region() +
".");
672 client_properties.get_integer(client_properties.get_aws_member_port());
673 if (aws_member_port_ < 0 || aws_member_port_ > 65535) {
674 BOOST_THROW_EXCEPTION(exception::invalid_configuration(
675 "aws_client::aws_client",
677 "Configured aws member port %1% is not "
678 "a valid port number. It should be between 0-65535 inclusive.") %
684 std::unordered_map<address, address>
685 aws_client::get_addresses()
688 impl::DescribeInstances(timeout_, aws_config_, endpoint_, logger_)
690 std::unordered_map<address, address> addr_map;
691 addr_map.reserve(addr_pair_map.size());
692 for (
const auto& addr_pair : addr_pair_map) {
693 addr_map.emplace(address{ addr_pair.first, aws_member_port_ },
694 address{ addr_pair.second, aws_member_port_ });
702 namespace hazelcast {
705 aws_client::aws_client(std::chrono::steady_clock::duration timeout,
706 config::client_aws_config& aws_config,
707 const client_properties& client_properties,
710 util::Preconditions::check_ssl(
"aws_client::aws_client");
713 std::unordered_map<address, address>
714 aws_client::get_addresses()
716 util::Preconditions::check_ssl(
"aws_client::get_addresses");
717 return std::unordered_map<address, address>();