1/*
2 * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License").
5 * You may not use this file except in compliance with the License.
6 * A copy of the License is located at
7 *
8 * http://aws.amazon.com/apache2.0
9 *
10 * or in the "license" file accompanying this file. This file is distributed
11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 * express or implied. See the License for the specific language governing
13 * permissions and limitations under the License.
14 */
15
16#include <aws/core/internal/AWSHttpResourceClient.h>
17#include <aws/core/client/DefaultRetryStrategy.h>
18#include <aws/core/http/HttpClient.h>
19#include <aws/core/http/HttpClientFactory.h>
20#include <aws/core/http/HttpResponse.h>
21#include <aws/core/utils/logging/LogMacros.h>
22#include <aws/core/utils/StringUtils.h>
23#include <aws/core/utils/HashingUtils.h>
24#include <aws/core/platform/Environment.h>
25#include <aws/core/client/AWSError.h>
26#include <aws/core/client/CoreErrors.h>
27#include <aws/core/utils/xml/XmlSerializer.h>
28#include <mutex>
29#include <sstream>
30
31using namespace Aws;
32using namespace Aws::Utils;
33using namespace Aws::Utils::Logging;
34using namespace Aws::Utils::Xml;
35using namespace Aws::Http;
36using namespace Aws::Client;
37using namespace Aws::Internal;
38
39static const char EC2_SECURITY_CREDENTIALS_RESOURCE[] = "/latest/meta-data/iam/security-credentials";
40static const char EC2_REGION_RESOURCE[] = "/latest/meta-data/placement/availability-zone";
41static const char EC2_IMDS_TOKEN_RESOURCE[] = "/latest/api/token";
42static const char EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE[] = "21600";
43static const char EC2_IMDS_TOKEN_TTL_HEADER[] = "x-aws-ec2-metadata-token-ttl-seconds";
44static const char EC2_IMDS_TOKEN_HEADER[] = "x-aws-ec2-metadata-token";
45static const char RESOURCE_CLIENT_CONFIGURATION_ALLOCATION_TAG[] = "AWSHttpResourceClient";
46static const char EC2_METADATA_CLIENT_LOG_TAG[] = "EC2MetadataClient";
47static const char ECS_CREDENTIALS_CLIENT_LOG_TAG[] = "ECSCredentialsClient";
48
49namespace Aws
50{
51 namespace Client
52 {
53 Aws::String ComputeUserAgentString();
54 }
55}
56
57static ClientConfiguration MakeDefaultHttpResourceClientConfiguration(const char *logtag)
58{
59 ClientConfiguration res;
60
61 res.maxConnections = 2;
62 res.scheme = Scheme::HTTP;
63
64#if defined(WIN32) && defined(BYPASS_DEFAULT_PROXY)
65 // For security reasons, we must bypass any proxy settings when fetching sensitive information, for example
66 // user credentials. On Windows, IXMLHttpRequest2 does not support bypassing proxy settings, therefore,
67 // we force using WinHTTP client. On POSIX systems, CURL is set to bypass proxy settings by default.
68 res.httpLibOverride = TransferLibType::WIN_HTTP_CLIENT;
69 AWS_LOGSTREAM_INFO(logtag, "Overriding the current HTTP client to WinHTTP to bypass proxy settings.");
70#else
71 (void) logtag; // To disable warning about unused variable
72#endif
73 // Explicitly set the proxy settings to empty/zero to avoid relying on defaults that could potentially change
74 // in the future.
75 res.proxyHost = "";
76 res.proxyUserName = "";
77 res.proxyPassword = "";
78 res.proxyPort = 0;
79
80 // EC2MetadataService throttles by delaying the response so the service client should set a large read timeout.
81 // EC2MetadataService delay is in order of seconds so it only make sense to retry after a couple of seconds.
82 res.connectTimeoutMs = 1000;
83 res.requestTimeoutMs = 5000;
84 res.retryStrategy = Aws::MakeShared<DefaultRetryStrategy>(RESOURCE_CLIENT_CONFIGURATION_ALLOCATION_TAG, 4, 1000);
85
86 return res;
87}
88
89AWSHttpResourceClient::AWSHttpResourceClient(const Aws::Client::ClientConfiguration& clientConfiguration, const char* logtag)
90: m_logtag(logtag), m_retryStrategy(clientConfiguration.retryStrategy), m_httpClient(nullptr)
91{
92 AWS_LOGSTREAM_INFO(m_logtag.c_str(),
93 "Creating AWSHttpResourceClient with max connections "
94 << clientConfiguration.maxConnections
95 << " and scheme "
96 << SchemeMapper::ToString(clientConfiguration.scheme));
97
98 m_httpClient = CreateHttpClient(clientConfiguration);
99}
100
101AWSHttpResourceClient::AWSHttpResourceClient(const char* logtag)
102: AWSHttpResourceClient(MakeDefaultHttpResourceClientConfiguration(logtag), logtag)
103{
104}
105
106AWSHttpResourceClient::~AWSHttpResourceClient()
107{
108}
109
110Aws::String AWSHttpResourceClient::GetResource(const char* endpoint, const char* resource, const char* authToken) const
111{
112 return GetResourceWithAWSWebServiceResult(endpoint, resource, authToken).GetPayload();
113}
114
115AmazonWebServiceResult<Aws::String> AWSHttpResourceClient::GetResourceWithAWSWebServiceResult(const char *endpoint, const char *resource, const char *authToken) const
116{
117 Aws::StringStream ss;
118 ss << endpoint << resource;
119 std::shared_ptr<HttpRequest> request(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET,
120 Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
121
122 request->SetUserAgent(ComputeUserAgentString());
123
124 if (authToken)
125 {
126 request->SetHeaderValue(Aws::Http::AWS_AUTHORIZATION_HEADER, authToken);
127 }
128
129 return GetResourceWithAWSWebServiceResult(request);
130}
131
132AmazonWebServiceResult<Aws::String> AWSHttpResourceClient::GetResourceWithAWSWebServiceResult(const std::shared_ptr<HttpRequest> &httpRequest) const
133{
134 AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Retrieving credentials from " << httpRequest->GetURIString());
135
136 for (long retries = 0;; retries++)
137 {
138 std::shared_ptr<HttpResponse> response(m_httpClient->MakeRequest(httpRequest));
139
140 if (response && response->GetResponseCode() == HttpResponseCode::OK)
141 {
142 Aws::IStreamBufIterator eos;
143 return {Aws::String(Aws::IStreamBufIterator(response->GetResponseBody()), eos), response ? response->GetHeaders() : HeaderValueCollection(), HttpResponseCode::OK};
144 }
145
146 const Aws::Client::AWSError<Aws::Client::CoreErrors> error = [this, &response]() {
147 if (!response || response->GetResponseBody().tellp() < 1)
148 {
149 AWS_LOGSTREAM_ERROR(m_logtag.c_str(), "Http request to retrieve credentials failed");
150 return AWSError<CoreErrors>(CoreErrors::NETWORK_CONNECTION, true); // Retryable
151 }
152 else if (m_errorMarshaller)
153 {
154 return m_errorMarshaller->Marshall(*response);
155 }
156 else
157 {
158 const auto responseCode = response->GetResponseCode();
159
160 AWS_LOGSTREAM_ERROR(m_logtag.c_str(), "Http request to retrieve credentials failed with error code "
161 << static_cast<int>(responseCode));
162 return CoreErrorsMapper::GetErrorForHttpResponseCode(responseCode);
163 }
164 }();
165
166 if (!m_retryStrategy->ShouldRetry(error, retries))
167 {
168 AWS_LOGSTREAM_ERROR(m_logtag.c_str(), "Can not retrive resource from " << httpRequest->GetURIString());
169 return {{}, response ? response->GetHeaders() : HeaderValueCollection(), error.GetResponseCode()};
170 }
171 auto sleepMillis = m_retryStrategy->CalculateDelayBeforeNextRetry(error, retries);
172 AWS_LOGSTREAM_WARN(m_logtag.c_str(), "Request failed, now waiting " << sleepMillis << " ms before attempting again.");
173 m_httpClient->RetryRequestSleep(std::chrono::milliseconds(sleepMillis));
174 }
175}
176
177EC2MetadataClient::EC2MetadataClient(const char* endpoint)
178 : AWSHttpResourceClient(EC2_METADATA_CLIENT_LOG_TAG), m_endpoint(endpoint), m_tokenRequired(true)
179{
180}
181
182EC2MetadataClient::EC2MetadataClient(const Aws::Client::ClientConfiguration &clientConfiguration, const char *endpoint)
183 : AWSHttpResourceClient(clientConfiguration, EC2_METADATA_CLIENT_LOG_TAG), m_endpoint(endpoint), m_tokenRequired(true)
184{
185}
186
187EC2MetadataClient::~EC2MetadataClient()
188{
189
190}
191
192Aws::String EC2MetadataClient::GetResource(const char* resourcePath) const
193{
194 return GetResource(m_endpoint.c_str(), resourcePath, nullptr/*authToken*/);
195}
196
197Aws::String EC2MetadataClient::GetDefaultCredentials() const
198{
199 std::unique_lock<std::recursive_mutex> locker(m_tokenMutex);
200 if (m_tokenRequired)
201 {
202 return GetDefaultCredentialsSecurely();
203 }
204
205 AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Getting default credentials for ec2 instance");
206 auto result = GetResourceWithAWSWebServiceResult(m_endpoint.c_str(), EC2_SECURITY_CREDENTIALS_RESOURCE, nullptr);
207 Aws::String credentialsString = result.GetPayload();
208 auto httpResponseCode = result.GetResponseCode();
209
210 // Note, if service is insane, it might return 404 for our initial secure call,
211 // then when we fall back to insecure call, it might return 401 ask for secure call,
212 // Then, SDK might get into a recursive loop call situation between secure and insecure call.
213 if (httpResponseCode == Http::HttpResponseCode::UNAUTHORIZED)
214 {
215 m_tokenRequired = true;
216 return {};
217 }
218 locker.unlock();
219
220 Aws::String trimmedCredentialsString = StringUtils::Trim(credentialsString.c_str());
221 if (trimmedCredentialsString.empty()) return {};
222
223 Aws::Vector<Aws::String> securityCredentials = StringUtils::Split(trimmedCredentialsString, '\n');
224
225 AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource, " << EC2_SECURITY_CREDENTIALS_RESOURCE
226 << " returned credential string " << trimmedCredentialsString);
227
228 if (securityCredentials.size() == 0)
229 {
230 AWS_LOGSTREAM_WARN(m_logtag.c_str(), "Initial call to ec2Metadataservice to get credentials failed");
231 return {};
232 }
233
234 Aws::StringStream ss;
235 ss << EC2_SECURITY_CREDENTIALS_RESOURCE << "/" << securityCredentials[0];
236 AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource " << ss.str());
237 return GetResource(ss.str().c_str());
238}
239
240Aws::String EC2MetadataClient::GetDefaultCredentialsSecurely() const
241{
242 std::unique_lock<std::recursive_mutex> locker(m_tokenMutex);
243 if (!m_tokenRequired)
244 {
245 return GetDefaultCredentials();
246 }
247
248 Aws::StringStream ss;
249 ss << m_endpoint << EC2_IMDS_TOKEN_RESOURCE;
250 std::shared_ptr<HttpRequest> tokenRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_PUT,
251 Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
252 tokenRequest->SetHeaderValue(EC2_IMDS_TOKEN_TTL_HEADER, EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE);
253 auto userAgentString = ComputeUserAgentString();
254 tokenRequest->SetUserAgent(userAgentString);
255 AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Calling EC2MetadataService to get token");
256 auto result = GetResourceWithAWSWebServiceResult(tokenRequest);
257 Aws::String tokenString = result.GetPayload();
258 Aws::String trimmedTokenString = StringUtils::Trim(tokenString.c_str());
259
260 if (result.GetResponseCode() == HttpResponseCode::BAD_REQUEST)
261 {
262 return {};
263 }
264 else if (result.GetResponseCode() != HttpResponseCode::OK || trimmedTokenString.empty())
265 {
266 m_tokenRequired = false;
267 AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Calling EC2MetadataService to get token failed, falling back to less secure way.");
268 return GetDefaultCredentials();
269 }
270 m_token = trimmedTokenString;
271 locker.unlock();
272 ss.str("");
273 ss << m_endpoint << EC2_SECURITY_CREDENTIALS_RESOURCE;
274 std::shared_ptr<HttpRequest> profileRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET,
275 Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
276 profileRequest->SetHeaderValue(EC2_IMDS_TOKEN_HEADER, trimmedTokenString);
277 profileRequest->SetUserAgent(userAgentString);
278 Aws::String profileString = GetResourceWithAWSWebServiceResult(profileRequest).GetPayload();
279
280 Aws::String trimmedProfileString = StringUtils::Trim(profileString.c_str());
281 Aws::Vector<Aws::String> securityCredentials = StringUtils::Split(trimmedProfileString, '\n');
282
283 AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource, " << EC2_SECURITY_CREDENTIALS_RESOURCE
284 << " with token returned profile string " << trimmedProfileString);
285 if (securityCredentials.size() == 0)
286 {
287 AWS_LOGSTREAM_WARN(m_logtag.c_str(), "Calling EC2Metadataservice to get profiles failed");
288 return {};
289 }
290
291 ss.str("");
292 ss << m_endpoint << EC2_SECURITY_CREDENTIALS_RESOURCE << "/" << securityCredentials[0];
293 std::shared_ptr<HttpRequest> credentialsRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET,
294 Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
295 credentialsRequest->SetHeaderValue(EC2_IMDS_TOKEN_HEADER, trimmedTokenString);
296 credentialsRequest->SetUserAgent(userAgentString);
297 AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource " << ss.str() << " with token.");
298 return GetResourceWithAWSWebServiceResult(credentialsRequest).GetPayload();
299}
300
301Aws::String EC2MetadataClient::GetCurrentRegion() const
302{
303 AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Getting current region for ec2 instance");
304
305 Aws::StringStream ss;
306 ss << m_endpoint << EC2_REGION_RESOURCE;
307 std::shared_ptr<HttpRequest> regionRequest(CreateHttpRequest(ss.str(), HttpMethod::HTTP_GET,
308 Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));
309 {
310 std::lock_guard<std::recursive_mutex> locker(m_tokenMutex);
311 if (m_tokenRequired)
312 {
313 regionRequest->SetHeaderValue(EC2_IMDS_TOKEN_HEADER, m_token);
314 }
315 }
316 regionRequest->SetUserAgent(ComputeUserAgentString());
317 Aws::String azString = GetResourceWithAWSWebServiceResult(regionRequest).GetPayload();
318
319 if (azString.empty())
320 {
321 AWS_LOGSTREAM_INFO(m_logtag.c_str() ,
322 "Unable to pull region from instance metadata service ");
323 return {};
324 }
325
326 Aws::String trimmedAZString = StringUtils::Trim(azString.c_str());
327 AWS_LOGSTREAM_DEBUG(m_logtag.c_str(), "Calling EC2MetadataService resource "
328 << EC2_REGION_RESOURCE << " , returned credential string " << trimmedAZString);
329
330 Aws::String region;
331 region.reserve(trimmedAZString.length());
332
333 bool digitFound = false;
334 for (auto character : trimmedAZString)
335 {
336 if(digitFound && !isdigit(character))
337 {
338 break;
339 }
340 if (isdigit(character))
341 {
342 digitFound = true;
343 }
344
345 region.append(1, character);
346 }
347
348 AWS_LOGSTREAM_INFO(m_logtag.c_str(), "Detected current region as " << region);
349 return region;
350}
351
352ECSCredentialsClient::ECSCredentialsClient(const char* resourcePath, const char* endpoint, const char* token)
353 : AWSHttpResourceClient(ECS_CREDENTIALS_CLIENT_LOG_TAG),
354 m_resourcePath(resourcePath), m_endpoint(endpoint), m_token(token)
355{
356}
357
358ECSCredentialsClient::ECSCredentialsClient(const Aws::Client::ClientConfiguration& clientConfiguration, const char* resourcePath, const char* endpoint, const char* token)
359 : AWSHttpResourceClient(clientConfiguration, ECS_CREDENTIALS_CLIENT_LOG_TAG),
360 m_resourcePath(resourcePath), m_endpoint(endpoint), m_token(token)
361{
362}
363
364static const char STS_RESOURCE_CLIENT_LOG_TAG[] = "STSResourceClient";
365STSCredentialsClient::STSCredentialsClient(const Aws::Client::ClientConfiguration& clientConfiguration)
366 : AWSHttpResourceClient(clientConfiguration, STS_RESOURCE_CLIENT_LOG_TAG)
367{
368 SetErrorMarshaller(Aws::MakeUnique<Aws::Client::XmlErrorMarshaller>(STS_RESOURCE_CLIENT_LOG_TAG));
369
370 Aws::StringStream ss;
371 if (clientConfiguration.scheme == Aws::Http::Scheme::HTTP)
372 {
373 ss << "http://";
374 }
375 else
376 {
377 ss << "https://";
378 }
379
380 static const int CN_NORTH_1_HASH = Aws::Utils::HashingUtils::HashString(Aws::Region::CN_NORTH_1);
381 static const int CN_NORTHWEST_1_HASH = Aws::Utils::HashingUtils::HashString(Aws::Region::CN_NORTHWEST_1);
382 auto hash = Aws::Utils::HashingUtils::HashString(clientConfiguration.region.c_str());
383
384 ss << "sts." << clientConfiguration.region << ".amazonaws.com";
385 if (hash == CN_NORTH_1_HASH || hash == CN_NORTHWEST_1_HASH)
386 {
387 ss << ".cn";
388 }
389 m_endpoint = ss.str();
390
391 AWS_LOGSTREAM_INFO(STS_RESOURCE_CLIENT_LOG_TAG, "Creating STS ResourceClient with endpoint: " << m_endpoint);
392}
393
394STSCredentialsClient::STSAssumeRoleWithWebIdentityResult STSCredentialsClient::GetAssumeRoleWithWebIdentityCredentials(const STSAssumeRoleWithWebIdentityRequest& request)
395{
396 //Calculate query string
397 Aws::StringStream ss;
398 ss << "/?Action=AssumeRoleWithWebIdentity"
399 << "&Version=2011-06-15"
400 << "&RoleSessionName=" << Aws::Utils::StringUtils::URLEncode(request.roleSessionName.c_str())
401 << "&RoleArn=" << Aws::Utils::StringUtils::URLEncode(request.roleArn.c_str())
402 << "&WebIdentityToken=" << Aws::Utils::StringUtils::URLEncode(request.webIdentityToken.c_str());
403
404 Aws::String credentialsStr = GetResource(m_endpoint.c_str(), ss.str().c_str()/*query string*/, nullptr/*no auth token needed*/);
405
406 //Parse credentials
407 STSAssumeRoleWithWebIdentityResult result;
408 if (credentialsStr.empty())
409 {
410 AWS_LOGSTREAM_WARN(STS_RESOURCE_CLIENT_LOG_TAG, "Get an empty credential from sts");
411 return result;
412 }
413
414 const Utils::Xml::XmlDocument xmlDocument = XmlDocument::CreateFromXmlString(credentialsStr);
415 XmlNode rootNode = xmlDocument.GetRootElement();
416 XmlNode resultNode = rootNode;
417 if (!rootNode.IsNull() && (rootNode.GetName() != "AssumeRoleWithWebIdentityResult"))
418 {
419 resultNode = rootNode.FirstChild("AssumeRoleWithWebIdentityResult");
420 }
421
422 if (!resultNode.IsNull())
423 {
424 XmlNode credentialsNode = resultNode.FirstChild("Credentials");
425 if (!credentialsNode.IsNull())
426 {
427 XmlNode accessKeyIdNode = credentialsNode.FirstChild("AccessKeyId");
428 if (!accessKeyIdNode.IsNull())
429 {
430 result.creds.SetAWSAccessKeyId(accessKeyIdNode.GetText());
431 }
432
433 XmlNode secretAccessKeyNode = credentialsNode.FirstChild("SecretAccessKey");
434 if (!secretAccessKeyNode.IsNull())
435 {
436 result.creds.SetAWSSecretKey(secretAccessKeyNode.GetText());
437 }
438
439 XmlNode sessionTokenNode = credentialsNode.FirstChild("SessionToken");
440 if (!sessionTokenNode.IsNull())
441 {
442 result.creds.SetSessionToken(sessionTokenNode.GetText());
443 }
444
445 XmlNode expirationNode = credentialsNode.FirstChild("Expiration");
446 if (!expirationNode.IsNull())
447 {
448 result.creds.SetExpiration(DateTime(StringUtils::Trim(expirationNode.GetText().c_str()).c_str(), DateFormat::ISO_8601));
449 }
450 }
451 }
452 return result;
453}
454