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