AWSHttpResourceClient.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. /**
  2. * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
  3. * SPDX-License-Identifier: Apache-2.0.
  4. */
  5. #include <aws/core/internal/AWSHttpResourceClient.h>
  6. #include <aws/core/client/DefaultRetryStrategy.h>
  7. #include <aws/core/http/HttpClient.h>
  8. #include <aws/core/http/HttpClientFactory.h>
  9. #include <aws/core/http/HttpResponse.h>
  10. #include <aws/core/utils/logging/LogMacros.h>
  11. #include <aws/core/utils/StringUtils.h>
  12. #include <aws/core/utils/HashingUtils.h>
  13. #include <aws/core/platform/Environment.h>
  14. #include <aws/core/client/AWSError.h>
  15. #include <aws/core/client/CoreErrors.h>
  16. #include <aws/core/utils/xml/XmlSerializer.h>
  17. #include <mutex>
  18. #include <sstream>
  19. using namespace Aws;
  20. using namespace Aws::Utils;
  21. using namespace Aws::Utils::Logging;
  22. using namespace Aws::Utils::Xml;
  23. using namespace Aws::Http;
  24. using namespace Aws::Client;
  25. using namespace Aws::Internal;
  26. static const char EC2_SECURITY_CREDENTIALS_RESOURCE[] = "/latest/meta-data/iam/security-credentials";
  27. static const char EC2_REGION_RESOURCE[] = "/latest/meta-data/placement/availability-zone";
  28. static const char EC2_IMDS_TOKEN_RESOURCE[] = "/latest/api/token";
  29. static const char EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE[] = "21600";
  30. static const char EC2_IMDS_TOKEN_TTL_HEADER[] = "x-aws-ec2-metadata-token-ttl-seconds";
  31. static const char EC2_IMDS_TOKEN_HEADER[] = "x-aws-ec2-metadata-token";
  32. static const char RESOURCE_CLIENT_CONFIGURATION_ALLOCATION_TAG[] = "AWSHttpResourceClient";
  33. static const char EC2_METADATA_CLIENT_LOG_TAG[] = "EC2MetadataClient";
  34. static const char ECS_CREDENTIALS_CLIENT_LOG_TAG[] = "ECSCredentialsClient";
  35. static const char SSO_GET_ROLE_RESOURCE[] = "/federation/credentials";
  36. namespace Aws
  37. {
  38. namespace Client
  39. {
  40. Aws::String ComputeUserAgentString();
  41. }
  42. namespace Internal
  43. {
  44. static ClientConfiguration MakeDefaultHttpResourceClientConfiguration(const char *logtag)
  45. {
  46. ClientConfiguration res;
  47. res.maxConnections = 2;
  48. res.scheme = Scheme::HTTP;
  49. #if defined(WIN32) && defined(BYPASS_DEFAULT_PROXY)
  50. // For security reasons, we must bypass any proxy settings when fetching sensitive information, for example
  51. // user credentials. On Windows, IXMLHttpRequest2 does not support bypassing proxy settings, therefore,
  52. // we force using WinHTTP client. On POSIX systems, CURL is set to bypass proxy settings by default.
  53. res.httpLibOverride = TransferLibType::WIN_HTTP_CLIENT;
  54. AWS_LOGSTREAM_INFO(logtag, "Overriding the current HTTP client to WinHTTP to bypass proxy settings.");
  55. #else
  56. (void) logtag; // To disable warning about unused variable
  57. #endif
  58. // Explicitly set the proxy settings to empty/zero to avoid relying on defaults that could potentially change
  59. // in the future.
  60. res.proxyHost = "";
  61. res.proxyUserName = "";
  62. res.proxyPassword = "";
  63. res.proxyPort = 0;
  64. // EC2MetadataService throttles by delaying the response so the service client should set a large read timeout.
  65. // EC2MetadataService delay is in order of seconds so it only make sense to retry after a couple of seconds.
  66. res.connectTimeoutMs = 1000;
  67. res.requestTimeoutMs = 1000;
  68. res.retryStrategy = Aws::MakeShared<DefaultRetryStrategy>(RESOURCE_CLIENT_CONFIGURATION_ALLOCATION_TAG, 1, 1000);
  69. return res;
  70. }
  71. AWSHttpResourceClient::AWSHttpResourceClient(const Aws::Client::ClientConfiguration& clientConfiguration, const char* logtag)
  72. : m_logtag(logtag), m_retryStrategy(clientConfiguration.retryStrategy), m_httpClient(nullptr)
  73. {
  74. AWS_LOGSTREAM_INFO(m_logtag.c_str(),
  75. "Creating AWSHttpResourceClient with max connections "
  76. << clientConfiguration.maxConnections
  77. << " and scheme "
  78. << SchemeMapper::ToString(clientConfiguration.scheme));
  79. m_httpClient = CreateHttpClient(clientConfiguration);
  80. }
  81. AWSHttpResourceClient::AWSHttpResourceClient(const char* logtag)
  82. : AWSHttpResourceClient(MakeDefaultHttpResourceClientConfiguration(logtag), logtag)
  83. {
  84. }
  85. AWSHttpResourceClient::~AWSHttpResourceClient()
  86. {
  87. }
  88. Aws::String AWSHttpResourceClient::GetResource(const char* endpoint, const char* resource, const char* authToken) const
  89. {
  90. return GetResourceWithAWSWebServiceResult(endpoint, resource, authToken).GetPayload();
  91. }
  92. AmazonWebServiceResult<Aws::String> AWSHttpResourceClient::GetResourceWithAWSWebServiceResult(const char *endpoint, const char *resource, const char *authToken) const
  93. {
  94. Aws::StringStream ss;
  95. ss << endpoint;
  96. if (resource)
  97. {
  98. ss << resource;
  99. }
  100. std::shared_ptr<HttpRequest> request(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET,
  101. Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
  102. request->SetUserAgent(ComputeUserAgentString());
  103. if (authToken)
  104. {
  105. request->SetHeaderValue(Aws::Http::AWS_AUTHORIZATION_HEADER, authToken);
  106. }
  107. return GetResourceWithAWSWebServiceResult(request);
  108. }
  109. AmazonWebServiceResult<Aws::String> AWSHttpResourceClient::GetResourceWithAWSWebServiceResult(const std::shared_ptr<HttpRequest> &httpRequest) const
  110. {
  111. AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Retrieving credentials from " << httpRequest->GetURIString());
  112. for (long retries = 0;; retries++)
  113. {
  114. std::shared_ptr<HttpResponse> response(m_httpClient->MakeRequest(httpRequest));
  115. if (response->GetResponseCode() == HttpResponseCode::OK)
  116. {
  117. Aws::IStreamBufIterator eos;
  118. return {Aws::String(Aws::IStreamBufIterator(response->GetResponseBody()), eos), response->GetHeaders(), HttpResponseCode::OK};
  119. }
  120. const Aws::Client::AWSError<Aws::Client::CoreErrors> error = [this, &response]() {
  121. if (response->HasClientError() || response->GetResponseCode() == HttpResponseCode::REQUEST_NOT_MADE)
  122. {
  123. AWS_LOGSTREAM_ERROR(m_logtag.c_str(), "Http request to retrieve credentials failed");
  124. return AWSError<CoreErrors>(CoreErrors::NETWORK_CONNECTION, true); // Retryable
  125. }
  126. else if (m_errorMarshaller && response->GetResponseBody().tellp() > 0)
  127. {
  128. return m_errorMarshaller->Marshall(*response);
  129. }
  130. else
  131. {
  132. const auto responseCode = response->GetResponseCode();
  133. AWS_LOGSTREAM_ERROR(m_logtag.c_str(), "Http request to retrieve credentials failed with error code "
  134. << static_cast<int>(responseCode));
  135. return CoreErrorsMapper::GetErrorForHttpResponseCode(responseCode);
  136. }
  137. }();
  138. if (!m_retryStrategy->ShouldRetry(error, retries))
  139. {
  140. AWS_LOGSTREAM_ERROR(m_logtag.c_str(), "Can not retrieve resource from " << httpRequest->GetURIString());
  141. return {{}, response->GetHeaders(), error.GetResponseCode()};
  142. }
  143. auto sleepMillis = m_retryStrategy->CalculateDelayBeforeNextRetry(error, retries);
  144. AWS_LOGSTREAM_WARN(m_logtag.c_str(), "Request failed, now waiting " << sleepMillis << " ms before attempting again.");
  145. m_httpClient->RetryRequestSleep(std::chrono::milliseconds(sleepMillis));
  146. }
  147. }
  148. EC2MetadataClient::EC2MetadataClient(const char *endpoint) :
  149. AWSHttpResourceClient(EC2_METADATA_CLIENT_LOG_TAG),
  150. m_endpoint(endpoint),
  151. m_disableIMDS(false),
  152. m_tokenRequired(true)
  153. {
  154. }
  155. EC2MetadataClient::EC2MetadataClient(const Aws::Client::ClientConfiguration &clientConfiguration,
  156. const char *endpoint) :
  157. AWSHttpResourceClient(clientConfiguration, EC2_METADATA_CLIENT_LOG_TAG),
  158. m_endpoint(endpoint),
  159. m_disableIMDS(clientConfiguration.disableIMDS),
  160. m_tokenRequired(true)
  161. {
  162. }
  163. EC2MetadataClient::~EC2MetadataClient()
  164. {
  165. }
  166. Aws::String EC2MetadataClient::GetResource(const char* resourcePath) const
  167. {
  168. return GetResource(m_endpoint.c_str(), resourcePath, nullptr/*authToken*/);
  169. }
  170. #if !defined(DISABLE_IMDSV1)
  171. Aws::String EC2MetadataClient::GetDefaultCredentials() const
  172. {
  173. if (m_disableIMDS) {
  174. AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Skipping call to IMDS Service");
  175. return {};
  176. }
  177. std::unique_lock<std::recursive_mutex> locker(m_tokenMutex);
  178. if (m_tokenRequired)
  179. {
  180. return GetDefaultCredentialsSecurely();
  181. }
  182. AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Getting default credentials for ec2 instance from " << m_endpoint);
  183. auto result = GetResourceWithAWSWebServiceResult(m_endpoint.c_str(), EC2_SECURITY_CREDENTIALS_RESOURCE, nullptr);
  184. Aws::String credentialsString = result.GetPayload();
  185. auto httpResponseCode = result.GetResponseCode();
  186. // Note, if service is insane, it might return 404 for our initial secure call,
  187. // then when we fall back to insecure call, it might return 401 ask for secure call,
  188. // Then, SDK might get into a recursive loop call situation between secure and insecure call.
  189. if (httpResponseCode == Http::HttpResponseCode::UNAUTHORIZED)
  190. {
  191. m_tokenRequired = true;
  192. return {};
  193. }
  194. locker.unlock();
  195. Aws::String trimmedCredentialsString = StringUtils::Trim(credentialsString.c_str());
  196. if (trimmedCredentialsString.empty()) return {};
  197. Aws::Vector<Aws::String> securityCredentials = StringUtils::Split(trimmedCredentialsString, '\n');
  198. AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource, " << EC2_SECURITY_CREDENTIALS_RESOURCE
  199. << " returned credential string " << trimmedCredentialsString);
  200. if (securityCredentials.size() == 0)
  201. {
  202. AWS_LOGSTREAM_WARN(m_logtag.c_str(), "Initial call to ec2Metadataservice to get credentials failed");
  203. return {};
  204. }
  205. Aws::StringStream ss;
  206. ss << EC2_SECURITY_CREDENTIALS_RESOURCE << "/" << securityCredentials[0];
  207. AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource " << ss.str());
  208. return GetResource(ss.str().c_str());
  209. }
  210. #endif
  211. Aws::String EC2MetadataClient::GetDefaultCredentialsSecurely() const
  212. {
  213. if (m_disableIMDS) {
  214. AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Skipping call to IMDS Service");
  215. return {};
  216. }
  217. std::unique_lock<std::recursive_mutex> locker(m_tokenMutex);
  218. #if !defined(DISABLE_IMDSV1)
  219. if (!m_tokenRequired) {
  220. return GetDefaultCredentials();
  221. }
  222. #endif
  223. Aws::StringStream ss;
  224. ss << m_endpoint << EC2_IMDS_TOKEN_RESOURCE;
  225. std::shared_ptr<HttpRequest> tokenRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_PUT,
  226. Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
  227. tokenRequest->SetHeaderValue(EC2_IMDS_TOKEN_TTL_HEADER, EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE);
  228. auto userAgentString = ComputeUserAgentString();
  229. tokenRequest->SetUserAgent(userAgentString);
  230. AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Calling EC2MetadataService to get token");
  231. auto result = GetResourceWithAWSWebServiceResult(tokenRequest);
  232. Aws::String tokenString = result.GetPayload();
  233. Aws::String trimmedTokenString = StringUtils::Trim(tokenString.c_str());
  234. if (result.GetResponseCode() == HttpResponseCode::BAD_REQUEST)
  235. {
  236. return {};
  237. }
  238. #if !defined(DISABLE_IMDSV1)
  239. else if (result.GetResponseCode() != HttpResponseCode::OK || trimmedTokenString.empty())
  240. {
  241. m_tokenRequired = false;
  242. AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Calling EC2MetadataService to get token failed, falling back to less secure way.");
  243. return GetDefaultCredentials();
  244. }
  245. #endif
  246. m_token = trimmedTokenString;
  247. locker.unlock();
  248. ss.str("");
  249. ss << m_endpoint << EC2_SECURITY_CREDENTIALS_RESOURCE;
  250. std::shared_ptr<HttpRequest> profileRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET,
  251. Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
  252. profileRequest->SetHeaderValue(EC2_IMDS_TOKEN_HEADER, trimmedTokenString);
  253. profileRequest->SetUserAgent(userAgentString);
  254. Aws::String profileString = GetResourceWithAWSWebServiceResult(profileRequest).GetPayload();
  255. Aws::String trimmedProfileString = StringUtils::Trim(profileString.c_str());
  256. Aws::Vector<Aws::String> securityCredentials = StringUtils::Split(trimmedProfileString, '\n');
  257. AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource, " << EC2_SECURITY_CREDENTIALS_RESOURCE
  258. << " with token returned profile string " << trimmedProfileString);
  259. if (securityCredentials.empty())
  260. {
  261. AWS_LOGSTREAM_WARN(m_logtag.c_str(), "Calling EC2Metadataservice to get profiles failed");
  262. return {};
  263. }
  264. ss.str("");
  265. ss << m_endpoint << EC2_SECURITY_CREDENTIALS_RESOURCE << "/" << securityCredentials[0];
  266. std::shared_ptr<HttpRequest> credentialsRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET,
  267. Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
  268. credentialsRequest->SetHeaderValue(EC2_IMDS_TOKEN_HEADER, trimmedTokenString);
  269. credentialsRequest->SetUserAgent(userAgentString);
  270. AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource " << ss.str() << " with token.");
  271. return GetResourceWithAWSWebServiceResult(credentialsRequest).GetPayload();
  272. }
  273. Aws::String EC2MetadataClient::GetCurrentRegion() const
  274. {
  275. if (m_disableIMDS) {
  276. AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Skipping call to IMDS Service");
  277. return {};
  278. }
  279. if (!m_region.empty())
  280. {
  281. return m_region;
  282. }
  283. AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Getting current region for ec2 instance");
  284. Aws::StringStream ss;
  285. ss << m_endpoint << EC2_REGION_RESOURCE;
  286. std::shared_ptr<HttpRequest> regionRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET,
  287. Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
  288. {
  289. std::lock_guard<std::recursive_mutex> locker(m_tokenMutex);
  290. if (m_tokenRequired)
  291. {
  292. GetDefaultCredentialsSecurely();
  293. regionRequest->SetHeaderValue(EC2_IMDS_TOKEN_HEADER, m_token);
  294. }
  295. }
  296. regionRequest->SetUserAgent(ComputeUserAgentString());
  297. Aws::String azString = GetResourceWithAWSWebServiceResult(regionRequest).GetPayload();
  298. if (azString.empty())
  299. {
  300. AWS_LOGSTREAM_INFO(m_logtag.c_str() ,
  301. "Unable to pull region from instance metadata service ");
  302. return {};
  303. }
  304. Aws::String trimmedAZString = StringUtils::Trim(azString.c_str());
  305. AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource "
  306. << EC2_REGION_RESOURCE << " , returned credential string " << trimmedAZString);
  307. Aws::String region;
  308. region.reserve(trimmedAZString.length());
  309. bool digitFound = false;
  310. for (auto character : trimmedAZString)
  311. {
  312. if(digitFound && !isdigit(character))
  313. {
  314. break;
  315. }
  316. if (isdigit(character))
  317. {
  318. digitFound = true;
  319. }
  320. region.append(1, character);
  321. }
  322. AWS_LOGSTREAM_INFO(m_logtag.c_str(), "Detected current region as " << region);
  323. m_region = region;
  324. return region;
  325. }
  326. void EC2MetadataClient::SetEndpoint(const Aws::String& endpoint)
  327. {
  328. m_endpoint = endpoint;
  329. }
  330. Aws::String EC2MetadataClient::GetEndpoint() const
  331. {
  332. return Aws::String(m_endpoint);
  333. }
  334. #ifdef _MSC_VER
  335. // VS2015 compiler's bug, warning s_ec2metadataClient: symbol will be dynamically initialized (implementation limitation)
  336. AWS_SUPPRESS_WARNING(4592,
  337. static std::shared_ptr<EC2MetadataClient> s_ec2metadataClient(nullptr);
  338. )
  339. #else
  340. static std::shared_ptr<EC2MetadataClient> s_ec2metadataClient(nullptr);
  341. #endif
  342. void InitEC2MetadataClient()
  343. {
  344. if (s_ec2metadataClient)
  345. {
  346. return;
  347. }
  348. Aws::String ec2MetadataServiceEndpoint = Aws::Environment::GetEnv("AWS_EC2_METADATA_SERVICE_ENDPOINT");
  349. if (ec2MetadataServiceEndpoint.empty())
  350. {
  351. Aws::String ec2MetadataServiceEndpointMode = Aws::Environment::GetEnv("AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE").c_str();
  352. if (ec2MetadataServiceEndpointMode.length() == 0 )
  353. {
  354. ec2MetadataServiceEndpoint = "http://169.254.169.254"; //default to IPv4 default endpoint
  355. }
  356. else
  357. {
  358. if (ec2MetadataServiceEndpointMode.length() == 4 )
  359. {
  360. if (Aws::Utils::StringUtils::CaselessCompare(ec2MetadataServiceEndpointMode.c_str(), "ipv4"))
  361. {
  362. ec2MetadataServiceEndpoint = "http://169.254.169.254"; //default to IPv4 default endpoint
  363. }
  364. else if (Aws::Utils::StringUtils::CaselessCompare(ec2MetadataServiceEndpointMode.c_str(), "ipv6"))
  365. {
  366. ec2MetadataServiceEndpoint = "http://[fd00:ec2::254]";
  367. }
  368. else
  369. {
  370. AWS_LOGSTREAM_ERROR(EC2_METADATA_CLIENT_LOG_TAG, "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE can only be set to ipv4 or ipv6, received: " << ec2MetadataServiceEndpointMode );
  371. }
  372. }
  373. else
  374. {
  375. AWS_LOGSTREAM_ERROR(EC2_METADATA_CLIENT_LOG_TAG, "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE can only be set to ipv4 or ipv6, received: " << ec2MetadataServiceEndpointMode );
  376. }
  377. }
  378. }
  379. AWS_LOGSTREAM_INFO(EC2_METADATA_CLIENT_LOG_TAG, "Using IMDS endpoint: " << ec2MetadataServiceEndpoint);
  380. s_ec2metadataClient = Aws::MakeShared<EC2MetadataClient>(EC2_METADATA_CLIENT_LOG_TAG, ec2MetadataServiceEndpoint.c_str());
  381. }
  382. void CleanupEC2MetadataClient()
  383. {
  384. if (!s_ec2metadataClient)
  385. {
  386. return;
  387. }
  388. s_ec2metadataClient = nullptr;
  389. }
  390. std::shared_ptr<EC2MetadataClient> GetEC2MetadataClient()
  391. {
  392. return s_ec2metadataClient;
  393. }
  394. ECSCredentialsClient::ECSCredentialsClient(const char* resourcePath, const char* endpoint, const char* token)
  395. : AWSHttpResourceClient(ECS_CREDENTIALS_CLIENT_LOG_TAG),
  396. m_resourcePath(resourcePath), m_endpoint(endpoint), m_token(token)
  397. {
  398. }
  399. ECSCredentialsClient::ECSCredentialsClient(const Aws::Client::ClientConfiguration& clientConfiguration, const char* resourcePath, const char* endpoint, const char* token)
  400. : AWSHttpResourceClient(clientConfiguration, ECS_CREDENTIALS_CLIENT_LOG_TAG),
  401. m_resourcePath(resourcePath), m_endpoint(endpoint), m_token(token)
  402. {
  403. }
  404. static const char STS_RESOURCE_CLIENT_LOG_TAG[] = "STSResourceClient";
  405. STSCredentialsClient::STSCredentialsClient(const Aws::Client::ClientConfiguration& clientConfiguration)
  406. : AWSHttpResourceClient(clientConfiguration, STS_RESOURCE_CLIENT_LOG_TAG)
  407. {
  408. SetErrorMarshaller(Aws::MakeUnique<Aws::Client::XmlErrorMarshaller>(STS_RESOURCE_CLIENT_LOG_TAG));
  409. Aws::StringStream ss;
  410. if (clientConfiguration.scheme == Aws::Http::Scheme::HTTP)
  411. {
  412. ss << "http://";
  413. }
  414. else
  415. {
  416. ss << "https://";
  417. }
  418. static const int CN_NORTH_1_HASH = Aws::Utils::HashingUtils::HashString(Aws::Region::CN_NORTH_1);
  419. static const int CN_NORTHWEST_1_HASH = Aws::Utils::HashingUtils::HashString(Aws::Region::CN_NORTHWEST_1);
  420. auto hash = Aws::Utils::HashingUtils::HashString(clientConfiguration.region.c_str());
  421. ss << "sts." << clientConfiguration.region << ".amazonaws.com";
  422. if (hash == CN_NORTH_1_HASH || hash == CN_NORTHWEST_1_HASH)
  423. {
  424. ss << ".cn";
  425. }
  426. m_endpoint = ss.str();
  427. AWS_LOGSTREAM_INFO(STS_RESOURCE_CLIENT_LOG_TAG, "Creating STS ResourceClient with endpoint: " << m_endpoint);
  428. }
  429. STSCredentialsClient::STSAssumeRoleWithWebIdentityResult STSCredentialsClient::GetAssumeRoleWithWebIdentityCredentials(const STSAssumeRoleWithWebIdentityRequest& request)
  430. {
  431. //Calculate query string
  432. Aws::StringStream ss;
  433. ss << "Action=AssumeRoleWithWebIdentity"
  434. << "&Version=2011-06-15"
  435. << "&RoleSessionName=" << Aws::Utils::StringUtils::URLEncode(request.roleSessionName.c_str())
  436. << "&RoleArn=" << Aws::Utils::StringUtils::URLEncode(request.roleArn.c_str())
  437. << "&WebIdentityToken=" << Aws::Utils::StringUtils::URLEncode(request.webIdentityToken.c_str());
  438. std::shared_ptr<HttpRequest> httpRequest(CreateHttpRequest(m_endpoint, HttpMethod::HTTP_POST,
  439. Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
  440. httpRequest->SetUserAgent(ComputeUserAgentString());
  441. std::shared_ptr<Aws::IOStream> body = Aws::MakeShared<Aws::StringStream>("STS_RESOURCE_CLIENT_LOG_TAG");
  442. *body << ss.str();
  443. httpRequest->AddContentBody(body);
  444. body->seekg(0, body->end);
  445. auto streamSize = body->tellg();
  446. body->seekg(0, body->beg);
  447. Aws::StringStream contentLength;
  448. contentLength << streamSize;
  449. httpRequest->SetContentLength(contentLength.str());
  450. httpRequest->SetContentType("application/x-www-form-urlencoded");
  451. Aws::String credentialsStr = GetResourceWithAWSWebServiceResult(httpRequest).GetPayload();
  452. //Parse credentials
  453. STSAssumeRoleWithWebIdentityResult result;
  454. if (credentialsStr.empty())
  455. {
  456. AWS_LOGSTREAM_WARN(STS_RESOURCE_CLIENT_LOG_TAG, "Get an empty credential from sts");
  457. return result;
  458. }
  459. const Utils::Xml::XmlDocument xmlDocument = XmlDocument::CreateFromXmlString(credentialsStr);
  460. XmlNode rootNode = xmlDocument.GetRootElement();
  461. XmlNode resultNode = rootNode;
  462. if (!rootNode.IsNull() && (rootNode.GetName() != "AssumeRoleWithWebIdentityResult"))
  463. {
  464. resultNode = rootNode.FirstChild("AssumeRoleWithWebIdentityResult");
  465. }
  466. if (!resultNode.IsNull())
  467. {
  468. XmlNode credentialsNode = resultNode.FirstChild("Credentials");
  469. if (!credentialsNode.IsNull())
  470. {
  471. XmlNode accessKeyIdNode = credentialsNode.FirstChild("AccessKeyId");
  472. if (!accessKeyIdNode.IsNull())
  473. {
  474. result.creds.SetAWSAccessKeyId(accessKeyIdNode.GetText());
  475. }
  476. XmlNode secretAccessKeyNode = credentialsNode.FirstChild("SecretAccessKey");
  477. if (!secretAccessKeyNode.IsNull())
  478. {
  479. result.creds.SetAWSSecretKey(secretAccessKeyNode.GetText());
  480. }
  481. XmlNode sessionTokenNode = credentialsNode.FirstChild("SessionToken");
  482. if (!sessionTokenNode.IsNull())
  483. {
  484. result.creds.SetSessionToken(sessionTokenNode.GetText());
  485. }
  486. XmlNode expirationNode = credentialsNode.FirstChild("Expiration");
  487. if (!expirationNode.IsNull())
  488. {
  489. result.creds.SetExpiration(DateTime(StringUtils::Trim(expirationNode.GetText().c_str()).c_str(), DateFormat::ISO_8601));
  490. }
  491. }
  492. }
  493. return result;
  494. }
  495. static const char SSO_RESOURCE_CLIENT_LOG_TAG[] = "SSOResourceClient";
  496. SSOCredentialsClient::SSOCredentialsClient(const Aws::Client::ClientConfiguration& clientConfiguration)
  497. : AWSHttpResourceClient(clientConfiguration, SSO_RESOURCE_CLIENT_LOG_TAG)
  498. {
  499. SetErrorMarshaller(Aws::MakeUnique<Aws::Client::JsonErrorMarshaller>(SSO_RESOURCE_CLIENT_LOG_TAG));
  500. m_endpoint = buildEndpoint(clientConfiguration, "portal.sso.", "federation/credentials");
  501. m_oidcEndpoint = buildEndpoint(clientConfiguration, "oidc.", "token");
  502. AWS_LOGSTREAM_INFO(SSO_RESOURCE_CLIENT_LOG_TAG, "Creating SSO ResourceClient with endpoint: " << m_endpoint);
  503. }
  504. Aws::String SSOCredentialsClient::buildEndpoint(
  505. const Aws::Client::ClientConfiguration& clientConfiguration,
  506. const Aws::String& domain,
  507. const Aws::String& endpoint)
  508. {
  509. Aws::StringStream ss;
  510. if (clientConfiguration.scheme == Aws::Http::Scheme::HTTP)
  511. {
  512. ss << "http://";
  513. }
  514. else
  515. {
  516. ss << "https://";
  517. }
  518. static const int CN_NORTH_1_HASH = Aws::Utils::HashingUtils::HashString(Aws::Region::CN_NORTH_1);
  519. static const int CN_NORTHWEST_1_HASH = Aws::Utils::HashingUtils::HashString(Aws::Region::CN_NORTHWEST_1);
  520. auto hash = Aws::Utils::HashingUtils::HashString(clientConfiguration.region.c_str());
  521. AWS_LOGSTREAM_DEBUG(SSO_RESOURCE_CLIENT_LOG_TAG, "Preparing SSO client for region: " << clientConfiguration.region);
  522. ss << domain << clientConfiguration.region << ".amazonaws.com/" << endpoint;
  523. if (hash == CN_NORTH_1_HASH || hash == CN_NORTHWEST_1_HASH)
  524. {
  525. ss << ".cn";
  526. }
  527. return ss.str();
  528. }
  529. SSOCredentialsClient::SSOGetRoleCredentialsResult SSOCredentialsClient::GetSSOCredentials(const SSOGetRoleCredentialsRequest &request)
  530. {
  531. Aws::StringStream ssUri;
  532. ssUri << m_endpoint << SSO_GET_ROLE_RESOURCE;
  533. std::shared_ptr<HttpRequest> httpRequest(CreateHttpRequest(m_endpoint, HttpMethod::HTTP_GET,
  534. Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
  535. httpRequest->SetHeaderValue("x-amz-sso_bearer_token", request.m_accessToken);
  536. httpRequest->SetUserAgent(ComputeUserAgentString());
  537. httpRequest->AddQueryStringParameter("account_id", Aws::Utils::StringUtils::URLEncode(request.m_ssoAccountId.c_str()));
  538. httpRequest->AddQueryStringParameter("role_name", Aws::Utils::StringUtils::URLEncode(request.m_ssoRoleName.c_str()));
  539. Aws::String credentialsStr = GetResourceWithAWSWebServiceResult(httpRequest).GetPayload();
  540. Json::JsonValue credentialsDoc(credentialsStr);
  541. AWS_LOGSTREAM_TRACE(SSO_RESOURCE_CLIENT_LOG_TAG, "Raw creds returned: " << credentialsStr);
  542. Aws::Auth::AWSCredentials creds;
  543. if (!credentialsDoc.WasParseSuccessful())
  544. {
  545. AWS_LOGSTREAM_ERROR(SSO_RESOURCE_CLIENT_LOG_TAG, "Failed to load credential from running. Error: " << credentialsStr);
  546. return SSOGetRoleCredentialsResult{creds};
  547. }
  548. Utils::Json::JsonView credentialsView(credentialsDoc);
  549. auto roleCredentials = credentialsView.GetObject("roleCredentials");
  550. creds.SetAWSAccessKeyId(roleCredentials.GetString("accessKeyId"));
  551. creds.SetAWSSecretKey(roleCredentials.GetString("secretAccessKey"));
  552. creds.SetSessionToken(roleCredentials.GetString("sessionToken"));
  553. creds.SetExpiration(roleCredentials.GetInt64("expiration"));
  554. SSOCredentialsClient::SSOGetRoleCredentialsResult result;
  555. result.creds = creds;
  556. return result;
  557. }
  558. // An internal SSO CreateToken implementation to lightweight core package and not introduce a dependency on sso-oidc
  559. SSOCredentialsClient::SSOCreateTokenResult SSOCredentialsClient::CreateToken(const SSOCreateTokenRequest& request)
  560. {
  561. std::shared_ptr<HttpRequest> httpRequest(CreateHttpRequest(m_oidcEndpoint, HttpMethod::HTTP_POST,
  562. Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
  563. SSOCreateTokenResult result;
  564. if(!httpRequest) {
  565. AWS_LOGSTREAM_FATAL(SSO_RESOURCE_CLIENT_LOG_TAG, "Failed to CreateHttpRequest: nullptr returned");
  566. return result;
  567. }
  568. httpRequest->SetUserAgent(ComputeUserAgentString());
  569. Json::JsonValue requestDoc;
  570. if(!request.clientId.empty()) {
  571. requestDoc.WithString("clientId", request.clientId);
  572. }
  573. if(!request.clientSecret.empty()) {
  574. requestDoc.WithString("clientSecret", request.clientSecret);
  575. }
  576. if(!request.grantType.empty()) {
  577. requestDoc.WithString("grantType", request.grantType);
  578. }
  579. if(!request.refreshToken.empty()) {
  580. requestDoc.WithString("refreshToken", request.refreshToken);
  581. }
  582. std::shared_ptr<Aws::IOStream> body = Aws::MakeShared<Aws::StringStream>("SSO_BEARER_TOKEN_CREATE_TOKEN");
  583. if(!body) {
  584. AWS_LOGSTREAM_FATAL(SSO_RESOURCE_CLIENT_LOG_TAG, "Failed to allocate body"); // exceptions disabled
  585. return result;
  586. }
  587. *body << requestDoc.View().WriteReadable();;
  588. httpRequest->AddContentBody(body);
  589. body->seekg(0, body->end);
  590. auto streamSize = body->tellg();
  591. body->seekg(0, body->beg);
  592. Aws::StringStream contentLength;
  593. contentLength << streamSize;
  594. httpRequest->SetContentLength(contentLength.str());
  595. httpRequest->SetContentType("application/json");
  596. Aws::String rawReply = GetResourceWithAWSWebServiceResult(httpRequest).GetPayload();
  597. Json::JsonValue refreshTokenDoc(rawReply);
  598. Utils::Json::JsonView jsonValue = refreshTokenDoc.View();
  599. if(jsonValue.ValueExists("accessToken")) {
  600. result.accessToken = jsonValue.GetString("accessToken");
  601. }
  602. if(jsonValue.ValueExists("tokenType")) {
  603. result.tokenType = jsonValue.GetString("tokenType");
  604. }
  605. if(jsonValue.ValueExists("expiresIn")) {
  606. result.expiresIn = jsonValue.GetInteger("expiresIn");
  607. }
  608. if(jsonValue.ValueExists("idToken")) {
  609. result.idToken = jsonValue.GetString("idToken");
  610. }
  611. if(jsonValue.ValueExists("refreshToken")) {
  612. result.refreshToken = jsonValue.GetString("refreshToken");
  613. }
  614. return result;
  615. }
  616. }
  617. }