diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core/source/internal/AWSHttpResourceClient.cpp | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core/source/internal/AWSHttpResourceClient.cpp')
-rw-r--r-- | contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core/source/internal/AWSHttpResourceClient.cpp | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core/source/internal/AWSHttpResourceClient.cpp b/contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core/source/internal/AWSHttpResourceClient.cpp new file mode 100644 index 0000000000..24145e4d92 --- /dev/null +++ b/contrib/libs/aws-sdk-cpp/aws-cpp-sdk-core/source/internal/AWSHttpResourceClient.cpp @@ -0,0 +1,506 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include <aws/core/internal/AWSHttpResourceClient.h> +#include <aws/core/client/DefaultRetryStrategy.h> +#include <aws/core/http/HttpClient.h> +#include <aws/core/http/HttpClientFactory.h> +#include <aws/core/http/HttpResponse.h> +#include <aws/core/utils/logging/LogMacros.h> +#include <aws/core/utils/StringUtils.h> +#include <aws/core/utils/HashingUtils.h> +#include <aws/core/platform/Environment.h> +#include <aws/core/client/AWSError.h> +#include <aws/core/client/CoreErrors.h> +#include <aws/core/utils/xml/XmlSerializer.h> +#include <mutex> +#include <sstream> + +using namespace Aws; +using namespace Aws::Utils; +using namespace Aws::Utils::Logging; +using namespace Aws::Utils::Xml; +using namespace Aws::Http; +using namespace Aws::Client; +using namespace Aws::Internal; + +static const char EC2_SECURITY_CREDENTIALS_RESOURCE[] = "/latest/meta-data/iam/security-credentials"; +static const char EC2_REGION_RESOURCE[] = "/latest/meta-data/placement/availability-zone"; +static const char EC2_IMDS_TOKEN_RESOURCE[] = "/latest/api/token"; +static const char EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE[] = "21600"; +static const char EC2_IMDS_TOKEN_TTL_HEADER[] = "x-aws-ec2-metadata-token-ttl-seconds"; +static const char EC2_IMDS_TOKEN_HEADER[] = "x-aws-ec2-metadata-token"; +static const char RESOURCE_CLIENT_CONFIGURATION_ALLOCATION_TAG[] = "AWSHttpResourceClient"; +static const char EC2_METADATA_CLIENT_LOG_TAG[] = "EC2MetadataClient"; +static const char ECS_CREDENTIALS_CLIENT_LOG_TAG[] = "ECSCredentialsClient"; + +namespace Aws +{ + namespace Client + { + Aws::String ComputeUserAgentString(); + } + + namespace Internal + { + static ClientConfiguration MakeDefaultHttpResourceClientConfiguration(const char *logtag) + { + ClientConfiguration res; + + res.maxConnections = 2; + res.scheme = Scheme::HTTP; + + #if defined(WIN32) && defined(BYPASS_DEFAULT_PROXY) + // For security reasons, we must bypass any proxy settings when fetching sensitive information, for example + // user credentials. On Windows, IXMLHttpRequest2 does not support bypassing proxy settings, therefore, + // we force using WinHTTP client. On POSIX systems, CURL is set to bypass proxy settings by default. + res.httpLibOverride = TransferLibType::WIN_HTTP_CLIENT; + AWS_LOGSTREAM_INFO(logtag, "Overriding the current HTTP client to WinHTTP to bypass proxy settings."); + #else + (void) logtag; // To disable warning about unused variable + #endif + // Explicitly set the proxy settings to empty/zero to avoid relying on defaults that could potentially change + // in the future. + res.proxyHost = ""; + res.proxyUserName = ""; + res.proxyPassword = ""; + res.proxyPort = 0; + + // EC2MetadataService throttles by delaying the response so the service client should set a large read timeout. + // EC2MetadataService delay is in order of seconds so it only make sense to retry after a couple of seconds. + res.connectTimeoutMs = 1000; + res.requestTimeoutMs = 1000; + res.retryStrategy = Aws::MakeShared<DefaultRetryStrategy>(RESOURCE_CLIENT_CONFIGURATION_ALLOCATION_TAG, 1, 1000); + + return res; + } + + AWSHttpResourceClient::AWSHttpResourceClient(const Aws::Client::ClientConfiguration& clientConfiguration, const char* logtag) + : m_logtag(logtag), m_retryStrategy(clientConfiguration.retryStrategy), m_httpClient(nullptr) + { + AWS_LOGSTREAM_INFO(m_logtag.c_str(), + "Creating AWSHttpResourceClient with max connections " + << clientConfiguration.maxConnections + << " and scheme " + << SchemeMapper::ToString(clientConfiguration.scheme)); + + m_httpClient = CreateHttpClient(clientConfiguration); + } + + AWSHttpResourceClient::AWSHttpResourceClient(const char* logtag) + : AWSHttpResourceClient(MakeDefaultHttpResourceClientConfiguration(logtag), logtag) + { + } + + AWSHttpResourceClient::~AWSHttpResourceClient() + { + } + + Aws::String AWSHttpResourceClient::GetResource(const char* endpoint, const char* resource, const char* authToken) const + { + return GetResourceWithAWSWebServiceResult(endpoint, resource, authToken).GetPayload(); + } + + AmazonWebServiceResult<Aws::String> AWSHttpResourceClient::GetResourceWithAWSWebServiceResult(const char *endpoint, const char *resource, const char *authToken) const + { + Aws::StringStream ss; + ss << endpoint; + if (resource) + { + ss << resource; + } + std::shared_ptr<HttpRequest> request(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod)); + + request->SetUserAgent(ComputeUserAgentString()); + + if (authToken) + { + request->SetHeaderValue(Aws::Http::AWS_AUTHORIZATION_HEADER, authToken); + } + + return GetResourceWithAWSWebServiceResult(request); + } + + AmazonWebServiceResult<Aws::String> AWSHttpResourceClient::GetResourceWithAWSWebServiceResult(const std::shared_ptr<HttpRequest> &httpRequest) const + { + AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Retrieving credentials from " << httpRequest->GetURIString()); + + for (long retries = 0;; retries++) + { + std::shared_ptr<HttpResponse> response(m_httpClient->MakeRequest(httpRequest)); + + if (response->GetResponseCode() == HttpResponseCode::OK) + { + Aws::IStreamBufIterator eos; + return {Aws::String(Aws::IStreamBufIterator(response->GetResponseBody()), eos), response->GetHeaders(), HttpResponseCode::OK}; + } + + const Aws::Client::AWSError<Aws::Client::CoreErrors> error = [this, &response]() { + if (response->HasClientError() || response->GetResponseBody().tellp() < 1) + { + AWS_LOGSTREAM_ERROR(m_logtag.c_str(), "Http request to retrieve credentials failed"); + return AWSError<CoreErrors>(CoreErrors::NETWORK_CONNECTION, true); // Retryable + } + else if (m_errorMarshaller) + { + return m_errorMarshaller->Marshall(*response); + } + else + { + const auto responseCode = response->GetResponseCode(); + + AWS_LOGSTREAM_ERROR(m_logtag.c_str(), "Http request to retrieve credentials failed with error code " + << static_cast<int>(responseCode)); + return CoreErrorsMapper::GetErrorForHttpResponseCode(responseCode); + } + }(); + + if (!m_retryStrategy->ShouldRetry(error, retries)) + { + AWS_LOGSTREAM_ERROR(m_logtag.c_str(), "Can not retrive resource from " << httpRequest->GetURIString()); + return {{}, response->GetHeaders(), error.GetResponseCode()}; + } + auto sleepMillis = m_retryStrategy->CalculateDelayBeforeNextRetry(error, retries); + AWS_LOGSTREAM_WARN(m_logtag.c_str(), "Request failed, now waiting " << sleepMillis << " ms before attempting again."); + m_httpClient->RetryRequestSleep(std::chrono::milliseconds(sleepMillis)); + } + } + + EC2MetadataClient::EC2MetadataClient(const char* endpoint) + : AWSHttpResourceClient(EC2_METADATA_CLIENT_LOG_TAG), m_endpoint(endpoint), m_tokenRequired(true) + { + } + + EC2MetadataClient::EC2MetadataClient(const Aws::Client::ClientConfiguration &clientConfiguration, const char *endpoint) + : AWSHttpResourceClient(clientConfiguration, EC2_METADATA_CLIENT_LOG_TAG), m_endpoint(endpoint), m_tokenRequired(true) + { + } + + EC2MetadataClient::~EC2MetadataClient() + { + + } + + Aws::String EC2MetadataClient::GetResource(const char* resourcePath) const + { + return GetResource(m_endpoint.c_str(), resourcePath, nullptr/*authToken*/); + } + + Aws::String EC2MetadataClient::GetDefaultCredentials() const + { + std::unique_lock<std::recursive_mutex> locker(m_tokenMutex); + if (m_tokenRequired) + { + return GetDefaultCredentialsSecurely(); + } + + AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Getting default credentials for ec2 instance"); + auto result = GetResourceWithAWSWebServiceResult(m_endpoint.c_str(), EC2_SECURITY_CREDENTIALS_RESOURCE, nullptr); + Aws::String credentialsString = result.GetPayload(); + auto httpResponseCode = result.GetResponseCode(); + + // Note, if service is insane, it might return 404 for our initial secure call, + // then when we fall back to insecure call, it might return 401 ask for secure call, + // Then, SDK might get into a recursive loop call situation between secure and insecure call. + if (httpResponseCode == Http::HttpResponseCode::UNAUTHORIZED) + { + m_tokenRequired = true; + return {}; + } + locker.unlock(); + + Aws::String trimmedCredentialsString = StringUtils::Trim(credentialsString.c_str()); + if (trimmedCredentialsString.empty()) return {}; + + Aws::Vector<Aws::String> securityCredentials = StringUtils::Split(trimmedCredentialsString, '\n'); + + AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource, " << EC2_SECURITY_CREDENTIALS_RESOURCE + << " returned credential string " << trimmedCredentialsString); + + if (securityCredentials.size() == 0) + { + AWS_LOGSTREAM_WARN(m_logtag.c_str(), "Initial call to ec2Metadataservice to get credentials failed"); + return {}; + } + + Aws::StringStream ss; + ss << EC2_SECURITY_CREDENTIALS_RESOURCE << "/" << securityCredentials[0]; + AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource " << ss.str()); + return GetResource(ss.str().c_str()); + } + + Aws::String EC2MetadataClient::GetDefaultCredentialsSecurely() const + { + std::unique_lock<std::recursive_mutex> locker(m_tokenMutex); + if (!m_tokenRequired) + { + return GetDefaultCredentials(); + } + + Aws::StringStream ss; + ss << m_endpoint << EC2_IMDS_TOKEN_RESOURCE; + std::shared_ptr<HttpRequest> tokenRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_PUT, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod)); + tokenRequest->SetHeaderValue(EC2_IMDS_TOKEN_TTL_HEADER, EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE); + auto userAgentString = ComputeUserAgentString(); + tokenRequest->SetUserAgent(userAgentString); + AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Calling EC2MetadataService to get token"); + auto result = GetResourceWithAWSWebServiceResult(tokenRequest); + Aws::String tokenString = result.GetPayload(); + Aws::String trimmedTokenString = StringUtils::Trim(tokenString.c_str()); + + if (result.GetResponseCode() == HttpResponseCode::BAD_REQUEST) + { + return {}; + } + else if (result.GetResponseCode() != HttpResponseCode::OK || trimmedTokenString.empty()) + { + m_tokenRequired = false; + AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Calling EC2MetadataService to get token failed, falling back to less secure way."); + return GetDefaultCredentials(); + } + m_token = trimmedTokenString; + locker.unlock(); + ss.str(""); + ss << m_endpoint << EC2_SECURITY_CREDENTIALS_RESOURCE; + std::shared_ptr<HttpRequest> profileRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod)); + profileRequest->SetHeaderValue(EC2_IMDS_TOKEN_HEADER, trimmedTokenString); + profileRequest->SetUserAgent(userAgentString); + Aws::String profileString = GetResourceWithAWSWebServiceResult(profileRequest).GetPayload(); + + Aws::String trimmedProfileString = StringUtils::Trim(profileString.c_str()); + Aws::Vector<Aws::String> securityCredentials = StringUtils::Split(trimmedProfileString, '\n'); + + AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource, " << EC2_SECURITY_CREDENTIALS_RESOURCE + << " with token returned profile string " << trimmedProfileString); + if (securityCredentials.size() == 0) + { + AWS_LOGSTREAM_WARN(m_logtag.c_str(), "Calling EC2Metadataservice to get profiles failed"); + return {}; + } + + ss.str(""); + ss << m_endpoint << EC2_SECURITY_CREDENTIALS_RESOURCE << "/" << securityCredentials[0]; + std::shared_ptr<HttpRequest> credentialsRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod)); + credentialsRequest->SetHeaderValue(EC2_IMDS_TOKEN_HEADER, trimmedTokenString); + credentialsRequest->SetUserAgent(userAgentString); + AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource " << ss.str() << " with token."); + return GetResourceWithAWSWebServiceResult(credentialsRequest).GetPayload(); + } + + Aws::String EC2MetadataClient::GetCurrentRegion() const + { + if (!m_region.empty()) + { + return m_region; + } + + AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Getting current region for ec2 instance"); + + Aws::StringStream ss; + ss << m_endpoint << EC2_REGION_RESOURCE; + std::shared_ptr<HttpRequest> regionRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod)); + { + std::lock_guard<std::recursive_mutex> locker(m_tokenMutex); + if (m_tokenRequired) + { + regionRequest->SetHeaderValue(EC2_IMDS_TOKEN_HEADER, m_token); + } + } + regionRequest->SetUserAgent(ComputeUserAgentString()); + Aws::String azString = GetResourceWithAWSWebServiceResult(regionRequest).GetPayload(); + + if (azString.empty()) + { + AWS_LOGSTREAM_INFO(m_logtag.c_str() , + "Unable to pull region from instance metadata service "); + return {}; + } + + Aws::String trimmedAZString = StringUtils::Trim(azString.c_str()); + AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource " + << EC2_REGION_RESOURCE << " , returned credential string " << trimmedAZString); + + Aws::String region; + region.reserve(trimmedAZString.length()); + + bool digitFound = false; + for (auto character : trimmedAZString) + { + if(digitFound && !isdigit(character)) + { + break; + } + if (isdigit(character)) + { + digitFound = true; + } + + region.append(1, character); + } + + AWS_LOGSTREAM_INFO(m_logtag.c_str(), "Detected current region as " << region); + m_region = region; + return region; + } + + #ifdef _MSC_VER + // VS2015 compiler's bug, warning s_ec2metadataClient: symbol will be dynamically initialized (implementation limitation) + AWS_SUPPRESS_WARNING(4592, + static std::shared_ptr<EC2MetadataClient> s_ec2metadataClient(nullptr); + ) + #else + static std::shared_ptr<EC2MetadataClient> s_ec2metadataClient(nullptr); + #endif + + void InitEC2MetadataClient() + { + if (s_ec2metadataClient) + { + return; + } + s_ec2metadataClient = Aws::MakeShared<EC2MetadataClient>(EC2_METADATA_CLIENT_LOG_TAG); + } + + void CleanupEC2MetadataClient() + { + if (!s_ec2metadataClient) + { + return; + } + s_ec2metadataClient = nullptr; + } + + std::shared_ptr<EC2MetadataClient> GetEC2MetadataClient() + { + return s_ec2metadataClient; + } + + + ECSCredentialsClient::ECSCredentialsClient(const char* resourcePath, const char* endpoint, const char* token) + : AWSHttpResourceClient(ECS_CREDENTIALS_CLIENT_LOG_TAG), + m_resourcePath(resourcePath), m_endpoint(endpoint), m_token(token) + { + } + + ECSCredentialsClient::ECSCredentialsClient(const Aws::Client::ClientConfiguration& clientConfiguration, const char* resourcePath, const char* endpoint, const char* token) + : AWSHttpResourceClient(clientConfiguration, ECS_CREDENTIALS_CLIENT_LOG_TAG), + m_resourcePath(resourcePath), m_endpoint(endpoint), m_token(token) + { + } + + static const char STS_RESOURCE_CLIENT_LOG_TAG[] = "STSResourceClient"; + STSCredentialsClient::STSCredentialsClient(const Aws::Client::ClientConfiguration& clientConfiguration) + : AWSHttpResourceClient(clientConfiguration, STS_RESOURCE_CLIENT_LOG_TAG) + { + SetErrorMarshaller(Aws::MakeUnique<Aws::Client::XmlErrorMarshaller>(STS_RESOURCE_CLIENT_LOG_TAG)); + + Aws::StringStream ss; + if (clientConfiguration.scheme == Aws::Http::Scheme::HTTP) + { + ss << "http://"; + } + else + { + ss << "https://"; + } + + static const int CN_NORTH_1_HASH = Aws::Utils::HashingUtils::HashString(Aws::Region::CN_NORTH_1); + static const int CN_NORTHWEST_1_HASH = Aws::Utils::HashingUtils::HashString(Aws::Region::CN_NORTHWEST_1); + auto hash = Aws::Utils::HashingUtils::HashString(clientConfiguration.region.c_str()); + + ss << "sts." << clientConfiguration.region << ".amazonaws.com"; + if (hash == CN_NORTH_1_HASH || hash == CN_NORTHWEST_1_HASH) + { + ss << ".cn"; + } + m_endpoint = ss.str(); + + AWS_LOGSTREAM_INFO(STS_RESOURCE_CLIENT_LOG_TAG, "Creating STS ResourceClient with endpoint: " << m_endpoint); + } + + STSCredentialsClient::STSAssumeRoleWithWebIdentityResult STSCredentialsClient::GetAssumeRoleWithWebIdentityCredentials(const STSAssumeRoleWithWebIdentityRequest& request) + { + //Calculate query string + Aws::StringStream ss; + ss << "Action=AssumeRoleWithWebIdentity" + << "&Version=2011-06-15" + << "&RoleSessionName=" << Aws::Utils::StringUtils::URLEncode(request.roleSessionName.c_str()) + << "&RoleArn=" << Aws::Utils::StringUtils::URLEncode(request.roleArn.c_str()) + << "&WebIdentityToken=" << Aws::Utils::StringUtils::URLEncode(request.webIdentityToken.c_str()); + + std::shared_ptr<HttpRequest> httpRequest(CreateHttpRequest(m_endpoint, HttpMethod::HTTP_POST, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod)); + + httpRequest->SetUserAgent(ComputeUserAgentString()); + + std::shared_ptr<Aws::IOStream> body = Aws::MakeShared<Aws::StringStream>("STS_RESOURCE_CLIENT_LOG_TAG"); + *body << ss.str(); + + httpRequest->AddContentBody(body); + body->seekg(0, body->end); + auto streamSize = body->tellg(); + body->seekg(0, body->beg); + Aws::StringStream contentLength; + contentLength << streamSize; + httpRequest->SetContentLength(contentLength.str()); + httpRequest->SetContentType("application/x-www-form-urlencoded"); + + Aws::String credentialsStr = GetResourceWithAWSWebServiceResult(httpRequest).GetPayload(); + + //Parse credentials + STSAssumeRoleWithWebIdentityResult result; + if (credentialsStr.empty()) + { + AWS_LOGSTREAM_WARN(STS_RESOURCE_CLIENT_LOG_TAG, "Get an empty credential from sts"); + return result; + } + + const Utils::Xml::XmlDocument xmlDocument = XmlDocument::CreateFromXmlString(credentialsStr); + XmlNode rootNode = xmlDocument.GetRootElement(); + XmlNode resultNode = rootNode; + if (!rootNode.IsNull() && (rootNode.GetName() != "AssumeRoleWithWebIdentityResult")) + { + resultNode = rootNode.FirstChild("AssumeRoleWithWebIdentityResult"); + } + + if (!resultNode.IsNull()) + { + XmlNode credentialsNode = resultNode.FirstChild("Credentials"); + if (!credentialsNode.IsNull()) + { + XmlNode accessKeyIdNode = credentialsNode.FirstChild("AccessKeyId"); + if (!accessKeyIdNode.IsNull()) + { + result.creds.SetAWSAccessKeyId(accessKeyIdNode.GetText()); + } + + XmlNode secretAccessKeyNode = credentialsNode.FirstChild("SecretAccessKey"); + if (!secretAccessKeyNode.IsNull()) + { + result.creds.SetAWSSecretKey(secretAccessKeyNode.GetText()); + } + + XmlNode sessionTokenNode = credentialsNode.FirstChild("SessionToken"); + if (!sessionTokenNode.IsNull()) + { + result.creds.SetSessionToken(sessionTokenNode.GetText()); + } + + XmlNode expirationNode = credentialsNode.FirstChild("Expiration"); + if (!expirationNode.IsNull()) + { + result.creds.SetExpiration(DateTime(StringUtils::Trim(expirationNode.GetText().c_str()).c_str(), DateFormat::ISO_8601)); + } + } + } + return result; + } + } +} |