AWSConfigFileProfileConfigLoader.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. /**
  2. * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
  3. * SPDX-License-Identifier: Apache-2.0.
  4. */
  5. #include <aws/core/config/AWSProfileConfigLoader.h>
  6. #include <aws/core/utils/memory/stl/AWSSet.h>
  7. #include <aws/core/utils/memory/stl/AWSStreamFwd.h>
  8. #include <aws/core/utils/StringUtils.h>
  9. #include <aws/core/utils/logging/LogMacros.h>
  10. #include <fstream>
  11. namespace Aws
  12. {
  13. namespace Config
  14. {
  15. using namespace Aws::Utils;
  16. using namespace Aws::Auth;
  17. static const char REGION_KEY[] = "region";
  18. static const char ACCESS_KEY_ID_KEY[] = "aws_access_key_id";
  19. static const char SECRET_KEY_KEY[] = "aws_secret_access_key";
  20. static const char SESSION_TOKEN_KEY[] = "aws_session_token";
  21. static const char SSO_START_URL_KEY[] = "sso_start_url";
  22. static const char SSO_REGION_KEY[] = "sso_region";
  23. static const char SSO_ACCOUNT_ID_KEY[] = "sso_account_id";
  24. static const char SSO_ROLE_NAME_KEY[] = "sso_role_name";
  25. static const char SSO_SESSION_KEY[] = "sso_session";
  26. static const char ROLE_ARN_KEY[] = "role_arn";
  27. static const char EXTERNAL_ID_KEY[] = "external_id";
  28. static const char CREDENTIAL_PROCESS_COMMAND[] = "credential_process";
  29. static const char SOURCE_PROFILE_KEY[] = "source_profile";
  30. static const char PROFILE_SECTION[] = "profile";
  31. static const char DEFAULT[] = "default";
  32. static const char SSO_SESSION_SECTION[] = "sso-session";
  33. static const char DEFAULTS_MODE_KEY[] = "defaults_mode";
  34. static const char EQ = '=';
  35. static const char LEFT_BRACKET = '[';
  36. static const char RIGHT_BRACKET = ']';
  37. static const char PARSER_TAG[] = "Aws::Config::ConfigFileProfileFSM";
  38. // generated by python from identifier regex pattern from the spec: R"([A-Za-z0-9_\-/.%@:\+]+)":
  39. // #py: ''.join(chr(i) for i in range(128) if re.match("[A-Za-z0-9_\-\/.%@:\+]", chr(i)))
  40. const char IDENTIFIER_ALLOWED_CHARACTERS[] = R"(%+-./0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz)";
  41. static const size_t IDENTIFIER_ALLOWED_CHARACTERS_SZ = sizeof(IDENTIFIER_ALLOWED_CHARACTERS) - 1;
  42. const char WHITESPACE_CHARACTERS[] = "\t ";
  43. static const size_t WHITESPACE_CHARACTERS_SZ = sizeof(WHITESPACE_CHARACTERS) - 1;
  44. const char COMMENT_START[] = "#;";
  45. static const size_t COMMENT_START_SZ = sizeof(COMMENT_START) - 1;
  46. struct ProfilePropertyAccessFunctions
  47. {
  48. const char* PropertyKey;
  49. std::function<void(Profile&, const Aws::String&)> Setter;
  50. std::function<const Aws::String&(const Profile&)> Getter;
  51. };
  52. static const ProfilePropertyAccessFunctions PROFILE_PROPERTY_FUNCS[] =
  53. {{REGION_KEY, &Profile::SetRegion, &Profile::GetRegion},
  54. //ACCESS_KEY_ID_KEY, - AwsCredentials require special handling
  55. //SECRET_KEY_KEY,
  56. //SESSION_TOKEN_KEY,
  57. {SSO_START_URL_KEY, &Profile::SetSsoStartUrl, &Profile::GetSsoStartUrl},
  58. {SSO_REGION_KEY, &Profile::SetSsoRegion, &Profile::GetSsoRegion},
  59. {SSO_ACCOUNT_ID_KEY, &Profile::SetSsoAccountId, &Profile::GetSsoAccountId},
  60. {SSO_ROLE_NAME_KEY, &Profile::SetSsoRoleName, &Profile::GetSsoRoleName},
  61. //SSO_SESSION_KEY - SsoSession requires special handling
  62. {ROLE_ARN_KEY, &Profile::SetRoleArn, &Profile::GetRoleArn},
  63. {EXTERNAL_ID_KEY, &Profile::SetExternalId, &Profile::GetExternalId},
  64. {CREDENTIAL_PROCESS_COMMAND, &Profile::SetCredentialProcess, &Profile::GetCredentialProcess},
  65. {SOURCE_PROFILE_KEY, &Profile::SetSourceProfile, &Profile::GetSourceProfile},
  66. {DEFAULTS_MODE_KEY, &Profile::SetDefaultsMode, &Profile::GetDefaultsMode}};
  67. template<typename EntryT, size_t N>
  68. const EntryT* FindInStaticArray(const EntryT (&array)[N], const Aws::String& searchKey)
  69. {
  70. const EntryT* found = std::find_if(array, array + N,
  71. [&searchKey](const EntryT& entry)
  72. {
  73. return searchKey == entry.PropertyKey;
  74. });
  75. if(!!found && found != array + N)
  76. return found;
  77. return nullptr;
  78. }
  79. static const char* PROFILE_KEY_SPECIAL_HANDLING[] =
  80. {ACCESS_KEY_ID_KEY, SECRET_KEY_KEY, SESSION_TOKEN_KEY, SSO_SESSION_KEY};
  81. static const size_t PROFILE_KEY_SPECIAL_HANDLING_SZ = sizeof(PROFILE_KEY_SPECIAL_HANDLING) / sizeof(PROFILE_KEY_SPECIAL_HANDLING[0]);
  82. struct SsoSessionPropertyAccessFunctions
  83. {
  84. const char* PropertyKey;
  85. std::function<void(Profile::SsoSession&, const Aws::String&)> Setter;
  86. std::function<const Aws::String&(const Profile::SsoSession&)> Getter;
  87. };
  88. static const SsoSessionPropertyAccessFunctions SSO_SESSION_PROPERTY_FUNCS[] =
  89. {{SSO_REGION_KEY, &Profile::SsoSession::SetSsoRegion, &Profile::SsoSession::GetSsoRegion},
  90. {SSO_START_URL_KEY, &Profile::SsoSession::SetSsoStartUrl, &Profile::SsoSession::GetSsoStartUrl}};
  91. class ConfigFileProfileFSM
  92. {
  93. public:
  94. ConfigFileProfileFSM(bool useProfilePrefix)
  95. : m_useProfilePrefix(useProfilePrefix)
  96. {}
  97. const Aws::Map<String, Profile>& GetProfiles() const { return m_foundProfiles; }
  98. void ParseStream(Aws::IStream& stream)
  99. {
  100. static const size_t ASSUME_EMPTY_LEN = 3;
  101. State currentState = START;
  102. Aws::String currentSectionName;
  103. Aws::Map<Aws::String, Aws::String> currentKeyValues;
  104. Aws::String rawLine;
  105. while(std::getline(stream, rawLine) && currentState != FAILURE)
  106. {
  107. Aws::String line = rawLine.substr(0, rawLine.find_first_of(COMMENT_START)); // ignore comments
  108. if (line.empty() || line.length() < ASSUME_EMPTY_LEN || line.find_first_not_of(WHITESPACE_CHARACTERS) == Aws::String::npos)
  109. {
  110. continue;
  111. }
  112. auto openPos = line.find(LEFT_BRACKET);
  113. auto closePos = line.find(RIGHT_BRACKET);
  114. if(openPos != std::string::npos && closePos != std::string::npos)
  115. {
  116. FlushSection(currentState, currentSectionName, currentKeyValues);
  117. currentKeyValues.clear();
  118. ParseSectionDeclaration(line, currentSectionName, currentState);
  119. continue;
  120. }
  121. if(PROFILE_FOUND == currentState || SSO_SESSION_FOUND == currentState)
  122. {
  123. auto equalsPos = line.find(EQ);
  124. if (equalsPos != std::string::npos)
  125. {
  126. auto key = StringUtils::Trim(line.substr(0, equalsPos).c_str());
  127. auto value = StringUtils::Trim(line.substr(equalsPos + 1).c_str());
  128. currentKeyValues[key] = value;
  129. continue;
  130. }
  131. }
  132. if(UNKNOWN_SECTION_FOUND == currentState)
  133. {
  134. // skip any unknown sections
  135. continue;
  136. }
  137. AWS_LOGSTREAM_ERROR(PARSER_TAG, "Unexpected line in the aws shared profile: " << rawLine);
  138. currentState = FAILURE;
  139. break;
  140. }
  141. FlushSection(currentState, currentSectionName, currentKeyValues);
  142. // Put sso-sessions into profiles
  143. for(auto& profile : m_foundProfiles)
  144. {
  145. const Aws::String& profileSsoSessionName = profile.second.GetValue(SSO_SESSION_KEY);
  146. if(!profileSsoSessionName.empty())
  147. {
  148. auto ssoSessionIt = m_foundSsoSessions.find(profileSsoSessionName);
  149. if(ssoSessionIt == m_foundSsoSessions.end())
  150. {
  151. AWS_LOGSTREAM_ERROR(PARSER_TAG, "AWS profile has reference to a missing sso_session: " << profileSsoSessionName);
  152. currentState = FAILURE;
  153. continue;
  154. }
  155. auto ssoSession = ssoSessionIt->second;
  156. auto prof = profile.second;
  157. // If sso session and profile have conflicting start url or region, fail to parse
  158. // the session/sso specific profile properties
  159. auto hasConflictingStartUrls = !ssoSession.GetSsoStartUrl().empty()
  160. && !prof.GetSsoStartUrl().empty()
  161. && ssoSession.GetSsoStartUrl() != prof.GetSsoStartUrl();
  162. auto hasConflictingRegions = !ssoSession.GetSsoRegion().empty()
  163. && !prof.GetSsoRegion().empty()
  164. && ssoSession.GetSsoRegion() != prof.GetSsoRegion();
  165. if (hasConflictingStartUrls || hasConflictingRegions) {
  166. AWS_LOGSTREAM_ERROR(PARSER_TAG,
  167. "SSO profile has a start url or region conflict with sso session");
  168. prof.SetSsoStartUrl("");
  169. prof.SetSsoRegion("");
  170. prof.SetSsoAccountId("");
  171. prof.SetSsoRoleName("");
  172. continue;
  173. }
  174. profile.second.SetSsoSession(ssoSessionIt->second);
  175. }
  176. }
  177. if(FAILURE == currentState)
  178. {
  179. AWS_LOGSTREAM_ERROR(PARSER_TAG, "AWS shared profile config parsing failed");
  180. }
  181. }
  182. private:
  183. // true means Shared Config parsing, false means Shared Credentials parsing
  184. bool m_useProfilePrefix = false;
  185. enum State
  186. {
  187. START = 0,
  188. PROFILE_FOUND,
  189. SSO_SESSION_FOUND,
  190. UNKNOWN_SECTION_FOUND,
  191. FAILURE
  192. };
  193. /**
  194. * Helper function to parse a single word (aka section identifier) containing allowed characters from a line and a pos
  195. * i.e. line="[ profile default ]";identifierBegin=10 will return "default"
  196. * @param line, a section definition line being parsed
  197. * @param identifierBegin, an Aws::String position to start parsing
  198. * @param oErrorMsg, a reference to Aws::String to store error message in case of a parsing error.
  199. * @return Aws::String, e.g. "default"
  200. */
  201. Aws::String ParseIdentifier(const Aws::String& line, Aws::String::size_type identifierBegin, Aws::String& oErrorMsg)
  202. {
  203. // pos at the beginning of section Identifier (or sso_session section keyword)
  204. Aws::String::size_type identifierLength = 0;
  205. Aws::String::size_type pos = identifierBegin;
  206. while(pos < line.length())
  207. {
  208. if(std::find(IDENTIFIER_ALLOWED_CHARACTERS,
  209. IDENTIFIER_ALLOWED_CHARACTERS + IDENTIFIER_ALLOWED_CHARACTERS_SZ,
  210. line[pos]) != IDENTIFIER_ALLOWED_CHARACTERS + IDENTIFIER_ALLOWED_CHARACTERS_SZ)
  211. {
  212. identifierLength++;
  213. pos++;
  214. }
  215. else
  216. {
  217. break;
  218. }
  219. }
  220. const Aws::String SECTION_END_CHARS_TO_SKIP = Aws::String(WHITESPACE_CHARACTERS) + RIGHT_BRACKET;
  221. if(identifierLength == 0)
  222. {
  223. oErrorMsg = "identifier is missing";
  224. return "";
  225. }
  226. if(pos >= line.size() || SECTION_END_CHARS_TO_SKIP.find(line[pos]) == Aws::String::npos) {
  227. oErrorMsg = "a blank space character or closing bracket is expected after Identifier";
  228. return "";
  229. }
  230. Aws::String sectionIdentifier = line.substr(identifierBegin, identifierLength);
  231. return sectionIdentifier;
  232. }
  233. /**
  234. * A helper function to parse config section declaration line
  235. * @param line, an input line, e.g. "[profile default]"
  236. * @param ioSectionName, a return argument representing parsed section Identifier, e.g. "default"
  237. * @param ioState, a return argument representing parser state, e.g. PROFILE_FOUND
  238. */
  239. void ParseSectionDeclaration(const Aws::String& line,
  240. Aws::String& ioSectionName,
  241. State& ioState)
  242. {
  243. do { // goto in a form of "do { break; } while(0);"
  244. Aws::String::size_type pos = 0;
  245. pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
  246. if(pos != Aws::String::npos && LEFT_BRACKET != line[pos])
  247. {
  248. AWS_LOGSTREAM_ERROR(PARSER_TAG, "First non-blank space character of a section definition must be [, line:" << line);
  249. break;
  250. }
  251. pos++;
  252. pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
  253. if(pos == Aws::String::npos || pos >= line.size())
  254. {
  255. AWS_LOGSTREAM_ERROR(PARSER_TAG, "Unknown section found in the aws config file: " << line);
  256. break;
  257. }
  258. bool defaultProfileOrSsoSectionRequired = false;
  259. if (m_useProfilePrefix)
  260. {
  261. // in configuration files, the profile name must start with profile. (eg. [profile profile-name]),
  262. // except where the profile name is default. When the profile name is default it may start with profile
  263. static const size_t PROFILE_KEYWORD_LENGTH = 7;
  264. if(line.rfind(PROFILE_SECTION, pos + PROFILE_KEYWORD_LENGTH) != Aws::String::npos)
  265. {
  266. // skipping required (optional for default) profile keyword
  267. pos += PROFILE_KEYWORD_LENGTH;
  268. if(pos >= line.size() ||
  269. std::find(WHITESPACE_CHARACTERS,
  270. WHITESPACE_CHARACTERS + WHITESPACE_CHARACTERS_SZ,
  271. line[pos]) == WHITESPACE_CHARACTERS + WHITESPACE_CHARACTERS_SZ)
  272. {
  273. AWS_LOGSTREAM_ERROR(PARSER_TAG, "Expected a blank space after \"profile\" keyword: " << line);
  274. break;
  275. }
  276. pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
  277. }
  278. else
  279. {
  280. defaultProfileOrSsoSectionRequired = true;
  281. }
  282. }
  283. Aws::String errorMsg;
  284. Aws::String sectionIdentifier = ParseIdentifier(line, pos, errorMsg);
  285. if (!errorMsg.empty())
  286. {
  287. AWS_LOGSTREAM_ERROR(PARSER_TAG, "Failed to parse section identifier: " << errorMsg << " " << line);
  288. break;
  289. }
  290. pos += sectionIdentifier.length();
  291. if(defaultProfileOrSsoSectionRequired)
  292. {
  293. if (sectionIdentifier != DEFAULT && sectionIdentifier != SSO_SESSION_SECTION)
  294. {
  295. AWS_LOGSTREAM_ERROR(PARSER_TAG, "In configuration files, the profile name must start with "
  296. "profile keyword (except default profile): " << line);
  297. break;
  298. }
  299. if (sectionIdentifier != SSO_SESSION_SECTION)
  300. {
  301. // profile found, still pending check for closing bracket
  302. ioState = PROFILE_FOUND;
  303. ioSectionName = sectionIdentifier;
  304. }
  305. }
  306. if(!m_useProfilePrefix || sectionIdentifier != SSO_SESSION_SECTION)
  307. {
  308. // profile found, still pending check for closing bracket
  309. ioState = PROFILE_FOUND;
  310. ioSectionName = sectionIdentifier;
  311. }
  312. if(m_useProfilePrefix && sectionIdentifier == SSO_SESSION_SECTION)
  313. {
  314. // "[sso_session..." found, continue parsing for sso_session identifier
  315. pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
  316. if(pos == Aws::String::npos)
  317. {
  318. AWS_LOGSTREAM_ERROR(PARSER_TAG, "Expected a blank space after \"sso_session\" keyword: " << line);
  319. break;
  320. }
  321. sectionIdentifier = ParseIdentifier(line, pos, errorMsg);
  322. if (!errorMsg.empty())
  323. {
  324. AWS_LOGSTREAM_ERROR(PARSER_TAG, "Failed to parse section identifier: " << errorMsg << " " << line);
  325. break;
  326. }
  327. pos += sectionIdentifier.length();
  328. // sso_session found, still pending check for closing bracket
  329. ioState = SSO_SESSION_FOUND;
  330. ioSectionName = sectionIdentifier;
  331. }
  332. pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
  333. if(pos == Aws::String::npos)
  334. {
  335. AWS_LOGSTREAM_ERROR(PARSER_TAG, "Expected a non-blank space after section identifier (i.e. missing \"]\"): " << line);
  336. break;
  337. }
  338. if(line[pos] != RIGHT_BRACKET)
  339. {
  340. AWS_LOGSTREAM_ERROR(PARSER_TAG, "Missing closing bracket after Section Identifier "
  341. "(i.e. missing \"]\" or extra non-blank characters before \"]\"): " << line);
  342. break;
  343. }
  344. pos++;
  345. pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
  346. if(pos != Aws::String::npos &&
  347. std::find(COMMENT_START, COMMENT_START + COMMENT_START_SZ, line[pos]) == COMMENT_START + COMMENT_START_SZ)
  348. {
  349. AWS_LOGSTREAM_ERROR(PARSER_TAG, "Found unexpected characters after closing bracket of Section Identifier " << line);
  350. break;
  351. }
  352. // the rest is a comment, and we don't care about it.
  353. if ((ioState != SSO_SESSION_FOUND && ioState != PROFILE_FOUND) || ioSectionName.empty())
  354. {
  355. AWS_LOGSTREAM_FATAL(PARSER_TAG, "Unexpected parser state after attempting to parse section " << line);
  356. break;
  357. }
  358. return;
  359. } while(0); // end of goto in a form of "do { break; } while(0);"
  360. ioSectionName.erase();
  361. ioState = UNKNOWN_SECTION_FOUND;
  362. return;
  363. }
  364. /**
  365. * A helper function to store currently being parsed section along with its properties
  366. * (i.e. [profile default] and its key1=val1 under).
  367. * @param currentState, a current parser State, e.g. PROFILE_FOUND
  368. * @param currentSectionName, a current section identifier, e.g. "default"
  369. * @param currentKeyValues, a map of parsed key-value properties of a section definition being recorded
  370. */
  371. void FlushSection(const State currentState, const Aws::String& currentSectionName, Aws::Map<Aws::String, Aws::String>& currentKeyValues)
  372. {
  373. if(START == currentState || currentSectionName.empty())
  374. {
  375. return; //nothing to flush
  376. }
  377. if(PROFILE_FOUND == currentState)
  378. {
  379. Profile& profile = m_foundProfiles[currentSectionName];
  380. for(const auto& keyVal : currentKeyValues)
  381. {
  382. auto setterFuncPtr = FindInStaticArray(PROFILE_PROPERTY_FUNCS, keyVal.first);
  383. if(setterFuncPtr)
  384. {
  385. AWS_LOGSTREAM_DEBUG(PARSER_TAG, "Found " << setterFuncPtr->PropertyKey << " " << keyVal.second);
  386. setterFuncPtr->Setter(profile, keyVal.second);
  387. }
  388. else
  389. {
  390. auto specialPropertyKey = std::find_if(PROFILE_KEY_SPECIAL_HANDLING, PROFILE_KEY_SPECIAL_HANDLING + PROFILE_KEY_SPECIAL_HANDLING_SZ,
  391. [&keyVal](const char* entry)
  392. {
  393. return !!entry && keyVal.first == entry;
  394. });
  395. if (specialPropertyKey && specialPropertyKey != PROFILE_KEY_SPECIAL_HANDLING + PROFILE_KEY_SPECIAL_HANDLING_SZ)
  396. {
  397. AWS_LOGSTREAM_INFO(PARSER_TAG, "Unknown property: " << keyVal.first << " in the profile: " << currentSectionName);
  398. }
  399. }
  400. }
  401. auto accessKeyIdIter = currentKeyValues.find(ACCESS_KEY_ID_KEY);
  402. Aws::String accessKey, secretKey, sessionToken;
  403. if (accessKeyIdIter != currentKeyValues.end())
  404. {
  405. accessKey = accessKeyIdIter->second;
  406. AWS_LOGSTREAM_DEBUG(PARSER_TAG, "found access key " << accessKey);
  407. auto secretAccessKeyIter = currentKeyValues.find(SECRET_KEY_KEY);
  408. auto sessionTokenIter = currentKeyValues.find(SESSION_TOKEN_KEY);
  409. if (secretAccessKeyIter != currentKeyValues.end())
  410. {
  411. secretKey = secretAccessKeyIter->second;
  412. }
  413. else
  414. {
  415. 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.");
  416. }
  417. if (sessionTokenIter != currentKeyValues.end())
  418. {
  419. sessionToken = sessionTokenIter->second;
  420. }
  421. profile.SetCredentials(Aws::Auth::AWSCredentials(accessKey, secretKey, sessionToken));
  422. }
  423. if (!profile.GetSsoStartUrl().empty() || !profile.GetSsoRegion().empty()
  424. || !profile.GetSsoAccountId().empty() || !profile.GetSsoRoleName().empty())
  425. {
  426. // If there is no sso session, all fields are required. If an SSO session is present,
  427. // then only account id and sso role name are required.
  428. auto hasSession = currentKeyValues.find(SSO_SESSION_KEY) != currentKeyValues.end();
  429. auto hasInvalidProfileWithoutSession = !hasSession &&
  430. (profile.GetSsoStartUrl().empty()
  431. || profile.GetSsoRegion().empty()
  432. || profile.GetSsoAccountId().empty()
  433. || profile.GetSsoRoleName().empty());
  434. auto hasInvalidProfileWithSession = hasSession &&
  435. (profile.GetSsoAccountId().empty()
  436. || profile.GetSsoRoleName().empty());
  437. if (hasInvalidProfileWithoutSession || hasInvalidProfileWithSession) {
  438. profile.SetSsoStartUrl("");
  439. profile.SetSsoRegion("");
  440. profile.SetSsoAccountId("");
  441. profile.SetSsoRoleName("");
  442. AWS_LOGSTREAM_ERROR(PARSER_TAG, "invalid SSO configuration for aws profile " << currentSectionName);
  443. }
  444. }
  445. profile.SetName(currentSectionName);
  446. profile.SetAllKeyValPairs(std::move(currentKeyValues));
  447. }
  448. else if (SSO_SESSION_FOUND == currentState) {
  449. Profile::SsoSession& ssoSession = m_foundSsoSessions[currentSectionName];
  450. for(const auto& keyVal : currentKeyValues)
  451. {
  452. auto setterFuncPtr = FindInStaticArray(SSO_SESSION_PROPERTY_FUNCS, keyVal.first);
  453. if(setterFuncPtr)
  454. {
  455. AWS_LOGSTREAM_DEBUG(PARSER_TAG, "Found sso-session property " << setterFuncPtr->PropertyKey << " " << keyVal.second);
  456. setterFuncPtr->Setter(ssoSession, keyVal.second);
  457. }
  458. else
  459. {
  460. AWS_LOGSTREAM_INFO(PARSER_TAG, "Unknown property: " << keyVal.first << " in the sso-session: " << currentSectionName);
  461. }
  462. }
  463. ssoSession.SetName(currentSectionName);
  464. ssoSession.SetAllKeyValPairs(std::move(currentKeyValues));
  465. }
  466. else
  467. {
  468. AWS_LOGSTREAM_FATAL(PARSER_TAG, "Unknown parser error: unexpected state " << currentState);
  469. }
  470. }
  471. Aws::Map<String, Profile> m_foundProfiles;
  472. Aws::Map<String, Profile::SsoSession> m_foundSsoSessions;
  473. };
  474. static const char* const CONFIG_FILE_LOADER = "Aws::Config::AWSConfigFileProfileConfigLoader";
  475. AWSConfigFileProfileConfigLoader::AWSConfigFileProfileConfigLoader(const Aws::String& fileName, bool useProfilePrefix) :
  476. m_fileName(fileName), m_useProfilePrefix(useProfilePrefix)
  477. {
  478. AWS_LOGSTREAM_INFO(CONFIG_FILE_LOADER, "Initializing config loader against fileName "
  479. << fileName << " and using profilePrefix = " << useProfilePrefix);
  480. }
  481. bool AWSConfigFileProfileConfigLoader::LoadInternal()
  482. {
  483. m_profiles.clear();
  484. Aws::IFStream inputFile(m_fileName.c_str());
  485. if(inputFile)
  486. {
  487. ConfigFileProfileFSM parser(m_useProfilePrefix);
  488. parser.ParseStream(inputFile);
  489. m_profiles = parser.GetProfiles();
  490. return m_profiles.size() > 0;
  491. }
  492. AWS_LOGSTREAM_INFO(CONFIG_FILE_LOADER, "Unable to open config file " << m_fileName << " for reading.");
  493. return false;
  494. }
  495. bool AWSConfigFileProfileConfigLoader::PersistInternal(const Aws::Map<Aws::String, Profile>& profiles)
  496. {
  497. Aws::OFStream outputFile(m_fileName.c_str(), std::ios_base::out | std::ios_base::trunc);
  498. if(outputFile)
  499. {
  500. Aws::UnorderedMap<Aws::String, std::reference_wrapper<const Profile::SsoSession>> ssoSessionsToDump;
  501. for(const auto& profile : profiles)
  502. {
  503. Aws::String prefix = m_useProfilePrefix ? PROFILE_SECTION : "";
  504. AWS_LOGSTREAM_DEBUG(CONFIG_FILE_LOADER, "Writing profile " << profile.first << " to disk.");
  505. outputFile << LEFT_BRACKET << prefix << " " << profile.second.GetName() << RIGHT_BRACKET << std::endl;
  506. const Aws::Auth::AWSCredentials& credentials = profile.second.GetCredentials();
  507. if (!credentials.GetAWSAccessKeyId().empty()) {
  508. outputFile << ACCESS_KEY_ID_KEY << EQ << credentials.GetAWSAccessKeyId() << std::endl;
  509. }
  510. if (!credentials.GetAWSSecretKey().empty()) {
  511. outputFile << SECRET_KEY_KEY << EQ << credentials.GetAWSSecretKey() << std::endl;
  512. }
  513. if(!credentials.GetSessionToken().empty()) {
  514. outputFile << SESSION_TOKEN_KEY << EQ << credentials.GetSessionToken() << std::endl;
  515. }
  516. // credentials.GetExpiration().Millis() <- is not present in a config.
  517. for(const auto& profilePropertyPair : PROFILE_PROPERTY_FUNCS)
  518. {
  519. const auto& profilePropertyValue = profilePropertyPair.Getter(profile.second);
  520. if(!profilePropertyValue.empty())
  521. {
  522. outputFile << profilePropertyPair.PropertyKey << EQ << profilePropertyValue << std::endl;
  523. }
  524. }
  525. if(profile.second.IsSsoSessionSet())
  526. {
  527. const auto& ssoSession = profile.second.GetSsoSession();
  528. const auto alreadyScheduledForDumpIt = ssoSessionsToDump.find(ssoSession.GetName());
  529. if (alreadyScheduledForDumpIt != ssoSessionsToDump.end() &&
  530. alreadyScheduledForDumpIt->second.get() != ssoSession)
  531. {
  532. AWS_LOGSTREAM_WARN(CONFIG_FILE_LOADER, "2 or more profiles reference 'sso-session' section "
  533. "with the same name but different properties: " << ssoSession.GetName());
  534. }
  535. else
  536. {
  537. ssoSessionsToDump.insert({ssoSession.GetName(), std::cref(ssoSession)});
  538. }
  539. outputFile << SSO_SESSION_KEY << EQ << ssoSession.GetName() << std::endl;
  540. }
  541. outputFile << std::endl;
  542. }
  543. for(const auto& ssoSessionPair : ssoSessionsToDump)
  544. {
  545. AWS_LOGSTREAM_DEBUG(CONFIG_FILE_LOADER, "Writing sso-session " << ssoSessionPair.first << " to disk.");
  546. const Profile::SsoSession& ssoSession = ssoSessionPair.second.get();
  547. outputFile << LEFT_BRACKET << SSO_SESSION_SECTION << " " << ssoSession.GetName() << RIGHT_BRACKET << std::endl;
  548. for(const auto& ssoSessionPropertyPair : SSO_SESSION_PROPERTY_FUNCS)
  549. {
  550. const auto& profilePropertyValue = ssoSessionPropertyPair.Getter(ssoSession);
  551. if(!profilePropertyValue.empty())
  552. {
  553. outputFile << ssoSessionPropertyPair.PropertyKey << EQ << profilePropertyValue << std::endl;
  554. }
  555. }
  556. outputFile << std::endl;
  557. }
  558. AWS_LOGSTREAM_INFO(CONFIG_FILE_LOADER, "Profiles written to config file " << m_fileName);
  559. return true;
  560. }
  561. AWS_LOGSTREAM_WARN(CONFIG_FILE_LOADER, "Unable to open config file " << m_fileName << " for writing.");
  562. return false;
  563. }
  564. } // Config namespace
  565. } // Aws namespace