diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h b/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h index ca68cf9351e..d58856d846b 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h @@ -324,6 +324,11 @@ namespace Aws */ Aws::Http::Version version = Http::Version::HTTP_VERSION_2TLS; + /** + * Disable all internal IMDSV1 Calls + */ + bool disableImdsV1 = false; + /** * A helper function to read config value from env variable or aws profile config */ diff --git a/src/aws-cpp-sdk-core/include/aws/core/internal/AWSHttpResourceClient.h b/src/aws-cpp-sdk-core/include/aws/core/internal/AWSHttpResourceClient.h index 644a836f97f..2d321e97bb9 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/internal/AWSHttpResourceClient.h +++ b/src/aws-cpp-sdk-core/include/aws/core/internal/AWSHttpResourceClient.h @@ -155,6 +155,7 @@ namespace Aws mutable Aws::String m_token; mutable bool m_tokenRequired; mutable Aws::String m_region; + bool m_disableIMDSV1 = false; }; void AWS_CORE_API InitEC2MetadataClient(); diff --git a/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp b/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp index 30e4fbabc03..7f0742ffbc5 100644 --- a/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp +++ b/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp @@ -36,6 +36,8 @@ static const char* USE_REQUEST_COMPRESSION_CONFIG_VAR = "use_request_compression static const char* REQUEST_MIN_COMPRESSION_SIZE_BYTES_ENV_VAR = "REQUEST_MIN_COMPRESSION_SIZE_BYTES"; static const char* REQUEST_MIN_COMPRESSION_SIZE_BYTES_CONFIG_VAR = "request_min_compression_size_bytes"; static const char* AWS_EXECUTION_ENV = "AWS_EXECUTION_ENV"; +static const char* DISABLE_IMDSV1_CONFIG_VAR = "AWS_EC2_METADATA_V1_DISABLED"; +static const char* DISABLE_IMDSV1_ENV_VAR = "ec2_metadata_v1_disabled"; Aws::String FilterUserAgentToken(char const * const source) { @@ -205,6 +207,18 @@ void setLegacyClientConfigurationParameters(ClientConfiguration& clientConfig) } } +void setConfigFromEnvOrProfile(ClientConfiguration &config) +{ + Aws::String disableIMDSv1 = ClientConfiguration::LoadConfigFromEnvOrProfile(DISABLE_IMDSV1_ENV_VAR, + config.profileName, + DISABLE_IMDSV1_CONFIG_VAR, + {"true", "false"}, + "false"); + if (disableIMDSv1 == "true") { + config.disableImdsV1 = true; + } +} + ClientConfiguration::ClientConfiguration() { this->disableIMDS = false; @@ -226,6 +240,7 @@ ClientConfiguration::ClientConfiguration() return; } region = Aws::String(Aws::Region::US_EAST_1); + setConfigFromEnvOrProfile(*this); } ClientConfiguration::ClientConfiguration(const ClientConfigurationInitValues &configuration) @@ -249,6 +264,7 @@ ClientConfiguration::ClientConfiguration(const ClientConfigurationInitValues &co return; } region = Aws::String(Aws::Region::US_EAST_1); + setConfigFromEnvOrProfile(*this); } ClientConfiguration::ClientConfiguration(const char* profile, bool shouldDisableIMDS) @@ -295,6 +311,7 @@ ClientConfiguration::ClientConfiguration(const char* profile, bool shouldDisable } AWS_LOGSTREAM_WARN(CLIENT_CONFIG_TAG, "User specified profile: [" << profile << "] is not found, will use the SDK resolved one."); + setConfigFromEnvOrProfile(*this); } ClientConfiguration::ClientConfiguration(bool /*useSmartDefaults*/, const char* defaultMode, bool shouldDisableIMDS) @@ -323,6 +340,7 @@ ClientConfiguration::ClientConfiguration(bool /*useSmartDefaults*/, const char* } Aws::Config::Defaults::SetSmartDefaultsConfigurationParameters(*this, defaultMode, hasEc2MetadataRegion, ec2MetadataRegion); + setConfigFromEnvOrProfile(*this); } std::shared_ptr InitRetryStrategy(Aws::String retryMode) diff --git a/src/aws-cpp-sdk-core/source/internal/AWSHttpResourceClient.cpp b/src/aws-cpp-sdk-core/source/internal/AWSHttpResourceClient.cpp index 723747bbf17..2af39e6098f 100644 --- a/src/aws-cpp-sdk-core/source/internal/AWSHttpResourceClient.cpp +++ b/src/aws-cpp-sdk-core/source/internal/AWSHttpResourceClient.cpp @@ -187,7 +187,8 @@ namespace Aws AWSHttpResourceClient(clientConfiguration, EC2_METADATA_CLIENT_LOG_TAG), m_endpoint(endpoint), m_disableIMDS(clientConfiguration.disableIMDS), - m_tokenRequired(true) + m_tokenRequired(true), + m_disableIMDSV1(clientConfiguration.disableImdsV1) { } @@ -209,6 +210,10 @@ namespace Aws AWS_LOGSTREAM_TRACE(m_logtag.c_str(), "Skipping call to IMDS Service"); return {}; } + if (m_disableIMDSV1) { + AWS_LOGSTREAM_INFO(m_logtag.c_str(), "Attempting to call IMDSv1 Service while disabled"); + return {}; + } std::unique_lock locker(m_tokenMutex); if (m_tokenRequired) { @@ -259,7 +264,7 @@ namespace Aws } std::unique_lock locker(m_tokenMutex); #if !defined(DISABLE_IMDSV1) - if (!m_tokenRequired) { + if (!m_disableIMDSV1 && !m_tokenRequired) { return GetDefaultCredentials(); } #endif @@ -280,7 +285,7 @@ namespace Aws return {}; } #if !defined(DISABLE_IMDSV1) - else if (result.GetResponseCode() != HttpResponseCode::OK || trimmedTokenString.empty()) + if (!m_disableIMDSV1 && (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."); diff --git a/tests/aws-cpp-sdk-core-tests/aws/auth/AWSHttpResourceClientTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/auth/AWSHttpResourceClientTest.cpp index 93e803d171a..bf85629d6e4 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/auth/AWSHttpResourceClientTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/auth/AWSHttpResourceClientTest.cpp @@ -813,6 +813,40 @@ namespace ASSERT_EQ("http://169.254.169.254/latest/meta-data/iam/security-credentials", mockRequests[3].GetURIString()); ASSERT_EQ("", cred); } + + TEST_F(AWSHttpResourceClientTest, TestEC2MetadataClientSkipsV1CallForConfiguration) { + clientConfig.disableImdsV1 = true; + auto ec2MetadataClient = Aws::MakeShared(ALLOCATION_TAG, clientConfig); + + // This mocked URI is used to initiate http response and has nothing to do with the requested URI actually sent out. + std::shared_ptr tokenRequest = CreateHttpRequest(URI("http://169.254.169.254/latest/api/token"), + HttpMethod::HTTP_PUT, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + std::shared_ptr tokenResponse = Aws::MakeShared(ALLOCATION_TAG, tokenRequest); + tokenResponse->SetResponseCode(HttpResponseCode::NOT_FOUND); + tokenResponse->GetResponseBody() << "error information"; // empty body will be treated as NETWORK_ERROR, hence retryable error. + mockHttpClient->AddResponseToReturn(tokenResponse); + + std::shared_ptr profileRequest = CreateHttpRequest(URI("http://169.254.169.254/latest/meta-data/iam/security-credentials"), + HttpMethod::HTTP_GET, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + std::shared_ptr profileResponse = Aws::MakeShared(ALLOCATION_TAG, profileRequest); + std::shared_ptr profileResponseRetry = Aws::MakeShared(ALLOCATION_TAG, profileRequest); + profileResponseRetry->SetResponseCode(HttpResponseCode::SERVICE_UNAVAILABLE); + profileResponseRetry->GetResponseBody() << "Throttling"; + mockHttpClient->AddResponseToReturn(profileResponseRetry); + profileResponse->SetResponseCode(HttpResponseCode::OK); + profileResponse->GetResponseBody() << "credentials"; + mockHttpClient->AddResponseToReturn(profileResponse); + + auto cred = ec2MetadataClient->GetDefaultCredentialsSecurely(); + auto mockRequests = mockHttpClient->GetAllRequestsMade(); + ASSERT_EQ(6u, mockRequests.size()); + ASSERT_EQ("http://169.254.169.254/latest/api/token", mockRequests[0].GetURIString()); + ASSERT_EQ(Aws::Http::HttpMethod::HTTP_PUT, mockRequests[0].GetMethod()); + ASSERT_EQ("21600", mockRequests[0].GetHeaderValue(EC2_IMDS_TOKEN_TTL_HEADER)); + for (size_t i = 1; i < mockRequests.size(); ++i) { + ASSERT_TRUE(mockRequests[i].HasHeader(EC2_IMDS_TOKEN_HEADER)); + } + } #endif TEST_F(AWSHttpResourceClientTest, TestEC2MetadataClientGetRegionWithNullResponse)