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>
49std::string ec2_request_signer::NEW_LINE =
"\n";
50size_t ec2_request_signer::DATE_LENGTH = 8;
52ec2_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)
61ec2_request_signer::~ec2_request_signer() =
default;
64ec2_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);
75ec2_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";
85ec2_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);
95ec2_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(
"");
107ec2_request_signer::get_canonical_headers()
const
109 std::ostringstream out;
110 out <<
"host:" << endpoint_ << NEW_LINE;
115ec2_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;
128std::vector<std::string>
129ec2_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));
140ec2_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);
151ec2_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);
162ec2_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";
173std::vector<unsigned char>
174ec2_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]);
199ec2_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);
207ec2_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);
218ec2_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];
231ec2_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);
241ec2_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);
250ec2_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);
259ec2_request_signer::hmac_sh_a256_bytes(
const void* key_buffer,
261 const unsigned char* data,
263 unsigned char* hash)
const
266#if OPENSSL_VERSION_NUMBER >= 0x30000000L
270 mdctx = EVP_MD_CTX_new();
271 EVP_PKEY *skey = NULL;
272 skey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, (
const unsigned char *)key_buffer, key_len);
273 EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, skey);
274 EVP_DigestSignUpdate(mdctx, data, data_len);
275 EVP_DigestSignFinal(mdctx, hash, &len);
277 EVP_MD_CTX_free(mdctx);
278 return static_cast<unsigned int>(len);
280#if OPENSSL_VERSION_NUMBER >= 0x10100000L
281 HMAC_CTX* hmac = HMAC_CTX_new();
283 HMAC_CTX* hmac =
new HMAC_CTX;
287 HMAC_Init_ex(hmac, key_buffer, key_len, EVP_sha256(), NULL);
288 HMAC_Update(hmac, data, data_len);
289 unsigned int len = 32;
290 HMAC_Final(hmac, hash, &len);
292#if OPENSSL_VERSION_NUMBER >= 0x10100000L
295 HMAC_CTX_cleanup(hmac);
303ec2_request_signer::sha256_hashhex(
const std::string& in)
const
305#if OPENSSL_VERSION_NUMBER >= 0x10100000L
306 EVP_MD_CTX* ctx_ptr = EVP_MD_CTX_new();
309 EVP_MD_CTX* ctx_ptr = &ctx;
310 EVP_MD_CTX_init(ctx_ptr);
313 unsigned int hash_len = 0;
314 unsigned char hash[EVP_MAX_MD_SIZE];
316 EVP_DigestInit_ex(ctx_ptr, EVP_sha256(),
nullptr);
317 EVP_DigestUpdate(ctx_ptr, in.c_str(), in.size());
318 EVP_DigestFinal_ex(ctx_ptr, hash, &hash_len);
320#if OPENSSL_VERSION_NUMBER >= 0x10100000L
321 EVP_MD_CTX_free(ctx_ptr);
323 EVP_MD_CTX_cleanup(ctx_ptr);
326 return convert_to_hex_string(hash, hash_len);
331const char* Constants::DATE_FORMAT =
"%Y%m%dT%H%M%SZ";
332const char* Constants::DOC_VERSION =
"2016-11-15";
333const char* Constants::SIGNATURE_METHOD_V4 =
"AWS4-HMAC-SHA256";
334const char* Constants::GET =
"GET";
335const char* Constants::ECS_CREDENTIALS_ENV_VAR_NAME =
336 "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI";
338Filter::Filter() =
default;
349Filter::add_filter(
const std::string& name,
const std::string& value)
351 std::stringstream out;
352 unsigned long index = filters_.size() + 1;
353 out <<
"Filter." << index <<
".Name";
354 filters_[out.str()] = name;
357 out <<
"Filter." << index <<
".Value.1";
358 filters_[out.str()] = value;
361const std::unordered_map<std::string, std::string>&
367const std::string DescribeInstances::QUERY_PREFIX =
"/?";
368const std::string DescribeInstances::IAM_ROLE_ENDPOINT =
"169.254.169.254";
369const std::string DescribeInstances::IAM_ROLE_QUERY =
370 "/latest/meta-data/iam/security-credentials/";
371const std::string DescribeInstances::IAM_TASK_ROLE_ENDPOINT =
"169.254.170.2";
373DescribeInstances::DescribeInstances(
374 std::chrono::steady_clock::duration timeout,
375 config::client_aws_config& aws_config,
376 const std::string& endpoint,
379 , aws_config_(aws_config)
380 , endpoint_(endpoint)
383 check_keys_from_iam_roles();
385 std::string timeStamp = get_formatted_timestamp();
386 rs_ = std::unique_ptr<security::ec2_request_signer>(
387 new security::ec2_request_signer(aws_config, timeStamp, endpoint));
388 attributes_[
"Action"] =
"DescribeInstances";
389 attributes_[
"Version"] = impl::Constants::DOC_VERSION;
390 attributes_[
"X-Amz-Algorithm"] = impl::Constants::SIGNATURE_METHOD_V4;
391 attributes_[
"X-Amz-Credential"] = rs_->create_formatted_credential();
392 attributes_[
"X-Amz-Date"] = timeStamp;
393 attributes_[
"X-Amz-SignedHeaders"] =
"host";
394 attributes_[
"X-Amz-Expires"] =
"30";
398DescribeInstances::~DescribeInstances() =
default;
400std::unordered_map<std::string, std::string>
401DescribeInstances::execute()
403 std::string signature = rs_->sign(attributes_);
404 attributes_[
"X-Amz-Signature"] = signature;
406 std::istream& stream = call_service();
407 return utility::cloud_utility::unmarshal_the_response(stream, logger_);
411DescribeInstances::get_formatted_timestamp()
413 using namespace boost::posix_time;
414 ptime now = second_clock::universal_time();
416 std::ostringstream out;
417 std::locale timeLocale(out.getloc(),
418 new time_facet(impl::Constants::DATE_FORMAT));
419 out.imbue(timeLocale);
425DescribeInstances::call_service()
427 std::string query = rs_->get_canonicalized_query_string(attributes_);
429 std::unique_ptr<util::SyncHttpsClient>(
new util::SyncHttpsClient(
430 endpoint_.c_str(), QUERY_PREFIX + query, timeout_));
431 return https_client_->connect_and_get_response();
435DescribeInstances::check_keys_from_iam_roles()
437 if (aws_config_.get_access_key().empty() ||
438 !aws_config_.get_iam_role().empty()) {
439 try_get_default_iam_role();
440 if (!aws_config_.get_iam_role().empty()) {
441 get_keys_from_iam_role();
443 get_keys_from_iam_task_role();
449DescribeInstances::try_get_default_iam_role()
452 if (!(aws_config_.get_iam_role().empty() ||
453 aws_config_.get_iam_role() ==
"DEFAULT")) {
458 util::SyncHttpClient httpClient(IAM_ROLE_ENDPOINT, IAM_ROLE_QUERY);
459 std::string roleName;
460 std::istream& responseStream = httpClient.open_connection();
461 responseStream >> roleName;
462 aws_config_.set_iam_role(roleName);
463 }
catch (exception::io& e) {
464 BOOST_THROW_EXCEPTION(exception::invalid_configuration(
465 "tryGetDefaultIamRole",
466 std::string(
"Invalid Aws Configuration. ") + e.what()));
471DescribeInstances::get_keys_from_iam_task_role()
476#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
481 const char* uri = std::getenv(Constants::ECS_CREDENTIALS_ENV_VAR_NAME);
482#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
486 BOOST_THROW_EXCEPTION(exception::illegal_argument(
487 "getKeysFromIamTaskRole",
488 "Could not acquire credentials! Did not find declared AWS access key "
489 "or IAM Role, and could not discover IAM Task Role or default "
493 util::SyncHttpClient httpClient(IAM_TASK_ROLE_ENDPOINT, uri);
496 std::istream& istream = httpClient.open_connection();
497 parse_and_store_role_creds(istream);
498 }
catch (exception::iexception& e) {
499 std::stringstream out;
500 out <<
"Unable to retrieve credentials from IAM Task Role. URI: " << uri
501 <<
". \n " << e.what();
502 BOOST_THROW_EXCEPTION(exception::invalid_configuration(
503 "getKeysFromIamTaskRole", out.str()));
508DescribeInstances::get_keys_from_iam_role()
510 std::string query =
"/latest/meta-data/iam/security-credentials/" +
511 aws_config_.get_iam_role();
513 util::SyncHttpClient httpClient(IAM_ROLE_ENDPOINT, query);
516 std::istream& istream = httpClient.open_connection();
517 parse_and_store_role_creds(istream);
518 }
catch (exception::iexception& e) {
519 std::stringstream out;
520 out <<
"Unable to retrieve credentials from IAM Task Role. URI: "
521 << query <<
". \n " << e.what();
522 BOOST_THROW_EXCEPTION(
523 exception::invalid_configuration(
"getKeysFromIamRole", out.str()));
528DescribeInstances::parse_and_store_role_creds(std::istream& in)
530 utility::cloud_utility::unmarshal_json_response(
531 in, aws_config_, attributes_);
538DescribeInstances::add_filters()
541 if (!aws_config_.get_tag_key().empty()) {
542 if (!aws_config_.get_tag_value().empty()) {
543 filter.add_filter(std::string(
"tag:") + aws_config_.get_tag_key(),
544 aws_config_.get_tag_value());
546 filter.add_filter(
"tag-key", aws_config_.get_tag_key());
548 }
else if (!aws_config_.get_tag_value().empty()) {
549 filter.add_filter(
"tag-value", aws_config_.get_tag_value());
552 if (!aws_config_.get_security_group_name().empty()) {
553 filter.add_filter(
"instance.group-name",
554 aws_config_.get_security_group_name());
557 filter.add_filter(
"instance-state-name",
"running");
558 const std::unordered_map<std::string, std::string>& filters =
559 filter.get_filters();
560 attributes_.insert(filters.begin(), filters.end());
567aws_url_encoder::url_encode(
const std::string& value)
569 std::string result = escape_encode(value);
570 boost::replace_all(result,
"+",
"%20");
575aws_url_encoder::escape_encode(
const std::string& value)
577 std::ostringstream escaped;
581 for (std::string::const_iterator i = value.begin(), n = value.end(); i != n;
583 std::string::value_type c = (*i);
586 if (isalnum(c) || c ==
'-' || c ==
'_' || c ==
'.' || c ==
'~') {
592 escaped << std::uppercase;
593 escaped <<
'%' << std::setw(2) << int((
unsigned char)c);
594 escaped << std::nouppercase;
597 return escaped.str();
600std::unordered_map<std::string, std::string>
601cloud_utility::unmarshal_the_response(std::istream& stream, logger& lg)
603 std::unordered_map<std::string, std::string> privatePublicPairs;
607 pt::read_xml(stream, tree);
608 }
catch (pt::xml_parser_error& e) {
612 boost::str(boost::format(
"The parsed xml stream has errors: %1%") %
614 return privatePublicPairs;
619 for (pt::ptree::value_type& item :
620 tree.get_child(
"DescribeInstancesResponse.reservationSet")) {
621 for (pt::ptree::value_type& instanceItem :
622 item.second.get_child(
"instancesSet")) {
624 instanceItem.second.get_optional<std::string>(
"privateIpAddress");
626 instanceItem.second.get_optional<std::string>(
"ipAddress");
628 auto prIp = privateIp.value_or(
"");
629 auto pubIp = publicIp.value_or(
"");
632 privatePublicPairs[prIp] = pubIp;
636 boost::format(
"Accepting EC2 instance [%1%][%2%]") %
638 .get_optional<std::string>(
"tagset.item.value")
644 return privatePublicPairs;
648cloud_utility::unmarshal_json_response(
649 std::istream& stream,
650 config::client_aws_config& aws_config,
651 std::unordered_map<std::string, std::string>& attributes)
654 pt::read_json(stream, json);
655 aws_config.set_access_key(
656 json.get_optional<std::string>(
"AccessKeyId").get_value_or(
""));
657 aws_config.set_secret_key(
658 json.get_optional<std::string>(
"SecretAccessKey").get_value_or(
""));
659 attributes[
"X-Amz-Security-Token"] =
660 json.get_optional<std::string>(
"Token").get_value_or(
"");
665aws_client::aws_client(std::chrono::steady_clock::duration timeout,
666 config::client_aws_config& aws_config,
667 const client_properties& client_properties,
670 , aws_config_(aws_config)
673 this->endpoint_ = aws_config.get_host_header();
674 if (!aws_config.get_region().empty() &&
675 aws_config.get_region().length() > 0) {
676 if (aws_config.get_host_header().find(
"ec2.") != 0) {
677 BOOST_THROW_EXCEPTION(exception::invalid_configuration(
678 "aws_client::aws_client",
679 "HostHeader should start with \"ec2.\" prefix"));
681 boost::replace_all(this->endpoint_,
683 std::string(
"ec2.") + aws_config.get_region() +
".");
687 client_properties.get_integer(client_properties.get_aws_member_port());
688 if (aws_member_port_ < 0 || aws_member_port_ > 65535) {
689 BOOST_THROW_EXCEPTION(exception::invalid_configuration(
690 "aws_client::aws_client",
692 "Configured aws member port %1% is not "
693 "a valid port number. It should be between 0-65535 inclusive.") %
699std::unordered_map<address, address>
700aws_client::get_addresses()
703 impl::DescribeInstances(timeout_, aws_config_, endpoint_, logger_)
705 std::unordered_map<address, address> addr_map;
706 addr_map.reserve(addr_pair_map.size());
707 for (
const auto& addr_pair : addr_pair_map) {
708 addr_map.emplace(address{ addr_pair.first, aws_member_port_ },
709 address{ addr_pair.second, aws_member_port_ });
720aws_client::aws_client(std::chrono::steady_clock::duration ,
721 config::client_aws_config& ,
722 const client_properties& ,
725 util::Preconditions::check_ssl(
"aws_client::aws_client");
728std::unordered_map<address, address>
729aws_client::get_addresses()
731 util::Preconditions::check_ssl(
"aws_client::get_addresses");
732 return std::unordered_map<address, address>();