diff --git a/tiledb/CMakeLists.txt b/tiledb/CMakeLists.txt index 49d4e203cf2..d018eccfdfc 100644 --- a/tiledb/CMakeLists.txt +++ b/tiledb/CMakeLists.txt @@ -192,6 +192,9 @@ set(TILEDB_CORE_SOURCES ${TILEDB_CORE_INCLUDE_DIR}/tiledb/sm/filesystem/posix.cc ${TILEDB_CORE_INCLUDE_DIR}/tiledb/sm/filesystem/s3.cc ${TILEDB_CORE_INCLUDE_DIR}/tiledb/sm/filesystem/s3_thread_pool_executor.cc + ${TILEDB_CORE_INCLUDE_DIR}/tiledb/sm/filesystem/s3/AWSCredentialsProviderChain.cc + ${TILEDB_CORE_INCLUDE_DIR}/tiledb/sm/filesystem/s3/GeneralHTTPCredentialsProvider.cc + ${TILEDB_CORE_INCLUDE_DIR}/tiledb/sm/filesystem/s3/STSCredentialsProvider.cc ${TILEDB_CORE_INCLUDE_DIR}/tiledb/sm/filesystem/s3/STSProfileWithWebIdentityCredentialsProvider.cc ${TILEDB_CORE_INCLUDE_DIR}/tiledb/sm/filesystem/ssl_config.cc ${TILEDB_CORE_INCLUDE_DIR}/tiledb/sm/filesystem/uri.cc diff --git a/tiledb/sm/filesystem/s3.cc b/tiledb/sm/filesystem/s3.cc index c6af58a93ec..e36fbcbe515 100644 --- a/tiledb/sm/filesystem/s3.cc +++ b/tiledb/sm/filesystem/s3.cc @@ -44,6 +44,7 @@ #include "tiledb/common/common.h" #include "tiledb/common/filesystem/directory_entry.h" +#include #include #include #include @@ -73,6 +74,7 @@ #endif #include "tiledb/sm/filesystem/s3.h" +#include "tiledb/sm/filesystem/s3/AWSCredentialsProviderChain.h" #include "tiledb/sm/filesystem/s3/STSProfileWithWebIdentityCredentialsProvider.h" #include "tiledb/sm/misc/parallel_functions.h" @@ -1388,6 +1390,24 @@ Status S3::init_client() const { s3_params_.connect_max_tries_, s3_params_.connect_scale_factor_); + // Use a copy of the config with a different retry strategy for the + // credentials providers. + auto auth_config = + make_shared(HERE(), client_config); + + auth_config->retryStrategy = + make_shared( + HERE(), + // Retry some errors that are retried by the providers' default retry + // strategies. + Aws::Vector{ + // STSAssumeRoleWebIdentityCredentialsProvider + "IDPCommunicationError", + "InvalidToken", + // SSOCredentialsProvider + "TooManyRequestsException"}, + s3_params_.connect_max_tries_); + // If the user says not to sign a request, use the // AnonymousAWSCredentialsProvider This is equivalent to --no-sign-request on // the aws cli @@ -1401,6 +1421,9 @@ Status S3::init_client() const { (s3_config_source == "config_files" ? 8 : 0) + (s3_config_source == "sts_profile_with_web_identity" ? 16 : 0)) { case 0: + credentials_provider_ = make_shared< + tiledb::sm::filesystem::s3::DefaultAWSCredentialsProviderChain>( + HERE(), auth_config); break; case 1: case 2: @@ -1443,7 +1466,7 @@ Status S3::init_client() const { session_name, external_id, load_frequency, - make_shared(HERE(), client_config)); + make_shared(HERE(), *auth_config)); break; } case 7: { @@ -1465,9 +1488,13 @@ Status S3::init_client() const { HERE(), Aws::Auth::GetConfigProfileName(), std::chrono::minutes(60), - [client_config](const auto& credentials) { + [config = auth_config](const auto& credentials) { return make_shared( - HERE(), credentials, client_config); + HERE(), + credentials, + // Create default endpoint provider. + make_shared(HERE()), + *config); }); break; } @@ -1491,22 +1518,14 @@ Status S3::init_client() const { static std::mutex static_client_init_mtx; { std::lock_guard static_lck(static_client_init_mtx); - if (credentials_provider_ == nullptr) { - client_ = make_shared( - HERE(), - s3_params_, - *client_config_, - Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, - s3_params_.use_virtual_addressing_); - } else { - client_ = make_shared( - HERE(), - s3_params_, - credentials_provider_, - *client_config_, - Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, - s3_params_.use_virtual_addressing_); - } + assert(credentials_provider_); + client_ = make_shared( + HERE(), + s3_params_, + credentials_provider_, + client_config, + Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, + s3_params_.use_virtual_addressing_); } return Status::Ok(); diff --git a/tiledb/sm/filesystem/s3.h b/tiledb/sm/filesystem/s3.h index deb37c66249..8de2a0070ef 100644 --- a/tiledb/sm/filesystem/s3.h +++ b/tiledb/sm/filesystem/s3.h @@ -55,7 +55,6 @@ #undef GetObject #include -#include #include #include #include @@ -352,15 +351,6 @@ struct S3Parameters { */ class TileDBS3Client : public Aws::S3::S3Client { public: - TileDBS3Client( - const S3Parameters& s3_params, - const Aws::Client::ClientConfiguration& client_config, - Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy sign_payloads, - bool use_virtual_addressing) - : Aws::S3::S3Client(client_config, sign_payloads, use_virtual_addressing) - , params_(s3_params) { - } - TileDBS3Client( const S3Parameters& s3_params, const std::shared_ptr& creds, diff --git a/tiledb/sm/filesystem/s3/AWSCredentialsProviderChain.cc b/tiledb/sm/filesystem/s3/AWSCredentialsProviderChain.cc new file mode 100644 index 00000000000..4c3cc598ec9 --- /dev/null +++ b/tiledb/sm/filesystem/s3/AWSCredentialsProviderChain.cc @@ -0,0 +1,159 @@ +/** + * @file AWSCredentialsProviderChain.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @section DESCRIPTION + * + * This file defines a vendored copy of the + * Aws::Auth::DefaultAWSCredentialsProviderChain class, updated to support + * getting a client configuration object. + * + * Changes made should be contributed upstream to the AWS SDK for C++ and when + * that happens, the vendored copy should be removed. + */ + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#ifdef HAVE_S3 + +#include "tiledb/sm/filesystem/s3/AWSCredentialsProviderChain.h" + +#include "tiledb/common/common.h" +#include "tiledb/sm/filesystem/s3/GeneralHTTPCredentialsProvider.h" +#include "tiledb/sm/filesystem/s3/STSCredentialsProvider.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace Aws::Auth; +using namespace Aws::Utils::Threading; + +namespace tiledb::sm::filesystem::s3 { +static const char AWS_EC2_METADATA_DISABLED[] = "AWS_EC2_METADATA_DISABLED"; +static const char DefaultCredentialsProviderChainTag[] = + "DefaultAWSCredentialsProviderChain"; + +DefaultAWSCredentialsProviderChain::DefaultAWSCredentialsProviderChain( + std::shared_ptr clientConfig) + : AWSCredentialsProviderChain() { + AddProvider(make_shared(HERE())); + AddProvider(make_shared(HERE())); + AddProvider(make_shared(HERE())); + // The vendored credentials providers in tiledb::sm::filesystem::s3 will be + // picked over the built-in ones. + AddProvider(make_shared( + HERE(), + clientConfig ? *clientConfig : Aws::Client::ClientConfiguration())); + // SSOCredentialsProvider is a complex provider and patching it to support + // ClientConfiguration would require vendoring several files. Since support is + // going to be added upstream soon with + // https://github.com/aws/aws-sdk-cpp/pull/2860, let's not update it for now. + AddProvider(make_shared(HERE())); + + // General HTTP Credentials (prev. known as ECS TaskRole credentials) only + // available when ENVIRONMENT VARIABLE is set + const auto relativeUri = Aws::Environment::GetEnv( + GeneralHTTPCredentialsProvider::AWS_CONTAINER_CREDENTIALS_RELATIVE_URI); + AWS_LOGSTREAM_DEBUG( + DefaultCredentialsProviderChainTag, + "The environment variable value " + << GeneralHTTPCredentialsProvider:: + AWS_CONTAINER_CREDENTIALS_RELATIVE_URI + << " is " << relativeUri); + + const auto absoluteUri = Aws::Environment::GetEnv( + GeneralHTTPCredentialsProvider::AWS_CONTAINER_CREDENTIALS_FULL_URI); + AWS_LOGSTREAM_DEBUG( + DefaultCredentialsProviderChainTag, + "The environment variable value " + << GeneralHTTPCredentialsProvider::AWS_CONTAINER_CREDENTIALS_FULL_URI + << " is " << absoluteUri); + + const auto ec2MetadataDisabled = + Aws::Environment::GetEnv(AWS_EC2_METADATA_DISABLED); + AWS_LOGSTREAM_DEBUG( + DefaultCredentialsProviderChainTag, + "The environment variable value " << AWS_EC2_METADATA_DISABLED << " is " + << ec2MetadataDisabled); + + if (!relativeUri.empty() || !absoluteUri.empty()) { + const Aws::String token = Aws::Environment::GetEnv( + GeneralHTTPCredentialsProvider::AWS_CONTAINER_AUTHORIZATION_TOKEN); + + auto genProvider = + clientConfig ? + make_shared( + HERE(), *clientConfig, relativeUri, absoluteUri, token) : + make_shared( + HERE(), relativeUri, absoluteUri, token); + if (genProvider && genProvider->IsValid()) { + AddProvider(std::move(genProvider)); + auto& uri = !relativeUri.empty() ? relativeUri : absoluteUri; + AWS_LOGSTREAM_INFO( + DefaultCredentialsProviderChainTag, + "Added General HTTP / ECS credentials provider with ur: [" + << uri << "] to the provider chain with a" + << (token.empty() ? "n empty " : " non-empty ") + << "authorization token."); + } else { + AWS_LOGSTREAM_ERROR( + DefaultCredentialsProviderChainTag, + "Unable to create GeneralHTTPCredentialsProvider"); + } + } else if ( + Aws::Utils::StringUtils::ToLower(ec2MetadataDisabled.c_str()) != "true") { + auto ec2MetadataClient = clientConfig ? + make_shared( + HERE(), *clientConfig) : + nullptr; + AddProvider(make_shared( + HERE(), + make_shared( + std::move(ec2MetadataClient)))); + AWS_LOGSTREAM_INFO( + DefaultCredentialsProviderChainTag, + "Added EC2 metadata service credentials provider to the provider " + "chain."); + } +} + +DefaultAWSCredentialsProviderChain::DefaultAWSCredentialsProviderChain( + const DefaultAWSCredentialsProviderChain& chain) { + for (const auto& provider : chain.GetProviders()) { + AddProvider(provider); + } +} +} // namespace tiledb::sm::filesystem::s3 + +#endif // HAVE_S3 diff --git a/tiledb/sm/filesystem/s3/AWSCredentialsProviderChain.h b/tiledb/sm/filesystem/s3/AWSCredentialsProviderChain.h new file mode 100644 index 00000000000..63f42047451 --- /dev/null +++ b/tiledb/sm/filesystem/s3/AWSCredentialsProviderChain.h @@ -0,0 +1,76 @@ +/** + * @file AWSCredentialsProviderChain.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @section DESCRIPTION + * + * This file defines a vendored copy of the + * Aws::Auth::DefaultAWSCredentialsProviderChain class, updated to support + * getting a client configuration object. + * + * Changes made should be contributed upstream to the AWS SDK for C++ and when + * that happens, the vendored copy should be removed. + */ + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#pragma once + +#include +#include +#include +#include + +namespace tiledb::sm::filesystem::s3 { +/** + * Creates an AWSCredentialsProviderChain which uses in order + * EnvironmentAWSCredentialsProvider, ProfileConfigFileAWSCredentialsProvider, + * ProcessCredentialsProvider, STSAssumeRoleWebIdentityCredentialsProvider and + * SSOCredentialsProvider. + */ +class DefaultAWSCredentialsProviderChain + : public Aws::Auth::AWSCredentialsProviderChain { + public: + /** + * Initializes the provider chain with EnvironmentAWSCredentialsProvider, + * ProfileConfigFileAWSCredentialsProvider, ProcessCredentialsProvider, + * STSAssumeRoleWebIdentityCredentialsProvider and SSOCredentialsProvider in + * that order. + * + * @param clientConfig Optional client configuration to use. + */ + DefaultAWSCredentialsProviderChain( + std::shared_ptr clientConfig = + nullptr); + + DefaultAWSCredentialsProviderChain( + const DefaultAWSCredentialsProviderChain& chain); +}; + +} // namespace tiledb::sm::filesystem::s3 diff --git a/tiledb/sm/filesystem/s3/GeneralHTTPCredentialsProvider.cc b/tiledb/sm/filesystem/s3/GeneralHTTPCredentialsProvider.cc new file mode 100644 index 00000000000..cbbabb10db5 --- /dev/null +++ b/tiledb/sm/filesystem/s3/GeneralHTTPCredentialsProvider.cc @@ -0,0 +1,352 @@ +/** + * @file GeneralHTTPCredentialsProvider.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @section DESCRIPTION + * + * This file defines a vendored copy of the + * Aws::Auth::GeneralHTTPCredentialsProvider class, updated to support + * getting a client configuration object. + * + * Changes made should be contributed upstream to the AWS SDK for C++ and when + * that happens, the vendored copy should be removed. + */ + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#ifdef HAVE_S3 + +#include "tiledb/sm/filesystem/s3/GeneralHTTPCredentialsProvider.h" +#include "tiledb/common/common.h" + +#include +#include +#include +#include +#include + +using namespace Aws::Auth; + +namespace tiledb::sm::filesystem::s3 { +static const int AWS_CREDENTIAL_PROVIDER_EXPIRATION_GRACE_PERIOD = 5 * 1000; +static const char GEN_HTTP_LOG_TAG[] = "GeneralHTTPCredentialsProvider"; + +const char + GeneralHTTPCredentialsProvider::AWS_CONTAINER_CREDENTIALS_RELATIVE_URI[] = + "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"; +const char + GeneralHTTPCredentialsProvider::AWS_CONTAINER_CREDENTIALS_FULL_URI[] = + "AWS_CONTAINER_CREDENTIALS_FULL_URI"; +const char GeneralHTTPCredentialsProvider::AWS_CONTAINER_AUTHORIZATION_TOKEN[] = + "AWS_CONTAINER_AUTHORIZATION_TOKEN"; + +const char GeneralHTTPCredentialsProvider::AWS_ECS_CONTAINER_HOST[] = + "169.254.170.2"; +const char GeneralHTTPCredentialsProvider::AWS_EKS_CONTAINER_HOST[] = + "169.254.170.23"; +const char GeneralHTTPCredentialsProvider::AWS_EKS_CONTAINER_HOST_IPV6[] = + "fd00:ec2::23"; + +bool IsAllowedIp(const Aws::String& authority) { + // address is an ECS / EKS container host + if (authority == + GeneralHTTPCredentialsProvider::AWS_ECS_CONTAINER_HOST) // ECS container + // host + { + return true; + } else if ( + authority == GeneralHTTPCredentialsProvider::AWS_EKS_CONTAINER_HOST || + authority == GeneralHTTPCredentialsProvider:: + AWS_EKS_CONTAINER_HOST_IPV6) { // EKS container host + return true; + } + + // address is within the loop back + // IPv4 + if (authority.rfind(Aws::String("127.0.0."), 0) == 0 && + authority.size() > 8 && authority.size() <= 11) { + Aws::String octet = authority.substr(8); + if ((octet.find_first_not_of("0123456789") != Aws::String::npos) || + (Aws::Utils::StringUtils::ConvertToInt32(octet.c_str()) > 255)) { + AWS_LOGSTREAM_WARN( + GEN_HTTP_LOG_TAG, + "Can't use General HTTP Provider: AWS_CONTAINER_CREDENTIALS_FULL_URI " + "ip address is malformed: " + << authority); + return false; + } else { + return true; + } + } + + // IPv6 + if (authority == "::1" || authority == "0:0:0:0:0:0:0:1" || + authority == "[::1]" || authority == "[0:0:0:0:0:0:0:1]") { + return true; + } + + return false; +} + +bool GeneralHTTPCredentialsProvider::ShouldCreateGeneralHTTPProvider( + const Aws::String& relativeUri, + const Aws::String& absoluteUri, + const Aws::String authToken) { + if (authToken.find("\r\n") != Aws::String::npos) { + AWS_LOGSTREAM_WARN( + GEN_HTTP_LOG_TAG, + "Can't use General HTTP Provider: AWS_CONTAINER_AUTHORIZATION_TOKEN " + "env value contains invalid characters (\\r\\n)"); + return false; + } + + if (!relativeUri.empty()) { + // The provider MAY choose to assert syntactical validity of the resulting + // URI perform very basic check here + if (relativeUri[0] != '/') { + AWS_LOGSTREAM_WARN( + GEN_HTTP_LOG_TAG, + "Can't use General HTTP Provider: " + "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI does not begin with /"); + return false; + } else { + // full URI is not used in case of a relative one present + return true; + } + } + + if (!absoluteUri.empty()) { + // If the resolved URI’s scheme is HTTPS, its hostname may be used in the + // request + if (Aws::Utils::StringUtils::ToLower(absoluteUri.c_str()) + .rfind(Aws::String("https://"), 0) == 0) // if starts_with + { + return true; + } + + Aws::Http::URI absUri(absoluteUri); + const Aws::String& authority = absUri.GetAuthority(); + + // Otherwise, implementations MUST fail to resolve when the URI hostname + // does not satisfy any of the following conditions + if (IsAllowedIp(authority)) { + return true; + } + + Aws::Crt::Io::HostResolver* pHostResolver = + Aws::Crt::ApiHandle::GetOrCreateStaticDefaultHostResolver(); + if (pHostResolver) { + bool shouldAllow = false; + bool hostResolved = false; + std::mutex hostResolverMutex; + std::condition_variable hostResolverCV; + auto onHostResolved = + [&shouldAllow, &hostResolved, &hostResolverCV, &hostResolverMutex]( + Aws::Crt::Io::HostResolver& resolver, + const Aws::Crt::Vector& addresses, + int errorCode) { + AWS_UNREFERENCED_PARAM(resolver); + if (AWS_ERROR_SUCCESS == errorCode) { + for (const auto& address : addresses) { + if (!IsAllowedIp(Aws::String( + (const char*)address.address->bytes, + address.address->len))) { + return; + } + } + std::unique_lock lock(hostResolverMutex); + shouldAllow = !addresses.empty(); + hostResolved = true; + hostResolverCV.notify_one(); + } else { + std::unique_lock lock(hostResolverMutex); + hostResolverCV.notify_one(); + } + }; + pHostResolver->ResolveHost(authority.c_str(), onHostResolved); + std::unique_lock lock(hostResolverMutex); + if (!hostResolved) { + hostResolverCV.wait_for(lock, std::chrono::milliseconds(1000)); + } + if (shouldAllow) { + return true; + } + } + + AWS_LOGSTREAM_WARN( + GEN_HTTP_LOG_TAG, + "Can't use General HTTP Provider: AWS_CONTAINER_CREDENTIALS_FULL_URI " + "is not HTTPS and is not within loop back CIDR: " + << authority); + return false; + } + + // both relativeUri and absoluteUri are empty + return false; +} + +GeneralHTTPCredentialsProvider::GeneralHTTPCredentialsProvider( + const Aws::Client::ClientConfiguration& clientConfig, + const Aws::String& relativeUri, + const Aws::String& absoluteUri, + const Aws::String& authToken, + long refreshRateMs, + ShouldCreateFunc shouldCreateFunc) + : m_loadFrequencyMs(refreshRateMs) { + if (shouldCreateFunc(relativeUri, absoluteUri, authToken)) { + AWS_LOGSTREAM_INFO( + GEN_HTTP_LOG_TAG, + "Creating GeneralHTTPCredentialsProvider with refresh rate " + << refreshRateMs); + if (!relativeUri.empty()) { + m_ecsCredentialsClient = make_shared( + HERE(), + clientConfig, + relativeUri.c_str(), + AWS_ECS_CONTAINER_HOST, + authToken.c_str()); + } else if (!absoluteUri.empty()) { + m_ecsCredentialsClient = make_shared( + HERE(), clientConfig, "", absoluteUri.c_str(), authToken.c_str()); + } + } +} + +GeneralHTTPCredentialsProvider::GeneralHTTPCredentialsProvider( + const Aws::String& relativeUri, + const Aws::String& absoluteUri, + const Aws::String& authToken, + long refreshRateMs, + ShouldCreateFunc shouldCreateFunc) + : m_loadFrequencyMs(refreshRateMs) { + if (shouldCreateFunc(relativeUri, absoluteUri, authToken)) { + AWS_LOGSTREAM_INFO( + GEN_HTTP_LOG_TAG, + "Creating GeneralHTTPCredentialsProvider with refresh rate " + << refreshRateMs); + if (!relativeUri.empty()) { + m_ecsCredentialsClient = make_shared( + HERE(), + relativeUri.c_str(), + AWS_ECS_CONTAINER_HOST, + authToken.c_str()); + } else if (!absoluteUri.empty()) { + m_ecsCredentialsClient = make_shared( + HERE(), "", absoluteUri.c_str(), authToken.c_str()); + } + } +} + +GeneralHTTPCredentialsProvider::GeneralHTTPCredentialsProvider( + const std::shared_ptr& client, + long refreshRateMs) + : m_ecsCredentialsClient(client) + , m_loadFrequencyMs(refreshRateMs) { + AWS_LOGSTREAM_INFO( + GEN_HTTP_LOG_TAG, + "Creating GeneralHTTPCredentialsProvider with a pre-allocated client " + << refreshRateMs); +} + +AWSCredentials GeneralHTTPCredentialsProvider::GetAWSCredentials() { + RefreshIfExpired(); + Aws::Utils::Threading::ReaderLockGuard guard(m_reloadLock); + return m_credentials; +} + +bool GeneralHTTPCredentialsProvider::ExpiresSoon() const { + return ( + (m_credentials.GetExpiration() - Aws::Utils::DateTime::Now()).count() < + AWS_CREDENTIAL_PROVIDER_EXPIRATION_GRACE_PERIOD); +} + +void GeneralHTTPCredentialsProvider::Reload() { + AWS_LOGSTREAM_INFO( + GEN_HTTP_LOG_TAG, + "Credentials have expired or will expire, attempting to re-pull from ECS " + "IAM Service."); + if (!m_ecsCredentialsClient) { + AWS_LOGSTREAM_ERROR( + GEN_HTTP_LOG_TAG, + "Unable to retrieve credentials: ECS Credentials client is not " + "initialized."); + return; + } + + auto credentialsStr = m_ecsCredentialsClient->GetECSCredentials(); + if (credentialsStr.empty()) + return; + + Aws::Utils::Json::JsonValue credentialsDoc(credentialsStr); + if (!credentialsDoc.WasParseSuccessful()) { + AWS_LOGSTREAM_ERROR( + GEN_HTTP_LOG_TAG, "Failed to parse output from ECSCredentialService."); + return; + } + + Aws::String accessKey, secretKey, token; + Aws::Utils::Json::JsonView credentialsView(credentialsDoc); + accessKey = credentialsView.GetString("AccessKeyId"); + secretKey = credentialsView.GetString("SecretAccessKey"); + token = credentialsView.GetString("Token"); + AWS_LOGSTREAM_DEBUG( + GEN_HTTP_LOG_TAG, + "Successfully pulled credentials from metadata service with access key " + << accessKey); + + m_credentials.SetAWSAccessKeyId(accessKey); + m_credentials.SetAWSSecretKey(secretKey); + m_credentials.SetSessionToken(token); + m_credentials.SetExpiration(Aws::Utils::DateTime( + credentialsView.GetString("Expiration"), + Aws::Utils::DateFormat::ISO_8601)); + AWSCredentialsProvider::Reload(); +} + +void GeneralHTTPCredentialsProvider::RefreshIfExpired() { + AWS_LOGSTREAM_DEBUG( + GEN_HTTP_LOG_TAG, "Checking if latest credential pull has expired."); + Aws::Utils::Threading::ReaderLockGuard guard(m_reloadLock); + if (!m_credentials.IsEmpty() && !IsTimeToRefresh(m_loadFrequencyMs) && + !ExpiresSoon()) { + return; + } + + guard.UpgradeToWriterLock(); + + if (!m_credentials.IsEmpty() && !IsTimeToRefresh(m_loadFrequencyMs) && + !ExpiresSoon()) { + return; + } + + Reload(); +} +} // namespace tiledb::sm::filesystem::s3 + +#endif // HAVE_S3 diff --git a/tiledb/sm/filesystem/s3/GeneralHTTPCredentialsProvider.h b/tiledb/sm/filesystem/s3/GeneralHTTPCredentialsProvider.h new file mode 100644 index 00000000000..38145594825 --- /dev/null +++ b/tiledb/sm/filesystem/s3/GeneralHTTPCredentialsProvider.h @@ -0,0 +1,190 @@ +/** + * @file GeneralHTTPCredentialsProvider.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @section DESCRIPTION + * + * This file defines a vendored copy of the + * Aws::Auth::GeneralHTTPCredentialsProvider class, updated to support + * getting a client configuration object. + * + * Changes made should be contributed upstream to the AWS SDK for C++ and when + * that happens, the vendored copy should be removed. + */ + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#pragma once + +#include + +namespace tiledb::sm::filesystem::s3 { +/** + * General HTTP Credentials Provider (previously known as ECS credentials + * provider) implementation that loads credentials from an arbitrary HTTP(S) + * endpoint specified by the environment or loop back / Amazon ECS / Amazon EKS + * container host metadata services by default. + */ +class GeneralHTTPCredentialsProvider + : public Aws::Auth::AWSCredentialsProvider { + private: + static bool ShouldCreateGeneralHTTPProvider( + const Aws::String& relativeUri, + const Aws::String& absoluteUri, + const Aws::String authToken); + + public: + using ShouldCreateFunc = std::function; + + /** + * Initializes the provider to retrieve credentials from a general http + * provided endpoint every 5 minutes or before it expires. + * @param clientConfig The client configuration to use when performing + * requests. + * @param relativeUri A path appended to the metadata service endpoint. OR + * @param absoluteUri The full URI to resolve to get credentials. + * @param authToken An optional authorization token passed to the URI via the + * 'Authorization' HTTP header. + * @param refreshRateMs The number of milliseconds after which the credentials + * will be fetched again. + * @param ShouldCreateFunc + */ + GeneralHTTPCredentialsProvider( + const Aws::Client::ClientConfiguration& clientConfig, + const Aws::String& relativeUri, + const Aws::String& absoluteUri, + const Aws::String& authToken = "", + long refreshRateMs = Aws::Auth::REFRESH_THRESHOLD, + ShouldCreateFunc shouldCreateFunc = ShouldCreateGeneralHTTPProvider); + + /** + * Initializes the provider to retrieve credentials from a general http + * provided endpoint every 5 minutes or before it expires. + * @param relativeUri A path appended to the metadata service endpoint. OR + * @param absoluteUri The full URI to resolve to get credentials. + * @param authToken An optional authorization token passed to the URI via the + * 'Authorization' HTTP header. + * @param refreshRateMs The number of milliseconds after which the credentials + * will be fetched again. + * @param ShouldCreateFunc + */ + GeneralHTTPCredentialsProvider( + const Aws::String& relativeUri, + const Aws::String& absoluteUri, + const Aws::String& authToken = "", + long refreshRateMs = Aws::Auth::REFRESH_THRESHOLD, + ShouldCreateFunc shouldCreateFunc = ShouldCreateGeneralHTTPProvider); + + /** + * Check if GeneralHTTPCredentialsProvider was initialized with allowed + * configuration + * @return true if provider configuration is valid + */ + bool IsValid() const { + if (!m_ecsCredentialsClient) + return false; + return true; + } + + /** + * Initializes the provider to retrieve credentials from the ECS metadata + * service every 5 minutes, or before it expires. + * @param resourcePath A path appended to the metadata service endpoint. + * @param refreshRateMs The number of milliseconds after which the credentials + * will be fetched again. + */ + // TODO: 1.12: AWS_DEPRECATED("This c-tor is deprecated, please use one + // above.") + GeneralHTTPCredentialsProvider( + const char* resourcePath, + long refreshRateMs = Aws::Auth::REFRESH_THRESHOLD) + : GeneralHTTPCredentialsProvider(resourcePath, "", "", refreshRateMs) { + } + + /** + * Initializes the provider to retrieve credentials from a provided endpoint + * every 5 minutes or before it expires. + * @param endpoint The full URI to resolve to get credentials. + * @param token An optional authorization token passed to the URI via the + * 'Authorization' HTTP header. + * @param refreshRateMs The number of milliseconds after which the credentials + * will be fetched again. + */ + // TODO: 1.12: AWS_DEPRECATED("This c-tor is deprecated, please use one + // above.") + GeneralHTTPCredentialsProvider( + const char* endpoint, + const char* token, + long refreshRateMs = Aws::Auth::REFRESH_THRESHOLD) + : GeneralHTTPCredentialsProvider("", endpoint, token, refreshRateMs) { + } + + /** + * Initializes the provider to retrieve credentials using the provided client. + * @param client The ECSCredentialsClient instance to use when retrieving + * credentials. + * @param refreshRateMs The number of milliseconds after which the credentials + * will be fetched again. + */ + GeneralHTTPCredentialsProvider( + const std::shared_ptr& client, + long refreshRateMs = Aws::Auth::REFRESH_THRESHOLD); + /** + * Retrieves the credentials if found, otherwise returns empty credential set. + */ + Aws::Auth::AWSCredentials GetAWSCredentials() override; + + static const char AWS_CONTAINER_CREDENTIALS_RELATIVE_URI[]; + static const char AWS_CONTAINER_CREDENTIALS_FULL_URI[]; + static const char AWS_CONTAINER_AUTHORIZATION_TOKEN[]; + + static const char AWS_ECS_CONTAINER_HOST[]; + static const char AWS_EKS_CONTAINER_HOST[]; + static const char AWS_EKS_CONTAINER_HOST_IPV6[]; + + protected: + void Reload() override; + + private: + bool ExpiresSoon() const; + void RefreshIfExpired(); + + std::shared_ptr m_ecsCredentialsClient; + + long m_loadFrequencyMs = Aws::Auth::REFRESH_THRESHOLD; + Aws::Auth::AWSCredentials m_credentials; +}; + +// GeneralHTTPCredentialsProvider was previously known as +// TaskRoleCredentialsProvider or "ECS credentials provider" +using TaskRoleCredentialsProvider = GeneralHTTPCredentialsProvider; +} // namespace tiledb::sm::filesystem::s3 diff --git a/tiledb/sm/filesystem/s3/STSCredentialsProvider.cc b/tiledb/sm/filesystem/s3/STSCredentialsProvider.cc new file mode 100644 index 00000000000..87d9fb0ec09 --- /dev/null +++ b/tiledb/sm/filesystem/s3/STSCredentialsProvider.cc @@ -0,0 +1,217 @@ +/** + * @file STSCredentialsProvider.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @section DESCRIPTION + * + * This file defines a vendored copy of the + * Aws::Auth::STSAssumeRoleWebIdentityCredentialsProvider class, updated to + * support getting a client configuration object. + * + * Changes made should be contributed upstream to the AWS SDK for C++ and when + * that happens, the vendored copy should be removed. + */ + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#ifdef HAVE_S3 + +#include "tiledb/sm/filesystem/s3/STSCredentialsProvider.h" +#include "tiledb/common/common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Aws::Utils; +using namespace Aws::Utils::Logging; +using namespace Aws::Auth; +using namespace Aws::Internal; +using namespace Aws::FileSystem; +using namespace Aws::Client; +using Aws::Utils::Threading::ReaderLockGuard; +using Aws::Utils::Threading::WriterLockGuard; + +static const char STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG[] = + "STSAssumeRoleWithWebIdentityCredentialsProvider"; +static const int STS_CREDENTIAL_PROVIDER_EXPIRATION_GRACE_PERIOD = 5 * 1000; + +namespace tiledb::sm::filesystem::s3 { +STSAssumeRoleWebIdentityCredentialsProvider:: + STSAssumeRoleWebIdentityCredentialsProvider( + Aws::Client::ClientConfiguration config) + : m_initialized(false) { + // check environment variables + m_roleArn = Aws::Environment::GetEnv("AWS_ROLE_ARN"); + m_tokenFile = Aws::Environment::GetEnv("AWS_WEB_IDENTITY_TOKEN_FILE"); + m_sessionName = Aws::Environment::GetEnv("AWS_ROLE_SESSION_NAME"); + + // check profile_config if either m_roleArn or m_tokenFile is not loaded from + // environment variable + if (m_roleArn.empty() || m_tokenFile.empty()) { + auto profile = + Aws::Config::GetCachedConfigProfile(Aws::Auth::GetConfigProfileName()); + // If either of these two were not found from environment, use whatever + // found for all three in config file + if (m_roleArn.empty() || m_tokenFile.empty()) { + m_roleArn = profile.GetRoleArn(); + m_tokenFile = profile.GetValue("web_identity_token_file"); + m_sessionName = profile.GetValue("role_session_name"); + } + } + + if (m_tokenFile.empty()) { + AWS_LOGSTREAM_WARN( + STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, + "Token file must be specified to use STS AssumeRole web identity creds " + "provider."); + return; // No need to do further constructing + } else { + AWS_LOGSTREAM_DEBUG( + STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, + "Resolved token_file from profile_config or environment variable to be " + << m_tokenFile); + } + + if (m_roleArn.empty()) { + AWS_LOGSTREAM_WARN( + STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, + "RoleArn must be specified to use STS AssumeRole web identity creds " + "provider."); + return; // No need to do further constructing + } else { + AWS_LOGSTREAM_DEBUG( + STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, + "Resolved role_arn from profile_config or environment variable to be " + << m_roleArn); + } + + if (m_sessionName.empty()) { + m_sessionName = Aws::Utils::UUID::PseudoRandomUUID(); + } else { + AWS_LOGSTREAM_DEBUG( + STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, + "Resolved session_name from profile_config or environment variable to " + "be " + << m_sessionName); + } + + config.scheme = Aws::Http::Scheme::HTTPS; + + if (!config.retryStrategy) { + Aws::Vector retryableErrors; + retryableErrors.push_back("IDPCommunicationError"); + retryableErrors.push_back("InvalidIdentityToken"); + + config.retryStrategy = make_shared( + HERE(), retryableErrors, 3 /*maxRetries*/); + } + + m_client = tdb_unique_ptr( + tdb_new(Aws::Internal::STSCredentialsClient, config)); + m_initialized = true; + AWS_LOGSTREAM_INFO( + STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, + "Creating STS AssumeRole with web identity creds provider."); +} + +AWSCredentials +STSAssumeRoleWebIdentityCredentialsProvider::GetAWSCredentials() { + // A valid client means required information like role arn and token file were + // constructed correctly. We can use this provider to load creds, otherwise, + // we can just return empty creds. + if (!m_initialized) { + return Aws::Auth::AWSCredentials(); + } + RefreshIfExpired(); + ReaderLockGuard guard(m_reloadLock); + return m_credentials; +} + +void STSAssumeRoleWebIdentityCredentialsProvider::Reload() { + AWS_LOGSTREAM_INFO( + STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, + "Credentials have expired, attempting to renew from STS."); + + Aws::IFStream tokenFile(m_tokenFile.c_str()); + if (tokenFile) { + Aws::String token( + (std::istreambuf_iterator(tokenFile)), + std::istreambuf_iterator()); + m_token = token; + } else { + AWS_LOGSTREAM_ERROR( + STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, + "Can't open token file: " << m_tokenFile); + return; + } + STSCredentialsClient::STSAssumeRoleWithWebIdentityRequest request{ + m_sessionName, m_roleArn, m_token}; + + auto result = m_client->GetAssumeRoleWithWebIdentityCredentials(request); + AWS_LOGSTREAM_TRACE( + STS_ASSUME_ROLE_WEB_IDENTITY_LOG_TAG, + "Successfully retrieved credentials with AWS_ACCESS_KEY: " + << result.creds.GetAWSAccessKeyId()); + m_credentials = result.creds; +} + +bool STSAssumeRoleWebIdentityCredentialsProvider::ExpiresSoon() const { + return ( + (m_credentials.GetExpiration() - Aws::Utils::DateTime::Now()).count() < + STS_CREDENTIAL_PROVIDER_EXPIRATION_GRACE_PERIOD); +} + +void STSAssumeRoleWebIdentityCredentialsProvider::RefreshIfExpired() { + ReaderLockGuard guard(m_reloadLock); + if (!m_credentials.IsEmpty() && !ExpiresSoon()) { + return; + } + + guard.UpgradeToWriterLock(); + if (!m_credentials.IsExpiredOrEmpty() && + !ExpiresSoon()) // double-checked lock to avoid refreshing twice + { + return; + } + + Reload(); +} +} // namespace tiledb::sm::filesystem::s3 + +#endif diff --git a/tiledb/sm/filesystem/s3/STSCredentialsProvider.h b/tiledb/sm/filesystem/s3/STSCredentialsProvider.h new file mode 100644 index 00000000000..7e606168e7c --- /dev/null +++ b/tiledb/sm/filesystem/s3/STSCredentialsProvider.h @@ -0,0 +1,86 @@ +/** + * @file STSCredentialsProvider.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @section DESCRIPTION + * + * This file defines a vendored copy of the + * Aws::Auth::STSAssumeRoleWebIdentityCredentialsProvider class, updated to + * support getting a client configuration object. + * + * Changes made should be contributed upstream to the AWS SDK for C++ and when + * that happens, the vendored copy should be removed. + */ + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "tiledb/common/common.h" + +namespace tiledb::sm::filesystem::s3 { +/** + * To support retrieving credentials of STS AssumeRole with web identity. + * Note that STS accepts request with protocol of queryxml. Calling + * GetAWSCredentials() will trigger (if expired) a query request using + * AWSHttpResourceClient under the hood. + */ +class STSAssumeRoleWebIdentityCredentialsProvider + : public Aws::Auth::AWSCredentialsProvider { + public: + STSAssumeRoleWebIdentityCredentialsProvider( + Aws::Client::ClientConfiguration clientConfig = + Aws::Client::ClientConfiguration()); + + /** + * Retrieves the credentials if found, otherwise returns empty credential set. + */ + Aws::Auth::AWSCredentials GetAWSCredentials() override; + + protected: + void Reload() override; + + private: + void RefreshIfExpired(); + + tdb_unique_ptr m_client; + Aws::Auth::AWSCredentials m_credentials; + Aws::String m_roleArn; + Aws::String m_tokenFile; + Aws::String m_sessionName; + Aws::String m_token; + bool m_initialized; + bool ExpiresSoon() const; +}; +} // namespace tiledb::sm::filesystem::s3 diff --git a/tiledb/sm/filesystem/s3/STSProfileWithWebIdentityCredentialsProvider.h b/tiledb/sm/filesystem/s3/STSProfileWithWebIdentityCredentialsProvider.h index 8af825209a2..4eebb5df17f 100644 --- a/tiledb/sm/filesystem/s3/STSProfileWithWebIdentityCredentialsProvider.h +++ b/tiledb/sm/filesystem/s3/STSProfileWithWebIdentityCredentialsProvider.h @@ -1,5 +1,5 @@ /** - * @file STSProfileWithWebIdentityCredentialsProvider.g + * @file STSProfileWithWebIdentityCredentialsProvider.h * * @section LICENSE *