/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include <aws/core/config/AWSProfileConfigLoader.h> #include <aws/core/internal/AWSHttpResourceClient.h> #include <aws/core/auth/AWSCredentialsProvider.h> #include <aws/core/utils/memory/stl/AWSList.h> #include <aws/core/utils/memory/stl/AWSStreamFwd.h> #include <aws/core/utils/StringUtils.h> #include <aws/core/utils/logging/LogMacros.h> #include <aws/core/utils/json/JsonSerializer.h> #include <fstream> namespace Aws { namespace Config { using namespace Aws::Utils; using namespace Aws::Auth; static const char* const CONFIG_LOADER_TAG = "Aws::Config::AWSProfileConfigLoader"; #ifdef _MSC_VER // VS2015 compiler's bug, warning s_CoreErrorsMapper: symbol will be dynamically initialized (implementation limitation) AWS_SUPPRESS_WARNING(4592, static Aws::UniquePtr<ConfigAndCredentialsCacheManager> s_configManager(nullptr); ) #else static Aws::UniquePtr<ConfigAndCredentialsCacheManager> s_configManager(nullptr); #endif static const char CONFIG_CREDENTIALS_CACHE_MANAGER_TAG[] = "ConfigAndCredentialsCacheManager"; bool AWSProfileConfigLoader::Load() { if(LoadInternal()) { AWS_LOGSTREAM_INFO(CONFIG_LOADER_TAG, "Successfully reloaded configuration."); m_lastLoadTime = DateTime::Now(); AWS_LOGSTREAM_TRACE(CONFIG_LOADER_TAG, "reloaded config at " << m_lastLoadTime.ToGmtString(DateFormat::ISO_8601)); return true; } AWS_LOGSTREAM_INFO(CONFIG_LOADER_TAG, "Failed to reload configuration."); return false; } bool AWSProfileConfigLoader::PersistProfiles(const Aws::Map<Aws::String, Profile>& profiles) { if(PersistInternal(profiles)) { AWS_LOGSTREAM_INFO(CONFIG_LOADER_TAG, "Successfully persisted configuration."); m_profiles = profiles; m_lastLoadTime = DateTime::Now(); AWS_LOGSTREAM_TRACE(CONFIG_LOADER_TAG, "persisted config at " << m_lastLoadTime.ToGmtString(DateFormat::ISO_8601)); return true; } AWS_LOGSTREAM_WARN(CONFIG_LOADER_TAG, "Failed to persist configuration."); return false; } static const char REGION_KEY[] = "region"; static const char ACCESS_KEY_ID_KEY[] = "aws_access_key_id"; static const char SECRET_KEY_KEY[] = "aws_secret_access_key"; static const char SESSION_TOKEN_KEY[] = "aws_session_token"; static const char ROLE_ARN_KEY[] = "role_arn"; static const char EXTERNAL_ID_KEY[] = "external_id"; static const char CREDENTIAL_PROCESS_COMMAND[] = "credential_process"; static const char SOURCE_PROFILE_KEY[] = "source_profile"; static const char PROFILE_PREFIX[] = "profile "; static const char EQ = '='; static const char LEFT_BRACKET = '['; static const char RIGHT_BRACKET = ']'; static const char PARSER_TAG[] = "Aws::Config::ConfigFileProfileFSM"; class ConfigFileProfileFSM { public: ConfigFileProfileFSM() : m_parserState(START) {} const Aws::Map<String, Profile>& GetProfiles() const { return m_foundProfiles; } void ParseStream(Aws::IStream& stream) { static const size_t ASSUME_EMPTY_LEN = 3; Aws::String line; while(std::getline(stream, line) && m_parserState != FAILURE) { if (line.empty() || line.length() < ASSUME_EMPTY_LEN) { continue; } auto openPos = line.find(LEFT_BRACKET); auto closePos = line.find(RIGHT_BRACKET); switch(m_parserState) { case START: if(openPos != std::string::npos && closePos != std::string::npos) { FlushProfileAndReset(line, openPos, closePos); m_parserState = PROFILE_FOUND; } break; //fallthrough here is intentional to reduce duplicate logic case PROFILE_KEY_VALUE_FOUND: if(openPos != std::string::npos && closePos != std::string::npos) { m_parserState = PROFILE_FOUND; FlushProfileAndReset(line, openPos, closePos); break; } // fall through case PROFILE_FOUND: { auto equalsPos = line.find(EQ); if (equalsPos != std::string::npos) { auto key = line.substr(0, equalsPos); auto value = line.substr(equalsPos + 1); m_profileKeyValuePairs[StringUtils::Trim(key.c_str())] = StringUtils::Trim(value.c_str()); m_parserState = PROFILE_KEY_VALUE_FOUND; } break; } default: m_parserState = FAILURE; break; } } FlushProfileAndReset(line, std::string::npos, std::string::npos); } private: void FlushProfileAndReset(Aws::String& line, size_t openPos, size_t closePos) { if(!m_currentWorkingProfile.empty() && !m_profileKeyValuePairs.empty()) { Profile profile; profile.SetName(m_currentWorkingProfile); auto regionIter = m_profileKeyValuePairs.find(REGION_KEY); if (regionIter != m_profileKeyValuePairs.end()) { AWS_LOGSTREAM_DEBUG(PARSER_TAG, "found region " << regionIter->second); profile.SetRegion(regionIter->second); } auto accessKeyIdIter = m_profileKeyValuePairs.find(ACCESS_KEY_ID_KEY); Aws::String accessKey, secretKey, sessionToken; if (accessKeyIdIter != m_profileKeyValuePairs.end()) { accessKey = accessKeyIdIter->second; AWS_LOGSTREAM_DEBUG(PARSER_TAG, "found access key " << accessKey); auto secretAccessKeyIter = m_profileKeyValuePairs.find(SECRET_KEY_KEY); auto sessionTokenIter = m_profileKeyValuePairs.find(SESSION_TOKEN_KEY); if (secretAccessKeyIter != m_profileKeyValuePairs.end()) { secretKey = secretAccessKeyIter->second; } else { AWS_LOGSTREAM_ERROR(PARSER_TAG, "No secret access key found even though an access key was specified. This will cause all signed AWS calls to fail."); } if (sessionTokenIter != m_profileKeyValuePairs.end()) { sessionToken = sessionTokenIter->second; } profile.SetCredentials(Aws::Auth::AWSCredentials(accessKey, secretKey, sessionToken)); } auto assumeRoleArnIter = m_profileKeyValuePairs.find(ROLE_ARN_KEY); if (assumeRoleArnIter != m_profileKeyValuePairs.end()) { AWS_LOGSTREAM_DEBUG(PARSER_TAG, "found role arn " << assumeRoleArnIter->second); profile.SetRoleArn(assumeRoleArnIter->second); } auto externalIdIter = m_profileKeyValuePairs.find(EXTERNAL_ID_KEY); if (externalIdIter != m_profileKeyValuePairs.end()) { AWS_LOGSTREAM_DEBUG(PARSER_TAG, "found external id " << externalIdIter->second); profile.SetExternalId(externalIdIter->second); } auto sourceProfileIter = m_profileKeyValuePairs.find(SOURCE_PROFILE_KEY); if (sourceProfileIter != m_profileKeyValuePairs.end()) { AWS_LOGSTREAM_DEBUG(PARSER_TAG, "found source profile " << sourceProfileIter->second); profile.SetSourceProfile(sourceProfileIter->second); } auto credentialProcessIter = m_profileKeyValuePairs.find(CREDENTIAL_PROCESS_COMMAND); if (credentialProcessIter != m_profileKeyValuePairs.end()) { AWS_LOGSTREAM_DEBUG(PARSER_TAG, "found credential process " << credentialProcessIter->second); profile.SetCredentialProcess(credentialProcessIter->second); } profile.SetAllKeyValPairs(m_profileKeyValuePairs); m_foundProfiles[profile.GetName()] = std::move(profile); m_currentWorkingProfile.clear(); m_profileKeyValuePairs.clear(); } if(!line.empty() && openPos != std::string::npos && closePos != std::string::npos) { m_currentWorkingProfile = StringUtils::Trim(line.substr(openPos + 1, closePos - openPos - 1).c_str()); StringUtils::Replace(m_currentWorkingProfile, PROFILE_PREFIX, ""); AWS_LOGSTREAM_DEBUG(PARSER_TAG, "found profile " << m_currentWorkingProfile); } } enum State { START = 0, PROFILE_FOUND, PROFILE_KEY_VALUE_FOUND, FAILURE }; Aws::String m_currentWorkingProfile; Aws::Map<String, String> m_profileKeyValuePairs; State m_parserState; Aws::Map<String, Profile> m_foundProfiles; }; static const char* const CONFIG_FILE_LOADER = "Aws::Config::AWSConfigFileProfileConfigLoader"; AWSConfigFileProfileConfigLoader::AWSConfigFileProfileConfigLoader(const Aws::String& fileName, bool useProfilePrefix) : m_fileName(fileName), m_useProfilePrefix(useProfilePrefix) { AWS_LOGSTREAM_INFO(CONFIG_FILE_LOADER, "Initializing config loader against fileName " << fileName << " and using profilePrefix = " << useProfilePrefix); } bool AWSConfigFileProfileConfigLoader::LoadInternal() { m_profiles.clear(); Aws::IFStream inputFile(m_fileName.c_str()); if(inputFile) { ConfigFileProfileFSM parser; parser.ParseStream(inputFile); m_profiles = parser.GetProfiles(); return m_profiles.size() > 0; } AWS_LOGSTREAM_INFO(CONFIG_FILE_LOADER, "Unable to open config file " << m_fileName << " for reading."); return false; } bool AWSConfigFileProfileConfigLoader::PersistInternal(const Aws::Map<Aws::String, Profile>& profiles) { Aws::OFStream outputFile(m_fileName.c_str(), std::ios_base::out | std::ios_base::trunc); if(outputFile) { for(auto& profile : profiles) { Aws::String prefix = m_useProfilePrefix ? PROFILE_PREFIX : ""; AWS_LOGSTREAM_DEBUG(CONFIG_FILE_LOADER, "Writing profile " << profile.first << " to disk."); outputFile << LEFT_BRACKET << prefix << profile.second.GetName() << RIGHT_BRACKET << std::endl; const Aws::Auth::AWSCredentials& credentials = profile.second.GetCredentials(); outputFile << ACCESS_KEY_ID_KEY << EQ << credentials.GetAWSAccessKeyId() << std::endl; outputFile << SECRET_KEY_KEY << EQ << credentials.GetAWSSecretKey() << std::endl; if(!credentials.GetSessionToken().empty()) { outputFile << SESSION_TOKEN_KEY << EQ << credentials.GetSessionToken() << std::endl; } if(!profile.second.GetRegion().empty()) { outputFile << REGION_KEY << EQ << profile.second.GetRegion() << std::endl; } if(!profile.second.GetRoleArn().empty()) { outputFile << ROLE_ARN_KEY << EQ << profile.second.GetRoleArn() << std::endl; } if(!profile.second.GetSourceProfile().empty()) { outputFile << SOURCE_PROFILE_KEY << EQ << profile.second.GetSourceProfile() << std::endl; } outputFile << std::endl; } AWS_LOGSTREAM_INFO(CONFIG_FILE_LOADER, "Profiles written to config file " << m_fileName); return true; } AWS_LOGSTREAM_WARN(CONFIG_FILE_LOADER, "Unable to open config file " << m_fileName << " for writing."); return false; } static const char* const EC2_INSTANCE_PROFILE_LOG_TAG = "Aws::Config::EC2InstanceProfileConfigLoader"; EC2InstanceProfileConfigLoader::EC2InstanceProfileConfigLoader(const std::shared_ptr<Aws::Internal::EC2MetadataClient>& client) : m_ec2metadataClient(client == nullptr ? Aws::MakeShared<Aws::Internal::EC2MetadataClient>(EC2_INSTANCE_PROFILE_LOG_TAG) : client) { } bool EC2InstanceProfileConfigLoader::LoadInternal() { auto credentialsStr = m_ec2metadataClient->GetDefaultCredentialsSecurely(); if(credentialsStr.empty()) return false; Json::JsonValue credentialsDoc(credentialsStr); if (!credentialsDoc.WasParseSuccessful()) { AWS_LOGSTREAM_ERROR(EC2_INSTANCE_PROFILE_LOG_TAG, "Failed to parse output from EC2MetadataService."); return false; } const char* accessKeyId = "AccessKeyId"; const char* secretAccessKey = "SecretAccessKey"; Aws::String accessKey, secretKey, token; auto credentialsView = credentialsDoc.View(); accessKey = credentialsView.GetString(accessKeyId); AWS_LOGSTREAM_INFO(EC2_INSTANCE_PROFILE_LOG_TAG, "Successfully pulled credentials from metadata service with access key " << accessKey); secretKey = credentialsView.GetString(secretAccessKey); token = credentialsView.GetString("Token"); auto region = m_ec2metadataClient->GetCurrentRegion(); Profile profile; profile.SetCredentials(AWSCredentials(accessKey, secretKey, token)); profile.SetRegion(region); profile.SetName(INSTANCE_PROFILE_KEY); m_profiles[INSTANCE_PROFILE_KEY] = profile; return true; } ConfigAndCredentialsCacheManager::ConfigAndCredentialsCacheManager() : m_credentialsFileLoader(Aws::Auth::ProfileConfigFileAWSCredentialsProvider::GetCredentialsProfileFilename()), m_configFileLoader(Aws::Auth::GetConfigProfileFilename(), true/*use profile prefix*/) { ReloadCredentialsFile(); ReloadConfigFile(); } void ConfigAndCredentialsCacheManager::ReloadConfigFile() { Aws::Utils::Threading::WriterLockGuard guard(m_configLock); m_configFileLoader.SetFileName(Aws::Auth::GetConfigProfileFilename()); m_configFileLoader.Load(); } void ConfigAndCredentialsCacheManager::ReloadCredentialsFile() { Aws::Utils::Threading::WriterLockGuard guard(m_credentialsLock); m_credentialsFileLoader.SetFileName(Aws::Auth::ProfileConfigFileAWSCredentialsProvider::GetCredentialsProfileFilename()); m_credentialsFileLoader.Load(); } bool ConfigAndCredentialsCacheManager::HasConfigProfile(const Aws::String& profileName) const { Aws::Utils::Threading::ReaderLockGuard guard(m_configLock); return (m_configFileLoader.GetProfiles().count(profileName) == 1); } Aws::Config::Profile ConfigAndCredentialsCacheManager::GetConfigProfile(const Aws::String& profileName) const { Aws::Utils::Threading::ReaderLockGuard guard(m_configLock); const auto& profiles = m_configFileLoader.GetProfiles(); const auto &iter = profiles.find(profileName); if (iter == profiles.end()) { return {}; } return iter->second; } Aws::Map<Aws::String, Aws::Config::Profile> ConfigAndCredentialsCacheManager::GetConfigProfiles() const { Aws::Utils::Threading::ReaderLockGuard guard(m_configLock); return m_configFileLoader.GetProfiles(); } Aws::String ConfigAndCredentialsCacheManager::GetConfig(const Aws::String& profileName, const Aws::String& key) const { Aws::Utils::Threading::ReaderLockGuard guard(m_configLock); const auto& profiles = m_configFileLoader.GetProfiles(); const auto &iter = profiles.find(profileName); if (iter == profiles.end()) { return {}; } return iter->second.GetValue(key); } bool ConfigAndCredentialsCacheManager::HasCredentialsProfile(const Aws::String& profileName) const { Aws::Utils::Threading::ReaderLockGuard guard(m_credentialsLock); return (m_credentialsFileLoader.GetProfiles().count(profileName) == 1); } Aws::Config::Profile ConfigAndCredentialsCacheManager::GetCredentialsProfile(const Aws::String& profileName) const { Aws::Utils::Threading::ReaderLockGuard guard(m_credentialsLock); const auto &profiles = m_credentialsFileLoader.GetProfiles(); const auto &iter = profiles.find(profileName); if (iter == profiles.end()) { return {}; } return iter->second; } Aws::Map<Aws::String, Aws::Config::Profile> ConfigAndCredentialsCacheManager::GetCredentialsProfiles() const { Aws::Utils::Threading::ReaderLockGuard guard(m_credentialsLock); return m_credentialsFileLoader.GetProfiles(); } Aws::Auth::AWSCredentials ConfigAndCredentialsCacheManager::GetCredentials(const Aws::String& profileName) const { Aws::Utils::Threading::ReaderLockGuard guard(m_credentialsLock); const auto& profiles = m_credentialsFileLoader.GetProfiles(); const auto &iter = profiles.find(profileName); if (iter == profiles.end()) { return {}; } return iter->second.GetCredentials(); } void InitConfigAndCredentialsCacheManager() { if (s_configManager) { return; } s_configManager = Aws::MakeUnique<ConfigAndCredentialsCacheManager>(CONFIG_CREDENTIALS_CACHE_MANAGER_TAG); } void CleanupConfigAndCredentialsCacheManager() { if (!s_configManager) { return; } s_configManager = nullptr; } void ReloadCachedConfigFile() { assert(s_configManager); s_configManager->ReloadConfigFile(); } void ReloadCachedCredentialsFile() { assert(s_configManager); s_configManager->ReloadCredentialsFile(); } bool HasCachedConfigProfile(const Aws::String& profileName) { assert(s_configManager); return s_configManager->HasConfigProfile(profileName); } Aws::Config::Profile GetCachedConfigProfile(const Aws::String& profileName) { assert(s_configManager); return s_configManager->GetConfigProfile(profileName); } Aws::Map<Aws::String, Aws::Config::Profile> GetCachedConfigProfiles() { assert(s_configManager); return s_configManager->GetConfigProfiles(); } Aws::String GetCachedConfigValue(const Aws::String &profileName, const Aws::String &key) { assert(s_configManager); return s_configManager->GetConfig(profileName, key); } Aws::String GetCachedConfigValue(const Aws::String &key) { assert(s_configManager); return s_configManager->GetConfig(Aws::Auth::GetConfigProfileName(), key); } bool HasCachedCredentialsProfile(const Aws::String& profileName) { assert(s_configManager); return s_configManager->HasCredentialsProfile(profileName); } Aws::Config::Profile GetCachedCredentialsProfile(const Aws::String &profileName) { assert(s_configManager); return s_configManager->GetCredentialsProfile(profileName); } Aws::Map<Aws::String, Aws::Config::Profile> GetCachedCredentialsProfiles() { assert(s_configManager); return s_configManager->GetCredentialsProfiles(); } Aws::Auth::AWSCredentials GetCachedCredentials(const Aws::String &profileName) { assert(s_configManager); return s_configManager->GetCredentials(profileName); } } // Config namespace } // Aws namespace