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